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

import com.eactive.eai.adapter.socket2.common.SMSLogger;
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.xecure2.Xecure2Constants;
import com.eactive.eai.common.util.Logger;
import com.sf.xc.KBXCServer;
import com.sf.xc.XCException;
import org.apache.mina.common.IoFilterAdapter;
import org.apache.mina.common.IoFilterChain;
import org.apache.mina.common.IoSession;

import java.util.Arrays;


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

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

	protected enum State {
		STATE_HANDSHAKE,
		STATE_MESSAGE;
	}

	protected static class FilterState {
		public State  state = State.STATE_MESSAGE;  //State.STATE_HANDSHAKE;
	}
	
    protected SocketAdapterContext context;
	protected ISocketAdapter adapter;
	
	protected Xecure2Filter(ISocketAdapter adapter) {
		this.adapter = adapter;
		this.context = adapter.getContext();

	    logger    = SocketLoggerManager.getInstance().getLogger(context.getAdapterGroupName());
	    errLogger = SocketLoggerManager.getInstance().getErrLogger();
	}
	
	@Override
	public void onPreAdd(IoFilterChain parent, String name, NextFilter nextFilter) throws Exception {
		if (parent.contains(Xecure2Filter.class)) {
			throw new IllegalStateException("A filter chain cannot contain more than one Xecure2Filter.");
		}
		
		IoSession session = parent.getSession();
		session.setAttribute(NEXT_FILTER, nextFilter);
	}
	
	@Override
	public void sessionCreated(NextFilter nextFilter, IoSession session) throws Exception {
		FilterState filterState = (FilterState) session.getAttribute(FILTER_STATE_KEY);

		if (filterState == null) {
			filterState = new FilterState();
			filterState.state = State.STATE_HANDSHAKE;
			session.setAttribute(FILTER_STATE_KEY, filterState);
		}

		nextFilter.sessionCreated(session);
	}
	
	@Override
    public void sessionOpened(NextFilter nextFilter, IoSession session) throws Exception {
		try {
			/**
			 * XCServer init
			 * JGY 20100423
//			 */
//			KBXCServer.init(context.getXecure2ConfigPath());
//			KBXCServer xs = new KBXCServer(context.getXecure2SmIp(), context.getXecure2SmPort());
			
			/**
			 * put xs into session
			 */
//			session.setAttribute(Xecure2Constants.XECURE2, xs);
			
			nextFilter.sessionOpened(session);
			
		} catch(Exception e) {
			adapter.addRecentError(context.getAdapterName() + " | xecure2 init error - " + e.getMessage(), e);
			logger.error(context.getAdapterName() + " | xecure2 init error - " + e.getMessage(), e);
			errLogger.error(context.getAdapterName() + " | xecure2 init error - " + e.getMessage(), e);
			
			try {
				adapter.getSessionPool().remove(session);
				session.close();
			} catch(Exception ignor) {
				logger.warn("session.close", ignor); 
			}
			
			throw new Exception(context.getAdapterName() + " | xecure2 init error - " + e.getMessage());
		}
	}
	
	@SuppressWarnings("static-access")
	@Override
	public void messageReceived(NextFilter nextFilter, IoSession session, Object message) throws Exception {
		
		FilterState filterState = (FilterState) session.getAttribute(FILTER_STATE_KEY);
		ByteMessage m = (ByteMessage) message;
		
		/**
		 * set state
		 * 거래구분 [28|4] 
		 * 거래코드 [32|4]
		 * 0800|1210 : 키공유요구
		 */
		String tranType = new String(m.getBytes(28, 4));
		String tranCode = new String(m.getBytes(32, 4));
		
		if ("0800".equals(tranType) && "1210".equals(tranCode)) {
			filterState.state = State.STATE_HANDSHAKE;
		} else {
			filterState.state = State.STATE_MESSAGE;
		}
		session.setAttribute(FILTER_STATE_KEY, filterState);
		
		try {
			KBXCServer xs = (KBXCServer) session.getAttribute(Xecure2Constants.XECURE2);
			
			if ( context.getTraceLevel() >= context.TRACE_LEVEL_TRACE ) {
				logger.debug(context.getAdapterName() + " | Xecure2ServerFilter | <RECV> " + filterState.state.toString() + " | " + m.hexDump());
			}

			/**
			 * 기관 SessionID set[12]
			 * sid : 이용자기관번호(40|8) + 단말번호(56|4)
			 * handshake 시 설정하며, keyFinal 호출 시 정보를 넘겨준다
			 */
			byte[] sid = new byte[12];
			System.arraycopy(m.getBytes(40, 8), 0, sid, 0, 8);
			System.arraycopy(m.getBytes(56, 4), 0, sid, 8, 4);
			
			switch (filterState.state) {
			case STATE_HANDSHAKE:
				/**
				 * Key Final
				 * received key init : offset|92, length|300
				 */
				try {
					byte[] finalKey = xs.keyFinal(m.getBytes(Xecure2Constants.COMMON_PART_LENGTH + 22, 300), sid, 1);
					
//					ByteMessage keyFinal = new ByteMessage(m.getBytes(0, Xecure2Constants.COMMON_PART_LENGTH + 22))
//											.append(finalKey).padding(392, (byte) 0x20)
//											.append("1".getBytes()).padding(500, (byte) 0x20);	//1:인증키승인, 2:세션키승인, 3:키승인오류
					ByteMessage keyFinal = new ByteMessage(m.getBytes(0, 37))
											.append("000".getBytes())
											.append(m.getBytes(40, 30 + 22))
											.append(finalKey).padding(500, (byte) 0x20)
					;	//공통정보[응답코드]에 '000' 설정, offset:37, length:3, exception 시 '799'
					
					filterState.state = State.STATE_MESSAGE;
					session.setAttribute(FILTER_STATE_KEY, filterState);
					
					nextFilter.messageReceived(session, keyFinal);
					
				} catch(XCException xce) {
					adapter.addRecentError(context.getAdapterName() + " | xecure2 key final error[XCException] - " + xce.getMessage(), xce);
					logger.error(context.getAdapterName() + " | xecure2 key final error[XCException] - " + xce.getMessage(), xce);
					errLogger.error(context.getAdapterName() + " | xecure2 key final error[XCException] - " + xce.getMessage(), xce);
//					throw new XCException(context.getAdapterName() + " | xecure2 key final error - " + xce.getMessage());
//					nextFilter.messageReceived(session, new ByteMessage(m.getByteBuffer().put(392, (byte) 0x33)));
					nextFilter.messageReceived(session, 
							new ByteMessage(m.getByteBuffer().put(37, (byte)0x37).put(38, (byte)0x39).put(39, (byte)0x39)));
				} catch(Exception e) {
					adapter.addRecentError(context.getAdapterName() + " | xecure2 key final error - " + e.getMessage(), e);
					logger.error(context.getAdapterName() + " | xecure2 key final error - " + e.getMessage(), e);
					errLogger.error(context.getAdapterName() + " | xecure2 key final error - " + e.getMessage(), e);
//					nextFilter.messageReceived(session, new ByteMessage(m.getByteBuffer().put(392, (byte) 0x33)));
					nextFilter.messageReceived(session, 
							new ByteMessage(m.getByteBuffer().put(37, (byte)0x37).put(38, (byte)0x39).put(39, (byte)0x39)));
				}
				
				break;
				
			case STATE_MESSAGE:
				/**
				 * decrypt
				 */
				try {
					xs.setSid(sid);
					m = decrypt(m, session);
					nextFilter.messageReceived(session, m);
				} catch(XCException xce) {
					adapter.addRecentError(context.getAdapterName() + " | xecure2 decrypt error[XCException] - " + xce.getMessage(), xce);
					logger.error(context.getAdapterName() + " | xecure2 decrypt error[XCException] - " + xce.getMessage(), xce);
					errLogger.error(context.getAdapterName() + " | xecure2 decrypt error[XCException] - " + xce.getMessage(), xce);
					nextFilter.messageReceived(session, 
							new ByteMessage(m.getByteBuffer().put(37, (byte)0x37).put(38, (byte)0x39).put(39, (byte)0x39)));
				} catch(Exception e) {
					adapter.addRecentError(context.getAdapterName() + " | xecure2 decrypt error - " + e.getMessage(), e);
					logger.error(context.getAdapterName() + " | xecure2 decrypt error - " + e.getMessage(), e);
					errLogger.error(context.getAdapterName() + " | xecure2 decrypt error - " + e.getMessage(), e);
					nextFilter.messageReceived(session, 
							new ByteMessage(m.getByteBuffer().put(37, (byte)0x37).put(38, (byte)0x39).put(39, (byte)0x39)));
				}
					
				break;
				
			default:
				throw new Exception("<RECV> invalid message during handshake | " + m.hexDump());
			}
			
		} catch(Exception e) {
			adapter.addRecentError(context.getAdapterName() + " | xecure2 error - " + e.getMessage(), e);
			logger.error(context.getAdapterName() + " | xecure2 error - " + e.getMessage(), e);
			errLogger.error(context.getAdapterName() + " | xecure2 error - " + e.getMessage(), e);
//			SMSLogger.loggingSMSWithCode(context.getAdapterName(), "RECEAINNA012");
			SMSLogger.loggingSMS4Soc(context.getAdapterName(), context.getDescription(), "RECEAINNA012");
			
			nextFilter.filterClose(session);
			adapter.getSessionPool().remove(session);
			session.close();

			throw new Exception(context.getAdapterName() + " | xecure2 error - " + e.getMessage());
		}
	}
	
	@SuppressWarnings("static-access")
	@Override
    public void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception {
		FilterState filterState = (FilterState) session.getAttribute(FILTER_STATE_KEY);
		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 {
			throw new Exception("Unknown data format");
		}

		if ( context.getTraceLevel() >= context.TRACE_LEVEL_TRACE ) {
			logger.debug(context.getAdapterName() + " | Xecure2Filter | <SEND> " + filterState.state.toString() + " | " + m.hexDump());
		}
		
		try {
			/**
			 * check polling message - skip polling
			 */
			if (!context.isPollingUse() || !isPollingPattern(m)) {
	
				/**
				 * set state
				 * 거래구분 [28|4] 
				 * 거래코드 [32|4]
				 * 0810|1210 : 키공유응답
				 */
				String tranType = new String(m.getBytes(28, 4));
				String tranCode = new String(m.getBytes(32, 4));
				
				if ("0810".equals(tranType) && "1210".equals(tranCode)) {
					filterState.state = State.STATE_HANDSHAKE;
				} else {
					filterState.state = State.STATE_MESSAGE;
				}
				session.setAttribute(FILTER_STATE_KEY, filterState);

				/**
				 * check 기관 SessionID set[12]
				 * sid : 이용자기관번호(40|8) + 단말번호(56|4)
				 */
				KBXCServer xs = (KBXCServer) session.getAttribute(Xecure2Constants.XECURE2);

				byte[] sid = new byte[12];
				System.arraycopy(m.getBytes(40, 8), 0, sid, 0, 8);
				System.arraycopy(m.getBytes(56, 4), 0, sid, 8, 4);

				if ( xs.mSid == null || !Arrays.equals(sid, xs.getSid()) ) {
//					System.arraycopy(sid, 0, xs.mSid, 0, sid.length);
					xs.setSid(sid);
				}
	
				switch (filterState.state) {
				case STATE_HANDSHAKE:
					/**
					 * change state
					 */
					filterState.state = State.STATE_MESSAGE;
					session.setAttribute(FILTER_STATE_KEY, filterState);

					break;
					
				case STATE_MESSAGE:
					/**
					 * encrypt
					 */
					m = encrypt(m, session);
					
					break;
				}
			}
			
		} catch(XCException xce) {
			adapter.addRecentError(context.getAdapterName() + " | xecure2 error - " + xce.getMessage(), xce);
			logger.error(context.getAdapterName() + " | xecure2 error - " + xce.getMessage(), xce);
			errLogger.error(context.getAdapterName() + " | xecure2 error - " + xce.getMessage(), xce);
			throw new XCException(context.getAdapterName() + " | xecure2 error - " + xce.getMessage());
		}

		/**
		 * next filter
		 */
		nextFilter.filterWrite(session, new WriteRequest(m, writeRequest.getFuture(), writeRequest.getDestination()));
    }	

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

		/**
		 * 암호화 구간(168 bytes)
		 * 카드 M/S DATA(104) + 비밀번호(16) + Filler(48)
		 */
		KBXCServer xs = (KBXCServer) session.getAttribute(Xecure2Constants.XECURE2);
		
		byte[] decrypted = xs.decrypt(m.getBytes(70, 168));
		
		ByteMessage dm = new ByteMessage(m.getBytes(0, 70))
							.append(decrypted).padding(70+168, (byte) 0x20)
							.append(m.getBytes(70+168));
		
		return dm;
	}
	
	protected ByteMessage encrypt(ByteMessage m, IoSession session) throws Exception {
		/**
		 * 암호화 여부 체크
		 */
		if ('7' != m.getByte(Xecure2Constants.ENCRYPTED_CODE_OFFSET))
			return m;

		KBXCServer xs = (KBXCServer) session.getAttribute(Xecure2Constants.XECURE2);

		/**
		 * 암호화 구간
		 * 
		 * 통장을 이용한 입금/통장정리(0210/4410, 0210/7280) [3800 bytes]
		 * - offset|109, recvLen|3616, sendLen|3691
		 * 기타 [500 bytes]
		 * - offset|110, recvLen|12, sendLen|128
		 */
		String tranType = new String(m.getBytes(28, 4));
		String tranCode = new String(m.getBytes(32, 4));
		byte[] encrypted = null;
		ByteMessage em = new ByteMessage();
		
		if ("0210".equals(tranType) && ("4410".equals(tranCode) || "7280".equals(tranCode))) {
			encrypted = xs.encrypt(m.getBytes(70+39, 3616));
			em.append(m.getBytes(0, 70+39))
			  .append(encrypted).padding(3800, (byte) 0x20);
//		} else if("0210".equals(tranType) && "4420".equals(tranCode)) {	//ATM 현금입금(카드)(0210/4420) 수용
//			encrypted = xs.encrypt(m.getBytes(70+40, 16));
//			em.append(m.getBytes(0, 70+40))
//			  .append(encrypted).padding(70+40+128, (byte) 0x20)
//			  .append(m.getBytes(70+40+128));
//		} else {
//			encrypted = xs.encrypt(m.getBytes(70+40, 12));
//			em.append(m.getBytes(0, 70+40))
//			  .append(encrypted).padding(70+40+128, (byte) 0x20)
//			  .append(m.getBytes(70+40+128));
//		}
		} else if("0210".equals(tranType) && "7281".equals(tranCode)){
			encrypted = xs.encrypt(m.getBytes(70+2, 2550));
			em.append(m.getBytes(0, 70+2))
			  .append(encrypted).padding(3000, (byte) 0x20);
		} else {
			encrypted = xs.encrypt(m.getBytes(70+40, 80));
			em.append(m.getBytes(0, 70+40))
			  .append(encrypted).padding(70+40+128, (byte) 0x20)
			  .append(m.getBytes(70+40+128));
		}

		
		return em;
	}
	
	public boolean isPollingPattern(ByteMessage m) throws Exception {
		ByteMessage bm = m;
		if ("Y".equals(context.getLlInclude())) {
			bm = (new ByteMessage())
			    .append(m.getBytes(0, context.getLlFieldOffset()))
			    .append(m.getBytes(context.getLlFieldOffset() + context.getLlFieldLength()));
		}
		
		if ((context.isPollingReceiver() && bm.getString().matches(context.getPollingResponsePattern()))
				|| (context.isPollingSender() && bm.getString().matches(context.getPollingRequestPattern())))
			return true;
		
		return false;
	}
	
	protected void setSessionState(IoSession session, String state) {
		session.setAttribute(SESSION_STATE, state);
	}
	
	protected String getSessionState(IoSession session) {
		return (String) session.getAttribute(SESSION_STATE);
	}
}
