package com.eactive.eai.adapter.socket2.protocol.inisafe.handshake;

import com.eactive.eai.adapter.socket2.common.SMSLogger;
import com.eactive.eai.adapter.socket2.common.SimpleFuture;
import com.eactive.eai.adapter.socket2.common.TimeUtil;
import com.eactive.eai.adapter.socket2.config.SocketAdapterContext;
import com.eactive.eai.adapter.socket2.config.SocketLoggerManager;
import com.eactive.eai.adapter.socket2.protocol.ByteMessage;
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.initech.inisafenet.HandShakeManager_RSA;
import com.initech.provider.crypto.InitechProvider;
import org.apache.mina.common.ByteBuffer;
import org.apache.mina.common.IdleStatus;
import org.apache.mina.common.IoFilterAdapter;
import org.apache.mina.common.IoSession;


abstract public class InisafeFilter extends IoFilterAdapter {
	protected final Logger logger;
	protected final Logger errLogger;
	private final Object lockObj = new Object();	
//	private static final String NEXT_FILTER = InisafeFilter.class.getName() + ".NextFilter";	
	public static final String FILTER_STATE_KEY = InisafeFilter.class.getName() + ".STATE";

	public static final String SESSION_STATE = "SESSION_STATE";
	public static final String INISAFE_SESSION_KEY = "INISAFE_SESSION_KEY";
	
	public enum State {
		STATE_START, 
		STATE_INIT,
		STATE_UPDATE,
		STATE_FINAL,
		STATE_MESSAGE;
	}

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

	static {
		new InitechProvider();
	}

	public InisafeFilter(ISocketAdapter adapter) {
		this.adapter = adapter;
		this.context = adapter.getContext();

	    logger    = SocketLoggerManager.getInstance().getLogger(context.getAdapterGroupName());
	    errLogger = SocketLoggerManager.getInstance().getErrLogger();
	}
	
	@Override
	public void sessionCreated(NextFilter nextFilter, IoSession session) throws Exception {
		synchronized(lockObj) {
			FilterState filterState = (FilterState) session.getAttribute(FILTER_STATE_KEY);
			if (filterState == null) {
				filterState = new FilterState();
				filterState.state = State.STATE_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 {
//		HandShakeManager_RSA inisafeSession = (HandShakeManager_RSA) session.getAttribute(INISAFE_SESSION_KEY);
//		inisafeSession.close();	
	
		nextFilter.sessionClosed(session);
	}

	
	@Override
    public void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception {
		ByteMessage m = null;
		
		if (writeRequest.getMessage() instanceof byte[]) {
			m = new ByteMessage((byte[]) writeRequest.getMessage());
		} else if (writeRequest.getMessage() instanceof ByteMessage) {
			m = (ByteMessage) writeRequest.getMessage();
		} else if (writeRequest.getMessage() instanceof String) {
			m = new ByteMessage((String) writeRequest.getMessage());
		} else if (writeRequest.getMessage() instanceof ByteBuffer) {
			m = new ByteMessage((ByteBuffer) writeRequest.getMessage());
		} else {
			throw new Exception("Unknown data format");
		}
		
		/**
		 * encrypt
		 */
		try {
			FilterState filterState = (FilterState) session.getAttribute(FILTER_STATE_KEY);
//			HandShakeManager_RSA inisafeSession = (HandShakeManager_RSA) session.getAttribute(INISAFE_SESSION_KEY);
	
			switch (filterState.state) {			
			case STATE_MESSAGE:
				validateMessage(m);
//				String sessionState = (String) session.getAttribute(ProtocolHandler.SESSION_STATE);
				m = encrypt(m, session);
				break;
				
			default:
				break;
			}
		} catch(Exception e) {
			adapter.addRecentError(context.getAdapterName() + " | initech error - " + e.getMessage(), e);
			logger.error(context.getAdapterName() + " | initech error - " + e.getMessage(), e);
			errLogger.error(context.getAdapterName() + " | initech error - " + e.getMessage(), e);
//			SMSLogger.loggingSMSWithCode(context.getAdapterName(), "RECEAINNA012");
			SMSLogger.loggingSMS4Soc(context.getAdapterName(), context.getDescription(), "RECEAINNA012");
			throw new Exception(context.getAdapterName() + " | initech error - " + e.getMessage());
		}

		nextFilter.filterWrite(session, new WriteRequest(m, writeRequest.getFuture(), writeRequest.getDestination()));
    }
	@Override
	public void sessionIdle(NextFilter nextFilter, IoSession session, IdleStatus status) throws Exception {
		FilterState filterState = (FilterState) session.getAttribute(FILTER_STATE_KEY);
		if (filterState.state != State.STATE_MESSAGE){
			StringBuffer sb = new StringBuffer();
			sb.append("idle:" + TimeUtil.elapsedTime(session.getLastIoTime()));
			sb.append(", handshake timeout:" + (context.getTimeout() -5));
			sb.append(", protocol:" + context.getProtocol());
			if (filterState != null) {
				sb.append(", state:" + filterState.state.toString());
			}
			sb.append("/" + session.getAttribute(SESSION_STATE));
			logger.error(context.getAdapterName() + " | handshake timeout -> disconnecting (" + session + ") " + sb.toString());
			adapter.addRecentError(context.getAdapterName() + " | handshake timeout -> disconnecting (" + session + ") " + sb.toString());
			
			nextFilter.sessionCreated(session);
			nextFilter.sessionOpened(session);
			adapter.getSessionPool().remove(session);
			session.close();
			
			
		}else{
			nextFilter.sessionIdle(session, status);
		}
	}

	protected String getSessionState(IoSession session) {
		return (String) session.getAttribute(SESSION_STATE);
	}
	
	
	protected void setSessionState(IoSession session, String state) {
		session.setAttribute(SESSION_STATE, state);
	}
	
	
	protected void validateMessage(ByteMessage m) throws Exception {
		if (m.size() < 200) {
			throw new Exception("cannot send message - message size < 200 - " + m.hexDump());
		}
		
		String ll = new String(m.getBytes(0, 5));
		if (!ll.matches("[0-9]{5}")) {
			throw new Exception("ll field (0-5) of message is not number - " + m.hexDump());
		}
	}
	
	
	protected ByteMessage encrypt(ByteMessage m, IoSession session) throws Exception {
		/**
		 * 암호화 여부 체크
		 */
		if ('Y' != m.getByte(99))
			return m;

		HandShakeManager_RSA inisafeSession = (HandShakeManager_RSA) session.getAttribute(INISAFE_SESSION_KEY);
		byte[] encryptPart = inisafeSession.i_encrypt(m.getBytes(200-16-16));
		ByteMessage encrypted = new ByteMessage(m.getBytes(0, 200-16-16)).append(encryptPart);
		
		return encrypted;		
	}

	protected ByteMessage decrypt(ByteMessage m, IoSession session) throws Exception {
		/**
		 * 암호화 여부 체크
		 */
		if ('Y' != m.getByte(99))
			return m;

		HandShakeManager_RSA inisafeSession = (HandShakeManager_RSA) session.getAttribute(INISAFE_SESSION_KEY);
		byte[] decryptPart = inisafeSession.i_decrypt(m.getBytes(200-16-16));
		ByteMessage decrypted = new ByteMessage(m.getBytes(0, 200-16-16)).append(decryptPart);
		
		return decrypted;
	}
}
