package com.eactive.eai.common.web; 

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.nio.charset.Charset;
import java.rmi.RemoteException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.sql.DataSource;

import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.remoting.rmi.RmiServiceExporter;

import com.eactive.eai.adapter.socket2.config.SocketAdapterManager;
import com.eactive.eai.common.dao.Keys;
import com.eactive.eai.common.exception.ExceptionUtil;
import com.eactive.eai.common.lifecycle.LifecycleException;
import com.eactive.eai.common.lifecycle.LifecycleManager;
import com.eactive.eai.common.logger.async.AsyncLoggingPoolManager;
import com.eactive.eai.common.property.PropManager;
import com.eactive.eai.common.routing.rmi.GWRemoteProxy;
import com.eactive.eai.common.routing.rmi.GWRemoteProxyImpl;
import com.eactive.eai.common.server.EAIServerManager;
import com.eactive.eai.common.server.EAIServerVO;
import com.eactive.eai.common.state.ElinkStateManager;
import com.eactive.eai.common.util.Logger;
import com.eactive.eai.common.util.ServiceLocator;
import com.eactive.eai.common.util.ServiceLocatorException;
import com.eactive.eai.env.ConfigKeys;
import com.eactive.eai.env.ElinkConfig;

/** 
 * eLink-GW  FrameWork이 초기화(Deploy)될 때 실행되어야 할 작업을 정의
 */
@SuppressWarnings("deprecation")
public class GWAppInitializer implements InitializingBean, DisposableBean {

	GWRemoteProxy remoteProxy = new GWRemoteProxyImpl();
	Class serviceInterface = GWRemoteProxy.class;
	RmiServiceExporter rmiServiceExporter = new org.springframework.remoting.rmi.RmiServiceExporter();

	@Autowired
	private LifecycleManager lifecycleManager;
	
	@Autowired
	private EAIServerManager eaiServerManager;

	private DataSource dataSource;

	private String instName;

	private String instPort;

	private String tableOwner;

	private String scalable;

	private boolean isScalable;

	private String routerUrl;

	private String systemType; // eLink System Type
	private String systemMode = "D"; // eLink-GW  System Mode : [P/T/D/S]

	private String eaiServerExtractor = "";	

	private String jndiName = "";
	
	private int shutdownWaitSecs = 60;
	private int shutdownWaitIntervalMs = 500;
	
	private static final String SHUTDOWN_WAIT_SECS = "shutdown.wait.secs";
	private static final String SHUTDOWN_WAIT_INTERVALMS = "shutdown.wait.intervalms";	
	
	public String getJndiName() {
		return jndiName;
	}

	public void setJndiName(String jndiName) {
		this.jndiName = jndiName;
	}
	
	public void setDataSource(DataSource dataSource) {
		this.dataSource = dataSource;
	}

	public void setInstName(String instName) {
		this.instName = instName;
	}

	public void setTableOwner(String tableOwner) {
		this.tableOwner = tableOwner;
	}

	public void setScalable(String scalable) {
		this.scalable = scalable;
	}

	public String getScalable() {
		return scalable;
	}

	public String getSystemType() {
		return systemType;
	}

	public void setSystemType(String systemType) {
		this.systemType = systemType;
	}
	
	public void setSystemMode(String systemMode) {
		this.systemMode = systemMode;
	}

	private String rmiPort;
	private String rmiServicePort;

	public String getRmiPort() {
		return rmiPort;
	}

	public void setRmiPort(String rmiPort) {
		this.rmiPort = rmiPort;
	}

	public String getRmiServicePort() {
		return rmiServicePort;
	}

	public void setRmiServicePort(String rmiServicePort) {
		this.rmiServicePort = rmiServicePort;
	}

	public String getRouterUrl() {
		return routerUrl;
	}

	public void setRouterUrl(String routerUrl) {
		this.routerUrl = routerUrl;
	}

	public String getInstPort() {
		return instPort;
	}

