/***************************** LICENSE START ***********************************

 Copyright 2022 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

#include "WsDecoder.h"

#include <cmath>

#include <QFile>
#include <QJsonArray>
#include <QJsonObject>
#include <QString>

#include "MvQJson.h"
#include "MvQStreamOper.h"
#include "MvLog.h"
#include "WsType.h"

//----------------------------------------------
// Local implementation
//----------------------------------------------

namespace
{
void makeJson(WsType* wst, QString name, QPointF* scenePos, const QList<QPointF>& coord,
              const MvRequest& styleReq, QJsonDocument& doc);
void setCoord(MvRequest& r, QList<QPointF> arr);
QList<QPointF> getCoord(const MvRequest& rIn);
QJsonObject makeStyle(const MvRequest& r);
void styleValueCore(const QJsonObject& obj, QString param, QJsonValue& val);

// Create a geojson object
void makeJson(WsType* wst, QString name, QPointF* scenePos, const QList<QPointF>& coord,
              const MvRequest& styleReq, QJsonDocument& doc)
{
    QJsonObject d;
    d["type"]       = "Feature";
    d["convention"] = "metview-ws";
    d["class_ws"]      = wst->reqVerb();
    d["type_ws"]       = name;

    // geometry
    QJsonObject geom;
    geom["type"]        = wst->geojsonType();
    geom["coordinates"] = MvQJson::fromPointFList(coord, wst->geojsonType());
    d["geometry"]       = geom;

    // properties
    QJsonObject p;
    p["style"] = makeStyle(styleReq);
    if (scenePos) {
        p["scenePos"] = MvQJson::fromPointF(*scenePos);
    }
    d["properties"] = p;

    doc.setObject(d);
}

// Create a style description to be stored in geojson
QJsonObject makeStyle(const MvRequest& r)
{
    QJsonObject p;
    MvRequest style = r;
    style.unsetParam("X");
    style.unsetParam("Y");
    style.unsetParam("PATH");
    return MvQJson::fromRequest(style);
}

void setCoord(MvRequest& r, QList<QPointF> arr)
{
    for (auto& i : arr) {
        //MvLog().dbg() << "[" << i << "]" << arr[i];
        r.addValue("X", i.x());
        r.addValue("Y", i.y());
    }
}

QList<QPointF> getCoord(const MvRequest& rIn)
{
    QList<QPointF> lst;
    MvRequest r = rIn;

    int numX = r.iterInit("X");
    for (int i = 0; i < numX; i++) {
        double d;
        if (!r.iterGetNextValue(d)) {
            return {};
        }
        lst << QPointF(d, 0);
    }

    int numY = r.iterInit("Y");
    if (numX != numY) {
        return {};
    }
    for (int i = 0; i < numY; i++) {
        double d;
        if (!r.iterGetNextValue(d)) {
            return {};
        }
        lst[i].setY(d);
    }
    return lst;
}

void styleValueCore(const QJsonObject& obj, QString param, QJsonValue& val)
{
    auto p = obj["properties"].toObject();
    if (!p.isEmpty()) {
        auto s = p["style"].toObject();
        if (!s.isEmpty()) {
            auto params = s["params"].toObject();
            if (!params.isEmpty()) {
                val = params[param];
            }
        }
    }
}

template <typename T>
void setStyleValueCore(QJsonObject& obj, QString param, const T& v)
{
    auto p = obj["properties"].toObject();
    if (!p.isEmpty()) {
       auto s = p["style"].toObject();
       if (!s.isEmpty()) {
           auto pObj = s["params"].toObject();
           pObj[param] = v;
           s["params"] = pObj;
           p["style"] = s;
           obj["properties"] = p;
       }
    }
}

}  // namespace

//----------------------------------------------
//
// WsDecoder
//
//----------------------------------------------

void WsDecoder::toJson(const MvRequest& rIn, QJsonDocument& doc)
{
    if(const char* chVerb = rIn.getVerb()) {
        // The request must alway be expanded!
        auto r = WsType::expandRequest(rIn);
//        MvLog().dbg() << MV_FN_INFO << "r=";
//        r.print();
        auto wst = WsType::findByReq(r);
        if (!wst) {
            const char* chVerb = r.getVerb();
            MvLog().warn() << "WsDecoder::toJson "
                           << "no WS type found for verb=" << ((chVerb) ? chVerb : "") << "!";
            return;
        }
        makeJson(wst, wst->nameFromRequest(r), nullptr, getCoord(r), r, doc);
    }
}

void WsDecoder::toJson(WsType* wst, QPointF scenePos, const QList<QPointF>& coord,
                       const MvRequest& styleReq, QJsonDocument& doc)
{
    makeJson(wst, wst->name(), &scenePos, coord, styleReq, doc);
}

void WsDecoder::toJson(WsType* wst, const QList<QPointF>& coord,
                       const MvRequest& styleReq, QJsonDocument& doc)
{
    makeJson(wst, wst->name(), nullptr, coord, styleReq, doc);
}

void WsDecoder::toJsonCollection(const QJsonArray& a, QJsonDocument& doc)
{
    QJsonObject d;
    d["type"]       = "FeatureCollection";
    d["class_ws"]      = "WS_COLLECTION";
    d["convention"] = "metview-ws";
    d["features"]   = a;
    doc.setObject(d);
}

void WsDecoder::readCollection(QString fPath, QJsonArray& a)
{
//    MvLog().dbg() << MV_FN_INFO << fPath;
    if (!fPath.isEmpty()) {
        QFile f(fPath);
        if (f.open(QIODevice::ReadOnly | QIODevice::Text)) {
            auto doc = QJsonDocument::fromJson(f.readAll());
            auto obj = doc.object();
            if (obj["type"].toString() == "FeatureCollection") {
                a = obj["features"].toArray();
            }
        }
    }
}

WsType* WsDecoder::getWsType(const QJsonDocument& doc)
{
    auto obj = doc.object();
    if (!obj.empty() && obj["convention"] == "metview-ws") {
        QString cls = obj["class_ws"].toString();
        QString name = obj["type_ws"].toString();
        return WsType::find(cls, name);
    }
    return nullptr;
}

void WsDecoder::toRequest(const QJsonDocument& doc, MvRequest& r, long flags)
{
    auto obj = doc.object();
    if (!obj.empty() && obj["convention"] == "metview-ws") {
        QString cls = obj["class_ws"].toString();
        //MvLog().dbg() << "CLS=" << cls;
        if (!cls.isEmpty()) {
            auto prop = obj["properties"].toObject();
            if (!prop.isEmpty()) {
                MvQJson::toRequest(prop["style"].toObject(), r);
                if (r) {
                    const char* verb = r.getVerb();
                    //MvLog().dbg() << "verb=" << verb;
                    if (!verb || cls != verb) {
                        r = MvRequest();
                        return;
                    }
                }
                else {
                    r = MvRequest(cls.toStdString().c_str());
                }

                QJsonObject g = obj["geometry"].toObject();
                auto coord    = MvQJson::toPointFList(g["coordinates"].toArray(), g["type"].toString());
                setCoord(r, coord);

                QString name = obj["type_ws"].toString();
                if (WsType *wst = WsType::find(cls, name)) {
                    wst->updateTypeInReq(r);
                }

                MvRequest rTmp = r;
                r = WsType::expandRequest(rTmp, flags);
            }
        }
    }
}

void WsDecoder::toRequest(const std::string& fPath, MvRequest& r, long flags)
{
    if (!fPath.empty()) {
        QJsonDocument doc;
        QFile f(QString::fromStdString(fPath));
        if (f.open(QIODevice::ReadOnly | QIODevice::Text)) {
            doc = QJsonDocument::fromJson(f.readAll());
            WsDecoder::toRequest(doc, r, flags);
        }
    }
}

void WsDecoder::toStyleRequest(const QJsonDocument& doc, MvRequest& style)
{
    auto obj = doc.object();
    if (!obj.empty() && obj["convention"] == "metview-ws") {
        QString cls = obj["class_ws"].toString();
        if (!cls.isEmpty()) {
            auto prop = obj["properties"].toObject();
            if (!prop.isEmpty()) {
                MvQJson::toRequest(prop["style"].toObject(), style);
                if (style) {
                    const char* verb = style.getVerb();
                    if (!verb || cls != verb) {
                        style = MvRequest();
                        return;
                    }
                    style.unsetParam("TYPE");
                    style.unsetParam("X");
                    style.unsetParam("Y");
                    style.unsetParam("PATH");
                    return;
                }
            }
        }
    }
    style = MvRequest();
}

void WsDecoder::styleValue(const QJsonObject& obj, QString param, double& v)
{
    QJsonValue val;
    styleValueCore(obj, param, val);
    v = MvQJson::toDouble(val);
}

void WsDecoder::styleValue(const QJsonObject& obj, QString param, std::vector<bool>& vec)
{
    QJsonValue val;
    styleValueCore(obj, param, val);
    auto v = val.toArray();
    for (auto vv : v) {
        vec.push_back(MvQJson::toBool(vv));
    }
}

void WsDecoder::styleValue(const QJsonObject& obj, QString param, std::vector<double>& vec)
{
    QJsonValue val;
    styleValueCore(obj, param, val);
    auto v = val.toArray();
    for (auto vv : v) {
        vec.push_back(MvQJson::toDouble(vv));
    }
}

void WsDecoder::setStyleValue(QJsonObject& obj, QString param, double v)
{
    QJsonValue val = v;
    setStyleValueCore(obj, param, val);
}

void WsDecoder::setStyleValue(QJsonObject& obj, QString param, const std::vector<bool>& vec)
{
    QJsonArray a;
    for (auto v : vec) {
        a << int(v);
    }
    setStyleValueCore(obj, param, a);
}

void WsDecoder::setStyleValue(QJsonObject& obj, QString param, const std::vector<double>& vec)
{
    QJsonArray a;
    for (auto v : vec) {
        a << v;
    }
    setStyleValueCore(obj, param, a);
}

QPointF WsDecoder::scenePos(const QJsonObject& obj)
{
    auto p = obj["properties"];
    if (p.isObject()) {
        return MvQJson::toPointF(p.toObject()["scenePos"].toArray());
    }
    return {};
}

void WsDecoder::identify(const QJsonObject& obj, QString& wsCls, QString& wsType)
{
    wsCls = obj["class_ws"].toString();
    wsType = obj["type_ws"].toString();
}

QString WsDecoder::toolTip(const QJsonDocument& doc)
{
    auto obj = doc.object();
    auto p   = obj["properties"].toObject();
    MvRequest r;
    MvQJson::toRequest(p["style"].toObject(), r);
    const char* ch = r("TOOLTIP");
    return (ch)?QString(ch):QString();
}

void WsDecoder::setToolTip(QJsonDocument& doc, QString toolTip)
{
    auto obj = doc.object();
    auto p   = obj["properties"].toObject();
    auto s   = p["style"].toObject();
    MvRequest r;
    MvQJson::toRequest(s, r);
    r("TOOLTIP")      = toolTip.toStdString().c_str();
    s                 = MvQJson::fromRequest(r);
    p["style"]        = s;
    obj["properties"] = p;
    doc.setObject(obj);
}

bool WsDecoder::geoLocked(const QJsonDocument& doc)
{
    auto obj = doc.object();
    auto p   = obj["properties"].toObject();
    MvRequest r;
    MvQJson::toRequest(p["style"].toObject(), r);
    const char* ch = r("GEOLOCK");
    return ch && strcmp(ch, "ON") == 0;
}

void WsDecoder::setGeoLocked(QJsonDocument& doc, bool geoLocked)
{
    auto obj = doc.object();
    auto p   = obj["properties"].toObject();
    auto s   = p["style"].toObject();
    MvRequest r;
    MvQJson::toRequest(s, r);
    r("GEOLOCK")      = ((geoLocked) ? "ON" : "OFF");
    s                 = MvQJson::fromRequest(r);
    p["style"]        = s;
    obj["properties"] = p;
    doc.setObject(obj);
}
