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

 Copyright 2019 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 "VerbFunctionHelp.h"

#include <fstream>
#include <iostream>

#include "fstream_mars_fix.h"

#include <QDebug>
#include <QFile>
#include <QTextStream>
#include <QMessageBox>
#include <QXmlStreamReader>
#include <QImageReader>

#include "IconClassHelp.h"
#include "LanguageHelper.h"
#include "MvIconLanguage.h"
#include "MvIconParameter.h"
#include "MvQPalette.h"
#include "MvQTheme.h"

//#define GENERATE_ICONCLASS_YAML
//#define GENERATE_ICONDEF_YAML

//========================================================
//
// VerbFunctionHelpParam
//
//========================================================

VerbFunctionHelpParam::VerbFunctionHelpParam(const MvIconParameter& param) :
    param_(param)
{
    param_.scan(*this);
}

void VerbFunctionHelpParam::next(const MvIconParameter&, const char* first, const char* second)
{
    if (!first)
        return;

    values_ << ((second) ? QString(second).toLower() : QString(first).toLower());
}

QString VerbFunctionHelpParam::name() const
{
    return QString(param_.name()).toLower();
}

QStringList VerbFunctionHelpParam::defaultValues() const
{
    QStringList lst;
    for (auto it : param_.defaults()) {
        lst << QString::fromStdString(it).toLower();
    }
    return lst;
}

QString VerbFunctionHelpParam::interface() const
{
    return QString(param_.interface()).toLower();
}

QString VerbFunctionHelpParam::interfaceFunction() const
{
    if (QString(param_.interface()).toLower() == "icon") {
        Request r = param_.interfaceRequest();
        if (r) {
            const std::vector<std::string>& cls = r.get("class");
            for (auto clsName : cls) {
                if (IconClassHelp* clsH = IconClassHelp::find(clsName)) {
                    if (QString::fromStdString(clsH->name()).toLower().startsWith("m")) {
                        return QString::fromStdString(clsH->macroFunction());
                    }
                }
            }

            if (cls.size() >= 1) {
                if (IconClassHelp* clsH = IconClassHelp::find(cls[0]))
                    return QString::fromStdString(clsH->macroFunction());
            }
        }
    }
    return {};
}


int VerbFunctionHelpParam::numValues() const
{
    return values_.count();
}

QString VerbFunctionHelpParam::valueName(int i) const
{
    return values_[i];
}

void VerbFunctionHelpParam::toYaml(std::ofstream& out)
{
    out << "- " << name().toLower().toStdString() << ":" << std::endl;
    auto dv             = defaultValues();
    std::string parType = interface().toStdString();

    if (dv.count() > 0) {
        auto s = dv[0].simplified().toStdString();
        out << "   default: \'" << s << "\'" << std::endl;
    }

    QStringList lst;
    for (int i = 0; i < numValues(); i++) {
        lst << valueName(i);
    }
    if (lst.count() > 0) {
        auto v = lst.join("/").toStdString();
        if (parType == "menu")
            parType = "combo";

        if (parType == "on_off" || parType == "combo") {
            out << "   values: " << v << std::endl;
        }

        if (parType == "combo" && lst[0] == "solid") {
            parType = "line_style";
        }
        else if (v == "*" && name().endsWith("_thickness")) {
            parType = "line_width";
        }
        else if (parType == "string" && v == "*") {
            parType = "number";
        }

        if ((parType == "string" || parType == "text") && lst.contains("/")) {
            if (lst.size() == 2 && lst.contains("*")) {
                parType = "number_list";
            }
            else {
                parType = "list";
            }
        }

        if (parType == "colourlist") {
            parType = "colour_list";
        }

        if (parType == "colour") {
            QStringList pseudoCols;
            foreach (QString cName, lst) {
                if (MvQPalette::isPseudo(cName.toUpper().toStdString())) {
                    pseudoCols << cName;
                }
            }
            if (!pseudoCols.isEmpty()) {
                out << "   values: " << pseudoCols.join("/").toStdString() << std::endl;
            }
        }

        if (!parType.empty())
            out << "   type: " << parType << std::endl;

        //out << "   interface: " << interface().toStdString() << std::endl;
    }
}

