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

 Copyright 2014 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 "MvQTheme.h"

#include <QApplication>
#include <QColor>
#include <QDebug>
#include <QIcon>
#include <QFile>
#include <QFileInfo>
#include <QFont>
#include <QLinearGradient>
#include <QProxyStyle>
#include <QResource>
#include <QFontMetrics>

#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
#include <QtCore5Compat/QRegExp>
#else
#include <QRegExp>
#endif

#include <QImage>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>

#include "MvMiscellaneous.h"
#include "MvQMethods.h"

#ifdef METVIEW
#include "MvRequest.h"
#include "MvApplication.h"
#endif

static QHash<QString, QPalette::ColorRole> paletteId;
MvQTheme* MvQTheme::current_ = nullptr;
QMap<QString, MvQTheme*> MvQTheme::themes_;
QStringList MvQTheme::resources_;

//#define MVQTHEME_DEBUG_

class DarkProxyStyle : public QProxyStyle
{
public:
    DarkProxyStyle(QStyle* style = nullptr) :
        QProxyStyle(style) {}
    QPixmap generatedIconPixmap(QIcon::Mode iconMode, const QPixmap& pixmap, const QStyleOption* option) const override;

protected:
    void greyOut(QImage& img) const;
};

QPixmap DarkProxyStyle::generatedIconPixmap(QIcon::Mode iconMode, const QPixmap& pixmap, const QStyleOption* option) const
{
    if (iconMode == QIcon::Disabled) {
        QImage img = pixmap.toImage();
        greyOut(img);
        return QPixmap::fromImage(img);
    }
    return QProxyStyle::generatedIconPixmap(iconMode, pixmap, option);
}


void DarkProxyStyle::greyOut(QImage& img) const
{
    int w = img.width();
    int h = img.height();
    QRgb c;
    int g;
    //int gBase     = MvQTheme::colour("folderview", "faded_base").red();
    int gBase     = 40;
    int gTop      = 130;
    float gFactor = static_cast<float>(gTop - gBase) / 255.;

    for (int i = 0; i < w; i++)
        for (int j = 0; j < h; j++) {
            c = img.pixel(i, j);
            if (qAlpha(c) != 0) {
                //g=qGray(c);
                g = (qRed(c) + qGreen(c) + qBlue(c)) / 3;
                g = gBase + gFactor * static_cast<float>(g);
                img.setPixel(i, j, qRgb(g, g, g));
            }
        }
}

void MvQTheme::init(QApplication* app, QStringList resources)
{
    resources_ = resources;
    MvQTheme::add(app, determineStyleName());
}

QString MvQTheme::determineStyleName()
{
    QString name = "light";

#ifdef ECCODES_UI
    QString sn = readCodesUiStyleName();
    if (!sn.isEmpty()) {
        name = sn;
    }

#else
    QString startupName, confName;
    if (const char* ch = getenv("MV_USER_UI_THEME")) {
        startupName = QString::fromStdString(std::string(ch)).toLower();
        if (startupName != "light" && startupName != "dark") {
            startupName.clear();
        }
        else {
            name = startupName;
        }
    }

    if (!startupName.isEmpty()) {
        marslog(LOG_INFO, "Ui theme defined on startup=%s", startupName.toStdString().c_str());
    }

    // get style name from config. We cannot use MvRequest here due to
    // codes_ui
    MvRequest r = MvApplication::getPreferences();
    if (const char* ch = (const char*)r("UI_THEME")) {
        confName = QString(ch);
    }
    confName = confName.toLower();
    if (confName.isEmpty()) {
        confName = "light";
    }

    marslog(LOG_INFO, "Ui theme defined in config=%s", confName.toStdString().c_str());

    std::string startupNameStr = startupName.toStdString();

    if (startupName.isEmpty()) {
        return confName;
        // update preferences with startup ui theme if needed
    }
    else if (confName != startupName) {
        marslog(LOG_INFO, "Update UI_THEME in config to %s", startupNameStr.c_str());
        if (!r) {
            r = MvRequest("GENERAL_PREFERENCES");
        }
        r("UI_THEME") = startupNameStr.c_str();
        MvApplication::setPreferences(r);
        return startupName;
    }
#endif
    return name;
}

