package com.eactive.eai.adapter.socket2.protocol.xecure;

import com.eactive.eai.adapter.socket2.common.SimpleFuture;
import com.eactive.eai.adapter.socket2.config.SocketAdapterContext;
import com.eactive.eai.adapter.socket2.config.SocketLoggerManager;
import com.eactive.eai.adapter.socket2.protocol.ISocketAdapter;
import com.eactive.eai.adapter.socket2.protocol.ProtocolHandler;
import com.eactive.eai.common.util.Logger;
import com.sf.xc3.XC_v3;
import org.apache.mina.common.IoFilterAdapter;
import org.apache.mina.common.IoFilterChain;
import org.apache.mina.common.IoSession;

import java.io.*;
import java.util.Properties;

public abstract class XecureFilter extends IoFilterAdapter {
	protected final Logger logger;
	protected final Logger errLogger;

//	private static final String NEXT_FILTER = XecureFilter.class.getName() + ".NextFilter";	
	protected static final String FILTER_STATE_KEY = XecureFilter.class.getName() + ".STATE";

	public static final String SESSION_STATE = "SESSION_STATE";
	
	protected enum State {
		STATE_WELCOME, 
		STATE_KEY_INIT,
		STATE_KEY_INIT_SEND,
		STATE_KEY_FINAL,
		STATE_KEY_FINAL_SEND,
		STATE_MESSAGE;
	}

	protected static class FilterState {
		public State state = null;
		public byte[] key;  // token?
		
		public FilterState() {}
	}	
	
    protected SocketAdapterContext context;
	protected ISocketAdapter adapter;
	
	protected boolean isConfigLoaded = false;
	protected byte[] cert;
	protected byte[] key;
	protected String password;
	protected byte[] remoteCert;	

	protected XecureFilter(ISocketAdapter adapter) {
		this.adapter = adapter;
		this.context = adapter.getContext();

	    logger    = SocketLoggerManager.getInstance().getLogger(context.getAdapterGroupName());
	    errLogger = SocketLoggerManager.getInstance().getErrLogger();
	}
	
	@Override
	public synchronized void onPreAdd(IoFilterChain parent, String name, NextFilter nextFilter) throws Exception {
		super.onPreAdd(parent, name, nextFilter);
		
		if (isConfigLoaded) {
			return;
		}
		
	    try {
	    	File file = new File(context.getXecureConfigPath());
	    	if (!file.exists()) {
	    		throw new Exception(context.getAdapterName() + " | " + getClass().getSimpleName() + " | xecure.config.path '" + context.getXecureConfigPath() + "' is invalid");
	    	}
	    	
	    	File pf = file.getParentFile();
	    	if(pf == null) {
	    		throw new Exception("'cert file path not found - " + file.getAbsolutePath());	    		
	    	}
	    	String certPath = null;
	    	if(pf.getParentFile() != null) {
	    		certPath = pf.getParentFile().getPath() + File.separator + "cert";
	    	}
	    	else {
	    		throw new Exception("'cert file path not found - " +pf.getAbsolutePath());
	    	}
	    	Properties prop = loadXecureConfigFile(context.getXecureConfigPath());

	    	if (context.isServer()) {
	    		/**
	    		 * local cert
	    		 */
			    String certFile = prop.getProperty("Local Certificate File");
			    if (certFile == null) {
			    	throw new Exception("'Local Certificate File' property does not exist in xecure config file - " + context.getXecureConfigPath());
			    }			    
			    try {
			    	cert = readFile(certPath + File.separator + certFile);
			    } catch (FileNotFoundException e) {
			    	throw new Exception(context.getAdapterName() + " | " + getClass().getSimpleName() + " | Local Certificate File '" + certFile + "' does not exist.");
			    }
			    if ( context.getTraceLevel() >= SocketAdapterContext.TRACE_LEVEL_TRACE ) {
					logger.debug(context.getAdapterName() + " | " + getClass().getSimpleName() + " | load local cert file - " + certPath + File.separator + certFile);
				}
			    
			    /**
			     * local key
			     */
			    String keyFile = prop.getProperty("Local Secret Key File");
			    if (keyFile == null) {
			    	throw new Exception("'Local Secret Key File' property does not exist in xecure config file - " + context.getXecureConfigPath());
			    }
			    try {
			    	key = readFile(certPath + File.separator + keyFile);
			    } catch (FileNotFoundException e) {
			    	throw new Exception(context.getAdapterName() + " | " + getClass().getSimpleName() + " | Local Secret Key File '" + keyFile + "' does not exist.");
			    }
				if ( context.getTraceLevel() >= SocketAdapterContext.TRACE_LEVEL_TRACE ) {
					logger.debug(context.getAdapterName() + " | " + getClass().getSimpleName() + " | load local key file - " + certPath + File.separator + keyFile);
				}
			    
			    /**
			     * password
			     */
			    String passwordString = prop.getProperty("Local Password");
			    if (passwordString == null) {
			    	throw new Exception("'Local Password' property does not exist in xecure config file - " + context.getXecureConfigPath());
			    }			    
//				if ( context.getTraceLevel() >= context.TRACE_LEVEL_TRACE ) {
//					logger.debug(context.getAdapterName() + " | read password - *****");
//				}
				password = passwordString;
	    	}
		    
	    	/**
	    	 * remote cert
	    	 */
	    	if (context.isClient()) {
			    String remoteCertFile = context.getXecureRemoteCert();
			    try {
			    	remoteCert = readFile(certPath + File.separator + remoteCertFile);
			    } catch (FileNotFoundException e) {
			    	throw new Exception(context.getAdapterName() + " | " + getClass().getSimpleName() + " | xecure.remote.cert '" + remoteCertFile + "' file does not exist.");
			    }
				if ( context.getTraceLevel() >= SocketAdapterContext.TRACE_LEVEL_DEBUG ) {
					logger.debug(context.getAdapterName() + " | " + getClass().getSimpleName() + " | load remote cert (public key) file - " + certPath + File.separator + remoteCertFile);
				}
	    	}
	    	
	    	isConfigLoaded = true;
	    } catch (Exception e) {
	    	throw e;
	    }
	}	
	