	public void setInstPort(String instPort) {
		this.instPort = instPort;
	}
	
	public String getEaiServerExtractor() {
		return eaiServerExtractor;
	}

	public void setEaiServerExtractor(String eaiServerExtractor) {
		this.eaiServerExtractor = eaiServerExtractor;
	}	

    public int getShutdownWaitSecs() {
		return shutdownWaitSecs;
	}

	public void setShutdownWaitSecs(int shutdownWaitSecs) {
		this.shutdownWaitSecs = shutdownWaitSecs;
	}

	public int getShutdownWaitIntervalMs() {
		return shutdownWaitIntervalMs;
	}

	public void setShutdownWaitIntervalMs(int shutdownWaitIntervalMs) {
		this.shutdownWaitIntervalMs = shutdownWaitIntervalMs;
	}	
	
    @SuppressWarnings("deprecation")
	private void initRmiServer(int registryPort, int servicePort) throws RemoteException {
		rmiServiceExporter.setServiceName("RemoteProxy");
		rmiServiceExporter.setService(remoteProxy);
		rmiServiceExporter.setServiceInterface(serviceInterface);
		rmiServiceExporter.setRegistryPort(registryPort);
		rmiServiceExporter.setServicePort(servicePort);
		rmiServiceExporter.setAlwaysCreateRegistry(true);
		rmiServiceExporter.afterPropertiesSet();
	}
    