//========================================================
//
// VerbFunctionHelpFunction
//
//========================================================

VerbFunctionHelpFunction::VerbFunctionHelpFunction(QString name, IconClassHelp* iconClass) :
    name_(name),
    iconClass_(iconClass),
    paramsLoaded_(false)
{
    if (!iconClass_)
        paramsLoaded_ = true;
}

VerbFunctionHelpFunction::~VerbFunctionHelpFunction()
{
    for (auto p : params_)
        delete p;
}

void VerbFunctionHelpFunction::loadParams()
{
    if (!paramsLoaded_ && iconClass_) {
        iconClass_->language().scan(*this);
        paramsLoaded_ = true;
    }
}

void VerbFunctionHelpFunction::next(const MvIconParameter& p)
{
    bool visible = true;
    Request r    = p.interfaceRequest();
    if (r) {
        if (const char* vs = r("visible")) {
            if (strcmp(vs, "false") == 0)
                visible = false;
        }
    }

    if (visible) {
        params_.push_back(new VerbFunctionHelpParam(p));
        paramMap_[params_.back()->name()] = params_.back();
    }
}

QIcon VerbFunctionHelpFunction::icon() const
{
    if (iconClass_) {
        return QIcon(MvQTheme::icIconFile(iconClass_->pixmap()));
    }

    return {};
}

VerbFunctionHelpParam* VerbFunctionHelpFunction::paramFromName(QString name) const
{
    if (name.isEmpty())
        return nullptr;

    auto it = paramMap_.find(name);
    return (it != paramMap_.end()) ? (it->second) : nullptr;
}

VerbFunctionHelpParam* VerbFunctionHelpFunction::param(int i) const
{
    return (i >= 0 && i < static_cast<int>(params_.size())) ? params_[i] : nullptr;
}

int VerbFunctionHelpFunction::indexOfParam(VerbFunctionHelpParam* param) const
{
    for (size_t i = 0; i < params_.size(); i++)
        if (params_[i] == param)
            return i;

    return -1;
}

QString VerbFunctionHelpFunction::descriptionAsStr(int idx) const
{
    return descriptions_[idx].second;

#if 0
    s += descriptions_.value(group) + " ";

    QString s;
    foreach(QString group, descriptions_.keys()) {
        s += descriptions_.value(group) + " ";
    }

    if (s.isEmpty()) {
        if (iconClass_) {
            return QString::fromStdString(iconClass_->defaultName());
        }
    }
    return s;
#endif
}

QString VerbFunctionHelpFunction::descriptionAsRt(int idx) const
{
    return descriptions_[idx].second;
#if 0
    QString s;
    if (!descriptions_.empty()) {
        //s += "<ul>";
        foreach(QString group, descriptions_.keys()) {
            s += "<li><b>" + group + "</b>: " + descriptions_.value(group) + "</li>";
        }
        //s += "</ul>";
    }

    if (s.isEmpty()) {
        if (iconClass_) {
            return QString::fromStdString(iconClass_->defaultName());
        }
    }

    return s;
#endif
}

void VerbFunctionHelpFunction::setDescription(QString group, QString desc)
{
    descriptions_ << qMakePair(group, desc);
}

void VerbFunctionHelpFunction::adjustDescriptions()
{
    if (descriptions_.isEmpty()) {
        if (iconClass_) {
            descriptions_ << qMakePair(QString(), QString::fromStdString(iconClass_->defaultName()));
        }
        else {
            descriptions_ << qMakePair(QString(), QString());
        }
    }
}

//========================================================
//
// VerbFunctionHelp
//
//========================================================

VerbFunctionHelp::VerbFunctionHelp(LanguageHelper* language) :
    language_(language)
{
}

VerbFunctionHelp::~VerbFunctionHelp()
{
    clear();
}

