package com.eactive.eai.adapter.socket2.protocol.cisco.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 CiscoDecoder extends ProtocolDecoderAdapter {
	protected final Logger logger;
	protected final Logger errLogger;

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

	private enum State {
		STATE_HEADER_READ, 
		STATE_PAYLOAD_READ;
	}

	private static class CodecState {
		State   	state = State.STATE_HEADER_READ;
		ByteBuffer 	partBuffer = ByteBuffer.allocate(CiscoConstants.CISCO_PACKET_SIZE);
		ByteBuffer  wholeBuffer = ByteBuffer.allocate(CiscoConstants.CISCO_PACKET_SIZE).setAutoExpand(true);
		int	    	payloadSize;
		boolean 	last;		
	}
	
	SocketAdapterContext context;
	ISocketAdapter adapter;
	
	public CiscoDecoder(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() + " | CiscoDecoder | decode : " + CommonLib.hexDump(in, context));
			adapter.addRecentRecvTrace(session, "CiscoDecoder | decode : " + CommonLib.hexDump(in, context));
		}
		
		CodecState codecState = (CodecState) session.getAttribute(DECODER_STATE_KEY);

		if (codecState == null) {
			codecState = new CodecState();
			codecState.partBuffer.limit(6);
			session.setAttribute(DECODER_STATE_KEY, codecState);
		}

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

			switch(codecState.state) {
			case STATE_HEADER_READ :
				// try to read first image
				if (readHeader(session, codecState, in)) {
					codecState.partBuffer.position(0).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_PAYLOAD_READ;
					session.setAttribute(ProtocolHandler.CODEC_STATE, codecState.state.toString());
				}
				break;
			case STATE_PAYLOAD_READ :
				if (readPayload(session, codecState, in)) {
					codecState.wholeBuffer.put(codecState.partBuffer);
					if (codecState.last) {
						byte[] bytes = new byte[codecState.wholeBuffer.position()];
						codecState.wholeBuffer.position(0);
						codecState.wholeBuffer.get(bytes);
						ByteMessage m = new ByteMessage(bytes);
						out.write(m);
						
						codecState.wholeBuffer.position(0).limit(6);
						codecState.last = false;
					} 
					codecState.partBuffer.position(0).limit(6);
					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 ) {
			logger.debug(context.getAdapterName() + " | read header : " + CommonLib.hexDump(in));
		}
		*/
		
        if (codecState.partBuffer.remaining() > in.remaining()) {
        	codecState.partBuffer.put(in);
            return false;
        }
        
    	while(codecState.partBuffer.hasRemaining()) {
    		codecState.partBuffer.put(in.get());
    	}
        
		if ((codecState.partBuffer.get(0) & 0xD7) == 0
		   || codecState.partBuffer.get(1) != 0x4A
		   || codecState.partBuffer.get(5) != 0x00) {
			throw new Exception(context.getAdapterName() + " | magic number is wrong.");
		}

		codecState.payloadSize = ((int) ((codecState.partBuffer.get(2)) & 0xFF) * 256)
				                 + ((int) ((codecState.partBuffer.get(3)) & 0xFF));
		codecState.last = codecState.partBuffer.get(4) == 0x00;

		return true;
	}

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