    private void stopRmiServer() {
    	if(rmiServiceExporter != null) {
    		try {
				rmiServiceExporter.destroy();
			} catch (RemoteException e) {
				e.printStackTrace();
			}
    	}
    }
    /** 
     * 기능 : eLink-GW  FrameWork의 웹어플리케이션이 될 때 초기화 작업을 수행 
     **/
	private void init() {
		ElinkStateManager.getInstance().setWaitMaxMs(shutdownWaitSecs * 1000);
		ElinkStateManager.getInstance().setSleepIntervalMs(shutdownWaitIntervalMs);
		ElinkStateManager.getInstance().starting();		
		
		if (StringUtils.isBlank(System.getProperty(com.eactive.eai.common.server.Keys.EAI_SYSTEM_TYPE))) {
			if (StringUtils.isNotBlank(systemType)) {
				System.setProperty(com.eactive.eai.common.server.Keys.EAI_SYSTEM_TYPE, systemType);
			}
		}
		
		if (StringUtils.isBlank(System.getProperty("scalable"))) {
			if (StringUtils.isNotBlank(scalable)) {
				System.setProperty("scalable", scalable);
			}
		}

		if (StringUtils.isBlank(System.getProperty("router.url"))) {
			if (StringUtils.isNotBlank(routerUrl)) {
				System.setProperty("router.url", routerUrl);
			}
		}

		this.isScalable = BooleanUtils.toBoolean(System.getProperty("scalable"));

		if (this.isScalable) {
			String uniqueInstName = instName;
			if (StringUtils.isBlank(uniqueInstName)) {
				uniqueInstName = RandomStringUtils.randomAlphabetic(20);
//            }else if(uniqueInstName.length() > 20){
//                int l = uniqueInstName.length();
//                uniqueInstName = uniqueInstName.substring(l-14, l) + "-" + RandomStringUtils.randomAlphabetic(5);
			}
			System.setProperty(com.eactive.eai.common.server.Keys.SERVER_KEY, uniqueInstName);
		} else {
			String serverName = System.getProperty(com.eactive.eai.common.server.Keys.SERVER_KEY);
			if (serverName == null) {
				System.setProperty(com.eactive.eai.common.server.Keys.SERVER_KEY, instName);
			}
		}

		String instPort = System.getProperty(com.eactive.eai.common.server.Keys.SERVER_PORT);
		if (instPort == null) {
			System.setProperty(com.eactive.eai.common.server.Keys.SERVER_PORT, this.instPort);
		}
		/**
		 * OpenJDK 11 이상 사용 시 -Dfile.encoding 프로퍼티로 ms949, euc-kr
		 * Charset.defaultCharset()의 값이 지정되지 않는 지정할 수 없는 버그가 존재한다
		 * 해당 버그를 Charset을 초기화 하여 우회하는 방법으로 처리한다.
		 */
		Field charset = null;
		try {
			charset = Charset.class.getDeclaredField("defaultCharset");
			charset.setAccessible(true);
			charset.set(null, null);
		} catch (Exception e) {
			e.printStackTrace();
		}

		String serverName = System.getProperty(com.eactive.eai.common.server.Keys.SERVER_KEY);
		if (serverName == null) {
			System.setProperty(com.eactive.eai.common.server.Keys.SERVER_KEY, instName);
		}

		String tOwner = System.getProperty(Keys.TABLE_OWNER_KEY);
		if (tOwner == null) {
			System.setProperty(Keys.TABLE_OWNER_KEY, tableOwner);
		}

		String sMode = System.getProperty(com.eactive.eai.common.server.Keys.EAI_SYSTEMMODE);
		if (sMode == null) {
			System.setProperty(com.eactive.eai.common.server.Keys.EAI_SYSTEMMODE, systemMode);
		}

		// set Spring dataSource to ServiceLoactor
		try {
			String datasourceName = System.getProperty(Keys.JDBC_NAME_KEY);
			if(StringUtils.isEmpty(datasourceName)) {
				System.setProperty(Keys.JDBC_NAME_KEY, this.jndiName);
			}
			ServiceLocator sl = ServiceLocator.getInstance();
			sl.putDataSource(Keys.EAI_DATASOURCE, dataSource);
			System.out.println("\n@@ " + Keys.EAI_DATASOURCE + " = " + dataSource);
		} catch (ServiceLocatorException e1) {
			System.out.println("ServiceLocator error");
		}

		System.out.println("@ eLink-GW  System Config Initializing -----------------------");
		
		try {
			if (StringUtils.isBlank(this.tableOwner)) {
				System.out.println("==========================================================================");
				System.out.println("# E R R O R : eLink System Configuration");
				System.out.println("#  Check Start Script Rule table Owner : -D"
						+ com.eactive.eai.common.dao.Keys.TABLE_OWNER_KEY + "=");
				System.out.println("#  Check Start Script eLink System Mode  : -D"
						+ com.eactive.eai.common.server.Keys.EAI_SYSTEMMODE + "=");
				System.out.println("#  eLink System Mode : [P/T/D/S]");
				System.out.println("==========================================================================");
				throw new Exception(" Rule Table Owner Not Configured ");
			}
			if (StringUtils.isBlank(this.systemMode)) {
				System.out.println("==========================================================================");
				System.out.println("# E R R O R : eLink System Configuration");
				System.out.println("#  Check Start Script Rule table Owner : -D"
						+ com.eactive.eai.common.dao.Keys.TABLE_OWNER_KEY + "=");
				System.out.println("#  Check Start Script eLink System Mode  : -D"
						+ com.eactive.eai.common.server.Keys.EAI_SYSTEMMODE + "=");
				System.out.println("#  eLink System Mode : [P/T/D/S]");
				System.out.println("==========================================================================");
				throw new Exception(" eLink System Mode Not Configured ");
			}
			System.out.println("==========================================================================");
			System.out.println("# S U C C E S S : eLink System Configuration");
			System.out.println("#  Rule table Owner : " + this.tableOwner);
			System.out.println("#  Rule JDBC Name : " + com.eactive.eai.common.dao.Keys.EAI_DATASOURCE);
			System.out.println("#  eLink System Mode  : " + this.systemMode);
			System.out.println("#  eLink System Mode : [P(Product)/T(Test)/D(Develop)/S(Staging)]");
			System.out.println(
					String.format("#  %s : %s", ConfigKeys.USE_INTERNAL_TOPIC, ElinkConfig.isUseCacheTopic()));
			System.out.println("==========================================================================");
			System.out.println(
					String.format("#  %s : %s", SHUTDOWN_WAIT_SECS, shutdownWaitSecs) );
			System.out.println(
					String.format("#  %s : %s", SHUTDOWN_WAIT_INTERVALMS, shutdownWaitIntervalMs) );
					
		} catch (Exception e) {
			throw new RuntimeException("eLink-GW  System Configuration Error - " + e.toString());
		}

		try {
			PropManager.getInstance().start();
		} catch (LifecycleException e) {
			throw new RuntimeException(ExceptionUtil.getErrorCode(e, "RFCEAICWB001"));
		}

		try {
			Logger.doConfigure();
		} catch (Exception e) {
			System.out.println("Logger.doConfigure error");
		}

		Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
        
        /**
         * 서버정보 입력
         */
		if (this.isScalable) {
			try {
				EAIServerVO vo = new EAIServerVO();
				vo.setAddress(InetAddress.getLocalHost().getHostAddress());
				vo.setHostName(InetAddress.getLocalHost().getHostName());
				vo.setName(System.getProperty(com.eactive.eai.common.server.Keys.SERVER_KEY));
				// scalable merge
//	        	vo.setPort(System.getProperty("tomcat.port.http"));
				vo.setPort(System.getProperty("inst.Port"));
				vo.setRouterUrl(System.getProperty("router.url"));
	            String failoverSvr = getFailoverSvr(System.getProperty(com.eactive.eai.common.server.Keys.SERVER_KEY));
	            if (failoverSvr != null) {
	               vo.setFailoverSvr(failoverSvr);
	            }
				System.out.println("----------------------------");
				System.out.println(vo.toString());
				System.out.println("----------------------------");

				//addServer 에서 존재여부 체크해서 updateg함
				eaiServerManager.addServer(vo);
				logger.info("Success Insert Server Info!!!!");
				System.out.println("Success Insert Server Info!!!!");
			} catch (Exception e) {
				logger.error("Refresh Server Info Error~~", e);
				System.out.println("Refresh Server Info Error~~"+ e.getMessage());
			}
		}

		// print system config to logger
		logger.warn("==========================================================================");
		logger.warn("# S U C C E S S : eLink-GW  System Configuration");
		logger.warn("#  Rule table Owner : " + this.tableOwner);
		logger.warn("#  System Mode  : " + this.systemMode);
		logger.warn("#  eLink-GW  System Mode : [P(Product)/T(Test)/D(Develop)/S(Staging)]");
		logger.warn("==========================================================================");

		// init RMI Server
		String pRmiPort = System.getProperty("eai.rmiport");
		if (pRmiPort != null) {
			this.rmiPort = pRmiPort;
		}

		String pRmiServicePort = System.getProperty("eai.rmiserviceport");
		if (pRmiServicePort != null) {
			this.rmiServicePort = pRmiServicePort;
		}

		try {
			logger.warn("\n@@ eLink-GW  RMI Register Port : " + rmiPort + ", Service Port : " + rmiServicePort);
			initRmiServer(Integer.parseInt(rmiPort), Integer.parseInt(rmiServicePort));
		} catch (NumberFormatException e2) {
			logger.error("eLink-GW  System RMI Server Configuration Error check port", e2);
			throw new RuntimeException("eLink-GW  System RMI Server Configuration Error check port - " + e2.toString());
		} catch (RemoteException e2) {
			logger.error("eLink-GW  System RMI Server Start Error ", e2);
			throw new RuntimeException("eLink-GW  System RMI Server Start Error - " + e2.toString());
		}

		// MonitoringContextFactory 기동
		try {
			Class clazz = Class.forName("com.eactive.eai.rms.client.context.MonitoringContextFactory");
			Object factory = clazz.newInstance();
			Method method = clazz.getMethod("create");
			method.invoke(factory);
			logger.warn("EMSCLIENT AGENT STARTED");
		} catch (InvocationTargetException e) {
			logger.error("EMSCLIENT AGENT CANNOT START - " + e.getTargetException().toString(), e.getTargetException());
			e.getTargetException().printStackTrace();
		} catch (Throwable e) {
			logger.error("EMSCLIENT AGENT CANNOT START - " + e.toString(), e);
		}

		logger.warn("@ eLink-GW Application Initializing -------------------------");
		logger.warn(ElinkConfig.getConfigLog());

		try {
			lifecycleManager.start();
		} catch (LifecycleException e) {
			throw new RuntimeException(ExceptionUtil.getErrorCode(e, "RECEAICWB002"));
		}
		ElinkStateManager.getInstance().started();
		
	}
    