MvQTheme::MvQTheme(QApplication* app, QString id) :
    id_(id)
{
    if (paletteId.isEmpty()) {
        paletteId["window"]          = QPalette::Window;
        paletteId["windowtext"]      = QPalette::WindowText;
        paletteId["base"]            = QPalette::Base;
        paletteId["alternatebase"]   = QPalette::AlternateBase;
        paletteId["tooltipbase"]     = QPalette::ToolTipBase;
        paletteId["tooltiptext"]     = QPalette::ToolTipText;
        paletteId["text"]            = QPalette::Text;
        paletteId["button"]          = QPalette::Button;
        paletteId["buttontext"]      = QPalette::ButtonText;
        paletteId["brighttext"]      = QPalette::BrightText;
        paletteId["light"]           = QPalette::Light;
        paletteId["midlight"]        = QPalette::Midlight;
        paletteId["dark"]            = QPalette::Dark;
        paletteId["mid"]             = QPalette::Mid;
        paletteId["shadow"]          = QPalette::Shadow;
        paletteId["higlight"]        = QPalette::Highlight;
        paletteId["highlightedtext"] = QPalette::HighlightedText;
        paletteId["link"]            = QPalette::Link;
        paletteId["linkvisited"]     = QPalette::LinkVisited;
#if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)
        paletteId["placeholdertext"] = QPalette::PlaceholderText;
#endif
    }
    load(app, id);
}

MvQTheme* MvQTheme::add(QApplication* app, QString id)
{
    auto theme  = new MvQTheme(app, id);
    themes_[id] = theme;
    current_    = theme;
    return theme;
}

void MvQTheme::load(QApplication* app, QString id)
{
#ifdef METVIEW
    marslog(LOG_INFO, "Setting ui style to %s", id.toStdString().c_str());
#endif

    if (id == "dark") {
        auto* dps = new DarkProxyStyle(app->style());
        app->setStyle(dps);
    }

    for (auto resId : resources_) {
        auto resName = resId + "_" + id + ".rcc";
        auto fName   = QString::fromStdString(metview::qtResourceDirFile(resName.toStdString()));
#ifdef METVIEW
        marslog(LOG_INFO, "Load rcc file: %s", fName.toStdString().c_str());
#else
        qDebug() << "Load rcc file:" << fName;
#endif
        QResource::registerResource(fName);
    }

#ifdef MVQTHEME_DEBUG_
    for (auto gr : QList<QPalette::ColorGroup>{QPalette::Active, QPalette::Inactive, QPalette::Disabled}) {
        QHashIterator<QString, QPalette::ColorRole> it(paletteId);
        while (it.hasNext()) {
            it.next();
            QColor c = app->palette().color(gr, it.value());
            std::cout << "    \"" << it.key().toStdString() << "\": \"rgb(" << c.red() << "," << c.green() << "," << c.blue() << ")\"," << std::endl;
        }
    }
#endif

    // get the palette, will be updated from the theme
    palette_ = app->palette();

    // load theme form json file
    std::string inFile = metview::appDefDirFile("uitheme_" + id.toStdString() + ".json");

    QFile fIn(inFile.c_str());
    if (!fIn.open(QIODevice::ReadOnly | QIODevice::Text)) {
        //marslog(LOG_WARN, "DocHughlighter::load() --> Could not open json config file: %s", fName.c_str());
        return;
    }

    QByteArray json   = fIn.readAll();
    QJsonDocument doc = QJsonDocument::fromJson(json);

    //This document is an array of groups
    Q_ASSERT(doc.isArray());

    QJsonArray arr = doc.array();
    for (int i = 0; i < arr.count(); i++) {
        QJsonObject obj  = arr[i].toObject();
        QString group    = obj.keys().front();
        QJsonObject vals = obj.value(group).toObject();
        QMap<QString, QString> m;
        for (QJsonObject::const_iterator it = vals.constBegin(); it != vals.constEnd(); it++) {
            m[it.key()] = it.value().toString();
        }
        parse(group, m);
    }

    // set the application palette
    app->setPalette(palette_);

    // load the qss file
    std::string sFile = metview::appDefDirFile(id.toStdString() + ".qss");
    QFile file(sFile.c_str());
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        return;
    }

    QString styleStr(file.readAll());
    app->setStyleSheet(styleStr);
}


