package com.eactive.eai.adapter.socket2.protocol.inisafe2020.codec;

import com.eactive.eai.adapter.EAIException;
import com.eactive.eai.adapter.socket2.common.CommonLib;
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.ProtocolHandler;
import com.eactive.eai.adapter.socket2.protocol.inisafe2020.handshake.InisafeFilter;
import com.eactive.eai.adapter.socket2.protocol.inisafe2020.handshake.InisafeFilter.FilterState;
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 InisafeDecoder extends ProtocolDecoderAdapter  {
	protected final Logger logger;
	protected final Logger errLogger;
	
	public static final int MAX_PAYLOAD_SIZE = 100 * 1024;   // 100KB

	private static final String DECODER_STATE_KEY = InisafeDecoder.class.getName() + ".STATE";

	private enum State {
		STATE_HEADER_READ, STATE_PAYLOAD_READ;
	}

	private static class CodecState {
		State       state = State.STATE_HEADER_READ; 
		ByteBuffer 	buffer = ByteBuffer.allocate(0).setAutoExpand(true);
		int      	payloadSize;
	}
	
	SocketAdapterContext context;
	ISocketAdapter adapter;

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

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

	@SuppressWarnings("static-access")
	public void decode(IoSession session, ByteBuffer in, ProtocolDecoderOutput out) throws Exception {
		if ( context.getTraceLevel() >= context.TRACE_LEVEL_TRACE ) {
			logger.debug(context.getAdapterName() + " | InisafeDecoder | decode : " + CommonLib.hexDump(in, context));
			adapter.addRecentRecvTrace(session, "InisafeDecoder | decode : " + CommonLib.hexDump(in, context));
		}
		
		boolean isHandshakeState = false;
		FilterState filterState = (FilterState) session.getAttribute(InisafeFilter.FILTER_STATE_KEY);
		if (filterState != null && filterState.state != InisafeFilter.State.STATE_MESSAGE) {
			isHandshakeState = true;
		}
		
		if (isHandshakeState) {
			decodeHandshake(session, in, out);
		} else {
			decodeMessage(session, in, out);
		}		
	}
	
	private void decodeHandshake(IoSession session, ByteBuffer in, ProtocolDecoderOutput out) throws Exception {
		CodecState codecState = (CodecState) session.getAttribute(DECODER_STATE_KEY);
		if (codecState == null) {
			codecState = new CodecState();
			codecState.buffer.limit(InisafeConstants.COMMON_PART_LENGTH);
			session.setAttribute(DECODER_STATE_KEY, codecState);
		}

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

			switch (codecState.state) {
			case STATE_HEADER_READ:		
				if (readHeader(session, codecState, in)) {				
					codecState.buffer.position(200).limit(200+codecState.payloadSize);					
					codecState.state = State.STATE_PAYLOAD_READ;
					session.setAttribute(ProtocolHandler.CODEC_STATE, codecState.state.toString());
				}
				break;
			case STATE_PAYLOAD_READ:
				if (readPayload(session, codecState, in)) {
					byte[] individualLL = new byte[4];
					codecState.buffer.position(200).get(individualLL);
					int individualLength = Integer.parseInt(new String(individualLL));
					byte[] bytes = new byte[individualLength-4];
					codecState.buffer.position(204).get(bytes);
					ByteMessage message = new ByteMessage(bytes);
					out.write(message);
					
					codecState.buffer.position(0).limit(200);
					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;
			}
			
			session.setAttribute(ProtocolHandler.CODEC_STATE_BUFFER, CommonLib.hexDumpRead(codecState.buffer, context));
		}
	}

	private void decodeMessage(IoSession session, ByteBuffer in, ProtocolDecoderOutput out) throws Exception {
		CodecState codecState = (CodecState) session.getAttribute(DECODER_STATE_KEY);
		if (codecState == null) {
			codecState = new CodecState();
			codecState.buffer.limit(InisafeConstants.COMMON_PART_LENGTH);
			session.setAttribute(DECODER_STATE_KEY, codecState);
		}

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

			switch (codecState.state) {
			case STATE_HEADER_READ:		
				if (readHeader(session, codecState, in)) {
					/**
					 * payload size°¡ 0ÀÎ °æ¿ì
					 */				
					if (codecState.payloadSize == 0) {
						codecState.state = State.STATE_HEADER_READ;
						session.setAttribute(ProtocolHandler.CODEC_STATE, codecState.state.toString());
						
						if (context.isClient() && context.isPermanent() && context.isPollingUse()) {
							session.setIdleTime(IdleStatus.BOTH_IDLE, context.getPollingTimeout());
						} else {
							session.setIdleTime(IdleStatus.BOTH_IDLE, context.getIdleTimeout());
						}

						byte[] bytes = new byte[codecState.buffer.limit()];
						codecState.buffer.position(0).get(bytes);
						ByteMessage message = new ByteMessage(bytes);
						out.write(message);

						codecState.buffer.position(0).limit(InisafeConstants.COMMON_PART_LENGTH);
					} else {			
						codecState.buffer.position(200).limit(codecState.payloadSize+200);					
						codecState.state = State.STATE_PAYLOAD_READ;
						session.setAttribute(ProtocolHandler.CODEC_STATE, codecState.state.toString());
					}
				}
				break;
			case STATE_PAYLOAD_READ:
				if (readPayload(session, codecState, in)) {
					byte[] bytes = new byte[codecState.buffer.limit()];
					codecState.buffer.position(0).get(bytes);
					ByteMessage message = new ByteMessage(bytes);
					out.write(message);
					
					codecState.buffer.position(0).limit(InisafeConstants.COMMON_PART_LENGTH);
					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;
			}
			
			session.setAttribute(ProtocolHandler.CODEC_STATE_BUFFER, CommonLib.hexDumpRead(codecState.buffer, context));
		}
	}
	
	private boolean readHeader(IoSession session, CodecState codecState, ByteBuffer in) throws Exception {
        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[] commonLL = new byte[5];			
			codecState.buffer.position(0).get(commonLL);
			codecState.payloadSize = Integer.parseInt(new String(commonLL));
			codecState.buffer.position(0);
		} catch (NumberFormatException e) {
//			SMSLogger.loggingSMSWithCode(context.getAdapterName(), "RECEAINNA008");
			SMSLogger.loggingSMS4Soc(context.getAdapterName(), context.getDescription(), "RECEAINNA008");
			throw new EAIException("RECEAINNA008", context.getAdapterName() +
					"when read header, number format is invalid - " 
					+ e.getMessage() + " | " + CommonLib.hexDump(codecState.buffer, context));
		}
		
		if (codecState.payloadSize > MAX_PAYLOAD_SIZE) {
			throw new Exception("max payload size(" + MAX_PAYLOAD_SIZE + ") exceeded : " + codecState.payloadSize);
		}

		return true;
	}

	private boolean readPayload(IoSession session, CodecState codecState, ByteBuffer in) throws Exception {
		if (codecState.buffer.remaining() > in.remaining()) {
        	codecState.buffer.put(in);
            return false;
        }
        
    	while(codecState.buffer.hasRemaining()) {
    		codecState.buffer.put(in.get());
    	}
		
		return true;
	}
}
