/*****************************************************************************/
/* CascMndxRoot.h                         Copyright (c) Ladislav Zezula 2014 */
/*---------------------------------------------------------------------------*/
/* Interface file for MNDX structures                                        */
/*---------------------------------------------------------------------------*/
/*   Date    Ver   Who  Comment                                              */
/* --------  ----  ---  -------                                              */
/* 15.05.14  1.00  Lad  Created                                              */
/*****************************************************************************/

#ifndef __CASC_MNDX_ROOT__
#define __CASC_MNDX_ROOT__

class TFileNameDatabase;

#define CASC_MAX_MAR_FILES           3          // Maximum of 3 MAR files are supported
#define CASC_MNDX_SIGNATURE 0x58444E4D          // 'MNDX'

#define CASC_MAX_ENTRIES(type) (0xFFFFFFFF / sizeof(type))

#define CASC_SEARCH_INITIALIZING    0
#define CASC_SEARCH_SEARCHING       2
#define CASC_SEARCH_FINISHED        4

typedef struct _TRIPLET
{
    DWORD BaseValue;
    DWORD Value2;
    DWORD Value3;
} TRIPLET, *PTRIPLET;

typedef struct _NAME_FRAG
{
    DWORD ItemIndex;                                // Back index to various tables
    DWORD NextIndex;                                // The following item index
    DWORD FragOffs;                                 // Higher 24 bits are 0xFFFFFF00 --> A single matching character
                                                    // Otherwise --> Offset to the name fragment table
} NAME_FRAG, *PNAME_FRAG;

typedef struct _PATH_STOP
{
    DWORD ItemIndex;
    DWORD field_4;
    DWORD field_8;
    DWORD field_C;
    DWORD field_10;
} PATH_STOP, *PPATH_STOP;

typedef union _ARRAY_POINTER
{
    LPBYTE Bytes;                                   // Pointer to an octet
    char * Chars;                                   // Pointer to a character
    PDWORD Uint32s;                                 // Pointer to a DWORD
    PTRIPLET Triplets;                              // Pointer to TRIPLET
    PNAME_FRAG NameFrags;                           // Pointer to name fragment entry
    PPATH_STOP PathStopPtr;                         // Pointer to path checkpoint
    PULONGLONG Int64Ptr;                            // Pointer to 64-bit integer

} ARRAY_POINTER, *PARRAY_POINTER;

// Simple access to various tables within TGenericArray
#define ByteArray     ArrayPointer.Bytes
#define CharArray     ArrayPointer.Chars
#define Uint32Array   ArrayPointer.Uint32s
#define TripletArray  ArrayPointer.Triplets
#define NameFragArray ArrayPointer.NameFrags

class TByteStream
{
    public:

    TByteStream();

    void ExchangeWith(TByteStream & Target);
    int GetBytes(DWORD cbByteCount, PARRAY_POINTER PtrArray);
    int SkipBytes(DWORD cbByteCount);
    int SetByteBuffer(LPBYTE pbNewMarData, DWORD cbNewMarData);
    int GetValue_DWORD(DWORD & Value);
    int GetValue_ItemCount(DWORD & NumberOfBytes, DWORD & ItemCount, DWORD ItemSize);
    int GetArray_DWORDs(PARRAY_POINTER PtrArray, DWORD ItemCount);
    int GetArray_Triplets(PARRAY_POINTER PtrArray, DWORD ItemCount);
    int GetArray_NameTable(PARRAY_POINTER PtrArray, DWORD ItemCount);
    int GetArray_BYTES(PARRAY_POINTER PtrArray, DWORD ItemCount);

    LPBYTE pbByteData;
    void * pvMappedFile;
    DWORD cbByteData;
    DWORD field_C;
    HANDLE hFile;
    HANDLE hMap;
};

class TGenericArray
{
    public:

    TGenericArray();
    ~TGenericArray();

    int SetArrayValid();

    void ExchangeWith(TGenericArray & Target);
    void CopyFrom(TGenericArray & Source);

    void SetMaxItems_CHARS(DWORD NewMaxItemCount);
    void SetMaxItems_PATH_STOP(DWORD NewMaxItemCount);

    void InsertOneItem_CHAR(char OneChar);
    void InsertOneItem_PATH_STOP(PATH_STOP & NewItem);

    void sub_19583A0(DWORD NewItemCount);

