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

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 FixedPayloadDecoder extends ProtocolDecoderAdapter  {
	final protected Logger logger;
    final protected Logger errLogger;
	
	public static final int MAX_PAYLOAD_SIZE = 25 * 1024 * 1024;   // 25MB

	private static final String DECODER_STATE_KEY = FixedPayloadDecoder.class.getName() + ".STATE";
	private static final String N_DECODER_STATE_KEY = "N" + FixedPayloadDecoder.class.getName() + ".STATE";

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

	public FixedPayloadDecoder(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() >= SocketAdapterContext.TRACE_LEVEL_TRACE ) {
			logger.debug(context.getAdapterName() + " | FixedPayloadDecoder | decode : " + CommonLib.hexDump(in, context));
			adapter.addRecentRecvTrace(session, "FixedPayloadDecoder | decode : " + CommonLib.hexDump(in, context));
		}
		
		CodecState codecState = (CodecState) session.getAttribute(DECODER_STATE_KEY);

		if ( (codecState == null) && (context.getRecvPayloadLength() > 0) ) {
			codecState = new CodecState();
			codecState.buffer.limit(context.getRecvPayloadLength());
			//codecState.payloadSize = context.getPayloadLength();
			session.setAttribute(DECODER_STATE_KEY, codecState);
		}
		
		/**
		 * undefined recvPayloadLength; it must negative value
		 */
		if (context.getRecvPayloadLength() < 0) {
			codecState = (CodecState) session.getAttribute(N_DECODER_STATE_KEY);
			
			if (codecState == null) {
				codecState = new CodecState();
			}
			
			if (codecState.beforeFirst) {
				byte[] hdr = new byte[49];
				in.position(0);
				in.get(hdr);
				ByteMessage bm = new ByteMessage(hdr);
				String trCd = new String(bm.getBytes(3, 6));
				int cnt = Integer.parseInt(new String(bm.getBytes(47, 2)));
				
				int recvPayloadLength = 49;
				if ("210905".equals(trCd) || "611204".equals(trCd)) { // 증권수탁배치|펀드원장정보
					recvPayloadLength = 49 + (1200 + 8) * cnt;
//					context.setRecvPayloadLength(recvPayloadLength);
					codecState.buffer.limit(recvPayloadLength);
				} else if ("611202".equals(trCd)) {
					recvPayloadLength = 49 + (1100 + 8) * cnt;
//					context.setRecvPayloadLength(recvPayloadLength);
					codecState.buffer.limit(recvPayloadLength);
				} else {	// 개시 및 종료 전문 등이 올수 있슴, Transaction Code 관계없이 49bytes
//					context.setRecvPayloadLength(49);
					codecState.buffer.limit(recvPayloadLength);
				}
				
				in.position(0);
				codecState.payloadSize = recvPayloadLength;
				session.setAttribute(N_DECODER_STATE_KEY, codecState);
			}
		}

		while(in.hasRemaining()) {
			session.setIdleTime(IdleStatus.BOTH_IDLE, context.getTimeout());
			if (readPayload(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);
				
				if (context.getRecvPayloadLength() < 0) {
					codecState.buffer.position(0).limit(codecState.payloadSize);
				} else {
					codecState.buffer.position(0).limit(context.getRecvPayloadLength());
				}

				if (context.isClient() && context.isPermanent() && context.isPollingUse()) {
					session.setIdleTime(IdleStatus.BOTH_IDLE, context.getPollingTimeout());
				} else {
					session.setIdleTime(IdleStatus.BOTH_IDLE, context.getIdleTimeout());
				}
			}
			session.setAttribute(ProtocolHandler.CODEC_STATE_BUFFER, CommonLib.hexDumpRead(codecState.buffer, context));
		}
	}

	private boolean readPayload(IoSession session, CodecState codecState, ByteBuffer in) throws Exception {
		/**
		 * send size 만큼 와도 message를 생성해서 넘긴다.
		 * SA 또는 SN 으로 시작하는 PING(ACK) 전문이 올 수 있다고 한다.
		 * concern undefined payload length; negative value
		 */
		if ((context.getSendPayloadLength() > 0) 
				&& (context.getSendPayloadLength() < context.getRecvPayloadLength())) {
			
	        if (codecState.buffer.position() + in.remaining() == context.getSendPayloadLength()) {
	        	codecState.buffer.limit(context.getSendPayloadLength());	        	
	        	while(codecState.buffer.hasRemaining()) {
	        		codecState.buffer.put(in.get());
	        	}	        	
	    		return true;
	        }
		}
        
        /**
         * 읽으려는 size 만큼 아직 data가 오지 못했다.
         */
        if (codecState.buffer.remaining() > in.remaining()) {
        	codecState.buffer.put(in);
        	codecState.beforeFirst = false;
            return false;
        }
        
        /**
         * 읽으려는 size 에 도달했다.
         */
    	while(codecState.buffer.hasRemaining()) {
    		codecState.buffer.put(in.get());
    		codecState.beforeFirst = true;
    	}
		
		return true;
	}
}
