/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.tomee.embedded.junit;

import org.apache.tomee.embedded.TomEEEmbeddedApplicationRunner;
import org.junit.rules.MethodRule;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runner.notification.RunListener;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.Statement;

import javax.enterprise.inject.Vetoed;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicReference;

/**
 * see org.apache.tomee.embedded.SingleInstanceRunnerTest for a sample.
 * idea is to reuse some part of ApplicationComposer API to get a single container for all tests in embedded mode.
 *
 * Base is to declare an @Application class which holds the model and some injections.
 * Note: this can be replaced setting tomee.application-composer.application property to the fully qualified name of the app.
 * Note: @Application classes are only searched in the same jar as the test.
 *
 * Model:
 * - @Configuration: programmatic properties - note injections don't work there.
 * - @Classes: only context value is used.
 * - @ContainerProperties: to configure the container
 * - @WebResource: first value can be used to set the docBase (other values are ignored)
 * - @TomEEEmbeddedSingleRunner.LifecycleTasks: allow to add some lifecycle tasks (like starting a ftp/sft/elasticsearch... server)
 *
 * Injections:
 * - CDI
 * - @RandomPort: with the value http or https. Supported types are URL (context base) and int (the port).
 */
@Vetoed
public class TomEEEmbeddedSingleRunner extends BlockJUnit4ClassRunner {
    private static final AtomicReference<TomEEEmbeddedApplicationRunner> RUNNER = new AtomicReference<>();

    // use when you use another runner like Parameterized of JUnit
    public static class Rule implements TestRule {
        private final Object test;

        public Rule(final Object test) {
            this.test = test;
        }

        @Override
        public Statement apply(final Statement base, final Description description) {
            return new Statement() {
                @Override
                public void evaluate() throws Throwable {
                    start(test);
                    RUNNER.get().composerInject(test);
                    base.evaluate();
                }
            };
        }
    }

    public static class Start extends RunListener {
        @Override
        public void testStarted(final Description description) throws Exception {
            start(null);
        }
    }

    private static void start(final Object marker) throws Exception {
        getRunner().start(marker.getClass(), (Properties) null);
    }

    public static void setApp(final Object o) {
        getRunner().setApp(o);
    }

    private static TomEEEmbeddedApplicationRunner getRunner() {
        final TomEEEmbeddedApplicationRunner runner = RUNNER.get();
        if (runner == null) {
            RUNNER.compareAndSet(null, new TomEEEmbeddedApplicationRunner());
        }
        return RUNNER.get();
    }

    public static void close() {
        final TomEEEmbeddedApplicationRunner runner = RUNNER.get();
        if (runner != null) {
            runner.close();
        }
    }

    public TomEEEmbeddedSingleRunner(final Class<?> klass) throws InitializationError {
        super(klass);
    }

    @Override
    protected List<MethodRule> rules(final Object test) {
        final List<MethodRule> rules = super.rules(test);
        rules.add(new MethodRule() {
            @Override
            public Statement apply(final Statement base, final FrameworkMethod method, final Object target) {
                return new Statement() {
                    @Override
                    public void evaluate() throws Throwable {
                        start(test);
                        getRunner().composerInject(target);
                        base.evaluate();
                    }
                };
            }
        });
        return rules;
    }
}