    /** 
     * eLink-GW  FrameWork의 웹어플리케이션이 Undeploy될 때 초기화된 Manager들을 중지시킴
     **/
	public void destroy() {
		Logger logger = Logger.getLogger(Logger.LOGGER_DEFAULT);
		if (logger.isWarn())
			logger.warn("@ eLink Application destroying.");
        
		// Check current transaction & wait specific seconds until finish
		ElinkStateManager.getInstance().stop();
		
		logger.warn("stopRmiServer");
		stopRmiServer();
		
		// MonitoringContextFactory 중지
		try {
			Class clazz = Class.forName("com.eactive.eai.rms.client.context.MonitoringContextFactory");
			Object factory = clazz.newInstance();
			Method method = clazz.getMethod("stop");
			method.invoke(factory);
			logger.warn("EMSCLIENT AGENT STOPPED");
		} catch (InvocationTargetException e) {
			logger.error("EMSCLIENT AGENT CANNOT STOP - " + e.getTargetException().toString(), e.getTargetException());
			e.getTargetException().printStackTrace();
		} catch (Throwable e) {
			logger.error("EMSCLIENT AGENT CANNOT STOP - " + e.toString(), e);
		}
		
//        /**
//         * 서버정보 삭제 GW 일경우 서버정보 삭제 안함- statefulset 형태로 fgw-0, fgw-1 의 host 명 으로 됨
//         */
//		if (this.isScalable) {
//			String instanceName = System.getProperty(Keys.SERVER_KEY);
//			logger.info("Starting, Delete Server Info: " + instanceName);
//			System.out.println("Starting, Delete Server Info: " + instanceName);
//			
//			if (StringUtils.isNotBlank(instanceName)) {
//				try {
//					eaiServerManager.removeServer(instanceName);
//
//					logger.info("Success Delete Server Info: " + instanceName);
//					System.out.println("Success Delete Server Info: " + instanceName);
//				} catch (Exception e) {
//					logger.error("Delete Server Info Error~~", e);
//					System.out.println("Delete Server Info Error~~" + instanceName + e.getMessage());
//				}
//			}
//		}

		try {
			lifecycleManager.stop();
		} catch (LifecycleException e) {
			throw new RuntimeException(ExceptionUtil.getErrorCode(e, "RECEAICWB002"));
		}

		try {
			PropManager.getInstance().stop();
		} catch (LifecycleException e) {
			throw new RuntimeException(ExceptionUtil.getErrorCode(e, "RECEAICWB004"));
		}

		try {
			SocketAdapterManager.getInstance().stop();
		} catch (Exception e) {
			throw new RuntimeException(ExceptionUtil.getErrorCode(e, "RECEAICWB005"));
		}
		System.out.println("\n\n");
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		init();
	}
    private String getFailoverSvr(String instName) {
        Matcher matcher = Pattern.compile("([\\S]*)-([0-9]{1,2})$").matcher(instName);
        String failover = "";
        while (matcher.find()) {
           String name = matcher.group(1).trim();
           String index = matcher.group(2).trim();
           int size = index.length();
           int num = Integer.parseInt(index);
           if (num %2 == 1) {
              num = num - 1;
           }else {
              num = num + 1;
           }
           failover = name + "-" + StringUtils.leftPad(Integer.toString(num), size, '0');
        }
         return failover;
      }
} 
