#pragma once
#include <WTypes.h>

#if REG_DWORD == REG_DWORD_LITTLE_ENDIAN
#define __INTEL_BYTE_ORDER__ 1
#endif

class FArchive
{
public:
    // FArchive interface.
    virtual ~FArchive()
    {
    }

    virtual void Serialize(void* V, INT Length)
    {
    }

    virtual void SerializeBits(void* V, INT LengthBits)
    {
        Serialize(V, (LengthBits + 7) / 8);
        if (IsLoading())
            ((BYTE*)V)[LengthBits / 8] &= ((1 << (LengthBits & 7)) - 1);
    }

    virtual void SerializeInt(DWORD& Value, DWORD Max)
    {
        *this << *(DWORD*)Value;
    }

    virtual void Preload(void* Object)
    {
    }

    virtual void CountBytes(SIZE_T InNum, SIZE_T InMax)
    {
    }

    virtual FArchive& operator<<(class FName& N)
    {
        return *this;
    }

    virtual FArchive& operator<<(class UObject*& Res)
    {
        return *this;
    }

    virtual INT MapName(FName* Name)
    {
        return 0;
    }

    virtual INT MapObject(UObject* Object)
    {
        return 0;
    }

    virtual INT Tell()
    {
        return -1;
    }

    virtual INT TotalSize()
    {
        return -1;
    }

    virtual BOOL AtEnd()
    {
        INT Pos = Tell();
        return Pos != -1 && Pos >= TotalSize();
    }

    virtual BOOL AtStopper()
    {
        BOOL res = FALSE;

        INT Size = TotalSize();
        if (Size != -1)
        {
            if (ArStopper != -1 && Size >= ArStopper)
                res = TRUE;
        }

        return res;
    }

    virtual void SetStopper(INT Stopper)
    {
        ArStopper = Stopper;
    }

    virtual void Seek(INT InPos)
    {
    }

    virtual void AttachLazyLoader(void* LazyLoader)
    {
    }

    virtual void DetachLazyLoader(void* LazyLoader)
    {
    }

    virtual void AttachRoughLoader(void* RoughLoader)
    {
    }

    virtual void DetachRoughLoader(void* RoughLoader)
    {
    }

    virtual void Precache(INT HintCount)
    {
    }

    virtual void Flush()
    {
    }

    virtual BOOL Close()
    {
        return !ArIsError;
    }

    virtual BOOL GetError()
    {
        return ArIsError;
    }

    virtual INT GetCurrentEncryptVersion()
    {
        return 0;
    }

    // Hardcoded datatype routines that may not be overridden.
    FArchive& ByteOrderSerialize(void* V, INT Length)
    {
#if __INTEL_BYTE_ORDER__
        Serialize(V, Length);
#else
        if( ArIsPersistent )
        {
        // Transferring between memory and file, so flip the byte order.
            for( INT i=Length-1; i>=0; i-- )
                Serialize( (BYTE*)V + i, 1 );
        }
        else
        {
        // Transferring around within memory, so keep the byte order.
            Serialize( V, Length );
        }
#endif
        return *this;
    }

    // Constructor.
    FArchive()
        : ArVer(/*PACKAGE_FILE_VERSION*/0x7B)
          , ArNetVer(/*ENGINE_NEGOTIATION_VERSION*/0x7D0)
          , ArLicenseeVer(/*PACKAGE_FILE_VERSION_LICENSEE*/0x1E)
          , ArIsLoading(0)
          , ArIsSaving(0)
          , ArIsTrans(0)
          , ArIsPersistent(0)
          , ArIsError(0)
          , ArForEdit(0)
          , ArForClient(0)
          , ArForServer(0)
          , ArUnk1(0)
          , ArStopper(-1)
    {
    }

    // Status accessors.
    INT Ver()
    {
        return ArVer;
    }

    INT NetVer()
    {
        return ArNetVer & 0x7fffffff;
    }