void VerbFunctionHelp::clear()
{
    foreach (VerbFunctionHelpFunction* v, functions_)
        delete v;

    functions_.clear();
    functionMap_.clear();
}

void VerbFunctionHelp::init()
{
    clear();

    if (!language_)
        return;

    if (functions_.size() > 0)
        return;

    for (auto it : IconClassHelp::items()) {
        QString fname = QString::fromStdString(it.second->macroFunction()).simplified().toLower();
        if (!it.second->isObsolete() && !fname.isEmpty() && it.second->type() != "File") {
            functionMap_[fname] = new VerbFunctionHelpFunction(fname, it.second);
        }
    }

    // load the full built-in function list
    QString path = language_->pathToFunctionList();
    QFile file(path);
    if (file.open(QFile::ReadOnly | QFile::Text)) {
        QTextStream in(&file);
        QString line;

        while (!file.atEnd() && !line.startsWith("-----")) {
            line    = file.readLine().simplified();
            int pos = line.indexOf(":");
            if (pos != -1)
                webIds_[line.left(pos).simplified()] = line.mid(pos + 1).simplified();
        }

        VerbFunctionHelpFunction* currentFn = nullptr;
        QString currentName;

        //------------------------------------------------------------
        // this part is a list of blocks each with this structure
        //
        //  function_name
        //      type: icon|script
        //      desc: [group] decription text
        //      desc: [group] decription text
        //      ...
        //
        //  when the type is icon the desc: item(s) is missing

        while (!file.atEnd())  // each line in the file is a function name to add to the list
        {
            QString item = file.readLine().simplified();

            // skip comment lines
            if (item.startsWith("#"))
                continue;

            if (item.startsWith("type:") && !currentName.isEmpty()) {
                currentFn       = nullptr;
                QStringList lst = item.split("type:");
                if (lst.count() == 2) {
                    if (lst[1].simplified() == "script") {
                        // it must be a non-iconclass functions!!
                        if (IconClassHelp::findByMacroFunction(currentName.toStdString()) == nullptr) {
                            auto* fn                  = new VerbFunctionHelpFunction(currentName);
                            currentFn                 = fn;
                            functionMap_[currentName] = fn;
                        }
                    }
                }
            }
            else if (item.startsWith("desc:") && currentFn) {
                QStringList lst = item.split("desc:");
                if (lst.count() == 2) {
                    QString ds = lst[1].simplified();
                    int p1     = ds.indexOf("[");
                    int p2     = ds.indexOf("]");
                    if (p1 >= 0 && p2 > p1) {
                        QString group = ds.mid(p1 + 1, p2 - p1 - 1);
                        currentFn->setDescription(group, ds.mid(p2 + 1).simplified());
                    }
                }
            }
            else {
                currentName = item;
                currentFn   = nullptr;
            }
        }
    }

    for (auto it : functionMap_) {
        functions_ << it.second;
        it.second->adjustDescriptions();
    }

    Q_ASSERT(totalFunctionIndex_.count() == 0);
    for (int i = 0; i < functions_.count(); i++) {
        Q_ASSERT(functions_[i]->descriptions().count() > 0);
        for (int j = 0; j < functions_[i]->descriptions().count(); j++) {
            totalFunctionIndex_ << qMakePair(i, j);
        }
    }


#ifdef GENERATE_ICONCLASS_YAML
    std::string outDir  = "/tmp/" + std::string(getenv("USER")) + "/icon_class";
    std::string outFile = outDir + "/index.yaml";

    std::ofstream out;
    out.open(outFile.c_str());

    for (auto it : IconClassHelp::items()) {
        IconClassHelp* kind = it.second;
        if (!kind->isObsolete() && !kind->isFamilyMember() &&
            QString(kind->type().c_str()).toLower() != "folder") {
            auto icFile = MvQTheme::icIconFile(kind->pixmap());
            Path pixPath(icFile.toStdString());
//            qDebug() << " fname" << QString::fromStdString(pixPath.str());
            if (pixPath.exists()) {
                out << "- icon: " << kind->name() << std::endl;
                out << "  web: " << kind->defaultName() << std::endl;

                if (kind->iconBox().empty())
                    out << "  type: " << kind->type() << std::endl;
                else
                    out << "  type: " << kind->iconBox() << std::endl;

                std::string pixName, pixSuffix;
                pixPath.nameAndSuffix(pixName, pixSuffix);

//                QIcon icon(pixPath.str().c_str());
                pixName += ".png";
                out << "  pixmap: " << pixName << std::endl;

                int pixSize = 48;
                QImageReader imgR(pixPath.str().c_str());
                if (imgR.canRead()) {
                    imgR.setScaledSize(QSize(pixSize, pixSize));
                    QImage img = imgR.read();
                    img.save(QString::fromStdString(outDir + "/" + pixName));
                }
            }
        }
    }
#endif

#ifdef GENERATE_ICONDEF_YAML
    std::string outDir = "/tmp/" + std::string(getenv("USER")) + "/icon_def";
    for (auto it : IconClassHelp::items()) {
        IconClassHelp* kind = it.second;
        if (!kind->isObsolete() && !kind->isFamilyMember() &&
            QString(kind->type().c_str()).toLower() != "folder" &&
            kind->definitionFile().exists()) {
            QString fname       = QString::fromStdString(kind->macroFunction()).simplified().toLower();
            std::string outFile = outDir + "/" + fname.toStdString() + "_def.yaml";
            std::ofstream out;
            out.open(outFile.c_str());

            auto fn = new VerbFunctionHelpFunction(fname, kind);
            fn->loadParams();
            out << "class: " << fname.toStdString() << std::endl;
            out << "params:" << std::endl;
            for (int i = 0; i < fn->numParams(); i++) {
                auto p = fn->param(i);
                p->toYaml(out);
            }
            out.close();
        }
    }
#endif
}

