#include "book.h"
#include "utils.h"
#include "document.h"

extern float BOOKMARK_RECT_SIZE;

bool operator==(const DocumentViewState& lhs, const DocumentViewState& rhs)
{
    return (lhs.book_state.offset_x == rhs.book_state.offset_x) &&
        (lhs.book_state.offset_y == rhs.book_state.offset_y) &&
        (lhs.book_state.zoom_level == rhs.book_state.zoom_level) &&
        (lhs.document_path == rhs.document_path);
}

bool operator==(const CachedPageData& lhs, const CachedPageData& rhs) {
    if (lhs.doc != rhs.doc) return false;
    if (lhs.page != rhs.page) return false;
    if (lhs.zoom_level != rhs.zoom_level) return false;
    return true;
}

Portal Portal::with_src_offset(float src_offset)
{
    Portal res = Portal();
    res.src_offset_y = src_offset;
    return res;
}

QJsonObject Mark::to_json(std::string doc_checksum) const
{
    QJsonObject res;
    res["y_offset"] = y_offset;
    res["symbol"] = symbol;
    add_metadata_to_json(res);
    return res;
}

void Annotation::add_metadata_to_json(QJsonObject& obj) const {
    obj["creation_time"] = QString::fromStdString(creation_time);
    obj["modification_time"] = QString::fromStdString(modification_time);
    obj["uuid"] = QString::fromStdString(uuid);
}

void Annotation::load_metadata_from_json(const QJsonObject& obj) {
    if (obj.contains("creation_time")) {
        creation_time = obj["creation_time"].toString().toStdString();
    }
    if (obj.contains("modification_time")) {
        modification_time = obj["modification_time"].toString().toStdString();
    }
    if (obj.contains("uuid")) {
        uuid = obj["uuid"].toString().toStdString();
    }
    else {
        uuid = utf8_encode(new_uuid());
    }
}

std::vector<std::pair<std::string, QVariant>> Annotation::to_tuples() {
    std::vector<std::pair<std::string, QVariant>> res;

    add_to_tuples(res);

    res.push_back({ "uuid", QString::fromStdString(uuid) });
    res.push_back({ "creation_time", QString::fromStdString(creation_time) });
    res.push_back({ "modification_time", QString::fromStdString(modification_time) });

    return res;
}

QDateTime Annotation::get_creation_datetime() const {
    return QDateTime::fromString(QString::fromStdString(creation_time), "yyyy-MM-dd HH:mm:ss");
}

QDateTime Annotation::get_modification_datetime() const {
    return QDateTime::fromString(QString::fromStdString(modification_time), "yyyy-MM-dd HH:mm:ss");
}

void Annotation::update_creation_time() {
    creation_time = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss").toStdString();
    update_modification_time();
}

void Annotation::update_modification_time() {
    modification_time = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss").toStdString();
}

void Mark::from_json(const QJsonObject& json_object)
{
    y_offset = json_object["y_offset"].toDouble();
    symbol = static_cast<char>(json_object["symbol"].toInt());

    load_metadata_from_json(json_object);

}

void Mark::add_to_tuples(std::vector<std::pair<std::string, QVariant>>& tuples) {
    tuples.push_back({ "offset_y", y_offset });
    tuples.push_back({ "symbol", QChar(symbol) });
}

QJsonObject BookMark::to_json(std::string doc_checksum) const
{
    QJsonObject res;
    res["y_offset"] = y_offset_;
    res["description"] = QString::fromStdWString(description);
    res["begin_x"] = begin_x;
    res["begin_y"] = begin_y;
    res["end_x"] = end_x;
    res["end_y"] = end_y;

    if (is_freetext()) {
        res["color_red"] = color[0];
        res["color_green"] = color[1];
        res["color_blue"] = color[2];
        res["font_size"] = font_size;
        res["font_face"] = QString::fromStdWString(font_face);
    }

    add_metadata_to_json(res);

    return res;

}

void BookMark::add_to_tuples(std::vector<std::pair<std::string, QVariant>>& tuples) {
    tuples.push_back({ "offset_y", y_offset_ });
    tuples.push_back({ "desc",  QString::fromStdWString(description) });
    tuples.push_back({ "begin_x",  begin_x });
    tuples.push_back({ "begin_y",  begin_y });
    tuples.push_back({ "end_x",  end_x });
    tuples.push_back({ "end_y",  end_y });
    tuples.push_back({ "color_red", color[0] });
    tuples.push_back({ "color_green", color[1] });
    tuples.push_back({ "color_blue", color[2] });
    tuples.push_back({ "font_size", font_size });
    tuples.push_back({ "font_face", QString::fromStdWString(font_face) });
}

