//
// Created by kikab on 04.06.2018.
//

#include <DatFile.h>
#include <DatSubsystems/DatBackupManager.h>

#include "DatSubsystems/DatBackupManager.h"

#include "EasyLogging++/easylogging++.h"

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


    /*!
     * \author Gi1dor
     * \date 11.07.2018
     * Проверка доступности и корректности бэкапа dat файла
     * \param[in] backup_datname путь к файлу бэкапа
     * \returns true - если файл доступен для чтения его структура корректна. Иначе false
     */

    bool DatBackupManager::CheckIfBackupAvailable(const std::string &backup_datname) {
        DatFile file;
        bool result = (file.Initialise(backup_datname, 0).result == SUCCESS && file.Initialized());
        file.Deinitialize();
        return result;
    }


    /*!
     * \author Gi1dor
     * \date 11.07.2018
     * Создание резервной копии инициализированного Dat файла.
     *
     * \param[in] backup_datname путь к файлу создаваемого бэкапа
     */

    DatOperationResult<> DatBackupManager::CreateBackup(const std::string &backup_datname) {
        FILE* file = fopen(backup_datname.c_str(), "w+b");
        if (!file)
            return DatOperationResult<>(ERROR, "CREATEBACKUP: cannot open file " + backup_datname);

        dat->GetStatusModule().SetStatus(DatStatus::E_BACKUP_CREATING);
        dat->GetStatusModule().SetDebugMessage("Creating backup " + backup_datname + " from " + dat->GetIO().GetFilename().value);
        auto operation = CopyDatFile(*dat, file);
        dat->GetStatusModule().SetDefaultStatus();

        fclose(file);

        if (operation.result != SUCCESS)
            return DatOperationResult<>(ERROR, "CREATEBACKUP: Error in copy dat file");
        return DatOperationResult<>(SUCCESS);
    }


    /*!
     * \author Gi1dor
     * \date 11.07.2018
     * Восстановление dat файла из резервной копии. Если восстановление не удалось, DatFile будет деинициализирован.
     * \param[in] backup_datname путь к файлу резервной копии
     */

    DatOperationResult<> DatBackupManager::RestoreFromBackup(const std::string &backup_datname) {
        if (!CheckIfBackupAvailable(backup_datname))
            return DatOperationResult<>(ERROR, "RESTOREFROMBACKUP: incorrect backup file " + backup_datname);

        DatFile backup_file;
        backup_file.Initialise(backup_datname, 0);

        std::string dat_filename = dat->GetIO().GetFilename().value;
        dat->Deinitialize();

        FILE* file = fopen(dat_filename.c_str(), "w+b");
        if (!file)
            return DatOperationResult<>(ERROR, "RESTOREFROMBACKUP: cannot open file " + backup_datname);

        dat->GetStatusModule().SetStatus(DatStatus::E_BACKUP_RESTORING);
        dat->GetStatusModule().SetFinishedParts(0);
        dat->GetStatusModule().SetDebugMessage("Restoring file " + dat->GetIO().GetFilename().value +  " from backup " + backup_datname);
        auto operation = CopyDatFile(backup_file, file);
        dat->GetStatusModule().SetDefaultStatus();

        fclose(file);
        if (operation.result == ERROR)
            return DatOperationResult<>(ERROR, "RESTOREFROMBACKUP: error in copy dat file");

        dat->Initialise(dat_filename, dat->GetDatID());
        return DatOperationResult<>(SUCCESS);
    }


    /*!
     * \author Gi1dor
     * \date 11.07.2018
     * Удаление резервной копии.
     * \param[in] backup_datname путь к файлу резервной копии
     */

    DatOperationResult<> DatBackupManager::RemoveBackup(const std::string &backup_datname) {
        dat->GetStatusModule().SetStatus(DatStatus::E_BACKUP_REMOVING);
        dat->GetStatusModule().SetFinishedParts(0);
        dat->GetStatusModule().SetTotalParts(1);
        dat->GetStatusModule().SetDebugMessage("Removing backup file " + backup_datname);

        if (remove(backup_datname.c_str()) != 0)
            LOG(INFO) << "Removed backup file " << backup_datname;

        dat->GetStatusModule().SetDefaultStatus();
        return DatOperationResult<>(SUCCESS);
    }

    /*!
     * \author Gi1dor
     * \date 11.07.2018
     * Копирует содержимое dat файла в файл target
     * \param[in] source dat файл для копирования. Должен быть проинициализирован
     * \param[in] target файл для записи. Должен быть открыт на запись.
     * \warning Файл target после выполнения функции НЕ будет закрыт. Требуется закрыть командой fclose;
     */

    DatOperationResult<> DatBackupManager::CopyDatFile(DatFile &source, FILE *target) {
        long long parts_count =
                source.GetIO().GetActualDatSize().value / COPY_BLOCK_SIZE + (source.GetIO().GetActualDatSize().value % COPY_BLOCK_SIZE != 0);
        long long newfile_size = 0;
        unsigned elapsed_size = unsigned(source.GetIO().GetActualDatSize().value - newfile_size);
        
        dat->GetStatusModule().SetTotalParts(source.GetIO().GetActualDatSize().value);

        BinaryData data(COPY_BLOCK_SIZE);
        for (unsigned i = 0; i < parts_count; i++) {
            dat->GetStatusModule().SetFinishedParts(i * COPY_BLOCK_SIZE);

            auto operation = source.GetIO().ReadData(data, std::min(COPY_BLOCK_SIZE, elapsed_size), newfile_size);
            if (operation.result == ERROR)
                return DatOperationResult<>(ERROR, "Copy failed. Read data error");
            fwrite(data.data(), std::min(COPY_BLOCK_SIZE, elapsed_size), 1, target);

            newfile_size += std::min(COPY_BLOCK_SIZE, elapsed_size);
            elapsed_size -= std::min(COPY_BLOCK_SIZE, elapsed_size);
        }

        return DatOperationResult<>(SUCCESS);
    }

}