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

#include <algorithm>


extern  "C++"
{
namespace LOTRO_DAT {

    BinaryData::BinaryData() {
        data_ = nullptr;
        size_ = 0;
    }

    BinaryData::BinaryData(const BinaryData &d) {
        size_ = d.size_;
        data_ = new unsigned char[size_];
        memcpy(data_, d.data_, size_);
    }

    BinaryData::BinaryData(BinaryData &&other) noexcept {
        size_ = other.size_;
        data_ = other.data_;
        other.size_ = 0;
        other.data_ = nullptr;
    }

    BinaryData::BinaryData(const char* data, unsigned int size) {
        size_ = size;
        data_ = new unsigned char[size_];
        memcpy(data_, data, size_);
    }

    BinaryData::BinaryData(unsigned int size) {
        data_ = new unsigned char[size];
        for (size_t i = 0; i < size; i++)
            data_[i] = 0;
        size_ = size;
    }

    BinaryData::~BinaryData() {
        delete[] data_;
    }

    unsigned char&  BinaryData::operator[](const unsigned int &pos) {
        if (pos >= size_)
            LOG(ERROR) << "Bad BinaryData::operator[]. Position " << pos
                       << " is out of range in BinaryData with size " << size_;
        return data_[pos];
    }
    
    const unsigned char& BinaryData::operator[] (const unsigned int &pos) const {
        if (pos >= size_)
            LOG(ERROR) << "Bad BinaryData::operator[]. Position " << pos
                       << " is out of range in BinaryData with size " << size_;
        return data_[pos];
    }

    BinaryData BinaryData::operator +(const BinaryData &b) {
        BinaryData res(size_ + b.size());
        if (size_ > 0)
            memcpy(res.data_, data_, size_);
        if (b.size() > 0)
            memcpy(res.data_ + size_, b.data_, b.size_);
        return res;
    }

    // Translates T bytes from data into number using UTF-16LE encoding of the .dat file
    template<unsigned int T>
    long long BinaryData::ToNumber(const long long &pos) const {
        unsigned long long ans = 0;

        if (pos + T > size_) {
            LOG(ERROR) << "Reading " << T << " bytes from " << pos << " offset with BinaryData size " << size_
                       << " Reached end of BinaryData!";
            return 0;
        }

        for (int i = T - 1; i >= 0; i--)
            ans = ((ans << 8ull) | data_[pos + i]);

        return ans;
    }

    // Translates T bytes from data into number in raw format
    template<unsigned int T>
    long long BinaryData::ToNumberRAW(const long long &pos) const {
        long long ans = 0;

        if (pos + T > size_) {
            LOG(ERROR) << "Reading " << T << " bytes from " << pos << " offset with BinaryData size " << size_
                       << " Reached end of BinaryData!";
            return 0;
        }

        for (unsigned i = 0; i < T; i++)
            ans = ((ans << 8ll) | data_[pos + i]);

        return ans;
    }

    // Makes data from specified T bytes of number in Little Endian encoding
    template<unsigned int T>
    BinaryData BinaryData::FromNumber(const long long &number) {
        if (T <= 0) {
            LOG(ERROR) << "Trying to make data from amount of bytes < 0";
            return BinaryData(0);
        }

        BinaryData data(T);
        for (size_t i = 0; i < T; i++)
            data.data_[i] = (unsigned char)((number >> (8 * i)) & 0xFF);
        return data;
    }

    // Makes data from specified T bytes of number in raw
    template<unsigned int T>
    BinaryData BinaryData::FromNumberRAW(const long long &number) {
        if (T <= 0) {
            LOG(ERROR) << "Trying to make data from amount of bytes < 0";
            return BinaryData(0);
        }

        BinaryData data = FromNumber<T>(number);
        std::reverse(data.data_, data.data_ + data.size());
        return data;
    }

    BinaryData &BinaryData::operator=(const BinaryData &data) {
        if (&data == this)
            return *this;

        if (size_ != 0 && data_ != nullptr)
            delete[] data_;

        size_ = data.size_;
        data_ = nullptr;

        if (size_ != 0) {
            data_ = new unsigned char[size_];
            memcpy(data_, data.data_, size_);
        }

        return *this;
    }

    size_t BinaryData::size() const {
        return size_;
    }

    unsigned char *BinaryData::data() const {
        return data_;
    }

    bool BinaryData::WriteToFile(const char *filename) const {
        FILE *f;
        //f = fopen64(filename, "wb");
		f = fopen(filename, "wb");
		if (f == nullptr) {
            LOG(ERROR) << "File " << std::string(filename) << " doesn't exist or is unreachable.. Cannot write data";
            return false;
        }

        fwrite(data(), size(), 1, f);
        fclose(f);
        return true;
    }

    bool BinaryData::WriteToFile(const std::string &filename) const {
        return WriteToFile(filename.c_str());
    }

    void BinaryData::ReadFromFile(const char *filename) {
        FILE *f;
        fopen_s(&f, filename, "rb");
        if (f == nullptr) {
            LOG(ERROR) << "File " << std::string(filename) << " doesn't exist.. Retuning null data";
            size_ = 0;
            delete[] data_;
            return;
        }

        _fseeki64(f, 0, SEEK_END);
        long long file_size = ftell(f);
        _fseeki64(f, 0, SEEK_SET);

        BinaryData temp_data = BinaryData(unsigned(file_size));

        fread(temp_data.data_, temp_data.size_, 1, f);
        *this = temp_data;
        fclose(f);
    }

    void BinaryData::ReadFromFile(const std::string &filename) {
        ReadFromFile(filename.c_str());
    }

    BinaryData BinaryData::CutData(long long first, long long last) const {
        if (last < 0)
            last = size();

        if (last > size()) {
            LOG(ERROR) << "Unable to cut data - parameter last is out of range";
            return BinaryData(0);
        }

        BinaryData newdata(unsigned(last - first));
        memcpy(newdata.data(), data() + first, newdata.size());
        return newdata;
    }

    bool BinaryData::operator==(const BinaryData &b) const {
        if (size() != b.size())
            return false;
        for (size_t i = 0; i < size(); i++)
            if (data_[i] != b.data_[i])
                return false;
        return true;
    }

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

    bool BinaryData::Empty() const {
        return size_ == 0;
    }

    void BinaryData::Append(const BinaryData &b, size_t append_offset) {
        if (append_offset + b.size() > size()) {
            LOG(ERROR) << "data for appending has more bytes than BinaryData size!";
            return;
        }
        memcpy(data_ + append_offset, b.data_, b.size_);
    }

    template long long BinaryData::ToNumber<1>(long long const&) const;
    template long long BinaryData::ToNumber<2>(long long const&) const;
    template long long BinaryData::ToNumber<4>(long long const&) const;
    template long long BinaryData::ToNumber<8>(long long const&) const;

    template BinaryData BinaryData::FromNumber<1>(const long long &);
    template BinaryData BinaryData::FromNumber<2>(const long long &);
    template BinaryData BinaryData::FromNumber<4>(const long long &);
    template BinaryData BinaryData::FromNumber<8>(const long long &);

    template long long BinaryData::ToNumberRAW<1>(long long const&) const;
    template long long BinaryData::ToNumberRAW<2>(long long const&) const;
    template long long BinaryData::ToNumberRAW<4>(long long const&) const;
    template long long BinaryData::ToNumberRAW<8>(long long const&) const;

    template BinaryData BinaryData::FromNumberRAW<1>(const long long &);
    template BinaryData BinaryData::FromNumberRAW<2>(const long long &);
    template BinaryData BinaryData::FromNumberRAW<4>(const long long &);
    template BinaryData BinaryData::FromNumberRAW<8>(const long long &);
}
}