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

import com.eactive.eai.adapter.EAIException;
import com.eactive.eai.adapter.socket2.common.CommonLib;
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 org.apache.mina.common.ByteBuffer;
import org.apache.mina.common.IdleStatus;
import org.apache.mina.common.IoSession;
import org.apache.mina.filter.codec.ProtocolDecoderAdapter;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;

public class XecureWelcomeDecoder extends ProtocolDecoderAdapter  {
	final protected Logger logger;
    final protected Logger errLogger;

	private static enum State {
		STATE_HEADER_READ, 
		STATE_PAYLOAD_READ,
		STATE_TAIL_READ;
	}

	public static class CodecState {
		State  	 	state = State.STATE_HEADER_READ; 
		byte 		securityType = '0';   // 0:plain 1:DES 2:XecureConnect
		ByteBuffer 	buffer = ByteBuffer.allocate(0).setAutoExpand(true);
		int      	payloadSize;
		byte[]		payload;
	}
	
	SocketAdapterContext context;
	ISocketAdapter adapter;

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

	    logger    = SocketLoggerManager.getInstance().getLogger(context.getAdapterGroupName());
	    errLogger = SocketLoggerManager.getInstance().getErrLogger();
	}

	public void decode(IoSession session, ByteBuffer in, ProtocolDecoderOutput out)
	throws Exception {
		if ( context.getTraceLevel() >= context.TRACE_LEVEL_TRACE ) {
			logger.debug(context.getAdapterName() + " | XecureWelcomeDecoder | decode : " + CommonLib.hexDump(in, context));
			adapter.addRecentRecvTrace(session, "XecureWelcomeDecoder | decode : " + CommonLib.hexDump(in, context));
		}
		
		CodecState codecState = (CodecState) session.getAttribute(XecureWelcomeConstants.CODEC_KEY);

		if (codecState == null) {
			codecState = new CodecState();
			session.setAttribute(XecureWelcomeConstants.CODEC_KEY, codecState);
		}

		while(in.hasRemaining()) {
			session.setIdleTime(IdleStatus.BOTH_IDLE, context.getTimeout());

			String sessionState = (String) session.getAttribute(ProtocolHandler.SESSION_STATE);
			
			if (sessionState.startsWith("STATE_WELCOME")) {
				decodeWelcomeWelcome(session, in, out, codecState);
			} else if (sessionState.startsWith("STATE_KEY_")) {
				decodeWelcomeHandshake(session, in, out, codecState);
			} else {
				decodeWelcomeMessage(session, in, out, codecState);
			}
		}
	}
	
	private void decodeWelcomeWelcome(IoSession session, ByteBuffer in, ProtocolDecoderOutput out, CodecState codecState)
	throws Exception {
		/*
		if ( context.getTraceLevel() >= context.TRACE_LEVEL_TRACE ) {
			logger.debug(context.getAdapterName() + " | XecureWelcomeDecoder | decodeWelcomeWelcome - "  + codecState.state.toString());
		}
		*/

		switch (codecState.state) {
		case STATE_HEADER_READ:
			codecState.buffer.limit(7);
			if (readHeader(session, codecState, in)) {	
				codecState.buffer.position(7).limit(codecState.payloadSize-2);
				//codecState.payloadSize = codecState.payloadSize - 7 - 2;

				if (context.isClient() && context.isPermanent() && context.isPollingUse()) {
					session.setIdleTime(IdleStatus.BOTH_IDLE, context.getPollingTimeout());
				} else {
					session.setIdleTime(IdleStatus.BOTH_IDLE, context.getIdleTimeout()); 
				}
				codecState.state = State.STATE_PAYLOAD_READ;
				session.setAttribute(ProtocolHandler.CODEC_STATE, codecState.state.toString());
			}
			break;
		case STATE_PAYLOAD_READ:
			if (readPayload(session, codecState, in)) {
				codecState.payload = new byte[codecState.buffer.limit()-7-17-2];
				codecState.buffer.position(7+17);
				codecState.buffer.get(codecState.payload);
				codecState.buffer.position(codecState.payloadSize-2).limit(codecState.payloadSize);
				
				if (context.isClient() && context.isPermanent() && context.isPollingUse()) {
					session.setIdleTime(IdleStatus.BOTH_IDLE, context.getPollingTimeout());
				} else {
					session.setIdleTime(IdleStatus.BOTH_IDLE, context.getIdleTimeout()); 
				}
				codecState.state = State.STATE_TAIL_READ;
				session.setAttribute(ProtocolHandler.CODEC_STATE, codecState.state.toString());
			}
			break;
		case STATE_TAIL_READ:
			if (readTail(session, codecState, in)) {		
				byte[] bytes = new byte[codecState.buffer.limit()];
				codecState.buffer.position(0);
				codecState.buffer.get(bytes);
				ByteMessage message = new ByteMessage(bytes);
				out.write(message);
				
				codecState.buffer.position(0).limit(7);
				if (context.isClient() && context.isPermanent() && context.isPollingUse()) {
					session.setIdleTime(IdleStatus.BOTH_IDLE, context.getPollingTimeout());
				} else {
					session.setIdleTime(IdleStatus.BOTH_IDLE, context.getIdleTimeout());
				}
				
				codecState.state = State.STATE_HEADER_READ;
				session.setAttribute(ProtocolHandler.CODEC_STATE, codecState.state.toString());
			}
			break;
		}
	}
	
	private void decodeWelcomeHandshake(IoSession session, ByteBuffer in, ProtocolDecoderOutput out, CodecState codecState)
	throws Exception {
		/*
		if ( context.getTraceLevel() >= context.TRACE_LEVEL_TRACE ) {
			logger.debug(context.getAdapterName() + " | XecureWelcomeDecoder | decodeWelcomeHandshake - " + codecState.state.toString());
		}
		*/

		switch (codecState.state) {
		case STATE_HEADER_READ:
			codecState.buffer.limit(7);
			if (readHeader(session, codecState, in)) {	
				codecState.buffer.position(7).limit(codecState.payloadSize-3);
				//codecState.payloadSize = codecState.payloadSize - 7 - 2;

				codecState.state = State.STATE_PAYLOAD_READ;
				session.setAttribute(ProtocolHandler.CODEC_STATE, codecState.state.toString());
			}
			break;
		case STATE_PAYLOAD_READ:
			if (readPayload(session, codecState, in)) {
				codecState.payload = new byte[codecState.buffer.limit()-7-23-3];
				codecState.buffer.position(7+23);
				codecState.buffer.get(codecState.payload);
				codecState.buffer.position(codecState.payloadSize-3).limit(codecState.payloadSize);
				
				codecState.state = State.STATE_TAIL_READ;
				session.setAttribute(ProtocolHandler.CODEC_STATE, codecState.state.toString());
			}
			break;
		case STATE_TAIL_READ:
			if (readTail(session, codecState, in)) {
				byte[] bytes = new byte[codecState.buffer.limit()];
				codecState.buffer.position(0);
				codecState.buffer.get(bytes);
				ByteMessage message = new ByteMessage(bytes);
				out.write(message);
				
				codecState.buffer.position(0).limit(7);
				if (context.isClient() && context.isPermanent() && context.isPollingUse()) {
					session.setIdleTime(IdleStatus.BOTH_IDLE, context.getPollingTimeout());
				} else {
					session.setIdleTime(IdleStatus.BOTH_IDLE, context.getIdleTimeout());
				}
				
				codecState.state = State.STATE_HEADER_READ;
				session.setAttribute(ProtocolHandler.CODEC_STATE, codecState.state.toString());
			}
			break;
		}
	}
	
	private void decodeWelcomeMessage(IoSession session, ByteBuffer in, ProtocolDecoderOutput out, CodecState codecState)
	throws Exception {
		/*
		if ( context.getTraceLevel() >= context.TRACE_LEVEL_TRACE ) {
			logger.debug(context.getAdapterName() + " | XecureWelcomeDecoder | decodeWelcomeMessage - "  + codecState.state.toString());
		}
		*/

		switch (codecState.state) {
		case STATE_HEADER_READ:
			codecState.buffer.limit(7);
			if (readHeader(session, codecState, in)) {	
				codecState.buffer.position(7).limit(codecState.payloadSize-2);
				//codecState.payloadSize = codecState.payloadSize - 7 - 2;

				codecState.state = State.STATE_PAYLOAD_READ;
				session.setAttribute(ProtocolHandler.CODEC_STATE, codecState.state.toString());
			}
			break;
		case STATE_PAYLOAD_READ:
			if (readPayload(session, codecState, in)) {
				codecState.payload = new byte[codecState.buffer.limit()-7-17-2];
				codecState.buffer.position(7+17);
				codecState.buffer.get(codecState.payload);
				codecState.buffer.position(codecState.payloadSize-2).limit(codecState.payloadSize);
				
				codecState.state = State.STATE_TAIL_READ;
				session.setAttribute(ProtocolHandler.CODEC_STATE, codecState.state.toString());
			}
			break;
		case STATE_TAIL_READ:
			if (readTail(session, codecState, in)) {
				byte[] bytes = new byte[codecState.buffer.limit()-2];
				codecState.buffer.position(0);
				codecState.buffer.get(bytes);

				ByteMessage message = new ByteMessage(bytes);
				out.write(message);
				
				codecState.buffer.position(0).limit(7);
				if (context.isClient() && context.isPermanent() && context.isPollingUse()) {
					session.setIdleTime(IdleStatus.BOTH_IDLE, context.getPollingTimeout());
				} else {
					session.setIdleTime(IdleStatus.BOTH_IDLE, context.getIdleTimeout());
				}
				
				codecState.state = State.STATE_HEADER_READ;
				session.setAttribute(ProtocolHandler.CODEC_STATE, codecState.state.toString());
			}
			break;
		}
	}

	private boolean readHeader(IoSession session, CodecState codecState, ByteBuffer in) throws Exception {
		if ( context.getTraceLevel() >= context.TRACE_LEVEL_TRACE ) {
			String sessionState = (String) session.getAttribute(ProtocolHandler.SESSION_STATE);
			logger.debug(context.getAdapterName() + " | " + sessionState + " - read header - " + CommonLib.hexDump(in, context));
		}
		
        if (codecState.buffer.remaining() > in.remaining()) {
        	codecState.buffer.put(in);
            return false;
        }

    	while(codecState.buffer.hasRemaining()) {
    		codecState.buffer.put(in.get());
    	}
    	
    	codecState.buffer.flip();

		try {
			byte[] header = new byte[codecState.buffer.limit()];
			codecState.buffer.get(header);
			codecState.payloadSize = Integer.parseInt(new String(header));					
			//codecState.securityType = codecState.buffer.get();
		} catch (NumberFormatException e) {
			throw new EAIException("RECEAINNA008", context.getAdapterName() + " | when read header, number format is invalid - " + e.getMessage() + " | " + CommonLib.hexDump(codecState.buffer, context));
		}

		return true;
	}

	private boolean readPayload(IoSession session, CodecState codecState, ByteBuffer in) throws Exception {
		if ( context.getTraceLevel() >= context.TRACE_LEVEL_TRACE ) {
			String sessionState = (String) session.getAttribute(ProtocolHandler.SESSION_STATE);
			logger.debug(context.getAdapterName() + " | " + sessionState + " - read payload : " + CommonLib.hexDump(in, context));
		}
		
        if (codecState.buffer.remaining() > in.remaining()) {
        	codecState.buffer.put(in);
            return false;
        }
        
    	while(codecState.buffer.hasRemaining()) {
    		codecState.buffer.put(in.get());
    	}
    	
    	codecState.buffer.flip();
		
		return true;
	}

	private boolean readTail(IoSession session, CodecState codecState, ByteBuffer in) throws Exception {
		if ( context.getTraceLevel() >= context.TRACE_LEVEL_TRACE ) {
			String sessionState = (String) session.getAttribute(ProtocolHandler.SESSION_STATE);
			logger.debug(context.getAdapterName() + " | " + sessionState + " - read tail : " + CommonLib.hexDump(in, context));
		}
		
        if (codecState.buffer.remaining() > in.remaining()) {
        	codecState.buffer.put(in);
            return false;
        }
        
    	while(codecState.buffer.hasRemaining()) {
    		codecState.buffer.put(in.get());
    	}
		
    	codecState.buffer.flip();
    	
		return true;
	}	
}