bool VerbFunctionHelp::isHelpAvailableForFunction(QString functionName)
{
    return function(functionName) != nullptr;
}

VerbFunctionHelpFunction* VerbFunctionHelp::function(QString functionName)
{
    auto it = functionMap_.find(functionName);
    return (it != functionMap_.end()) ? (it->second) : nullptr;
}

VerbFunctionHelpFunction* VerbFunctionHelp::itemAt(int i) const
{
    return functions_[i];
}

QPair<VerbFunctionHelpFunction*, int> VerbFunctionHelp::itemAtInTotal(int i) const
{
    return qMakePair(functions_[totalFunctionIndex_[i].first],
                     totalFunctionIndex_[i].second);
}

int VerbFunctionHelp::totalIndexOf(VerbFunctionHelpFunction* fn, int descIndex)
{
    if (!fn) {
        return -1;
    }

    int idx = functions_.indexOf(fn);
    if (idx >= 0) {
        for (int i = 0; i < totalFunctionIndex_.count(); i++) {
            if (totalFunctionIndex_[i].first == idx) {
                return i + descIndex;
            }
        }
    }

    return -1;
}

QString VerbFunctionHelp::url(QString webGroup, QString fnName)
{
    if (webGroup.isEmpty())
        return {};

    QString base = webIds_.value("url");
    if (!base.isEmpty()) {
        if (fnName.isEmpty())
            return base + webIds_.value(webGroup);
        else {
            QString page       = webIds_.value(webGroup);
            QString pageAnchor = page;
            return base + page + "#" + pageAnchor.replace("+", "") + "-" + fnName;
        }
    }

    return {};
}

QString VerbFunctionHelp::iconUrl(VerbFunctionHelpFunction* fn)
{
    if (fn) {
        QString base = webIds_.value("url");
        if (!base.isEmpty()) {
            if (IconClassHelp* ic = fn->iconClass()) {
                QString page = QString::fromStdString(ic->helpPage());
                return base + page;
            }
        }
    }

    return {};
}

QString VerbFunctionHelp::scopePrefix() const
{
    return (language_) ? (language_->scopePrefix()) : QString();
}
