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

#include "Subfile.h"
#include "BinaryData.h"
#include "DatFile.h"
#include "SubfileData.h"
#include "EasyLogging++/easylogging++.h"

#include <algorithm>

const long long MAXSIZE = 50ll * 1024ll * 1024ll; // Setting maximal file size 50 MB; Files with size more than 50 mb
                                                  // will be recognized as incorrect and passed.
                                                  // This should be done because of not completely correct implementation
                                                  // of Subfiles and Subdirectories search in DatFile.
namespace LOTRO_DAT {

    Subfile::Subfile() = default;

    Subfile::Subfile(DatFile *dat, const BinaryData &header) {
        category = 0;

        dat_ = dat;

        unknown1_ = header.ToNumber<4>(0); // unknown1
        file_id_ = header.ToNumber<4>(4); // file_id
        file_offset_ = header.ToNumber<4>(8); // file_offset
        file_size_ = header.ToNumber<4>(12); // block_size
        timestamp_ = header.ToNumber<4>(16); // timestamp
        version_ = header.ToNumber<4>(20); // version
        block_size_ = header.ToNumber<4>(24); // block_size
        unknown2_ = header.ToNumber<4>(28); // unknown2
    }

    Subfile::Subfile(DatFile *dat, long long dictionary_offset, long long unknown1, long long file_id,
                                long long file_offset,
                                long long file_size, long long timestamp, long long version, long long block_size, long long unknown2) :
            category(0), dat_(dat), dictionary_offset_(dictionary_offset), unknown1_(unknown1), file_id_(file_id),
            file_offset_(file_offset),
            file_size_(file_size), timestamp_(timestamp), version_(version), block_size_(block_size), unknown2_(unknown2) {

        if (file_size_ > MAXSIZE) {
            LOG(ERROR) << "Bad Subfile::Subfile() - File size of file " << file_id << " with offset " << file_offset
                       <<" is too much... Maybe it's incorrect..?";
            file_id_ = -1;
            return;
        }
    }

    /// Typical getters of private fields, if there's need for getting information about SubFile from outside class.


    long long Subfile::dictionary_offset() const {
        return dictionary_offset_;
    }

    long long Subfile::unknown1() const {
        return unknown1_;
    }

    long long Subfile::file_id() const {
        return file_id_;
    }

    long long Subfile::file_offset() const {
        return file_offset_;
    }

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

    long long Subfile::timestamp() const {
        return timestamp_;
    }

    long long Subfile::version() const {
        return version_;
    }

    long long Subfile::block_size() const {
        return block_size_;
    }

    long long Subfile::unknown2() const {
        return unknown2_;
    }

    /// bool Subfile::FileType(...);
    /// Virtual function, can (and should) be redefined in child class, otherwise an exception will be thrown while exporting/importing file.
    /// Returns enum FILE_TYPE value, which is declared in DatFile.h

    FILE_TYPE Subfile::FileType() const {
        LOG(ERROR) << "INCORRECT IMPLEMENTATION!";
        return UNKNOWN;
    }

    /// std::string Subfile::Extension()
    /// Virtual function, can (and should) be redefined in child class, otherwise an exception will be thrown while exporting/importing file.
    /// Returns std::string with extension, beggined with '.', ex. ".jpg", ".txt" and so on;

    std::string Subfile::Extension() const {
        LOG(ERROR) << "INCORRECT IMPLEMENTATION!";
        return ".subfile";
    }

    /// bool Subfile::PrepareForExport(...);
    /// Virtual function, can be redefined in child class, otherwise an exception will be thrown while exporting file.
    /// Takes constant BinaryData& file_data, which contains all file data in .dat file, except first 8 bytes before file_id.
    /// Takes references to export_size - amount of exported files/strings, and content of exported data such as:
    ///  1) field binary_data - exported as RAW data
    ///  2) field text_data - UTF-16 text, exporting in UTF-8
    ///  3) field options - YAML field, which consists of some parameters of file such as file_id, extension and so on.
    /// Returns true if preparation was success. Otherwise returns false;

    SubfileData Subfile::PrepareForExport(const BinaryData &) {
        LOG(ERROR) << "INCORRECT IMPLEMENTATION!";
        return SubfileData();
    }


    /// BinaryData Subfile::PrepareForImport(...);
    /// Virtual function, can be redefined in child class, otherwise an exception will be thrown while importing file.
    /// Takes constant BinaryData& file_data, which contains all file data in .dat file, including first 8 bytes befire file_id.
    ///  1) const field binary_data - exported as RAW data
    ///  2) const field text_data - UTF-16 text, exporting in UTF-8
    ///  3) const field options - YAML field, which consists of some parameters of file such as file_id, extension and so on.
    /// Returns BinaryData - bytes array, prepared for writing in .dat file

    BinaryData Subfile::MakeForImport(const BinaryData &, const SubfileData &) {
        LOG(ERROR) << "INCORRECT IMPLEMENTATION!";
        return BinaryData(0);
    }

    BinaryData Subfile::MakeHeaderData() const {
        BinaryData header = BinaryData::FromNumber<4>(unknown1_)
                            + BinaryData::FromNumber<4>(file_id_)
                            + BinaryData::FromNumber<4>(file_offset_)
                            + BinaryData::FromNumber<4>(file_size_)
                            + BinaryData::FromNumber<4>(timestamp_)
                            + BinaryData::FromNumber<4>(version_)
                            + BinaryData::FromNumber<4>(block_size_)
                            + BinaryData::FromNumber<4>(unknown2_);
        return header;
    }


    bool Subfile::operator==(const Subfile &b) const {
        return unknown1_ == b.unknown1_
               && file_id_ == b.file_id_
               && file_offset_ == b.file_offset_
               && file_size_ == b.file_size_
               && timestamp_ == b.timestamp_
               && version_ == b.version_
               && block_size_ == b.block_size_
               && unknown2_ == b.unknown2_;
    }

    bool Subfile::operator!=(const Subfile &b) const {
        return !(*this == b);
    }

    Subfile &Subfile::operator=(const Subfile &b) {
        if (*this == b)
            return *this;

        category = b.category;
        dat_ = b.dat_;
        unknown1_ = b.unknown1_; // unknown1
        file_id_ = b.file_id_; // file_id
        file_offset_ = b.file_offset_; // file_offset
        file_size_ = b.file_size_; // block_size
        timestamp_ = b.timestamp_; // timestamp
        version_ = b.version_; // version
        block_size_ = b.block_size_; // block_size
        unknown2_ = b.unknown2_; // unknown2
        return *this;
    }
};