package com.hmsoft.remote.rmi.codec;

import java.io.IOException;
import java.io.InputStream;
import org.apache.log4j.Logger;
import com.hmsoft.remote.rmi.buffer.ChannelBuffer;
import com.hmsoft.remote.rmi.buffer.ChannelBufferInputStream;
import com.hmsoft.remote.rmi.buffer.ChannelBufferOutputStream;
import com.hmsoft.remote.rmi.common.io.Bytes;
import com.hmsoft.remote.rmi.common.io.StreamUtils;
import com.hmsoft.remote.rmi.common.serialize.ObjectInput;
import com.hmsoft.remote.rmi.common.serialize.ObjectOutput;
import com.hmsoft.remote.rmi.common.serialize.Serialization;
import com.hmsoft.remote.rmi.common.serialize.support.hessian.Hessian2Serialization;
import com.hmsoft.remote.rmi.common.utils.StringUtils;
import com.hmsoft.remote.rmi.exception.RemotingException;
import com.hmsoft.remote.rmi.future.TransFuture;
import com.hmsoft.remote.rmi.model.Request;
import com.hmsoft.remote.rmi.model.Response;
import com.hmsoft.remote.rmi.transport.ExceedPayloadLimitException;
import com.hmsoft.remote.rmi.transport.NettyChannel;

/**
 * 
 * @author zq
 *
 */
public class TransCodec implements Codec {
	
	private static final Logger logger = Logger.getLogger(TransCodec.class);
	
	protected static final int HEADER_LENGTH = 16;
	protected static final short MAGIC = (short) 0xdcc;
    protected static final byte MAGIC_HIGH = Bytes.short2bytes(MAGIC)[0];
    protected static final byte MAGIC_LOW = Bytes.short2bytes(MAGIC)[1];
    protected static final byte FLAG_MGR = (byte) 0x10;
    protected static final byte FLAG_EVENT = (byte) 0x20;
    protected static final byte FLAG_TWOWAY = (byte) 0x40;
    protected static final byte FLAG_REQUEST = (byte) 0x80;
    protected static final byte FLAG_CLI = (byte) 0x120;
    
    @Override
    public void encode(NettyChannel channel, ChannelBuffer buffer, Object msg) throws IOException {
        if (msg instanceof Request) {
            encodeRequest(channel, buffer, (Request) msg);
        } else if (msg instanceof Response) {
            encodeResponse(channel, buffer, (Response) msg);
        } else {
        	//TODO ..
        }
    }

    @Override
    public Object decode(NettyChannel channel, ChannelBuffer buffer) throws IOException {
        int readable = buffer.readableBytes();
        byte[] header = new byte[Math.min(readable, HEADER_LENGTH)];
        buffer.readBytes(header);
        return decode(channel, buffer, readable, header);
    }
    
    protected Object decode(NettyChannel channel, ChannelBuffer buffer, int readable, byte[] header) throws IOException {
        // check magic number.
        if (readable > 0 && header[0] != MAGIC_HIGH
                || readable > 1 && header[1] != MAGIC_LOW) {
            int length = header.length;
            if (header.length < readable) {
                header = Bytes.copyOf(header, readable);
                buffer.readBytes(header, length, readable - length);
            }
            for (int i = 1; i < header.length - 1; i++) {
                if (header[i] == MAGIC_HIGH && header[i + 1] == MAGIC_LOW) {
                    buffer.readerIndex(buffer.readerIndex() - header.length + i);
                    header = Bytes.copyOf(header, i);
                    break;
                }
            }
            //return super.decode(channel, buffer, readable, header);
            //TODO ...
        }
        // check length.
        if (readable < HEADER_LENGTH) {
            return DecodeResult.NEED_MORE_INPUT;
        }

        // get data length.
        int len = Bytes.bytes2int(header, 12);//12~15 bytes
        //checkPayload(channel, len); 暂时不做文件大小限制！！！！

        int tt = len + HEADER_LENGTH;
        if (readable < tt) {
            return DecodeResult.NEED_MORE_INPUT;
        }

        // limit input stream.
        ChannelBufferInputStream is = new ChannelBufferInputStream(buffer, len);

        try {
            return decodeBody(channel, is, header);
        } finally {
            if (is.available() > 0) {
                try {
                    logger.warn("Skip input stream " + is.available());
                    StreamUtils.skipUnusedStream(is);
                } catch (IOException e) {
                    logger.warn(e.getMessage(), e);
                }
            }
        }
    }

