#pragma once

#include <cstdint>
#include <cassert>
#include <cstdbool>
#include <cstring>
#include <string>

#include <brynet/base/NonCopyable.hpp>
#include <brynet/base/endian/Endian.hpp>

namespace brynet { namespace base {

    class BasePacketWriter : public NonCopyable
    {
    public:
        BasePacketWriter(char* buffer,
            size_t len,
            bool useBigEndian = false,
            bool isAutoMalloc = false)
            :
            mIsAutoMalloc(isAutoMalloc),
            mBigEndian(useBigEndian)
        {
            mMaxLen = len;
            mPos = 0;
            mBuffer = buffer;
            mMallocBuffer = nullptr;
        }

        virtual ~BasePacketWriter()
        {
            if (mMallocBuffer != nullptr)
            {
                free(mMallocBuffer);
                mMallocBuffer = nullptr;
            }
        }

        void    init()
        {
            mPos = 0;
        }

        size_t getMaxLen() const
        {
            return mMaxLen;
        }

        size_t      getPos() const
        {
            return mPos;
        }

        const char* getData() const
        {
            return mBuffer;
        }

        bool    isAutoGrow() const
        {
            return mIsAutoMalloc;
        }

        bool    writeBool(bool value)
        {
            static_assert(sizeof(bool) == sizeof(int8_t), "");
            return writeBuffer((char*)&value, sizeof(value));
        }
        bool    writeINT8(int8_t value)
        {
            return writeBuffer((char*)&value, sizeof(value));
        }
        bool    writeUINT8(uint8_t value)
        {
            return writeBuffer((char*)&value, sizeof(value));
        }
        bool    writeINT16(int16_t value)
        {
            value = endian::hostToNetwork16(value, mBigEndian);
            return writeBuffer((char*)&value, sizeof(value));
        }
        bool    writeUINT16(uint16_t value)
        {
            value = endian::hostToNetwork16(value, mBigEndian);
            return writeBuffer((char*)&value, sizeof(value));
        }
        bool    writeINT32(int32_t value)
        {
            value = endian::hostToNetwork32(value, mBigEndian);
            return writeBuffer((char*)&value, sizeof(value));
        }
        bool    writeUINT32(uint32_t value)
        {
            value = endian::hostToNetwork32(value, mBigEndian);
            return writeBuffer((char*)&value, sizeof(value));
        }
        bool    writeINT64(int64_t value)
        {
            value = endian::hostToNetwork64(value, mBigEndian);
            return writeBuffer((char*)&value, sizeof(value));
        }
        bool    writeUINT64(uint64_t value)
        {
            value = endian::hostToNetwork64(value, mBigEndian);
            return writeBuffer((char*)&value, sizeof(value));
        }

        bool    writeBinary(const std::string& binary)
        {
            return writeBuffer(binary.c_str(), binary.size());
        }
        bool    writeBinary(const char* binary, size_t binaryLen)
        {
            return writeBuffer(binary, binaryLen);
        }
        bool    writeBuffer(const char* buffer, size_t len)
        {
            growBuffer(len);

            if (mMaxLen < (mPos + len))
            {
                return false;
            }

            memcpy(mBuffer + mPos, buffer, len);
            mPos += len;
            return true;
        }

        BasePacketWriter & operator << (const bool &v)
        {
            writeBool(v);
            return *this;
        }
        BasePacketWriter & operator << (const uint8_t &v)
        {
            writeUINT8(v);
            return *this;
        }
        BasePacketWriter & operator << (const int8_t &v)
        {
            writeINT8(v);
            return *this;
        }
        BasePacketWriter & operator << (const int16_t &v)
        {
            writeINT16(v);
            return *this;
        }
        BasePacketWriter & operator << (const uint16_t &v)
        {
            writeUINT16(v);
            return *this;
        }
        BasePacketWriter & operator << (const int32_t &v)
        {
            writeINT32(v);
            return *this;
        }
        BasePacketWriter & operator << (const uint32_t &v)
        {
            writeUINT32(v);
            return *this;
        }
        BasePacketWriter & operator << (const int64_t &v)
        {
            writeINT64(v);
            return *this;
        }
        BasePacketWriter & operator << (const uint64_t &v)
        {
            writeUINT64(v);
            return *this;
        }
        BasePacketWriter & operator << (const char* const &v)
        {
            writeBinary(v);
            return *this;
        }
        BasePacketWriter & operator << (const std::string &v)
        {
            writeBinary(v);
            return *this;
        }

    private:
        // 为了避免直接<<导致没有指定字节序导致隐藏BUG,因为此函数设置为私有
        template<typename T>
        BasePacketWriter & operator << (const T& v)
        {
            static_assert(!std::is_pointer<T>::value, "T must is't a pointer");
            static_assert(std::is_class <T>::value, "T must a class or struct type");
            static_assert(std::is_pod <T>::value, "T must a pod type");
            writeBuffer((const char*)&v, sizeof(v));
            return *this;
        }