void MvQTheme::parse(QString group, QMap<QString, QString> vals)
{
    if (group == "palette_active") {
        readPalette(QPalette::Active, vals);
    }
    else if (group == "palette_disabled") {
        readPalette(QPalette::Disabled, vals);
    }
    else if (group == "palette_inactive") {
        readPalette(QPalette::Inactive, vals);
    }
    else if (group == "options") {
        readOptions(vals);
    }
    else {
        readGroup(group, vals);
    }
}

void MvQTheme::readPalette(QPalette::ColorGroup group, QMap<QString, QString> vals)
{
    QHashIterator<QString, QPalette::ColorRole> it(paletteId);
    while (it.hasNext()) {
        it.next();
        auto v = vals.value(it.key());
        if (!v.isEmpty()) {
            if (v.startsWith("rgb")) {
                palette_.setColor(group, it.value(), makeColour(v));
            }
            else if (v.startsWith("gradient")) {
                palette_.setBrush(group, it.value(), makeBrush(v));
            }
        }
    }
}

void MvQTheme::readGroup(QString group, QMap<QString, QString> vals)
{
    bool generic = (group == "GENERIC");

#ifdef MVQTHEME_DEBUG_
    qDebug() << "MvQTheme::readGroup"
             << "group:" << group;
#endif
    auto item = new MvQThemeGroup(group);
    QMapIterator<QString, QString> it(vals);
    while (it.hasNext()) {
        it.next();
        auto par = it.key();
        auto v   = it.value();
        if (!par.isEmpty()) {
            if (v.startsWith("Q") && v.endsWith("}")) {
                item->sh_[par] = v;
            }
            else {
                item->brush_[par] = makeBrush(v);
                if (generic) {
#ifdef MVQTHEME_DEBUG_
                    qDebug() << par << v << item->brush_[par] << item->brush_[par].color();
#endif
                    cols_[par] = item->brush_[par].color();
                }
            }
        }
    }

    groups_[group] = item;
}

void MvQTheme::readOptions(QMap<QString, QString> vals)
{
    QMapIterator<QString, QString> it(vals);
    while (it.hasNext()) {
        it.next();
        auto par = it.key();
        auto v   = it.value().toLower();
        if (par == "boldhtmltabletitle") {
            boldHtmlTableTitle_ = (v.toLower() == "true") ? true : false;
        }
        else if (par == "boldfileinfotitle") {
            boldFileInfoTitle_ = (v.toLower() == "true") ? true : false;
        }
    }
}

QColor MvQTheme::makeColour(QString name)
{
    QColor col;
    QRegExp rx("rgb\\((\\d+),(\\d+),(\\d+),?(\\d+)?");

    if (rx.indexIn(name) > -1) {
        if (rx.captureCount() >= 3) {
            col = QColor(rx.cap(1).toInt(),
                         rx.cap(2).toInt(),
                         rx.cap(3).toInt(),
                         rx.cap(4).isEmpty() ? 255 : rx.cap(4).toInt());
        }
    }
    return col;
}

