package com.eactive.eai.adapter.socket2.protocol.cert.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.ISocketAdapter;
import com.eactive.eai.adapter.socket2.protocol.ProtocolHandler;
import com.eactive.eai.adapter.socket2.protocol.cert.message.CertMessage;
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 CertDecoder extends ProtocolDecoderAdapter {
	protected final Logger logger;
	protected final Logger errLogger;

	SocketAdapterContext context;
	ISocketAdapter adapter;

	private enum State {
		STATE_LL_READ, 
		STATE_PAYLOAD_READ
	}

	public static class CodecState {
		State      state = State.STATE_LL_READ;
		ByteBuffer buffer = ByteBuffer.allocate(0).setAutoExpand(true);
		int        payloadSize;
	}

	public CertDecoder(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() + " | CertDecoder | decode : " + CommonLib.hexDump(in, context));
			adapter.addRecentRecvTrace(session, "CertDecoder | decode : " + CommonLib.hexDump(in, context));
		}

		CodecState codecState = (CodecState) session.getAttribute(CertConstants.CODEC_KEY);

		if (codecState == null) {
			codecState = new CodecState();
			codecState.buffer.limit(CertMessage.LL_LENGTH);
			session.setAttribute(CertConstants.CODEC_KEY, codecState);
		}

		while (in.hasRemaining()) {
			session.setIdleTime(IdleStatus.BOTH_IDLE, context.getTimeout());
			
			switch (codecState.state) {
			case STATE_LL_READ:
				if (readHeader(codecState, in)) {
					codecState.buffer.position(CertMessage.LL_LENGTH).limit(CertMessage.LL_LENGTH + 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(codecState, in)) {
					byte[] bytes = new byte[codecState.buffer.limit()];
					codecState.buffer.get(bytes);

					CertMessage message = new CertMessage(bytes);
					out.write(message);
					
					codecState.buffer.position(0).limit(CertMessage.LL_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_LL_READ;
					session.setAttribute(ProtocolHandler.CODEC_STATE, codecState.state.toString());
				}
				break;
			default :
			}
		}
	}

	private boolean readHeader(CodecState codecState, ByteBuffer in) throws Exception {
		/*
		if (context.getTraceLevel() >= context.TRACE_LEVEL_TRACE) {
			logger.debug(context.getAdapterName() + " | <RECV> read header : " + CommonLib.hexDump(in));
		}
		*/

		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[CertMessage.LL_LENGTH];
			codecState.buffer.get(header);
			codecState.payloadSize = Integer.parseInt(new String(header));
		} 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(CodecState codecState, ByteBuffer in) throws Exception {
		/*
		if (context.getTraceLevel() >= context.TRACE_LEVEL_TRACE) {
			logger.debug(context.getAdapterName() + " | <RECV> read payload : " + CommonLib.hexDump(in));
		}
		*/

		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;
	}
}