        template<typename Arg1, typename... Args>
        void            writev(const Arg1& arg1, const Args&... args)
        {
            this->operator<<(arg1);
            writev(args...);
        }

        void            writev()
        {
        }

    protected:
        void        growBuffer(size_t len)
        {
            if (!mIsAutoMalloc || (mPos + len) <= mMaxLen)
            {
                return;
            }

            auto newBuffer = (char*)malloc(mMaxLen + len);
            if (newBuffer == nullptr)
            {
                return;
            }

            memcpy(newBuffer, mBuffer, mPos);

            if (mMallocBuffer != nullptr)
            {
                free(mMallocBuffer);
                mMallocBuffer = nullptr;
            }

            mMaxLen += len;
            mMallocBuffer = newBuffer;
            mBuffer = newBuffer;
        }

    protected:
        const bool  mIsAutoMalloc;
        bool        mBigEndian;
        size_t      mPos;
        size_t      mMaxLen;
        char*       mBuffer;
        char*       mMallocBuffer;
    };

    class BasePacketReader
    {
    public:
        BasePacketReader(const char* buffer,
            size_t len,
            bool useBigEndian = false) :
                mBigEndian(useBigEndian),
                mSize(len)
        {
            mPos = 0;
            mSavedPos = 0;
            mBuffer = buffer;
        }

        virtual ~BasePacketReader() = default;

        void    useBigEndian()
        {
            mBigEndian = true;
        }

        void    useLittleEndian()
        {
            mBigEndian = false;
        }

        void    savePos()
        {
            mSavedPos = mPos;
        }

        size_t  savedPos() const
        {
            return mSavedPos;
        }

        size_t          getLeft() const
        {
            if (mPos > mSize)
            {
                throw std::out_of_range("current pos is greater than max len");
            }
            return mSize - mPos;
        }

        bool    enough(size_t len) const
        {
            if (mPos > mSize)
            {
                return false;
            }
            return (mSize - mPos) >= len;
        }

        const char*     begin() const
        {
            return mBuffer;
        }

        const char*     currentBuffer() const
        {
            return mBuffer+mPos;
        }

        void            consumeAll()
        {
            mPos = mSize;
            savePos();
        }

        size_t          currentPos() const
        {
            return mPos;
        }

        size_t          size() const
        {
            return mSize;
        }

        void            addPos(size_t diff)
        {
            const auto tmpPos = mPos + diff;
            if (tmpPos > mSize)
            {
                throw std::out_of_range("diff is to big");
            }

            mPos = tmpPos;
        }

        bool            readBool()
        {
            static_assert(sizeof(bool) == sizeof(int8_t), "");
            bool  value = false;
            read(value);
            return value;
        }
        int8_t          readINT8()
        {
            int8_t  value = 0;
            read(value);
            return value;
        }
        uint8_t         readUINT8()
        {
            uint8_t  value = 0;
            read(value);
            return value;
        }
        int16_t         readINT16()
        {
            int16_t  value = 0;
            read(value);
            return endian::networkToHost16(value, mBigEndian);
        }
        uint16_t        readUINT16()
        {
            uint16_t  value = 0;
            read(value);
            return endian::networkToHost16(value, mBigEndian);
        }
        int32_t         readINT32()
        {
            int32_t  value = 0;
            read(value);
            return endian::networkToHost32(value, mBigEndian);
        }
        uint32_t        readUINT32()
        {
            uint32_t  value = 0;
            read(value);
            return endian::networkToHost32(value, mBigEndian);
        }
        int64_t         readINT64()
        {
            int64_t  value = 0;
            read(value);
            return endian::networkToHost64(value, mBigEndian);
        }
        uint64_t        readUINT64()
        {
            uint64_t  value = 0;
            read(value);
            return endian::networkToHost64(value, mBigEndian);
        }

    private:
        // 为了避免直接read(uintXXX)导致没有指定字节序造成隐患BUG,因为此函数设置为私有
        template<typename T>
        void            read(T& value)
        {
            static_assert(std::is_same<T, typename std::remove_pointer<T>::type>::value,
                "T must a normal type");
            static_assert(std::is_pod<T>::value,
                "T must a pod type");

            if ((mPos + sizeof(value)) > mSize)
            {
                throw std::out_of_range("T size is to big");
            }

            value = *(T*)(mBuffer + mPos);
            mPos += sizeof(value);
        }

    protected:
        bool            mBigEndian;
        const size_t    mSize;
        const char*     mBuffer;
        size_t          mPos;
        size_t          mSavedPos;
    };

    template<size_t SIZE>
    class AutoMallocPacket : public BasePacketWriter
    {
    public:
        explicit AutoMallocPacket(bool useBigEndian = false,
            bool isAutoMalloc = false)
            :
            BasePacketWriter(mData, SIZE, useBigEndian, isAutoMalloc)
        {}
    private:
        char        mData[SIZE];
    };

    using BigPacket = AutoMallocPacket<32 * 1024>;

} }