    protected Object decodeBody(NettyChannel channel, InputStream is, byte[] header) throws IOException {
        byte flag = header[2];
        Serialization s = new Hessian2Serialization();
        ObjectInput in = s.deserialize(channel.getUrl(), is);
        // get request id.
        long id = Bytes.bytes2long(header, 4);
        if ((flag & FLAG_REQUEST) == 0) {
            // decode response.
            Response res = new Response(id);
            if ((flag & FLAG_EVENT) != 0) {
                res.setEvent(Response.HEARTBEAT_EVENT);
            }
            // get status.
            byte status = header[3];
            res.setStatus(status);
            if (status == Response.OK) {
                try {
                    Object data;
                    if (res.isHeartbeat()) {
                        data = decodeHeartbeatData(channel, in);
                    } else if (res.isEvent()) {
                        data = decodeEventData(channel, in);
                    } else {
                        data = decodeResponseData(channel, in, getRequestData(id));
                    }
                    res.setResult(data);
                } catch (Throwable t) {
                    res.setStatus(Response.CLIENT_ERROR);
                    res.setErrMsg(StringUtils.toString(t));
                }
            } else {
                res.setErrMsg(in.readUTF());
            }
            return res;
        } else {
            // decode request.
            Request req = new Request(id);
            req.setVersion("1.0");
            req.setTwoWay((flag & FLAG_TWOWAY) != 0);
            req.setRedirectMgr((flag & FLAG_MGR) != 0);
            req.setRedirectCli((flag & FLAG_CLI) != 0);
            if ((flag & FLAG_EVENT) != 0) {
                req.setEvent(Request.HEARTBEAT_EVENT);
            }
            try {
                Object data;
                if (req.isHeartbeat()) {
                    data = decodeHeartbeatData(channel, in);
                } else if (req.isEvent()) {
                    data = decodeEventData(channel, in);
                } else {
                    data = decodeRequestData(channel, in);
                }
                req.setData(data);
            } catch (Throwable t) {
                // bad request
                req.setBroken(true);
                req.setData(t);
            }
            return req;
        }
    }
    
    protected Object getRequestData(long id) {
    	TransFuture future = TransFuture.getFuture(id);
        if (future == null)
            return null;
        Request req = future.getRequest();
        if (req == null)
            return null;
        return req.getData();
    }
    
    protected void encodeRequest(NettyChannel channel, ChannelBuffer buffer, Request req) throws IOException {
    	Serialization serialization = new Hessian2Serialization();
        // header.
        byte[] header = new byte[HEADER_LENGTH];
        // set magic number.
        Bytes.short2bytes(MAGIC, header);

        // set request and serialization flag.
        header[2] = (byte) (FLAG_REQUEST | serialization.getContentTypeId());

        //注意业务设置req过程中,只能设置isRedirectMgr,isTwoWay,isEvent,isRedirectCli其中之一！！
        if (req.isRedirectMgr()) header[2] |= FLAG_MGR;
        if (req.isTwoWay()) header[2] |= FLAG_TWOWAY;
        if (req.isEvent()) header[2] |= FLAG_EVENT;
        if (req.isRedirectCli()) header[2] |= FLAG_CLI;
        
        // set request id.
        Bytes.long2bytes(req.getId(), header, 4);

        // encode request data.
        int savedWriteIndex = buffer.writerIndex();
        buffer.writerIndex(savedWriteIndex + HEADER_LENGTH);
        ChannelBufferOutputStream bos = new ChannelBufferOutputStream(buffer);
        ObjectOutput out = serialization.serialize(channel.getUrl(), bos);
        if (req.isEvent()) {
            encodeEventData(channel, out, req.getData());
        } else {
            encodeRequestData(channel, out, req.getData(), req.getVersion());
        }
        out.flushBuffer();
        /*if (out instanceof Cleanable) {
            ((Cleanable) out).cleanup();
        }*/
        bos.flush();
        bos.close();
        int len = bos.writtenBytes();
        //checkPayload(channel, len);
        Bytes.int2bytes(len, header, 12);

        // write
        buffer.writerIndex(savedWriteIndex);
        buffer.writeBytes(header); // write header.
        buffer.writerIndex(savedWriteIndex + HEADER_LENGTH + len);
    }

