#include <DatFile.h>
#include <DatSubsystems/DatExporter.h>
#include <DatSubsystems/DatFileSystem.h>
#include <BinaryData.h>
#include <SubfileData.h>
#include <SubFile.h>

#include <string>
#include <fstream>
#include <algorithm>

namespace LOTRO_DAT {
    DatExporter::DatExporter(DatFile *datFilePtr) : dat(datFilePtr) {
    }

    /*!
     * \author Gi1dor
     * \date 05.07.2018
     * Извлечение в папку всех файлов, имеющих заданный тип
     * \param[in] type Тип файлов
     * \param[in] output_directory_path путь к папке, куда будут записаны файлы. Имя экспортируемого файла - file_id.ext, где file_id - id файла, ext - его расширение
     *
     * \returns DatOperationResult.value - количество извлечённых файлов
     */


    DatOperationResult<int> DatExporter::ExtractAllFilesByType(const FILE_TYPE &type, std::string output_directory_path) {
        int success_exported = 0;
        int iterated_files = 0;

        dat->GetStatusModule().SetStatus(DatStatus::E_EXTRACTING);
        dat->GetStatusModule().SetFinishedParts(0);
        dat->GetStatusModule().SetTotalParts(dat->GetFileSystem().GetInitialisedFilesNumber());

        dat->GetStatusModule().SetDebugMessage("Starting extracting files of type " + std::to_string(type) + ", this may take long time, please be patient!");
        LOG(INFO) << "Extracting files by type " + std::to_string(type) + "to database...";

        auto operation = dat->GetFileSystem().PerformOperationOnAllFiles([&iterated_files, &success_exported, this, type, &output_directory_path](std::shared_ptr<SubFile>& file) -> void {
            iterated_files++;
            dat->GetStatusModule().SetFinishedParts(iterated_files);
            dat->GetStatusModule().SetTotalParts(dat->GetFileSystem().GetInitialisedFilesNumber());

            if ((file->FileType() & type) == NO_TYPE)
                return;

            SubfileData export_file = file->PrepareForExport(dat->GetFileSystem().GetFileData(*file, 8).value);
            dat->GetStatusModule().SetDebugMessage("Extracting file " + std::to_string(file->file_id()) + " (Files by type -> directory)");

            if (export_file.Empty())
                return;

            bool result;

            if (type == TEXT)
                result = WriteStringToFile(export_file.text_data, output_directory_path + std::to_string(file->file_id()) + file->Extension()).result;
            else
                result = export_file.binary_data.WriteToFile(output_directory_path + std::to_string(file->file_id()) + file->Extension());

            if (result)
                success_exported++;
        });

        LOG(INFO) << "Extracting files: successfully exported " << success_exported << " files";
        dat->GetStatusModule().SetDefaultStatus();
        return DatOperationResult<int>(success_exported, SUCCESS);
    }


    /*!
     * \author Gi1dor
     * \date 05.07.2018
     * Извлечение файла по его id
     * \param[in] file_id id файла
     * \param[in] output_filename - имя файла, куда будет записаны данные.
     */

    DatOperationResult<> DatExporter::ExtractFileById(long long file_id, std::string output_filename) {
        dat->GetStatusModule().SetStatus(DatStatus::E_EXTRACTING);
        dat->GetStatusModule().SetFinishedParts(0);
        dat->GetStatusModule().SetTotalParts(1);
        dat->GetStatusModule().SetDebugMessage("Extracting file " + std::to_string(file_id) + " to file " + output_filename);

        auto operation_GetFile = dat->GetFileSystem().GetFile(file_id);
        if (operation_GetFile.result == ERROR) {
            dat->GetStatusModule().SetDefaultStatus();
            return DatOperationResult<>(ERROR, "EXTRACTFILEBYID: File not found! id = " + std::to_string(file_id));
        }
        std::shared_ptr<SubFile> file = operation_GetFile.value;

        auto operation_GetFileData = dat->GetFileSystem().GetFileData(*file, 8);
        if (operation_GetFileData.result == ERROR) {
            dat->GetStatusModule().SetDefaultStatus();
            return DatOperationResult<>(ERROR, "EXTRACTFILEBYID: Can't get data for id = " + std::to_string(file_id));
        }

        SubfileData export_data = file->PrepareForExport(operation_GetFileData.value);

        bool result;

        if (file->FileType() == TEXT)
            result = WriteStringToFile(export_data.text_data, output_filename  + file->Extension()).result;
        else
            result = export_data.binary_data.WriteToFile(output_filename + file->Extension());

        dat->GetStatusModule().SetDefaultStatus();
        if (!result)
            return DatOperationResult<>(ERROR, "EXTRACTFILEBYID: Cannot write to file" + output_filename + file->Extension());

        return DatOperationResult<>(SUCCESS);
    }


