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

#include "DatFile.h"

#include "BinaryData.h"
#include "DatException.h"
#include "SubDirectory.h"
#include "Subfile.h"

#include <locale>

extern "C++"
{
namespace LOTRO_DAT {
    DatFile::DatFile() {
        dat_state_ = CLOSED;
    }

    DatFile::DatFile(const char *filename) {
        dat_state_ = CLOSED;
        OpenDatFile(filename);
        ReadSuperBlock();
        MakeDirectories();
        try {
            MakeDictionary();
        } catch (...) {
            fprintf(stderr, "Unable to make dictionary!! Unable to init DatFile!!!");
            return;
        }
        if (dat_state_ == SUCCESS_DICTIONARY)
            dat_state_ = READY;
        else
            throw DatException("Bad DatFile initialization! Not all init states were successfully passed!",
                               INIT_EXCEPTION);
    }

    DatFile::DatFile(const std::string &filename) {
        dat_state_ = CLOSED;
        OpenDatFile(filename.c_str());
        ReadSuperBlock();
        MakeDirectories();
        MakeDictionary();
        if (dat_state_ == SUCCESS_DICTIONARY)
            dat_state_ = READY;
        else
            throw DatException("Bad DatFile initialization! Not all init states were successfully passed!",
                               INIT_EXCEPTION);
    }

    DatFile::~DatFile() {
        if (file_handler_ != nullptr)
            fclose(file_handler_);
        delete file_handler_;
        delete root_directory_;
    }

    void DatFile::OpenDatFile(const char *dat_name) {
        if (dat_state_ != CLOSED)
            throw DatException("Bad initialisation of DatFile - current DatFile isn't in correct state!",
                               INIT_EXCEPTION);

        fopen_s(&file_handler_, dat_name, "r+b");

        if (file_handler_ == nullptr) {
            std::string err = "Bad DatFile::OpenDatFile. Unable to open file ";
            err += dat_name;
            throw DatException(err.c_str(), INIT_EXCEPTION);
        }

        _fseeki64(file_handler_, 0, SEEK_END);
        file_size_ = _ftelli64(file_handler_);
        _fseeki64(file_handler_, 0, SEEK_SET);

        dat_state_ = SUCCESS_OPENED;
    }

    void DatFile::ReadSuperBlock() {
        if (dat_state_ != SUCCESS_OPENED)
            throw DatException("Bad DatFile::ReadSuperBlock() - DatFile isn't in valid state!", INIT_EXCEPTION);

        BinaryData data(1024);
        ReadData(data, 1024);

        constant1_ = data.ToNumber<4>(0x100);
        constant2_ = data.ToNumber<4>(0x140);
        version1_ = data.ToNumber<4>(0x14C);
        version2_ = data.ToNumber<4>(0x150);
        root_directory_offset_ = data.ToNumber<4>(0x160);

        auto size1 = data.ToNumber<4>(0x148);

        if (constant1_ != 0x4C5000)
            throw DatException(
                    "Bad DatFile::ReadSuperBlock - variable at position 0x100 is not equal to .dat file constant!",
                    INIT_EXCEPTION);

        if (constant2_ != 0x5442)
            throw DatException(
                    "Bad DatFile::ReadSuperBlock - variable at position 0x140 is not equal to .dat file constant!",
                    INIT_EXCEPTION);

        if (file_size_ != size1)
            throw DatException(
                    "Bad DatFile::ReadSuperBlock - variable at 0x148 position is not equal to .dat file size!",
                    INIT_EXCEPTION);

        dat_state_ = SUCCESS_SUPERBLOCK;
    }

    void DatFile::MakeDirectories() {
        if (dat_state_ != SUCCESS_SUPERBLOCK)
            throw DatException("Bad DatFile::MakeDirectories() - DatFile isn't in valid state!", INIT_EXCEPTION);
        root_directory_ = new SubDirectory((unsigned) root_directory_offset_, this);
        dat_state_ = SUCCESS_DIRECTORIES;
    }

    void DatFile::MakeDictionary() {
        if (dat_state_ != SUCCESS_DIRECTORIES)
            throw DatException("Bad DatFile::MakeDictionary() - DatFile isn't in valid state!", INIT_EXCEPTION);
		try {
			root_directory_->MakeDictionary(dictionary_);
		} catch (...) {
			fprintf(stderr, "Bad DatFile::MakeDictionary() - File is corrupted?\n");
			return;
		}
		dat_state_ = SUCCESS_DICTIONARY;
    }

    void DatFile::ReadData(BinaryData &data, long long size, long long offset, long long data_offset) {
        if (dat_state_ == CLOSED)
            throw DatException("Bad DatFile::ReadData() - DatFile isn't in valid state!", READ_EXCEPTION);

        if (data_offset + size > data.size()) {
            std::string err = "Bad DatFile::ReadData - trying to read more than BinaryData size\n";
            err += std::string("Reading ") + std::to_string(size) + std::string(" bytes from ")
                   + std::to_string(offset) + std::string(" position in dat file.");
            throw DatException(err.c_str(), READ_EXCEPTION);
        }

        if (offset + size > file_size()) {
            std::string err = "Bad DatFile::ReadData - trying to read more than DatFile size elapsed\n";
            err += std::string("Reading ") + std::to_string(size) + std::string(" bytes from ")
                   + std::to_string(offset) + std::string(" position in dat file.");
            throw DatException(err.c_str(), READ_EXCEPTION);
        }

        _fseeki64(file_handler_, offset, SEEK_SET);
        fread(data.data() + data_offset, size, 1, file_handler_);
        data.CheckCompression();
    }

    void DatFile::WriteData(const BinaryData &data, long long size, long long offset, long long data_offset) {
        if (dat_state_ != READY)
            throw DatException("Bad DatFile::WriteData() - DatFile isn't in valid state!", WRITE_EXCEPTION);

        _fseeki64(file_handler_, offset, SEEK_SET);
        if (data_offset + size > data.size())
            throw DatException("Bad DatFile::WriteData - trying to write more than BinaryData size", WRITE_EXCEPTION);

        fwrite(data.data() + data_offset, size, 1, file_handler_);
    }

    long long DatFile::constant1() const {
        return constant1_;
    }

    long long DatFile::constant2() const {
        return constant2_;
    }

    long long DatFile::file_size() const {
        return file_size_;
    }

    long long DatFile::version1() const {
        return version1_;
    }

    long long DatFile::version2() const {
        return version2_;
    }

    const std::unordered_map<long long, Subfile *> &DatFile::dictionary() {
        return dictionary_;
    }

}
}