    INT LicenseeVer()
    {
        return ArLicenseeVer;
    }

    BOOL IsLoading()
    {
        return ArIsLoading;
    }

    BOOL IsSaving()
    {
        return ArIsSaving;
    }

    BOOL IsTrans()
    {
        return ArIsTrans;
    }

    BOOL IsNet()
    {
        return (ArNetVer & 0x80000000) != 0;
    }

    BOOL IsPersistent()
    {
        return ArIsPersistent;
    }

    BOOL IsError()
    {
        return ArIsError;
    }

    BOOL ForEdit()
    {
        return ArForEdit;
    }

    BOOL ForClient()
    {
        return ArForClient;
    }

    BOOL ForServer()
    {
        return ArForServer;
    }

    // Friend archivers.
    friend FArchive& operator<<(FArchive& Ar, PCHAR& C)
    {
        Ar.Serialize(&C, 1);
        return Ar;
    }

    friend FArchive& operator<<(FArchive& Ar, BYTE& B)
    {
        Ar.Serialize(&B, 1);
        return Ar;
    }

    friend FArchive& operator<<(FArchive& Ar, CHAR& B)
    {
        Ar.Serialize(&B, 1);
        return Ar;
    }

    friend FArchive& operator<<(FArchive& Ar, WORD& W)
    {
        Ar.ByteOrderSerialize(&W, sizeof(W));
        return Ar;
    }

    friend FArchive& operator<<(FArchive& Ar, SHORT& S)
    {
        Ar.ByteOrderSerialize(&S, sizeof(S));
        return Ar;
    }

    friend FArchive& operator<<(FArchive& Ar, DWORD& D)
    {
        Ar.ByteOrderSerialize(&D, sizeof(D));
        return Ar;
    }

    friend FArchive& operator<<(FArchive& Ar, INT& I)
    {
        Ar.ByteOrderSerialize(&I, sizeof(I));
        return Ar;
    }

    friend FArchive& operator<<(FArchive& Ar, FLOAT& F)
    {
        Ar.ByteOrderSerialize(&F, sizeof(F));
        return Ar;
    }

    friend FArchive& operator<<(FArchive& Ar, DWORD64& Q)
    {
        Ar.ByteOrderSerialize(&Q, sizeof(Q));
        return Ar;
    }

    friend FArchive& operator<<(FArchive& Ar, long long& S)
    {
        Ar.ByteOrderSerialize(&S, sizeof(S));
        return Ar;
    }

    friend FArchive& operator<<(FArchive& Ar, /*FTime*/INT& F);

public:
    // Status variables.
    INT ArVer;
    INT ArNetVer;
    INT ArLicenseeVer;
    BOOL ArIsLoading;
    BOOL ArIsSaving;
    BOOL ArIsTrans;
    BOOL ArIsPersistent;
    BOOL ArForEdit;
    BOOL ArForClient;
    BOOL ArForServer;
    BOOL ArIsError;
    BOOL ArUnk1;
    INT ArStopper;
};

class FArchiveFileReader : public FArchive
{
public:
    FArchiveFileReader(HANDLE InFile, void* InError, INT InSize)
        : File(InFile)
          , Error(InError)
          , Size(InSize)
          , Pos(0)
          , BufferBase(0)
          , BufferCount(0)
          , SleepFactor(0.0f)
    {
        SetFilePointer(File, 0, 0, FILE_BEGIN);
        ArIsLoading = ArIsPersistent = 1;
    }

    ~FArchiveFileReader()
    {
        if (File)
        {
            FArchiveFileReader::Close();
        }
    }

    void Precache(INT HintCount) override
    {
        BufferBase = Pos;
        BufferCount = min( min( HintCount, (INT)(ARRAYSIZE(Buffer) - (Pos & (ARRAYSIZE(Buffer)-1))) ), Size-Pos );

        if (!ReadFile(File, Buffer, BufferCount) && BufferCount != 0)
        {
            ArIsError = 1;
            //Error->Logf( TEXT("fread failed: BufferCount=%i"), BufferCount );
            return;
        }
    }

