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

#include <DatFile.h>
#include <DatOperationResult.h>
#include <EasyLogging++/easylogging++.h>

#ifdef WIN32
#define fseek _fseeki64
#define ftell _ftelli64
#endif

#define VERSION "7.1.0"

INITIALIZE_EASYLOGGINGPP

extern "C++"
{
namespace LOTRO_DAT {

    //------------------------------------------------//
    // INIT SECTION
    //------------------------------------------------//

    DatFile::DatFile() {
        el::Configurations defaultConf;
        el::Loggers::addFlag(el::LoggingFlag::LogDetailedCrashReason);
        el::Loggers::addFlag(el::LoggingFlag::ImmediateFlush);
        el::Loggers::addFlag(el::LoggingFlag::StrictLogFileSizeCheck);
        
        defaultConf.setToDefault();
        defaultConf.setGlobally(el::ConfigurationType::Format, "%datetime %level : %msg (function: %func)");

        defaultConf.setGlobally(el::ConfigurationType::ToFile, "true");
        defaultConf.setGlobally(el::ConfigurationType::Filename, "dat_library.log");
        defaultConf.setGlobally(el::ConfigurationType::ToStandardOutput, "false");
        defaultConf.setGlobally(el::ConfigurationType::MaxLogFileSize, "15728640"); // 15MB

        #ifndef NDEBUG
        defaultConf.set(el::Level::Debug, el::ConfigurationType::Enabled, "true");
        defaultConf.set(el::Level::Debug, el::ConfigurationType::Filename, "dat_library_debug.log");
        #elif NDEBUG
        defaultConf.set(el::Level::Debug, el::ConfigurationType::Enabled, "false");
        #endif
        
        el::Loggers::reconfigureAllLoggers(defaultConf);

        LOG(INFO) << "==================================================================";
        LOG(INFO) << "Starting new DatFile class instance";

        io_ = std::make_unique<DatIO>(this);
        fileSystem_ = std::make_unique<DatFileSystem>(this);
        localeManager_ = std::make_unique<DatLocaleManager>(this);
        exporter_ = std::make_unique<DatExporter>(this);
        patcher_ = std::make_unique<DatPatcher>(this);
        backupManager_ = std::make_unique<DatBackupManager>(this);
        status_ = std::make_unique<DatStatus>(this);
    }


    DatFile::~DatFile() {
        if (Initialized())
            Deinitialize();
    }

    LOTRO_DAT::DatLocaleManager &DatFile::GetLocaleManager() {
        return *localeManager_;
    }

    DatExporter &DatFile::GetExporter() {
        return *exporter_;
    }

    LOTRO_DAT::DatPatcher &DatFile::GetPatcher() {
        return *patcher_;
    }

    DatBackupManager &DatFile::GetBackupManager() {
        return *backupManager_;
    }

    DatIO &DatFile::GetIO() {
        return *io_;
    }

    DatFileSystem &DatFile::GetFileSystem() {
        return *fileSystem_;
    }

    DatStatus &DatFile::GetStatusModule() {
        return *status_;
    }

    DatOperationResult<> DatFile::Initialise(const std::string &filename, long long dat_id) {
        status_->SetStatus(DatStatus::E_INITIALISING);

        dat_id_ = dat_id;

        if (initialized_ && io_->GetFilename().result == SUCCESS && io_->GetFilename().value == filename)
            return DatOperationResult<>();

        initialized_ = false;

        auto operation = io_->Init(filename);
        if (operation.result != SUCCESS) {
            Deinitialize();
            status_->SetDefaultStatus();
            return DatOperationResult<>(ERROR, "DATINIT: Error, cannot initialize dat due to internal IO error");
        }

        operation = fileSystem_->Init();
        if (operation.result != SUCCESS) {
            Deinitialize();
            status_->SetDefaultStatus();
            return DatOperationResult<>(ERROR, "DATINIT: Error, cannot initialize dat due to filesystem parsing error");
        }

        operation = localeManager_->Init();
        if (operation.result != SUCCESS) {
            Deinitialize();
            status_->SetDefaultStatus();
            return DatOperationResult<>(ERROR, "DATINIT: Error, cannot initialize dat due to locale manager initialisation error");
        }

        initialized_ = true;

        status_->SetDefaultStatus();
        return DatOperationResult<>();
    }

    DatOperationResult<> DatFile::GatherInformation(const std::string &output_filename) {
        FILE *out = fopen(output_filename.c_str(), "w");
        if (!out)
            return DatOperationResult<>(ERROR, "GATHERDATINFO: Cannot open file " + output_filename);

        status_->SetStatus(DatStatus::E_GATHERING_INFO);

        fprintf(out, "########################################################################################\n"
                     "# LOTRO Dat library version: %8s                                                  #\n"
                     "# Author: Gi1dor (e1.gildor@gmail.com)                                                 #\n"
                     "# Is part of LOTRO Legacy project (http://translate.lotros.ru/)                        #\n"
                     "# Last version is available on http://git.gi1dor.ru/LotRO_Legacy/Universal_dat_library #\n"
                     "########################################################################################\n\n\n",
                VERSION);

        io_->PrintInformaion(out);
        fileSystem_->PrintInformaion(out);
        localeManager_->PrintInformaion(out);

        fclose(out);
        status_->SetDefaultStatus();
        return DatOperationResult<>(SUCCESS);
    }

    DatOperationResult<> DatFile::Deinitialize() {
        status_->SetStatus(DatStatus::E_COMMITING);

        auto operation = localeManager_->DeInit();
        if (operation.result != SUCCESS) {
            status_->SetDefaultStatus();
            return DatOperationResult<>(ERROR, "DATDEINIT: Error, cannot deinitialize. msg: " + operation.msg);
        }

        operation = fileSystem_->DeInit();
        if (operation.result != SUCCESS) {
            status_->SetDefaultStatus();
            return DatOperationResult<>(ERROR, "DATDEINIT: Error, cannot deinitialize. msg: " + operation.msg);
        }

        operation = io_->DeInit();
        if (operation.result != SUCCESS) {
            status_->SetDefaultStatus();
            return DatOperationResult<>(ERROR, "DATDEINIT: Error, cannot deinitialize. msg: " + operation.msg);
        }

        initialized_ = false;
        status_->SetDefaultStatus();
        return DatOperationResult<>();
    }


    bool DatFile::Initialized() {
        return initialized_;
    }

    long long DatFile::GetDatID() {
        return dat_id_;
    }
}
}