    int LoadDwordsArray(TByteStream & InStream);
    int LoadTripletsArray(TByteStream & InStream);
    int LoadByteArray(TByteStream & InStream);
    int LoadFragmentInfos(TByteStream & InStream);
    int LoadStrings(TByteStream & InStream);

    int LoadDwordsArray_Copy(TByteStream & InStream);
    int LoadTripletsArray_Copy(TByteStream & InStream);
    int LoadBytes_Copy(TByteStream & InStream);
    int LoadFragmentInfos_Copy(TByteStream & InStream);
    int LoadStringsWithCopy(TByteStream & InStream);

    ARRAY_POINTER DataBuffer;
    ARRAY_POINTER FirstValid;

    ARRAY_POINTER ArrayPointer;
    DWORD ItemCount;                    // Number of items in the array
    DWORD MaxItemCount;                 // Capacity of the array
    bool  bIsValidArray;
};

class TBitEntryArray : public TGenericArray
{
    public:

    TBitEntryArray();
    ~TBitEntryArray();

    DWORD GetBitEntry(DWORD EntryIndex)
    {
        DWORD dwItemIndex = (EntryIndex * BitsPerEntry) >> 0x05;
        DWORD dwStartBit = (EntryIndex * BitsPerEntry) & 0x1F;
        DWORD dwEndBit = dwStartBit + BitsPerEntry;
        DWORD dwResult;

        // If the end bit index is greater than 32,
        // we also need to load from the next 32-bit item
        if(dwEndBit > 0x20)
        {
            dwResult = (Uint32Array[dwItemIndex + 1] << (0x20 - dwStartBit)) | (Uint32Array[dwItemIndex] >> dwStartBit);
        }
        else
        {
            dwResult = Uint32Array[dwItemIndex] >> dwStartBit;
        }

        // Now we also need to mask the result by the bit mask
        return dwResult & EntryBitMask;
    }

    void ExchangeWith(TBitEntryArray & Target);
    int LoadFromStream(TByteStream & InStream);
    int LoadFromStream_Exchange(TByteStream & InStream);

    DWORD BitsPerEntry;
    DWORD EntryBitMask;
    DWORD TotalEntries;
};

class TStruct40
{
    public:

    TStruct40();

    void InitSearchBuffers();

    TGenericArray array_00;
    TGenericArray PathStops;            // Array of path checkpoints
    DWORD ItemIndex;                    // Current name fragment: Index to various tables
    DWORD CharIndex;
    DWORD ItemCount;
    DWORD SearchPhase;                  // 0 = initializing, 2 = searching, 4 = finished
};

class TMndxFindResult
{
    public:

    TMndxFindResult();
    ~TMndxFindResult();

    int CreateStruct40();
    void FreeStruct40();

    int SetSearchPath(const char * szNewSearchPath, size_t cchNewSearchPath);

    const char * szSearchMask;          // Search mask without wildcards
    size_t cchSearchMask;               // Length of the search mask
    DWORD field_8;
    const char * szFoundPath;           // Found path name
    size_t cchFoundPath;                // Length of the found path name
    DWORD FileNameIndex;                // Index of the file name
    TStruct40 * pStruct40;
};

class TSparseArray
{
    public:

    TSparseArray();

    void ExchangeWith(TSparseArray & TargetObject);
    int LoadFromStream(TByteStream & InStream);
    int LoadFromStream_Exchange(TByteStream & InStream);

    // Returns true if the item at n-th position is present
    DWORD IsItemPresent(DWORD ItemIndex)
    {
        return (ItemBits.Uint32Array[ItemIndex >> 0x05] & (1 << (ItemIndex & 0x1F)));
    }

    DWORD GetItemValue(DWORD ItemIndex);

    TGenericArray ItemBits;             // Bit array for each item (1 = item is present)
    DWORD TotalItemCount;               // Total number of items in the array
    DWORD ValidItemCount;               // Number of present items in the array
    TGenericArray BaseValues;           // Array of base values for item indexes >= 0x200
    TGenericArray ArrayDwords_38;
    TGenericArray ArrayDwords_50;
};

class TNameIndexStruct
{
    public:

    TNameIndexStruct();
    ~TNameIndexStruct();

