package ru.yandex.qe.spring;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import ru.yandex.qe.logging.LoggingInitializer;

/**
 * @author lvovich
 */
public class QeMain {
    private final static Logger LOG = LoggerFactory.getLogger(QeMain.class);

    static {
        LoggingInitializer.initialize();
        final Thread.UncaughtExceptionHandler delegateExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
            Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
                LOG.error(String.format("Uncaught exception from %s", t.toString()), e);
                if (delegateExceptionHandler != null) {
                    delegateExceptionHandler.uncaughtException(t, e);
                }
            });
    }

    private void run(final ContextFactory contextFactory) {
        try {
            final long startTs = System.currentTimeMillis();
            ApplicationVersionHolder.initialize(getClass());
            final ConfigurableApplicationContext ctx = contextFactory.create();
            ctx.refresh();
            customize(ctx);
            ctx.start();
            final Logger logger = LoggerFactory.getLogger(getClass());
            final String artifactId = ApplicationVersionHolder.getArtifactId();
            final String version = ApplicationVersionHolder.getVersion();
            if (artifactId != null && version != null) {
                logger.info("{} v.{} started in {} millis",
                        artifactId,
                        version,
                        (System.currentTimeMillis() - startTs));
            } else {
                logger.info("Started in {} millis",
                        (System.currentTimeMillis() - startTs));
            }
        } catch (final Exception e) {
            LOG.error("Unhandled exception", e);
            throw e;
        }
    }

    public final void run() {
        run(new AnnotationContextFactory(this.getClass()));
    }

    public final void run(String configLocation, String... extraLocations) {
        String[] locations = new String[extraLocations.length + 1];
        locations[0] = configLocation;
        System.arraycopy(extraLocations, 0, locations, 1, extraLocations.length);
        run(new GenericContextFactory(locations));
    }

    protected String[] getAdditionalProfiles() {
        // empty by default
        return new String[0];
    }

    protected void customize(@SuppressWarnings("UnusedParameters") ConfigurableApplicationContext ctx) {
        // nothing by default
    }

    private void addExtraProfiles(ConfigurableApplicationContext appContext) {
        ConfigurableEnvironment environment = appContext.getEnvironment();
        String[] activeProfiles = environment.getActiveProfiles();
        String[] additionalProfiles = getAdditionalProfiles();
        String[] gluedProfiles = new String[activeProfiles.length + additionalProfiles.length];
        System.arraycopy(activeProfiles, 0, gluedProfiles, 0, activeProfiles.length);
        System.arraycopy(additionalProfiles, 0, gluedProfiles, activeProfiles.length, additionalProfiles.length);
        environment.setActiveProfiles(gluedProfiles);
    }

    private interface ContextFactory {
        ConfigurableApplicationContext create();
    }

    private class AnnotationContextFactory implements ContextFactory {
        private final Class<?> configClass;

        private AnnotationContextFactory(final Class<?> configClass) {
            this.configClass = configClass;
        }

        @Override
        public ConfigurableApplicationContext create() {
            AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
            new ProfileActivator().initialize(ctx);
            addExtraProfiles(ctx);
            ctx.register(configClass);
            return ctx;
        }
    }

    private class GenericContextFactory implements ContextFactory {
        private final String[] configLocations;

        private GenericContextFactory(final String[] configLocations) {
            this.configLocations = configLocations;
        }

        @Override
        public ConfigurableApplicationContext create() {
            final GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();
            new ProfileActivator().initialize(ctx);
            addExtraProfiles(ctx);
            ctx.load(configLocations);
            return ctx;
        }
    }


}
