/*
 * Copyright 1999-2011 Alibaba Group.
 *  
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *  
 *      http://www.apache.org/licenses/LICENSE-2.0
 *  
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alibaba.dubbo.common.serialize.support.dubbo;

import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UTFDataFormatException;

import com.alibaba.dubbo.common.serialize.DataInput;

/**
 * Default DataInput impl.
 * Not thread-safe.
 *
 * @author qian.lei
 */

public class GenericDataInput implements DataInput, GenericDataFlags {
    private static final String EMPTY_STRING = "";

    private static final byte[] EMPTY_BYTES = {};

    private final InputStream mInput;

    private final byte[] mBuffer;

    private int mRead = 0;

    private int mPosition = 0;

    public GenericDataInput(InputStream is) {
        this(is, 1024);
    }

    public GenericDataInput(InputStream is, int buffSize) {
        mInput = is;
        mBuffer = new byte[buffSize];
    }

    public boolean readBool() throws IOException {
        byte b = read0();

        switch (b) {
            case VARINT_0:
                return false;
            case VARINT_1:
                return true;
            default:
                throw new IOException("Tag error, expect BYTE_TRUE|BYTE_FALSE, but get " + b);
        }
    }

    public byte readByte() throws IOException {
        byte b = read0();

        switch (b) {
            case VARINT8:
                return read0();
            case VARINT_0:
                return 0;
            case VARINT_1:
                return 1;
            case VARINT_2:
                return 2;
            case VARINT_3:
                return 3;
            case VARINT_4:
                return 4;
            case VARINT_5:
                return 5;
            case VARINT_6:
                return 6;
            case VARINT_7:
                return 7;
            case VARINT_8:
                return 8;
            case VARINT_9:
                return 9;
            case VARINT_A:
                return 10;
            case VARINT_B:
                return 11;
            case VARINT_C:
                return 12;
            case VARINT_D:
                return 13;
            case VARINT_E:
                return 14;
            case VARINT_F:
                return 15;
            case VARINT_10:
                return 16;
            case VARINT_11:
                return 17;
            case VARINT_12:
                return 18;
            case VARINT_13:
                return 19;
            case VARINT_14:
                return 20;
            case VARINT_15:
                return 21;
            case VARINT_16:
                return 22;
            case VARINT_17:
                return 23;
            case VARINT_18:
                return 24;
            case VARINT_19:
                return 25;
            case VARINT_1A:
                return 26;
            case VARINT_1B:
                return 27;
            case VARINT_1C:
                return 28;
            case VARINT_1D:
                return 29;
            case VARINT_1E:
                return 30;
            case VARINT_1F:
                return 31;
            default:
                throw new IOException("Tag error, expect VARINT, but get " + b);
        }
    }

    public short readShort() throws IOException {
        return (short) readVarint32();
    }

    public int readInt() throws IOException {
        return readVarint32();
    }

    public long readLong() throws IOException {
        return readVarint64();
    }

    public float readFloat() throws IOException {
        return Float.intBitsToFloat(readVarint32());
    }

    public double readDouble() throws IOException {
        return Double.longBitsToDouble(readVarint64());
    }

    public String readUTF() throws IOException {
        byte b = read0();

        switch (b) {
            case OBJECT_BYTES:
                int len = readUInt();
                StringBuilder sb = new StringBuilder();

                for (int i = 0; i < len; i++) {
                    byte b1 = read0();
                    if ((b1 & 0x80) == 0) {
                        sb.append((char) b1);
                    } else if ((b1 & 0xE0) == 0xC0) {
                        byte b2 = read0();
                        sb.append((char) (((b1 & 0x1F) << 6) | (b2 & 0x3F)));
                    } else if ((b1 & 0xF0) == 0xE0) {
                        byte b2 = read0(), b3 = read0();
                        sb.append((char) (((b1 & 0x0F) << 12) | ((b2 & 0x3F) << 6) | (b3 & 0x3F)));
                    } else
                        throw new UTFDataFormatException("Bad utf-8 encoding at " + b1);
                }
                return sb.toString();
            case OBJECT_NULL:
                return null;
            case OBJECT_DUMMY:
                return EMPTY_STRING;
            default:
                throw new IOException("Tag error, expect BYTES|BYTES_NULL|BYTES_EMPTY, but get " + b);
        }
    }

    public byte[] readBytes() throws IOException {
        byte b = read0();

        switch (b) {
            case OBJECT_BYTES:
                return read0(readUInt());
            case OBJECT_NULL:
                return null;
            case OBJECT_DUMMY:
                return EMPTY_BYTES;
            default:
                throw new IOException("Tag error, expect BYTES|BYTES_NULL|BYTES_EMPTY, but get " + b);
        }
    }