    bool CheckNameFragment(TMndxFindResult * pStruct1C, DWORD dwFragOffs);
    bool CheckAndCopyNameFragment(TMndxFindResult * pStruct1C, DWORD dwFragOffs);
    void CopyNameFragment(TMndxFindResult * pStruct1C, DWORD dwFragOffs);

    void ExchangeWith(TNameIndexStruct & Target);
    int LoadFromStream(TByteStream & InStream);
    int LoadFromStream_Exchange(TByteStream & InStream);

    TGenericArray NameFragments;
    TSparseArray Struct68;
};

class TStruct10
{
    public:

    TStruct10();

    void CopyFrom(TStruct10 & Target);
    int sub_1956FD0(DWORD dwBitMask);
    int sub_1957050(DWORD dwBitMask);
    int sub_19572E0(DWORD dwBitMask);
    int sub_1957800(DWORD dwBitMask);

    DWORD field_0;
    DWORD field_4;
    DWORD field_8;
    DWORD field_C;
};

class TFileNameDatabasePtr
{
    public:

    TFileNameDatabasePtr();
    ~TFileNameDatabasePtr();

    int FindFileInDatabase(TMndxFindResult * pStruct1C);
    int sub_1956CE0(TMndxFindResult * pStruct1C, bool * pbFindResult);

    int GetFileNameCount(PDWORD PtrFileNameCount);
    int CreateDatabase(LPBYTE pbMarData, DWORD cbMarData);
    int SetDatabase(TFileNameDatabase * pNewDB);

    TFileNameDatabase * pDB;
};

class TFileNameDatabase
{
    public:

    TFileNameDatabase();

    void ExchangeWith(TFileNameDatabase & Target);
    int LoadFromStream(TByteStream & InStream);
    int LoadFromStream_Exchange(TByteStream & InStream);

    DWORD sub_1959CB0(DWORD dwHashValue);
    DWORD sub_1959F50(DWORD arg_0);

    // Retrieves the name fragment distance
    // HOTS: 19573D0/inlined
    DWORD GetNameFragmentOffsetEx(DWORD LoBitsIndex, DWORD HiBitsIndex)
    {
        return (FrgmDist_HiBits.GetBitEntry(HiBitsIndex) << 0x08) | FrgmDist_LoBits.ByteArray[LoBitsIndex];
    }

    // HOTS: 1957350, inlined
    DWORD GetNameFragmentOffset(DWORD LoBitsIndex)
    {
        return GetNameFragmentOffsetEx(LoBitsIndex, Struct68_D0.GetItemValue(LoBitsIndex));
    }

    bool sub_1957B80(TMndxFindResult * pStruct1C, DWORD dwKey);
    bool CheckNextPathFragment(TMndxFindResult * pStruct1C);
    bool FindFileInDatabase(TMndxFindResult * pStruct1C);

    void sub_1958D70(TMndxFindResult * pStruct1C, DWORD arg_4);
    bool sub_1959010(TMndxFindResult * pStruct1C, DWORD arg_4);
    bool sub_1958B00(TMndxFindResult * pStruct1C);
    bool sub_1959460(TMndxFindResult * pStruct1C);

    TSparseArray Struct68_00;
    TSparseArray FileNameIndexes;               // Array of file name indexes
    TSparseArray Struct68_D0;

    // This pair of arrays serves for fast conversion from name hash to fragment offset
    TGenericArray  FrgmDist_LoBits;             // Array of lower 8 bits of name fragment offset
    TBitEntryArray FrgmDist_HiBits;             // Array of upper x bits of name fragment offset

    TNameIndexStruct IndexStruct_174;
    TFileNameDatabasePtr NextDB;

    TGenericArray NameFragTable;

    DWORD NameFragIndexMask;
    DWORD field_214;
    TStruct10 Struct10;
    TByteStream MarStream;
};

typedef struct _MAR_FILE
{
    TFileNameDatabasePtr * pDatabasePtr;
    LPBYTE pbMarData;
    DWORD cbMarData;
} MAR_FILE, *PMAR_FILE;

//-----------------------------------------------------------------------------
// Macros

// Returns nonzero if the name fragment match is a single-char match
inline bool IS_SINGLE_CHAR_MATCH(TGenericArray & Table, DWORD ItemIndex)
{
    return ((Table.NameFragArray[ItemIndex].FragOffs & 0xFFFFFF00) == 0xFFFFFF00);
}

#endif  // __CASC_MNDX_ROOT__
