//
// Created by Иван_Архипов on 31.10.2017.
//

#ifndef LOTRO_DAT_PATCHER_DATFILE_H
#define LOTRO_DAT_PATCHER_DATFILE_H

#ifdef LOTRO_DAT_EXPORTS
#define LOTRO_DAT_API __declspec(dllexport)
#else
#define LOTRO_DAT_API __declspec(dllimport)
#endif

#include <fstream>
#include <map>
#include <unordered_map>
#include <set>
#include <vector>
#include <yaml-cpp/node/node.h>
#include <unordered_set>
#include "Database.h"

// Dat file names definitions

extern "C++"
{
namespace LOTRO_DAT {
    class BinaryData;

    class DatException;

    class SubDirectory;

    class Subfile;

    class SubfileData;

    enum FILE_TYPE : int {
        TEXT,
        JPG,
        DDS,
        WAV,
        OGG,
        FONT,
        UNKNOWN
    };

    enum DAT_RESULT : int {
        //----BASE----///
        FAILED = 0,
        SUCCESS = 1,

        //----WARNINGS----//
        CORRUPTED_FILE_WARNING = 2,

        //----ERRORS----//
        INCORRECT_STATE_ERROR = -1,
        NO_FILE_ERROR = -2,
        WRITE_TO_FILE_ERROR = -3,
        INCORRECT_DAT_ID = -4,
        INCORRECT_SUPERBLOCK_ERROR = -5,
        INIT_ERROR = -6,
        DUBLICATE_PATCH_FILES_ERROR = -8,
        INCORRECT_PATCH_FILE = -9,
        DAT_PATCH_FILE_ERROR = -10,
        DAT_READ_ERROR = -11,
        DAT_WRITE_ERROR = -12,
        CRITICAL_DAT_ERROR = -14,
        NO_BACKUP_ERROR = -15,
        REMOVE_FILE_ERROR = -16
    };

    enum DAT_STATE {
        CLOSED = 1,
        SUCCESS_OPENED = 2,
        SUCCESS_SUPERBLOCK = 3,
        SUCCESS_DIRECTORIES = 4,
        SUCCESS_DICTIONARY = 5,
        READY = 6,
        UPDATED = 7
    };

    enum LOCALE : unsigned {
        ORIGINAL = 0,
        PATCHED = 1
    };

    class DatFile {
        friend class SubDirectory;

    public:
        DatFile();

        DAT_RESULT InitDatFile(const std::string &filename, int dat_id);

        DAT_STATE DatFileState() const;

        DAT_RESULT PerformDictionaryCheck();

        ~DatFile();

        // EXTRACT BASE SECTION

        DAT_RESULT ExtractFile(long long file_id, const std::string &path = "");

        DAT_RESULT ExtractFile(long long file_id, Database *db);

        int ExtractAllFilesByType(FILE_TYPE type, std::string path = "");

        int ExtractAllFilesByType(FILE_TYPE type, Database *db);

        // PATCH BASE SECTION

        //DAT_RESULT PatchFile(const char *filename, YAML::Node options);

        DAT_RESULT PatchFile(const SubfileData &data);

        DAT_RESULT PatchAllDatabase(Database *db);


        long long files_number() const;

        const std::string& filename() const;

        BinaryData GetFileData(const Subfile *file, long long offset = 0);

        DAT_RESULT CloseDatFile();

    private:
        // INIT SECTION
        DAT_RESULT OpenDatFile(const char *dat_name);

        DAT_RESULT ReadSuperBlock();

        DAT_RESULT MakeDirectories();

        DAT_RESULT MakeDictionary();

        DAT_RESULT ModifyFragmentationJournal();

        // READ-WRITE SECTION

        DAT_RESULT ReadData(BinaryData &data, long long size, long long offset = 0, long long data_offset = 0);

        DAT_RESULT WriteData(const BinaryData &data, long long size, long long offset = 0, long long data_offset = 0);

        // PATCH SECTION

        DAT_RESULT ApplyFilePatch(Subfile *file, BinaryData &data);

    private:
        long long free_buffered_size_;

        const long long MAX_BUFFERED_SIZE = 10 * 1024 * 1024; // 50 megabytes;
        const long long MIN_BUFFERED_SIZE = 1 * 1024 * 1024; // 5 megabytes;

        void AddBufferedSize();

        // COMMIT UPDATE SECTION

        DAT_RESULT UpdateHeader();

        // LOCALE MANAGING SECTION
    private:
        DAT_RESULT InitLocales();

        DAT_RESULT CommitLocales();

        DAT_RESULT CommitDirectories();

    public:
        DAT_RESULT SetLocale(LOCALE locale);

        LOCALE current_locale();

        // CATEGORY MANAGEMENT SECTION

        DAT_RESULT EnableCategory(int category);

        DAT_RESULT DisableCategory(int category);

        const std::set<long long>& GetInactiveCategoriesList();

        // SOME PRIOR TOOLS

        DAT_RESULT RepairDatFile();

        bool CorrectSubfile(Subfile *file);

        bool CheckIfUpdatedByGame();

        DAT_RESULT WriteUnorderedDictionary(std::string path) const;

        bool CheckIfNotPatched();

        bool CheckIfPatchedByOldLauncher();

        bool CheckIfBackupExists(const std::string &backup_datname);

        DAT_RESULT CreateBackup(const std::string &backup_datname);

        DAT_RESULT RestoreFromBackup(const std::string &backup_datname);

        DAT_RESULT RemoveBackup(const std::string &backup_datname);
    private:
        std::map<long long, Subfile*>* GetLocaleDictReference(LOCALE locale);

    private:
        LOCALE current_locale_;

        std::string filename_;

        std::map<long long, Subfile*> orig_dict_;
        std::map<long long, Subfile*> patch_dict_;
        std::set<long long> pending_patch_;
        std::set<long long> inactive_categories;

    private:
        FILE *file_handler_;

        SubDirectory *root_directory_;

        std::set<long long> pending_dictionary_;
        std::map<long long, Subfile *> dictionary_;

        long long constant1_;
        long long constant2_;
        long long file_size_;
        long long version1_;
        long long version2_;
        long long fragmentation_journal_size_;
        long long fragmentation_journal_end_;
        long long root_directory_offset_;
        long long fragmentation_journal_offset_;
        long long free_dat_size_;

        long long actual_dat_size_;
        DAT_STATE dat_state_;

        int dat_id_;
        bool dat_without_patches_;
    };
}
}

#endif //LOTRO_DAT_PATCHER_DATFILE_H