	@Override
	public void sessionCreated(NextFilter nextFilter, IoSession session) throws Exception {
		/*
		if (context.getTraceLevel() >= context.TRACE_LEVEL_DEBUG) {
			logger.debug(context.getAdapterName() + " | session created : " + session);
		}
		*/	
		
		synchronized(session) {
			FilterState filterState = (FilterState) session.getAttribute(FILTER_STATE_KEY);
			if (filterState == null) {
				filterState = new FilterState();
				filterState.state = State.STATE_KEY_INIT;
				session.setAttribute(FILTER_STATE_KEY, filterState);
				//setSessionState(session, filterState.state.toString());			
			}
		}
		
		// handshake future
		session.setAttribute(ProtocolHandler.HANDSHAKE_FUTURE, new SimpleFuture());
	}	
  
	@Override
	public void sessionClosed(NextFilter nextFilter, IoSession session) throws Exception {
		XC_v3 xc = (XC_v3) session.getAttribute(XecureConstants.XECURE);
		xc.close();
		
		nextFilter.sessionClosed(session);
	}

	protected String getSessionState(IoSession session) {
		return (String) session.getAttribute(SESSION_STATE);
	}
	
	protected void setSessionState(IoSession session, String state) {
		session.setAttribute(SESSION_STATE, state);
	}
	

	protected Properties loadXecureConfigFile(String filename) throws Exception {
		Properties prop = new Properties();
		
		
		try ( BufferedReader br = new BufferedReader(new FileReader(filename)) ) {
//			StringBuffer sb = new StringBuffer();
			String line = null;		
			while ((line = br.readLine()) != null) {
				if (line.matches("(Server|Client|Local) (Certificate File|Secret Key File|Password)[\\s]+=.+")) {
					String[] columns = line.split("=", 2);
					prop.put(columns[0].trim(), columns[1].trim());
				}
			}
		}catch(Exception ex) {
			throw ex;
		}
		return prop;
	}
	
    protected byte[] readFile(String filename) throws Exception	{
		File file = new File(filename);
		int fsize = (int)file.length();
		byte[] buffer = new byte[fsize];
		
		try (
			FileInputStream fis = new FileInputStream(filename);
			BufferedInputStream bis = new BufferedInputStream(fis);
			) {
			bis.read(buffer);
		}catch(Exception ex) {
			throw ex;
		}
		return buffer;
	}
}