    void Seek(INT InPos) override
    {
        //assert(InPos >= 0);
        //assert(InPos <= Size);

        if (SetFilePointer(File, InPos, 0, FILE_BEGIN) == INVALID_SET_FILE_POINTER)
        {
            ArIsError = 1;
            //Error->Logf( TEXT("Seek failed: %i/%i: %i"), InPos, Size, Pos );
        }

        Pos = InPos;
        BufferBase = Pos;
        BufferCount = 0;
    }

    INT Tell() override
    {
        return Pos;
    }

    INT TotalSize() override
    {
        return Size;
    }

    BOOL Close() override
    {
        if (File)
            CloseHandle(File);
        File = NULL;

        return !ArIsError;
    }

    void Serialize(void* V, INT Length) override
    {
        while (Length > 0)
        {
            INT Copy = min( Length, BufferBase+BufferCount-Pos );
            if (Copy == 0)
            {
                if (Length >= ARRAYSIZE(Buffer))
                {
                    if (!ReadFile(File, V, Length))
                    {
                        ArIsError = 1;
                        //Error->Logf( TEXT("fread failed: Length=%i"), Length );
                    }

                    Pos += Length;
                    BufferBase += Length;
                    return;
                }

                Precache(MAXINT);

                Copy = min( Length, BufferBase + BufferCount - Pos );
                if (Copy <= 0)
                {
                    ArIsError = 1;
                    //Error->Logf( TEXT("ReadFile beyond EOF %i+%i/%i"), Pos, Length, Size );
                }

                if (ArIsError)
                    return;
            }

            ::memcpy(V, Buffer + Pos - BufferBase, Copy);
            Pos += Copy;
            Length -= Copy;
            V = (BYTE*)V + Copy;
        }
    }

    BOOL ReadFile(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead)
    {
        DWORD dwRead = 0;
        BOOL readOk = ::ReadFile(hFile, lpBuffer, nNumberOfBytesToRead, &dwRead, 0) && nNumberOfBytesToRead == dwRead;

        return readOk;
    }

public:
    HANDLE File;
    void* Error;
    INT Size;
    INT Pos;
    INT BufferBase;
    INT BufferCount;
    float SleepFactor;
    BYTE Buffer[1024];
};

extern wchar_t GLineage2Ver[12];

DWORD Get121Key(const wchar_t* FileName);

class FL2FileDecryptArchive : public FArchive
{
public:
    FL2FileDecryptArchive(HANDLE InFile, void* InError, INT InSize, const wchar_t* FileName) : ArchiveFileReader(nullptr), CryptVer(0), HeaderSize(0), CryptKey(0xAC)
    {
        FArchiveFileReader* reader = new FArchiveFileReader(InFile, InError, GetFileSize(InFile, 0));

        ArchiveFileReader = reader;

        if (InSize <= 0x18)
        {
            ArchiveFileReader->Seek(0);
        }
        else
        {
            for (int i = 0; i < 11; ++i)
            {
                wchar_t c;
                ArchiveFileReader->Serialize(&c, sizeof(c));
                if (c != GLineage2Ver[i])
                {
                    ArchiveFileReader->Seek(0);
                    return;
                }
            }

            wchar_t buf_ver[3];
            ArchiveFileReader->Serialize(&buf_ver[0], sizeof(wchar_t));
            ArchiveFileReader->Serialize(&buf_ver[1], sizeof(wchar_t));
            ArchiveFileReader->Serialize(&buf_ver[2], sizeof(wchar_t));

            long ver = wcstol(buf_ver, 0, 10);

            CryptVer = ver;
            HeaderSize = 28;

            if (CryptVer == 111)
            {
                CryptKey = 0xAC;
            }
            else if (CryptVer == 121)
            {
                CryptKey = static_cast<BYTE>(Get121Key(FileName));
            }
            else
            {
                CryptKey = 0;
            }
        }
    }