    protected void encodeResponse(NettyChannel channel, ChannelBuffer buffer, Response res) throws IOException {
        int savedWriteIndex = buffer.writerIndex();
        try {
            Serialization serialization = new Hessian2Serialization();
            // header.
            byte[] header = new byte[HEADER_LENGTH];
            // set magic number.
            Bytes.short2bytes(MAGIC, header);
            // set request and serialization flag.
            header[2] = serialization.getContentTypeId();
            if (res.isHeartbeat()) header[2] |= FLAG_EVENT;
            // set response status.
            byte status = res.getStatus();
            header[3] = status;
            // set request id.
            Bytes.long2bytes(res.getId(), header, 4);

            buffer.writerIndex(savedWriteIndex + HEADER_LENGTH);
            ChannelBufferOutputStream bos = new ChannelBufferOutputStream(buffer);
            ObjectOutput out = serialization.serialize(channel.getUrl(), bos);
            // encode response data or error message.
            if (status == Response.OK) {
                if (res.isHeartbeat()) {
                    encodeHeartbeatData(channel, out, res.getResult());
                } else {
                    encodeResponseData(channel, out, res.getResult(), res.getVersion());
                }
            } else out.writeUTF(res.getErrMsg());
            out.flushBuffer();
            /*if (out instanceof Cleanable) {
                ((Cleanable) out).cleanup();
            }*/
            bos.flush();
            bos.close();

            int len = bos.writtenBytes();
            //checkPayload(channel, len);  暂不限制大小!
            Bytes.int2bytes(len, header, 12);
            // write
            buffer.writerIndex(savedWriteIndex);
            buffer.writeBytes(header); // write header.
            buffer.writerIndex(savedWriteIndex + HEADER_LENGTH + len);
        } catch (Throwable t) {
            // clear buffer
            buffer.writerIndex(savedWriteIndex);
            // send error message to Consumer, otherwise, Consumer will wait till timeout.
            if (!res.isEvent() && res.getStatus() != Response.BAD_RESPONSE) {
                Response r = new Response(res.getId(), res.getVersion());
                r.setStatus(Response.BAD_RESPONSE);

                if (t instanceof ExceedPayloadLimitException) {
                    logger.warn(t.getMessage(), t);
                    try {
                        r.setErrMsg(t.getMessage());
                        channel.send(r);
                        return;
                    } catch (RemotingException e) {
                        logger.warn("Failed to send bad_response info back: " + t.getMessage() + ", cause: " + e.getMessage(), e);
                    }
                } else {
                    // FIXME log error message in Codec and handle in caught() of IoHanndler?
                    logger.warn("Fail to encode response: " + res + ", send bad_response info instead, cause: " + t.getMessage(), t);
                    try {
                        r.setErrMsg("Failed to send response: " + res + ", cause: " + StringUtils.toString(t));
                        channel.send(r);
                        return;
                    } catch (RemotingException e) {
                        logger.warn("Failed to send bad_response info back: " + res + ", cause: " + e.getMessage(), e);
                    }
                }
            }

            // Rethrow exception
            if (t instanceof IOException) {
                throw (IOException) t;
            } else if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else if (t instanceof Error) {
                throw (Error) t;
            } else {
                throw new RuntimeException(t.getMessage(), t);
            }
        }
    }
    
    protected Object decodeHeartbeatData(NettyChannel channel, ObjectInput in) throws IOException {
        try {
            return in.readObject();
        } catch (ClassNotFoundException e) {
            throw new IOException(StringUtils.toString("Read object failed.", e));
        }
    }

    protected Object decodeEventData(NettyChannel channel, ObjectInput in) throws IOException {
        try {
            return in.readObject();
        } catch (ClassNotFoundException e) {
            throw new IOException(StringUtils.toString("Read object failed.", e));
        }
    }
    
    protected Object decodeResponseData(ObjectInput in) throws IOException {
        try {
            return in.readObject();
        } catch (ClassNotFoundException e) {
            throw new IOException(StringUtils.toString("Read object failed.", e));
        }
    }
    
    protected Object decodeResponseData(NettyChannel channel, ObjectInput in) throws IOException {
        return decodeResponseData(in);
    }


    protected Object decodeResponseData(NettyChannel channel, ObjectInput in, Object requestData) throws IOException {
        return decodeResponseData(channel, in);
    }

    protected Object decodeRequestData(NettyChannel channel, ObjectInput in) throws IOException {
        return decodeRequestData(in);
    }
    
    protected Object decodeRequestData(ObjectInput in) throws IOException {
        try {
            return in.readObject();
        } catch (ClassNotFoundException e) {
            throw new IOException(StringUtils.toString("Read object failed.", e));
        }
    }

    private void encodeEventData(ObjectOutput out, Object data) throws IOException {
        out.writeObject(data);
    }
    
    private void encodeEventData(NettyChannel channel, ObjectOutput out, Object data) throws IOException {
        encodeEventData(out, data);
    }

    protected void encodeRequestData(ObjectOutput out, Object data) throws IOException {
        out.writeObject(data);
    }
    
    protected void encodeRequestData(NettyChannel channel, ObjectOutput out, Object data, String version) throws IOException {
        encodeRequestData(out, data);
    }

    protected void encodeHeartbeatData(ObjectOutput out, Object data) throws IOException {
        encodeEventData(out, data);
    }
    
    protected void encodeHeartbeatData(NettyChannel channel, ObjectOutput out, Object data) throws IOException {
        encodeHeartbeatData(out, data);
    }

    protected void encodeResponseData(ObjectOutput out, Object data) throws IOException {
        out.writeObject(data);
    }
    
    protected void encodeResponseData(NettyChannel channel, ObjectOutput out, Object data) throws IOException {
        encodeResponseData(out, data);
    }

    protected void encodeResponseData(NettyChannel channel, ObjectOutput out, Object data, String version) throws IOException {
        encodeResponseData(out, data);
    }

}
