#include "datfile.h"
#include "subfiledata.h"
#include "subfiles/ddssubfile.h"

#include <iostream>

#include <yaml-cpp/yaml.h>
#include <EasyLogging++/easylogging++.h>

INITIALIZE_EASYLOGGINGPP

namespace LOTRO_DAT {
DatExportApi DatFile::api_;

DatFile::DatFile(int file_handle) {
    initialized_ = false;
    file_handle_ = file_handle;
    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);

    export_data_buf_ = BinaryData(64 * 1024 * 1024); // 64 MB - max file size;
}

DatFile::~DatFile() {
    Deinit();
}

bool DatFile::Init(const std::string& filename) {
    if (api_.OpenDatFile(file_handle_, filename.c_str(), 130) == file_handle_) {
        initialized_ = true;
        return true;  
    }

    return false;
}

void DatFile::Deinit() {
    if (initialized_) {
        api_.CloseDatFile(file_handle_);
    }
}

size_t DatFile::GetFilesNumInDatFile() {
    return api_.GetNumSubfiles(file_handle_);
}

size_t DatFile::PatchAllFilesFromDatabase(Database& db) {
    size_t patched_files_num = 0;

    SubfileData file;
    while (!(file = db.GetNextFile()).Empty()) {
        if (!file.options["fid"]) {
            LOG(ERROR) << "Incorrect db entry - no file_id specified";
            continue;
        }

        PatchFile(file);
    }
    return 0;
}

void DatFile::PatchFile(SubfileData file_data) {
    int file_id = file_data.options["fid"].as<int>();
    int version = 0;
    int size = api_.GetSubfileData(file_handle_, file_id, export_data_buf_.data(), version);
    BinaryData old_data = export_data_buf_.CutData(0, size);  

    BinaryData file = BuildForImport(old_data, file_data);
    api_.PutSubfileData(file_handle_, file_id, file.data(), 0, file.size(), 10, 99999);
}

void DatFile::PatchFile(int file_id, FILE_TYPE type, std::string path_to_file) {  
    BinaryData new_data(64 * 1024 * 1024);
    std::ifstream in(path_to_file, std::ifstream::binary);
    in.read((char*)new_data.data(), new_data.size());
    std::streamsize data_size = in.gcount();
    in.close();

    SubfileData imported_subfile;
    imported_subfile.binary_data = new_data.CutData(0, data_size);
    imported_subfile.options["ext"] = StringFromFileType(type);
    imported_subfile.options["fid"] = file_id;

    PatchFile(imported_subfile);
}

void DatFile::ExportFilesByTypeToDatabase(FILE_TYPE, Database&) {

}

void DatFile::ExportFilesByTypeToDirectory(FILE_TYPE, std::string) {

}

void DatFile::ExportFileById(int file_id, std::string target_file_path) {
    int version = 0;
    int size = api_.GetSubfileData(file_handle_, file_id, export_data_buf_.data(), version);
    auto data = export_data_buf_.CutData(0, size);
    std::cerr << "Exporting file with id = " << file_id << " and size = " << size << std::endl;
    SubfileData file = BuildForExport(file_id, data);

    std::ofstream out(target_file_path, std::ofstream::binary);
    out.write((char*)file.binary_data.data(), file.binary_data.size());
    out.close();
}

void DatFile::ExportFileByIdToDatabase(int file_id, Database& db) {
    int version = 0;
    int size = api_.GetSubfileData(file_handle_, file_id, export_data_buf_.data(), version);
    auto data = export_data_buf_.CutData(0, size);
    std::cerr << "Exporting file with id = " << file_id << " and size = " << size << std::endl;
    SubfileData file = BuildForExport(file_id, data);

    db.PushFile(file);
}

}; // namespace LOTRO_DAT