QBrush MvQTheme::makeBrush(QString name)
{
    if (name.startsWith("rgb")) {
        return QBrush(makeColour(name));
    }
    else if (name.startsWith("p:")) {
        auto p = palette_;
        return QBrush(p.color(paletteId[name.mid(2)]));
    }
    else if (cols_.keys().contains(name)) {
        return QBrush(cols_[name]);
    }

    QColor col1, col2;
    float x1, y1, x2, y2;

    QRegExp rx("p1\\((\\d+),(\\d+)");
    if (rx.indexIn(name) > -1 && rx.captureCount() == 2) {
        x1 = rx.cap(1).toFloat();
        y1 = rx.cap(2).toFloat();
    }
    else
        return QBrush();

    rx = QRegExp("p2\\((\\d+),(\\d+)");
    if (rx.indexIn(name) > -1 && rx.captureCount() == 2) {
        x2 = rx.cap(1).toFloat();
        y2 = rx.cap(2).toFloat();
    }
    else
        return QBrush();

    rx = QRegExp("rgb\\((\\d+),(\\d+),(\\d+),?(\\d+)?\\),rgb\\((\\d+),(\\d+),(\\d+),?(\\d+)?");
    if (rx.indexIn(name) > -1 && rx.captureCount() == 8) {
        col1 = QColor(rx.cap(1).toInt(),
                      rx.cap(2).toInt(),
                      rx.cap(3).toInt(),
                      rx.cap(4).isEmpty() ? 255 : rx.cap(4).toInt());

        col2 = QColor(rx.cap(5).toInt(),
                      rx.cap(6).toInt(),
                      rx.cap(7).toInt(),
                      rx.cap(8).isEmpty() ? 255 : rx.cap(8).toInt());
    }
    else
        return QBrush();

    QLinearGradient grad;

    grad.setCoordinateMode(QGradient::ObjectBoundingMode);
    grad.setStart(x1, y1);
    grad.setFinalStop(x2, y2);
    grad.setColorAt(0, col1);
    grad.setColorAt(1, col2);

    return QBrush(grad);
}

QBrush MvQTheme::brush(QString group, QString item)
{
    if (MvQTheme::current_) {
        auto it = MvQTheme::current_->groups_.find(group);
        if (it != MvQTheme::current_->groups_.end()) {
            return it.value()->brush_.value(item, QColor());
        }
    }
    return QColor();
}


QPen MvQTheme::pen(QString group, QString item, float width, Qt::PenStyle style)
{
    if (MvQTheme::current_)
        return QPen(MvQTheme::current_->brush(group, item).color(), width, style);
    return {};
}

QColor MvQTheme::colour(QString group, QString item)
{
    if (MvQTheme::current_)
        return MvQTheme::current_->brush(group, item).color();
    return {};
}

QColor MvQTheme::colour(QString item)
{
    if (MvQTheme::current_)
        return MvQTheme::current_->cols_[item];
    return QColor();
}


QString MvQTheme::styleSheet(QString group, QString item)
{
    if (MvQTheme::current_) {
        auto it = MvQTheme::current_->groups_.find(group);
        if (it != MvQTheme::current_->groups_.end()) {
            return it.value()->sh_.value(item, QString());
            //            auto itC = it.value().sh_.find(item);
            //            if (itC != it.value().end())
            //                return itC.value();
        }
    }
    return {};
}

QColor MvQTheme::text()
{
    if (MvQTheme::current_)
        return MvQTheme::current_->cols_["text"];
    return QColor();
}

QColor MvQTheme::subText()
{
    if (MvQTheme::current_)
        return MvQTheme::current_->cols_["subtext"];
    return QColor();
}

QColor MvQTheme::accentText()
{
    if (MvQTheme::current_)
        return MvQTheme::current_->cols_["accenttext"];
    return QColor();
}

QColor MvQTheme::imageBorder()
{
    if (MvQTheme::current_)
        return MvQTheme::current_->cols_["imageborder"];
    return QColor();
}

QColor MvQTheme::treeItemBorder()
{
    if (MvQTheme::current_)
        return MvQTheme::current_->cols_["treeitemborder"];
    return QColor();
}

QColor MvQTheme::htmlTableText()
{
    if (MvQTheme::current_)
        return MvQTheme::current_->cols_["htmltable_text"];
    return QColor();
}

QColor MvQTheme::htmlTableBg()
{
    if (MvQTheme::current_)
        return MvQTheme::current_->cols_["htmltable_bg"];
    return QColor();
}

QString MvQTheme::highlighterFile()
{
    if (MvQTheme::current_) {
        return QString::fromStdString(metview::appDefDirFile("highlighter_" + MvQTheme::current_->id_.toStdString() + ".json"));
    }
    return {};
}

