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


import java.util.Arrays;

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;

import com.eactive.eai.adapter.EAIException;
import com.eactive.eai.adapter.socket2.common.CommonLib;
import com.eactive.eai.adapter.socket2.common.HexUtil;
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;

public class DelimiterDecoder extends ProtocolDecoderAdapter  {
	protected final Logger logger;
	protected final Logger errLogger;
	
	public static final int MAX_PAYLOAD_SIZE = 25 * 1024 * 1024;   // 25MB

	private static final String DECODER_STATE_KEY = DelimiterDecoder.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 DelimiterDecoder(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 {
		CodecState codecState = (CodecState) session.getAttribute(DECODER_STATE_KEY);
		if (codecState == null) {
			codecState = new CodecState();
			byte[] delimiter = HexUtil.string2Hex(context.getDelimiter()); 
			codecState.buffer.limit(delimiter.length);
			session.setAttribute(DECODER_STATE_KEY, codecState);
		}

		if ( context.getTraceLevel() >= context.TRACE_LEVEL_TRACE ) {
			StringBuilder sb = new StringBuilder();
			sb.append("DelimiterDecoder");
			if (codecState.state == State.STATE_PAYLOAD_READ) {
				sb.append("(" + codecState.payloadSize + ")");
			}
			sb.append(" | decode : " + CommonLib.hexDump(in, context));
			logger.debug(sb.toString());
			adapter.addRecentRecvTrace(session, sb.toString());
		}

		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) {
						throw new EAIException("RECEAINNA008", context.getAdapterName() +
								" (" + context.getLlFieldType() + "," + context.getLlFieldOffset() + "," + context.getLlFieldLength() +
								") | when read header, ll length field is invalid | " + CommonLib.hexDump(codecState.buffer, context));						
					} 
						
					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);
					codecState.buffer.get(bytes);
					ByteMessage message = new ByteMessage(bytes);
					out.write(message);
					byte[] delimiter = HexUtil.string2Hex(context.getDelimiter()); 

					codecState.buffer.position(0).limit(delimiter.length);
					
					//by short11
					//polling.role 이 Server 가 sender일 경우 오류가 있었음
					if (context.isPollingUse() && context.isPollingSender() && context.isPermanent()) {
						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;
			default :
			}
			
			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.payloadSize = 0;

		return true;
	}

	private boolean readPayload(IoSession session, CodecState codecState, ByteBuffer in) throws Exception {
		byte[] delimiter = HexUtil.string2Hex(context.getDelimiter()); 
		
		if (delimiter.length >1 ){
			ByteMessage dst = new ByteMessage();
			int position = codecState.buffer.position();
			for(int i=delimiter.length-1;i >0;i--){
				dst.append(codecState.buffer.position(position-i).get());
			}
			while(in.hasRemaining()){
				byte b = in.get();
				dst.append(b);
				if (Arrays.equals(dst.getBytes(), delimiter)){
					codecState.buffer.put(b);
					int lposition = codecState.buffer.position();
					codecState.buffer.limit(lposition - delimiter.length);
					return true;
				}else{
					codecState.buffer.put(b);
				}
				dst =dst.exclude(0, 1);
			}			
		}else{
			while(in.hasRemaining()){
				byte b = in.get();
				if (b == delimiter[0]){
					return true;
				}else{
					codecState.buffer.put(b);
				}
			}
		}
		return false;

	}

	
}