    /*!
     * \author Gi1dor
     * \date 05.07.2018
     * Извлечение в базу данных всех файлов, имеющих заданный тип
     * \param[in] type Тип файлов
     * \param[in] db Указатель на объект базы данных
     *
     * \returns DatOperationResult.value - количество извлечённых файлов
     */


    DatOperationResult<int> DatExporter::ExtractAllFilesByType(const FILE_TYPE &type, Database *db) {
        if (!db)
            return DatOperationResult<int>(0, ERROR, "EXTRACTALLBYTYPETODB: database is nullptr");

        dat->GetStatusModule().SetStatus(DatStatus::E_EXTRACTING);
        dat->GetStatusModule().SetFinishedParts(0);
        dat->GetStatusModule().SetTotalParts(dat->GetFileSystem().GetInitialisedFilesNumber());
        dat->GetStatusModule().SetDebugMessage("Initialising extraction of files by type " + std::to_string(type) + "to database... This may take long time, please be patient!\"");
        LOG(INFO) << "Extracting files by type " + std::to_string(type) + "to database...";

        int success_exported = 0;
        int iterated_files = 0;

        auto operation = dat->GetFileSystem().PerformOperationOnAllFiles([&success_exported, &iterated_files, this, type, db](std::shared_ptr<SubFile>& file) {
            iterated_files++;
            dat->GetStatusModule().SetFinishedParts(iterated_files);

            if ((file->FileType() & type) == NO_TYPE)
                return;

            dat->GetStatusModule().SetDebugMessage("Extracting file " + std::to_string(file->file_id()) + " (Files by type -> database)");

            SubfileData export_file = file->PrepareForExport(dat->GetFileSystem().GetFileData(*file, 8).value);

            bool result = db->PushFile(export_file);

            if (result)
                success_exported++;
        });

        LOG(INFO) << "Extracting files: successfully exported " << success_exported << " files";
        dat->GetStatusModule().SetDefaultStatus();
        return DatOperationResult<int>(success_exported, SUCCESS);
    }

    /*!
     * \author Ivan Arkhipov
     * \date 21.10.2018
     * Извлечение в базу данных файла с заданным id
     * \param[in] file_id ID файла
     * \param[in] db Указатель на объект базы данных
     */

    DatOperationResult<> DatExporter::ExtractFileById(long long file_id, Database *db) {
        if (!db)
            return DatOperationResult<>(ERROR, "EXTRACTFILEBYIDTODB: database is nullptr");

        dat->GetStatusModule().SetStatus(DatStatus::E_EXTRACTING);
        dat->GetStatusModule().SetFinishedParts(0);
        dat->GetStatusModule().SetTotalParts(1);
        LOG(INFO) << "Extracting file with id = " << file_id << "to database...";

        auto operation_GetFile = dat->GetFileSystem().GetFile(file_id);
        if (operation_GetFile.result == ERROR) {
            dat->GetStatusModule().SetDefaultStatus();
            return DatOperationResult<>(ERROR, "EXTRACTFILEBYIDTODB: File not found! id = " + std::to_string(file_id));
        }
        std::shared_ptr<SubFile> file = operation_GetFile.value;

        auto operation_GetFileData = dat->GetFileSystem().GetFileData(*file, 8);
        if (operation_GetFileData.result == ERROR) {
            dat->GetStatusModule().SetDefaultStatus();
            return DatOperationResult<>(ERROR, "EXTRACTFILEBYIDTODB: Can't get data for id = " + std::to_string(file_id));
        }

        SubfileData export_data = file->PrepareForExport(operation_GetFileData.value);

        bool result = db->PushFile(export_data);

        dat->GetStatusModule().SetDefaultStatus();
        if (!result)
            return DatOperationResult<>(ERROR, "EXTRACTFILEBYIDTODB: Cannot export file" + std::to_string(file_id));

        LOG(INFO) << "Successfully exported file " << file_id << " to database";
        return DatOperationResult<>(SUCCESS);
    }

    /*!
     * \author Gi1dor
     * \date 05.07.2018
     * Запись строки в формате UTF-16 в файл
     * \param[in] str Строка для записи в файл
     * \param[in] path Путь к файлу, в который строка будет записана
     *
     * \warning Путь к файлу должен быть корректным (папки должны существовать). Если и сам файл существует, то он будет перезаписан
     */

    DatOperationResult<> DatExporter::WriteStringToFile(const std::u16string &str, const std::string &path) {
        std::basic_ofstream<char16_t> output_stream(path, std::ios::out);
        if (!output_stream.is_open())
            return DatOperationResult<>(ERROR, "WRITESTRINGTOFILE: cant open file " + path);

        output_stream << str;
        output_stream.close();

        return DatOperationResult<>();
    }
};