package zmo.technology.lite.application;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.springframework.boot.SpringApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;

import zmo.technology.lite.core.ZeroException;
import zmo.technology.lite.logging.ZeroLogger;

public class ZeroApplication {

    private static ZeroApplication ZERO_APPLICATION;

    @FunctionalInterface
    public interface ZeroPropertyLauncher {
        void launch(ZeroApplication property);
    }

    private static String appName;
    private static SpringApplication application;
    private static Class<?> mainClass;

    private String[] args;
    private AtomicBoolean isRunning;
    private ZeroPropertyLauncher launcher;
    private ApplicationListener<?>[] listeners;

    private ZeroApplication(Class<?> mainClass, String[] args, ZeroPropertyLauncher launcher) {
        super();

        if (mainClass.getAnnotation(ZeroSpringBootApplication.class) == null) {
            System.err.println("err : main class need @ZeroSpringBootApplication annotation");
            System.exit(-1);
        }

        ZeroApplication.appName = mainClass.getAnnotation(ZeroSpringBootApplication.class).value();
        ZeroApplication.mainClass = mainClass;

        this.args = args;
        this.launcher = launcher;
        this.isRunning = new AtomicBoolean(false);

        launcherRunparams();
        application = new SpringApplication(mainClass);
    }

    public static synchronized ZeroApplication build(Class<?> mainClass, String[] args, ZeroPropertyLauncher launcher)
            throws ZeroException {
        if (ZERO_APPLICATION != null)
            throw new ZeroException("ZeroApplication already initialized");
        ZERO_APPLICATION = new ZeroApplication(mainClass, args, launcher);
        return ZERO_APPLICATION;
    }

    public static synchronized ZeroApplication build(Class<?> mainClass, String[] args)
            throws ZeroException {
        return build(mainClass, args, null);
    }

    public ZeroApplication addListener(ApplicationListener<?>... listeners) {
        this.listeners = listeners;
        return this;
    }

    public static String getAppName() {
        return appName;
    }

    public static SpringApplication getApplication() {
        return application;
    }

    public static Class<?> getMainClass() {
        return mainClass;
    }

    public void start() throws ZeroException {
        if (isRunning.get())
            throw new ZeroException("application is running");
        if (listeners != null)
            application.addListeners(listeners);
        isRunning.set(true);
        application.run(args);
    }

    private static ApplicationContext context;

    public static void setContext(ApplicationContext context) {
        ZeroApplication.context = context;
    }

    public static ApplicationContext getContext() {
        return context;
    }

    public static <T> T getBean(Class<T> beanClass) {
        return context.getBean(beanClass);
    }

    public void setDefault(String propName, String propDefault) {
        System.getProperties().setProperty(propName, System.getProperties().getProperty(propName, propDefault));
    }

    public void setURLEncodeDefault(String propName, String propDefault) {
        try {
            setDefault(propName, URLEncoder.encode(propDefault, "utf-8"));
        } catch (UnsupportedEncodingException e) {
            ZeroLogger.WARN(e.toString());
            setDefault(propName, propDefault);
        }
    }

    private void launcherRunparams() {
        String regex = "^\\-\\-(.*)\\=(.*)$";
        for (int i = 0; i < args.length; i++) {
            Matcher matcher = Pattern.compile(regex).matcher(args[i]);
            if (matcher.find())
                setDefault(matcher.group(1), matcher.group(2));
        }
    }

    private void launcherProperties(Properties properties) {
        for (Map.Entry<Object, Object> entry : properties.entrySet())
            setDefault(entry.getKey().toString(), entry.getValue().toString());
        setDefault("spring.profiles.active", "dev");

        if (System.getProperties().getProperty("spring.profiles.active").equals("dev") && launcher != null)
            launcher.launch(this);

        setDefault("easydo.application.name", appName);
        setDefault("easydo.http.prefix", "api");

        // setDefault("easydo.redis.host", "127.0.0.1");
        // setDefault("easydo.redis.port", "6379");
        // setDefault("easydo.redis.password", "");
        // setDefault("easydo.redis.database", "0");
        // setDefault("easydo.redis.ssl", "false");
        // setDefault("easydo.redis.jedis.pool.max-active", "8");
        // setDefault("easydo.redis.jedis.pool.max-idle", "8");
        // setDefault("easydo.redis.jedis.pool.min-idle", "1");
        // setDefault("easydo.redis.jedis.pool.max-wait", "-1");
    }

    public static void initPropertys(Properties properties) {
        ZERO_APPLICATION.launcherProperties(properties);
    }

    public static String getProperty(String propName, String propDefault) {
        return System.getProperties().getProperty(propName, propDefault);
    }

    public static String getProperty(String propName) {
        return getProperty(propName, "");
    }

    public static String getURLDecodeProperty(String propName) {
        try {
            return URLDecoder.decode(System.getProperties().getProperty(propName, ""), "utf-8");
        } catch (UnsupportedEncodingException e) {
            ZeroLogger.WARN(e.toString());
            return System.getProperties().getProperty(propName, "");
        }
    }

    public static int getIntProperty(String propName) {
        return Integer.parseInt(getProperty(propName, "0"));
    }

    public static long getLongProperty(String propName) {
        return Long.parseLong(getProperty(propName, "0"));
    }

    public static double getDoubleProperty(String propName) {
        return Double.parseDouble(getProperty(propName, "0.0"));
    }

    public static boolean getBooleanProperty(String propName) {
        return Boolean.parseBoolean(getProperty(propName, "false"));
    }
}