QString MvQTheme::htmlTableCss()
{
    return htmlTableCss("GENERIC");
}

QString MvQTheme::htmlTableCss(QString group)
{
    if (MvQTheme::current_) {
        //        QFont f;
        //        int fs = f.pointSize();
        //        fs -= 1;
        //QString s = "* {font-size: " + QString::number(fs) + " pt;} div {color: " + MvQTheme::subText().name() +
        //";} td {font-size: " + QString::number(fs) + "pt; background: "+  MvQTheme::htmlTableBg().name() +
        QString s(
            "div {color: %1;} "
            "td {background: %2; color: %3; padding-left: 4px; padding-right: 3px;}"
            "td.first {background: %4; color: %5;  padding-left: 4px; padding-right: 3px;}"
            "td.highlight {background: %6; color: %7;  padding-left: 4px; padding-right: 3px;}"
            "td.title { background-color: %8; color: %9; font-weight: %10;"
            "padding-left: 2px; padding-top: 1px; padding-bottom: 1px;}");
        s = s.arg(MvQTheme::subText().name()).arg(MvQTheme::colour(group, "htmltable_bg").name()).arg(MvQTheme::colour(group, "htmltable_text").name()).arg(MvQTheme::colour(group, "htmltable_bg").name()).arg(MvQTheme::colour(group, "htmltable_first_text").name()).arg(MvQTheme::colour(group, "htmltable_highlight_bg").name()).arg(MvQTheme::colour(group, "htmltable_highlight_text").name()).arg(MvQTheme::colour(group, "htmltable_title_bg").name()).arg(MvQTheme::colour(group, "htmltable_title_text").name()).arg(MvQTheme::current_->boldHtmlTableTitle_ ? "bold" : "normal");
        return s;
    }
    return {};
}

QString MvQTheme::icIconFile(const std::string& icPixPath)
{
    if (MvQTheme::current_) {
        if (!icPixPath.empty()) {
            QFileInfo f(QString::fromStdString(icPixPath));
            QString fname = f.fileName();
            if (fname.endsWith(".icon")) {
                fname = fname.left(fname.length() - 5);
            }

            QString psvg;
            // try with current style
            if (MvQTheme::current_->id_ != "light") {
                psvg = QString::fromStdString(metview::iconDirFile(
                    MvQTheme::current_->id_.toStdString(),
                    fname.toStdString() + ".svg"));
                f    = QFileInfo(psvg);
                if (f.exists()) {
                    return psvg;
                }
            }

            // try with the default style
            psvg = QString::fromStdString(metview::iconDirFile(fname.toStdString() + ".svg"));
            f    = QFileInfo(psvg);
            if (f.exists()) {
                return psvg;
            }
            else {
                QString pxpm = QString::fromStdString(metview::iconDirFile(fname.toStdString() + ".xpm"));
                f            = QFileInfo(pxpm);
                if (f.exists()) {
                    return pxpm;
                }
            }
        }
    }

    return {};
}

bool MvQTheme::boldFileInfoTitle()
{
    if (MvQTheme::current_) {
        return MvQTheme::current_->boldFileInfoTitle_;
    }
    return true;
}

QString MvQTheme::id()
{
    if (MvQTheme::current_) {
        return MvQTheme::current_->id_;
    }
    return {};
}

QPixmap MvQTheme::logo()
{
    return QPixmap(":/window/metview.svg");
}

#ifdef ECCODES_UI
QString MvQTheme::readCodesUiStyleName()
{
    if (char* ch = getenv("CODES_UI_STYLE")) {
        std::string s(ch);
        return QString::fromStdString(s);
    }

    QSettings settings(MVQ_QSETTINGS_DEFAULT_ARGS, "codesui");
    auto s = settings.value("style").toString();
    return s;
}

void MvQTheme::writeCodesUiStyleName(QString name)
{
    QSettings settings(MVQ_QSETTINGS_DEFAULT_ARGS, "codesui");
    settings.setValue("style", name);
}
#endif
