//(C) Stephan Vedder 2014
#pragma once
#include <stdint.h>
#include <fstream>
#include <vector>
#include <algorithm>
#include <string>

//Dll Handling
#ifdef _WIN32
#pragma warning(disable:4996)
#pragma warning(disable:4251)
#ifdef BIG_EXPORT
#define BIG_EDIT __declspec(dllexport) 
#else
#define BIG_EDIT __declspec(dllimport) 
#endif
//Unix systems don't need to export the symbols
#else
#define BIG_EDIT
#endif

//represents a BigArchive
class BIG_EDIT BigFile
{
private:
    //a single entry inside an archive file
    struct BIG_EDIT BigEntry
    {
        std::string     name;
        uint32_t        size;
        uint32_t        offset;
        uint32_t        new_offset;
        //use this to store files that will be written
        std::string     data;

        BigEntry();
        BigEntry(const BigEntry& copy);
        BigEntry(std::string name, uint32_t size, std::string data);
        BigEntry(const char* name, uint32_t size, const char* data);
        //will release the file in memory
        void Release();
    };
public:
    //use this constructor if you want to create a new archive
    BigFile();
    //use this constructor if you want to open an existing archive
    BigFile(const char* filename);
    ~BigFile();
    
    //returns the total size of the archive in bytes
    const uint32_t          GetSize();
    //returns the total amount of entries inside a file
    const uint32_t          GetNumEntries();
    //returns the content of an entry (by index)
    const char*             GetEntry(const uint32_t entryid);
    //returns the content of an entry (by name)
    const char*             GetEntryByName(const char* entryname);
    //returns the size of an entry (by index)
    const uint32_t          GetEntrySize(const uint32_t entryid);
    //returns the size of an entry (by name)
    const uint32_t          GetEntrySizeByName(const char* entryname);
    //returns all entrynames in this archive
    const char**            GetFileNames();
    //returns the index of a entryfile
    const uint32_t          GetEntryIndex(const char* entryname);
    //returns weither or not the entry could be removed (by index)
    bool                    RemoveEntry(const uint32_t entryid);
    //returns weither or not the entry could be removed (by name)
    bool                    RemoveEntryByName(const char* entryname);
    //add a new entry to the archive
    bool                    AddEntry(const char* name, const char* data, uint32_t size, bool overwrite = true);
    //overwrites the opened archive with the new data
    bool                    Write();
    //write this archive to the specified filename, will return true on success and false on failure
    bool                    Write(const char* filename);

private:
    std::ifstream           m_fin;
    uint32_t                m_numEntries;
    uint32_t                m_archiveSize;
    uint32_t                m_offsetFirst;
    std::vector<BigEntry>   m_entries;
    std::string             m_filename;

private:
    //Get the reverse order of the bytes
    inline uint32_t Reverse(uint32_t v)
    {
        _asm {
            mov eax, v
                xchg al, ah
                rol eax, 16
                xchg al, ah
        }
    }

    //Read any fixed-size data type
    template <typename T>
    inline const T Read()
    {
        T result = T();

        m_fin.read(reinterpret_cast<char*>(&result), sizeof(T));

        return result;
    }

    //Read a nullterminated string
    inline std::string ReadCString()
    {
        std::string buffer;
        char c;
        while ((c = m_fin.get()) != '\0') {
            buffer += c;
        }

        return buffer;
    }

    inline char* ReadFixedString(const uint32_t offset, const uint32_t size)
    {
        char* buffer = new char[size + 1];
        memset(buffer, '\0', size + 1);
        m_fin.seekg(offset, std::ios::beg);
        m_fin.read(buffer, size);
        return buffer;
    }

    template <typename T>
    inline void Write(T var, char*& buffer)
    {
        for (auto i = 0; i < sizeof(T); ++i)
            buffer[3 - i] = (var >> (i * 8));

        buffer += sizeof(T);
    }


    inline void Write(const char* var, char*& buffer)
    {
        for (auto i = 0; i < strlen(var); ++i)
        {
            buffer[i] = var[i];
        }
        buffer += strlen(var);
    }

    inline void WriteCString(std::string var, char*& buffer)
    {
        for (auto i = 0; i < var.size(); ++i)
        {
            buffer[i] = var[i];
        }

        buffer += var.size()+1;
    }

    inline void WriteFixedString(const uint32_t offset, const uint32_t size,std::string data,char* buffer)
    {
        memcpy(&buffer[offset], data.c_str(), size);
    }

    inline bool Equal(std::string str1, std::string str2)
    {
        std::transform(str1.begin(), str1.end(), str1.begin(), ::tolower);
        std::transform(str2.begin(), str2.end(), str2.begin(), ::tolower);
        return str1 == str2;
    }
};