    ~FL2FileDecryptArchive()
    {
        if(ArchiveFileReader)
        {
            delete ArchiveFileReader;
            ArchiveFileReader = 0;
        }
    }

    void Serialize(void* V, INT Length) override
    {
        int ver = CryptVer;
        if (ver > 212)
        {
            if (ver != 311 && ver - 411 > 2)
                return;

            ArchiveFileReader->Serialize(V, Length);
            return;
        }

        if (ver >= 211 || !ver)
        {
            ArchiveFileReader->Serialize(V, Length);
            return;
        }

        if (ver == 111 || ver == 121)
        {
            ArchiveFileReader->Serialize(V, Length);

            for (int i = 0; i < Length; ++i)
                *((BYTE*)V + i) ^= this->CryptKey;
        }
    }

    INT Tell() override
    {
        int ver = CryptVer;

        if (ver > 212)
        {
            if (ver != 311 && ver - 411 > 2)
                return -1;

            return ArchiveFileReader->Tell() - this->HeaderSize;
        }

        if (ver >= 211)
            return ArchiveFileReader->Tell() - this->HeaderSize;

        if (!ver)
            return ArchiveFileReader->Tell();

        if (ver == 111 || ver == 121)
            return ArchiveFileReader->Tell() - this->HeaderSize;

        return -1;
    }

    INT TotalSize() override
    {
        int ver = CryptVer;

        if (ver > 212)
        {
            if (ver != 311 && ver - 411 > 2)
                return -1;

            return ArchiveFileReader->TotalSize() - this->HeaderSize;
        }

        if (ver >= 211)
            return ArchiveFileReader->TotalSize() - this->HeaderSize;

        if (!ver)
            return ArchiveFileReader->TotalSize();

        if (ver == 111 || ver == 121)
            return ArchiveFileReader->TotalSize() - this->HeaderSize;

        return -1;
    }

    void Seek(INT InPos) override
    {
        int ver = this->CryptVer;
        if (ver > 212)
        {
            if (ver != 311 && ver - 411 > 2)
                return;

            ArchiveFileReader->Seek(InPos + this->HeaderSize);
            return;
        }

        if (ver >= 211)
        {
            ArchiveFileReader->Seek(InPos + this->HeaderSize);
            return;
        }

        if (!ver)
        {
            ArchiveFileReader->Seek(InPos);
            return;
        }

        if (ver == 111 || ver == 121)
            ArchiveFileReader->Seek(InPos + this->HeaderSize);
    }

    void Precache(INT HintCount) override
    {
        int ver = this->CryptVer;
        if (ver > 212)
        {
            if (ver != 311 && (unsigned int)(ver - 411) > 2)
                return;

            ArchiveFileReader->Precache(HintCount);
            return;
        }

        if (ver >= 211 || !ver)
        {
            ArchiveFileReader->Precache(HintCount);
        }
        else if (ver == 111 || ver == 121)
        {
            ArchiveFileReader->Precache(HintCount);
        }
    }

    BOOL Close() override
    {
        int ver = this->CryptVer;
        if (ver > 212)
        {
            if (ver != 311 && (unsigned int)(ver - 411) > 2)
                return ArForServer == 0;

            return ArchiveFileReader->Close();
        }

        if (ver >= 211 || !ver)
        {
            return ArchiveFileReader->Close();
        }

        if (ver == 111 || ver == 121)
        {
            return ArchiveFileReader->Close();
        }

        return ArForServer == 0;
    }

    INT GetCurrentEncryptVersion() override
    {
        return CryptVer;
    }

public:
    FArchiveFileReader* ArchiveFileReader;
    int CryptVer;
    int HeaderSize;
    BYTE CryptKey;
};