    public int readUInt() throws IOException {
        byte tmp = read0();
        if (tmp < 0)
            return tmp & 0x7f;

        int ret = tmp & 0x7f;
        if ((tmp = read0()) < 0) {
            ret |= (tmp & 0x7f) << 7;
        } else {
            ret |= tmp << 7;
            if ((tmp = read0()) < 0) {
                ret |= (tmp & 0x7f) << 14;
            } else {
                ret |= tmp << 14;
                if ((tmp = read0()) < 0) {
                    ret |= (tmp & 0x7f) << 21;
                } else {
                    ret |= tmp << 21;
                    ret |= (read0() & 0x7f) << 28;
                }
            }
        }
        return ret;
    }

    protected byte read0() throws IOException {
        if (mPosition == mRead)
            fillBuffer();

        return mBuffer[mPosition++];
    }

    protected byte[] read0(int len) throws IOException {
        int rem = mRead - mPosition;
        byte[] ret = new byte[len];
        if (len <= rem) {
            System.arraycopy(mBuffer, mPosition, ret, 0, len);
            mPosition += len;
        } else {
            System.arraycopy(mBuffer, mPosition, ret, 0, rem);
            mPosition = mRead;

            len -= rem;
            int read, pos = rem;

            while (len > 0) {
                read = mInput.read(ret, pos, len);
                if (read == -1)
                    throw new EOFException();
                pos += read;
                len -= read;
            }
        }
        return ret;
    }

    private int readVarint32() throws IOException {
        byte b = read0();

        switch (b) {
            case VARINT8:
                return read0();
            case VARINT16: {
                byte b1 = read0(), b2 = read0();
                return (short) ((b1 & 0xff) | ((b2 & 0xff) << 8));
            }
            case VARINT24: {
                byte b1 = read0(), b2 = read0(), b3 = read0();
                int ret = (b1 & 0xff) | ((b2 & 0xff) << 8) | ((b3 & 0xff) << 16);
                if (b3 < 0)
                    return ret | 0xff000000;
                return ret;
            }
            case VARINT32: {
                byte b1 = read0(), b2 = read0(), b3 = read0(), b4 = read0();
                return ((b1 & 0xff) |
                        ((b2 & 0xff) << 8) |
                        ((b3 & 0xff) << 16) |
                        ((b4 & 0xff) << 24));
            }
            case VARINT_NF:
                return -15;
            case VARINT_NE:
                return -14;
            case VARINT_ND:
                return -13;
            case VARINT_NC:
                return -12;
            case VARINT_NB:
                return -11;
            case VARINT_NA:
                return -10;
            case VARINT_N9:
                return -9;
            case VARINT_N8:
                return -8;
            case VARINT_N7:
                return -7;
            case VARINT_N6:
                return -6;
            case VARINT_N5:
                return -5;
            case VARINT_N4:
                return -4;
            case VARINT_N3:
                return -3;
            case VARINT_N2:
                return -2;
            case VARINT_N1:
                return -1;
            case VARINT_0:
                return 0;
            case VARINT_1:
                return 1;
            case VARINT_2:
                return 2;
            case VARINT_3:
                return 3;
            case VARINT_4:
                return 4;
            case VARINT_5:
                return 5;
            case VARINT_6:
                return 6;
            case VARINT_7:
                return 7;
            case VARINT_8:
                return 8;
            case VARINT_9:
                return 9;
            case VARINT_A:
                return 10;
            case VARINT_B:
                return 11;
            case VARINT_C:
                return 12;
            case VARINT_D:
                return 13;
            case VARINT_E:
                return 14;
            case VARINT_F:
                return 15;
            case VARINT_10:
                return 16;
            case VARINT_11:
                return 17;
            case VARINT_12:
                return 18;
            case VARINT_13:
                return 19;
            case VARINT_14:
                return 20;
            case VARINT_15:
                return 21;
            case VARINT_16:
                return 22;
            case VARINT_17:
                return 23;
            case VARINT_18:
                return 24;
            case VARINT_19:
                return 25;
            case VARINT_1A:
                return 26;
            case VARINT_1B:
                return 27;
            case VARINT_1C:
                return 28;
            case VARINT_1D:
                return 29;
            case VARINT_1E:
                return 30;
            case VARINT_1F:
                return 31;
            default:
                throw new IOException("Tag error, expect VARINT, but get " + b);
        }
    }