void BookMark::from_json(const QJsonObject& json_object)
{
    y_offset_ = json_object["y_offset"].toDouble();
    description = json_object["description"].toString().toStdWString();
    begin_x = json_object["begin_x"].toDouble();
    begin_y = json_object["begin_y"].toDouble();
    end_x = json_object["end_x"].toDouble();
    end_y = json_object["end_y"].toDouble();

    if (json_object.contains("color_red")) {
        color[0] = json_object["color_red"].toDouble();
        color[1] = json_object["color_green"].toDouble();
        color[2] = json_object["color_blue"].toDouble();
        font_size = json_object["font_size"].toDouble();
        font_face = json_object["font_face"].toString().toStdWString();
    }

    load_metadata_from_json(json_object);
}

bool BookMark::is_freetext() const {
    return (begin_y > -1) && (end_y > -1);
}

std::optional<char> BookMark::get_type() const{
    if (is_box()) {
        if (description.size() > 1) {
            return description[1];
        }
    }
    return {};
}

bool BookMark::is_box() const {
    if (description.size() > 0) {
        return description[0] == '#';
    }
    return false;
}

bool BookMark::is_marked() const {
    return (begin_y > -1) && (end_y == -1);
}

QJsonObject Highlight::to_json(std::string doc_checksum) const
{
    QJsonObject res;
    res["selection_begin_x"] = selection_begin.x;
    res["selection_begin_y"] = selection_begin.y;
    res["selection_end_x"] = selection_end.x;
    res["selection_end_y"] = selection_end.y;
    res["description"] = QString::fromStdWString(description);
    res["text_annot"] = QString::fromStdWString(text_annot);
    res["type"] = type;
    add_metadata_to_json(res);
    return res;
}

void Highlight::add_to_tuples(std::vector<std::pair<std::string, QVariant>>& tuples) {
    tuples.push_back({ "begin_x", selection_begin.x });
    tuples.push_back({ "begin_y", selection_begin.y });
    tuples.push_back({ "end_x", selection_end.x });
    tuples.push_back({ "end_y", selection_end.y });
    tuples.push_back({ "desc", QString::fromStdWString(description) });
    tuples.push_back({ "text_annot", QString::fromStdWString(text_annot) });
    tuples.push_back({ "type", QChar(type) });
}

void Highlight::from_json(const QJsonObject& json_object)
{
    selection_begin.x = json_object["selection_begin_x"].toDouble();
    selection_begin.y = json_object["selection_begin_y"].toDouble();
    selection_end.x = json_object["selection_end_x"].toDouble();
    selection_end.y = json_object["selection_end_y"].toDouble();
    description = json_object["description"].toString().toStdWString();
    type = static_cast<char>(json_object["type"].toInt());

    load_metadata_from_json(json_object);
}

QJsonObject Portal::to_json(std::string doc_checksum) const
{
    QJsonObject res;
    res["src_offset_y"] = src_offset_y;
    res["dst_checksum"] = QString::fromStdString(dst.document_checksum);
    res["dst_offset_x"] = dst.book_state.offset_x;
    res["dst_offset_y"] = dst.book_state.offset_y;
    res["dst_zoom_level"] = dst.book_state.zoom_level;

    if (src_offset_x) {
        res["src_offset_x"] = src_offset_x.value();
    }

    res["same"] = (doc_checksum == dst.document_checksum);
    add_metadata_to_json(res);
    return res;
}

void Portal::from_json(const QJsonObject& json_object)
{
    src_offset_y = json_object["src_offset_y"].toDouble();
    dst.document_checksum = json_object["dst_checksum"].toString().toStdString();
    dst.book_state.offset_x = json_object["dst_offset_x"].toDouble();
    dst.book_state.offset_y = json_object["dst_offset_y"].toDouble();
    dst.book_state.zoom_level = json_object["dst_zoom_level"].toDouble();

    if (json_object.contains("src_offset_x")) {

        src_offset_x = json_object["src_offset_x"].toDouble();
    }

    load_metadata_from_json(json_object);
}

void Portal::add_to_tuples(std::vector<std::pair<std::string, QVariant>>& tuples) {
    tuples.push_back({ "src_offset_y", src_offset_y });
    tuples.push_back({ "dst_document", QString::fromStdString(dst.document_checksum) });
    tuples.push_back({ "dst_offset_x", dst.book_state.offset_x });
    tuples.push_back({ "dst_offset_y", dst.book_state.offset_y });
    tuples.push_back({ "dst_zoom_level", dst.book_state.zoom_level });
    if (src_offset_x) {
        tuples.push_back({ "src_offset_x", src_offset_x.value()});
    }

}

