package rocks.inspectit.server;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.SystemUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.access.BeanFactoryLocator;
import org.springframework.beans.factory.access.BeanFactoryReference;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.access.ContextSingletonBeanFactoryLocator;

import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
import ch.qos.logback.core.util.StatusPrinter;
import rocks.inspectit.server.util.Converter;
import rocks.inspectit.shared.all.minlog.MinlogToSLF4JLogger;
import rocks.inspectit.shared.all.util.ResourcesPathResolver;
import rocks.inspectit.shared.all.version.VersionService;
import uk.org.lidalia.sysoutslf4j.context.SysOutOverSLF4J;

/**
 * Main class of the Central Measurement Repository. The main method is used to start the
 * application.
 *
 * @author Patrice Bouillet
 *
 */
public final class CMR {

	/**
	 * The logger of this class.
	 */
	private static final Logger LOGGER = LoggerFactory.getLogger(CMR.class);

	/**
	 * Default name of the log file.
	 */
	public static final String DEFAULT_LOG_FILE_NAME = "logging-config.xml";

	/**
	 * JVM property for the log file location.
	 */
	private static final String LOG_FILE_PROPERTY = "inspectit.logging.config";

	/**
	 * The spring bean factory to get the registered beans.
	 */
	private static BeanFactory beanFactory; // NOPMD

	/**
	 * startedAsService holds the value if CMR was started as a Windows Service.
	 */
	private static boolean startedAsService;

	/**
	 * This class will start the Repository.
	 */
	private CMR() {
	}

	/**
	 * Pseudo main method of class.
	 */
	private static void startCMR() {
		initLogger();

		long startTime = System.nanoTime();
		LOGGER.info("Central Measurement Repository is starting up!");
		LOGGER.info("==============================================");

		startRepository();

		LOGGER.info("CMR started in " + Converter.nanoToMilliseconds(System.nanoTime() - startTime) + " ms");
	}

	/**
	 * This class will start the Repository.
	 */
	private static void startRepository() {
		checkForCorrectJvmVersion();

		LOGGER.info("Initializing Spring...");

		BeanFactoryLocator beanFactoryLocator = ContextSingletonBeanFactoryLocator.getInstance();
		BeanFactoryReference beanFactoryReference = beanFactoryLocator.useBeanFactory("ctx");
		beanFactory = beanFactoryReference.getFactory();

		if (beanFactory instanceof ConfigurableApplicationContext) {
			((ConfigurableApplicationContext) beanFactory).registerShutdownHook();
		}

		LOGGER.info("Spring successfully initialized");

		if (LOGGER.isInfoEnabled()) {
			VersionService versionService = (VersionService) getBeanFactory().getBean("versionService");
			LOGGER.info("Starting CMR in version " + versionService.getVersionAsString()
			+ ". Please note that inspectIT does not provide any guarantee on backwards compatibility. Only if the version match exactly we ensure that the components are compatible.");
		}
	}

	/**
	 * This method checks if the JVM the CMR is started on is compatible.
	 */
	private static void checkForCorrectJvmVersion() {
		String version = System.getProperty("java.version");
		// searching for Zulu version pattern: 1.8.0_131
		Matcher matcher = Pattern.compile("(\\d+)\\.(\\d+)\\.(\\d+)[_-]u?(\\d+)").matcher(version);
		boolean correctVersion = true;

		if (matcher.find() && (matcher.groupCount() >= 4)) {
			try {
				int majorFirst = Integer.parseInt(matcher.group(1));
				int majorSecond = Integer.parseInt(matcher.group(2));
				int minor = Integer.parseInt(matcher.group(3));
				int update = Integer.parseInt(matcher.group(4));

				if ((majorFirst != 1) || (majorSecond != 8) || (minor != 0)) {
					correctVersion = false;
				}

				if (update < 131) {
					correctVersion = false;
				}
			} catch (NumberFormatException e) {
				correctVersion = false;
			}
		} else {
			correctVersion = false;
		}

		if (!correctVersion) {
			LOGGER.warn("ATTENTION: The version of this JVM (" + version + ") is not compatible with the CMR! Please use the JVM that is provided with the installation!");
		} else {
			LOGGER.info("The version of this JVM (" + version + ") is compatible with the CMR.");
		}
	}

	/**
	 * Start function. Needed by Procrun.
	 *
	 * @param args
	 *            The arguments.
	 */
	public static void start(String[] args) {
		startedAsService = true;
		startCMR();
	}

	/**
	 * Stop function. Needed by Procrun.
	 *
	 * @param args
	 *            The arguments.
	 */
	public static void stop(String[] args) {
		System.exit(0);
	}

	/**
	 * Initializes the logger.
	 */
	private static void initLogger() {
		LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();

		JoranConfigurator configurator = new JoranConfigurator();
		configurator.setContext(context);
		context.reset();

		InputStream is = null;

		try {
			// first check if it's supplied as parameter
			String logFileLocation = System.getProperty(LOG_FILE_PROPERTY);
			if (null != logFileLocation) {
				Path logPath = Paths.get(logFileLocation).toAbsolutePath();
				if (Files.exists(logPath)) {
					is = Files.newInputStream(logPath, StandardOpenOption.READ);
				}
			}

			// then fail to default if none is specified
			if (null == is) {
				Path logPath = ResourcesPathResolver.getResourceFile(DEFAULT_LOG_FILE_NAME).toPath().toAbsolutePath();
				if (Files.exists(logPath)) {
					is = Files.newInputStream(logPath, StandardOpenOption.READ);
				}
			}

			if (null != is) {
				try {
					configurator.doConfigure(is);
				} catch (JoranException e) { // NOPMD NOCHK StatusPrinter will handle this
				} finally {
					is.close();
				}
			}
		} catch (IOException e) { // NOPMD NOCHK StatusPrinter will handle this
		}

		StatusPrinter.printInCaseOfErrorsOrWarnings(context);

		// use sysout-over-slf4j to redirect out and err calls to logger
		SysOutOverSLF4J.sendSystemOutAndErrToSLF4J();

		// initialize out minlog bridge to the slf4j
		MinlogToSLF4JLogger.init();
	}

	/**
	 * Main method of class.
	 *
	 * @param args
	 *            The arguments.
	 */
	public static void main(String[] args) {
		// Start Apache Procrun only if it's a Windows operating system.
		if ((args.length == 1) && SystemUtils.IS_OS_WINDOWS) {
			switch (args[0]) {
			case "start":
				start(args);
				break;
			case "stop":
				stop(args);
				break;
			default:
				startCMR();
			}
		} else {
			startCMR();
		}
	}

	/**
	 * Returns the spring bean factory.
	 *
	 * @return The spring bean factory.
	 */
	public static BeanFactory getBeanFactory() {
		return beanFactory;
	}

	/**
	 * Getter method for property <code>startedAsService</code>.
	 *
	 * @return startedAsService.
	 */
	public static boolean isStartedAsService() {
		return startedAsService;
	}
}