    private long readVarint64() throws IOException {
        byte b = read0();

        switch (b) {
            case VARINT8:
                return read0();
            case VARINT16: {
                byte b1 = read0(), b2 = read0();
                return (short) ((b1 & 0xff) | ((b2 & 0xff) << 8));
            }
            case VARINT24: {
                byte b1 = read0(), b2 = read0(), b3 = read0();
                int ret = (b1 & 0xff) | ((b2 & 0xff) << 8) | ((b3 & 0xff) << 16);
                if (b3 < 0)
                    return ret | 0xff000000;
                return ret;
            }
            case VARINT32: {
                byte b1 = read0(), b2 = read0(), b3 = read0(), b4 = read0();
                return ((b1 & 0xff) |
                        ((b2 & 0xff) << 8) |
                        ((b3 & 0xff) << 16) |
                        ((b4 & 0xff) << 24));
            }
            case VARINT40: {
                byte b1 = read0(), b2 = read0(), b3 = read0(), b4 = read0(), b5 = read0();
                long ret = ((long) b1 & 0xff) |
                        (((long) b2 & 0xff) << 8) |
                        (((long) b3 & 0xff) << 16) |
                        (((long) b4 & 0xff) << 24) |
                        (((long) b5 & 0xff) << 32);
                if (b5 < 0)
                    return ret | 0xffffff0000000000l;
                return ret;
            }
            case VARINT48: {
                byte b1 = read0(), b2 = read0(), b3 = read0(), b4 = read0(), b5 = read0(), b6 = read0();
                long ret = ((long) b1 & 0xff) |
                        (((long) b2 & 0xff) << 8) |
                        (((long) b3 & 0xff) << 16) |
                        (((long) b4 & 0xff) << 24) |
                        (((long) b5 & 0xff) << 32) |
                        (((long) b6 & 0xff) << 40);
                if (b6 < 0)
                    return ret | 0xffff000000000000l;
                return ret;
            }
            case VARINT56: {
                byte b1 = read0(), b2 = read0(), b3 = read0(), b4 = read0(), b5 = read0(), b6 = read0(), b7 = read0();
                long ret = ((long) b1 & 0xff) |
                        (((long) b2 & 0xff) << 8) |
                        (((long) b3 & 0xff) << 16) |
                        (((long) b4 & 0xff) << 24) |
                        (((long) b5 & 0xff) << 32) |
                        (((long) b6 & 0xff) << 40) |
                        (((long) b7 & 0xff) << 48);
                if (b7 < 0)
                    return ret | 0xff00000000000000l;
                return ret;
            }
            case VARINT64: {
                byte b1 = read0(), b2 = read0(), b3 = read0(), b4 = read0();
                byte b5 = read0(), b6 = read0(), b7 = read0(), b8 = read0();
                return (((long) b1 & 0xff) |
                        (((long) b2 & 0xff) << 8) |
                        (((long) b3 & 0xff) << 16) |
                        (((long) b4 & 0xff) << 24) |
                        (((long) b5 & 0xff) << 32) |
                        (((long) b6 & 0xff) << 40) |
                        (((long) b7 & 0xff) << 48) |
                        (((long) b8 & 0xff) << 56));
            }
            case VARINT_NF:
                return -15;
            case VARINT_NE:
                return -14;
            case VARINT_ND:
                return -13;
            case VARINT_NC:
                return -12;
            case VARINT_NB:
                return -11;
            case VARINT_NA:
                return -10;
            case VARINT_N9:
                return -9;
            case VARINT_N8:
                return -8;
            case VARINT_N7:
                return -7;
            case VARINT_N6:
                return -6;
            case VARINT_N5:
                return -5;
            case VARINT_N4:
                return -4;
            case VARINT_N3:
                return -3;
            case VARINT_N2:
                return -2;
            case VARINT_N1:
                return -1;
            case VARINT_0:
                return 0;
            case VARINT_1:
                return 1;
            case VARINT_2:
                return 2;
            case VARINT_3:
                return 3;
            case VARINT_4:
                return 4;
            case VARINT_5:
                return 5;
            case VARINT_6:
                return 6;
            case VARINT_7:
                return 7;
            case VARINT_8:
                return 8;
            case VARINT_9:
                return 9;
            case VARINT_A:
                return 10;
            case VARINT_B:
                return 11;
            case VARINT_C:
                return 12;
            case VARINT_D:
                return 13;
            case VARINT_E:
                return 14;
            case VARINT_F:
                return 15;
            case VARINT_10:
                return 16;
            case VARINT_11:
                return 17;
            case VARINT_12:
                return 18;
            case VARINT_13:
                return 19;
            case VARINT_14:
                return 20;
            case VARINT_15:
                return 21;
            case VARINT_16:
                return 22;
            case VARINT_17:
                return 23;
            case VARINT_18:
                return 24;
            case VARINT_19:
                return 25;
            case VARINT_1A:
                return 26;
            case VARINT_1B:
                return 27;
            case VARINT_1C:
                return 28;
            case VARINT_1D:
                return 29;
            case VARINT_1E:
                return 30;
            case VARINT_1F:
                return 31;
            default:
                throw new IOException("Tag error, expect VARINT, but get " + b);
        }
    }

    private void fillBuffer() throws IOException {
        mPosition = 0;
        mRead = mInput.read(mBuffer);

        if (mRead == -1) {
            mRead = 0;
            throw new EOFException();
        }
    }
}