bool operator==(const Mark& lhs, const Mark& rhs)
{
    return (lhs.symbol == rhs.symbol) && (lhs.y_offset == rhs.y_offset);
}

bool operator==(const BookMark& lhs, const BookMark& rhs)
{
    return  (lhs.y_offset_ == rhs.y_offset_) && (lhs.description == rhs.description);
}

bool operator==(const fz_point& lhs, const fz_point& rhs) {
    return (lhs.y == rhs.y) && (lhs.x == rhs.x);
}

bool operator==(const Highlight& lhs, const Highlight& rhs)
{
    return  (lhs.selection_begin.x == rhs.selection_begin.x) && (lhs.selection_end.x == rhs.selection_end.x) &&
        (lhs.selection_begin.y == rhs.selection_begin.y) && (lhs.selection_end.y == rhs.selection_end.y);
}

bool operator==(const Portal& lhs, const Portal& rhs)
{
    return  (lhs.src_offset_y == rhs.src_offset_y) && (lhs.dst.document_checksum == rhs.dst.document_checksum);
}

bool are_same(const BookMark& lhs, const BookMark& rhs) {
    return are_same(lhs.begin_x, rhs.begin_x) && are_same(lhs.begin_y, rhs.begin_y) && are_same(lhs.end_x, rhs.end_x) && are_same(lhs.end_y, rhs.end_y);
}

bool are_same(const Highlight& lhs, const Highlight& rhs) {
    return are_same(lhs.selection_begin, rhs.selection_begin) && are_same(lhs.selection_end, rhs.selection_end);
}

bool Portal::is_visible() const {
    return src_offset_x.has_value();
}

AbsoluteRect BookMark::get_rectangle() const{
    if (end_y > -1) {

        return AbsoluteRect(
            AbsoluteDocumentPos{ begin_x, begin_y },
            AbsoluteDocumentPos{ end_x, end_y }
        );
    }
    else {
        return AbsoluteRect(
            AbsoluteDocumentPos{ begin_x - BOOKMARK_RECT_SIZE, begin_y - BOOKMARK_RECT_SIZE },
            AbsoluteDocumentPos{ begin_x + BOOKMARK_RECT_SIZE, begin_y + BOOKMARK_RECT_SIZE }
        );
    }
}

AbsoluteRect Portal::get_rectangle() const{

    return AbsoluteRect(
        AbsoluteDocumentPos{ src_offset_x.value() - BOOKMARK_RECT_SIZE, src_offset_y - BOOKMARK_RECT_SIZE},
        AbsoluteDocumentPos{ src_offset_x.value() + BOOKMARK_RECT_SIZE, src_offset_y + BOOKMARK_RECT_SIZE}
    );
}

float BookMark::get_y_offset() const{
    if (begin_y != -1) return begin_y;
    return y_offset_;
}

AbsoluteDocumentPos BookMark::begin_pos() {
    return AbsoluteDocumentPos{ begin_x, begin_y };
}

AbsoluteDocumentPos BookMark::end_pos() {
    return AbsoluteDocumentPos{ end_x, end_y };
}

AbsoluteRect BookMark::rect() {
    return AbsoluteRect(begin_pos(), end_pos());
}

AbsoluteRect FreehandDrawing::bbox(){
    AbsoluteRect res;
    if (points.size() > 0) {
        res.x0 = points[0].pos.x;
        res.x1 = points[0].pos.x;
        res.y0 = points[0].pos.y;
        res.y1 = points[0].pos.y;
        for (int i = 1; i < points.size(); i++) {
            res.x0 = std::min(points[i].pos.x, res.x0);
            res.x1 = std::max(points[i].pos.x, res.x1);
            res.y0 = std::min(points[i].pos.y, res.y0);
            res.y1 = std::max(points[i].pos.y, res.y1);
        }
    }
    return res;
}

void SearchResult::fill(Document* doc) {
    if (rects.size() == 0) {
        doc->fill_search_result(this);
    }
}

std::string reference_type_string(ReferenceType rt) {
    if (rt == ReferenceType::None) return "none";
    if (rt == ReferenceType::Equation) return "equation";
    if (rt == ReferenceType::Reference) return "reference";
    if (rt == ReferenceType::Abbreviation) return "abbreviation";
    if (rt == ReferenceType::Generic) return "generic";
    if (rt == ReferenceType::Link) return "link";
    return "";
}