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

 Copyright 2013 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 <QDebug>
#include <QShortcut>

#include "MvQContextMenu.h"

#include "Command.h"
#include "IconClass.h"
#include "Folder.h"
#include "IconObject.h"
#include "MvLog.h"
#include "MvRequest.h"
#include "MvQMethods.h"
#include "MvQStreamOper.h"

MvQContextMenu* MvQContextMenu::instance_ = nullptr;

//======================================
//
// MvQContextItemSet
//
//======================================

MvQContextItemSet::MvQContextItemSet(const std::string& name)
{
    MvRequest r;
    if (const char* dshare = getenv("METVIEW_DIR_SHARE")) {
        std::string p(dshare);
        p += "/etc/UiSpec." + name;
        r.read(p.c_str());
        if (!r)
            return;

        do
            add(r);
        while (r.advance());
    }
}

void MvQContextItemSet::add(const MvRequest& r)
{
    const char* verb = r.getVerb();
    if (!verb)
        return;

    MvQContextItem* item = nullptr;

    if (strcmp(verb, "icon_menu") == 0) {
        if ((item = MvQContextItem::build(r, icon_)) != nullptr)
            icon_ << item;
    }
    if (strcmp(verb, "group_menu") == 0) {
        if ((item = MvQContextItem::build(r, icon_)) != nullptr)
            multiIcon_ << item;
    }

    else if (strcmp(verb, "desktop_menu") == 0) {
        if ((item = MvQContextItem::build(r, desktop_)) != nullptr)
            desktop_ << item;
    }

    else if (strcmp(verb, "container_menu") == 0) {
        if ((item = MvQContextItem::build(r, container_)) != nullptr)
            container_ << item;
    }
}

QShortcut* MvQContextItemSet::makeDefaultIconShortCut(QWidget* w)
{
    foreach (MvQContextItem* item, icon_) {
        if (item->hasShortCut("Return"))
            return nullptr;
    }

    auto* sc = new QShortcut(Qt::Key_Return, w);
    sc->setContext(Qt::WidgetShortcut);
    return sc;
}

//======================================
//
// MvQContextItemCondition
//
//======================================

MvQContextItemCondition::MvQContextItemCondition(QString c) :
    valid_(false)
{
    if (c.isEmpty())
        return;

    if (c.contains("!="))
        operand_ = "!=";
    else if (c.contains("="))
        operand_ = "=";
    else
        return;

    QStringList lst = c.split(operand_);
    if (lst.count() == 2) {
        key_ = lst[0];
        value_ = lst[1];

        // Need to resolve items paths
        if (key_ == "path" && !value_.startsWith("/")) {
            IconObject* wbo = Folder::folder(value_.toStdString().c_str(), false);
            if (wbo)
                value_ = QString::fromStdString(wbo->fullName());
        }

        valid_ = true;
    }
}

bool MvQContextItemCondition::check(QString c)
{
    if (!isValid())
        return true;

    MvQContextItemCondition cnd(c);
    if (!cnd.isValid())
        return true;

    if (cnd.isValid() && cnd.key() == key_) {
        if (operand_ == "=")
            return (value_ == cnd.value());

        else if (operand_ == "!=")
            return (value_ != cnd.value());
    }

    return false;
}

//======================================
//
// MvQContextItem
//
//======================================

MvQContextItem* MvQContextItem::build(const MvRequest& r, QList<MvQContextItem*> items)
{
    QMenu* parent = nullptr;
    MvQContextSubMenu* parentSubMenu = nullptr;
    MvQContextItem* item = nullptr;

    const char* verb = r.getVerb();
    if (verb && strcmp(verb, "group_menu") == 0) {
        const char* icon_menu_id = r("icon_menu_id");
        if (icon_menu_id) {
            foreach (MvQContextItem* c, items)
                if (!c->id().isEmpty() && c->id() == QString(icon_menu_id))
                    return c;
        }
    }

    if (const char* parent_menu = r("parent_menu")) {
        foreach (MvQContextItem* c, items)
            if (c->id() == QString(parent_menu)) {
                parentSubMenu = static_cast<MvQContextSubMenu*>(c);
                if (parentSubMenu)
                    parent = parentSubMenu;
                break;
            }
    }

    if (const char* type = r("type")) {
        if (strcmp(type, "separator") == 0) {
            item = new MvQContextSeparator(parent);
            return (parent) ? nullptr : item;
        }
        else if (strcmp(type, "submenu") == 0) {
            return new MvQContextSubMenu(r);
        }
    }
    else {
        if (checkEnv(r)) {
            item = new MvQContextAction(parent, r);
            if (parentSubMenu) {
                parentSubMenu->addChildCommand(item->command());
            }
            return (parent) ? nullptr : item;
        }
        else {
            return nullptr;
        }
    }

    return nullptr;
}

MvQContextItem::MvQContextItem() :
    condition_("")
{
}

MvQContextItem::MvQContextItem(const MvRequest& r) :
    condition_(QString(r("condition")))
{
    id_ = QString(r("id"));
    command_ = QString(r("cmd"));
    service_ = QString(r("service"));
    shortCut_ = QString(r("shortcut"));
}

bool MvQContextItem::checkCondition(QString txt)
{
    return condition_.check(txt);
}

// some menu items will be hidden if a certain environment
// variable is not set
bool MvQContextItem::checkEnv(const MvRequest& r)
{
    const char* envVar = r("env");
    if (envVar && (getenv(envVar) == nullptr))
        return false;
    else
        return true;
}


QString MvQContextItem::iconPath(const MvRequest& r)
{
    if (const char* ic = r("icon"))
        return ":desktop/" + QString(ic) + ".svg";
    return {};
}

bool MvQContextItem::hasShortCut(QString s)
{
    return (shortCut_ == s);
}

bool MvQContextItem::checkCommand(const std::set<std::string>& commands) const
{
    return command().isEmpty() || commands.find(command().toStdString()) != commands.end();
}

//======================================
//
// MvQContextSubMenu
//
//======================================

MvQContextSubMenu::MvQContextSubMenu(QString text, QString iconPath) :
    QMenu(nullptr)
{
    setTitle(text);
    if (!iconPath.isEmpty()) {
        setIcon(QPixmap(iconPath));
    }
}

MvQContextSubMenu::MvQContextSubMenu(const MvRequest& r) :
    QMenu(nullptr),
    MvQContextItem(r)
{
    QString text(r("label"));
    QString ic = iconPath(r);

    setTitle(text);
    if (!ic.isEmpty()) {
        setIcon(QPixmap(ic));
    }
}

void MvQContextSubMenu::addToMenu(QMenu* menu)
{
    if (menu)
        menu->addMenu(this);
}

void MvQContextSubMenu::addChildCommand(QString cmd)
{
    childCommands_ << cmd;
    static QStringList specCmds = {"compress", "archive"};
    for (auto s : specCmds) {
        if (cmd.startsWith(s + "_")) {
            if (!childCommands_.contains(s)) {
                childCommands_ << s;
            }
        }
    }
}

bool MvQContextSubMenu::checkCommand(const std::set<std::string>& commands) const
{
    if (childCommands_.isEmpty()) {
        return true;
    }
    for (auto cmd : childCommands_) {
        if (commands.find(cmd.toStdString()) != commands.end()) {
            return true;
        }
    }
    return false;
}

//======================================
//
// MvQContextAction
//
//======================================

MvQContextAction::MvQContextAction(QMenu* menu, const MvRequest& r) :
    QAction(nullptr),
    MvQContextItem(r)
{
    QString text(r("label"));
    QString shortcut(r("shortcut"));
    QString ic = iconPath(r);
    QString data(r("cmd"));
    MvQ::showShortcutInContextMenu(this);

    init(text, shortcut, data, ic);
    if (menu)
        menu->addAction(this);
}

void MvQContextAction::init(QString text, QString shortcut, QString data, QString iconPath)
{
    setText(text);
    setShortcut(shortcut);
    if (!iconPath.isEmpty()) {
        setIcon(QPixmap(iconPath));
    }
    setData(data);
}

void MvQContextAction::addToMenu(QMenu* m)
{
    if (m)
        m->addAction(this);
}

QShortcut* MvQContextAction::makeShortCut(QWidget* parent)
{
    if (!shortcut().isEmpty()) {
        auto* sc = new QShortcut(shortcut(), parent);
        sc->setContext(Qt::WidgetShortcut);
        sc->setProperty("cmd", command_);
        return sc;
    }
    return nullptr;
}

//======================================
//
// MvQContextSeparator
//
//======================================

MvQContextSeparator::MvQContextSeparator(QMenu* menu) :
    QAction(nullptr)
{
    setSeparator(true);
    if (menu)
        menu->addAction(this);
}


void MvQContextSeparator::addToMenu(QMenu* m)
{
    if (m)
        m->addAction(this);
}

//==================================
//
// MvQContextMenu
//
//==================================

MvQContextMenu* MvQContextMenu::instance()
{
    if (!instance_)
        instance_ = new MvQContextMenu;

    return instance_;
}

MvQContextMenu::MvQContextMenu() = default;

QString MvQContextMenu::exec(QList<MvQContextItem*> lst, IconObject* obj, QPoint pos, QWidget* parent)
{
    auto* menu = new QMenu(parent);
    QString retVal;
    QAction* defaultAction = nullptr;
    QString defaultMethod = QString::fromStdString(obj->iconClass().defaultMethod()).toLower();
    std::set<std::string> iconCommands = obj->can();

    for (MvQContextItem* item : lst) {
        if (item->checkCommand(iconCommands)) {
            item->addToMenu(menu);
            if (!defaultMethod.isEmpty() &&
                defaultMethod == item->command()) {
                QFont font;
                font.setBold(true);
                defaultAction = menu->actions().back();
                defaultAction->setFont(font);
            }
        }
    }

    if (!menu->isEmpty()) {
        QAction* ac = menu->exec(pos);

        if (!ac || ac->isSeparator()) {
            retVal = QString();
        }
        else
            retVal = ac->data().toString();
    }

    delete menu;

    // Reset font for the default action
    if (defaultAction)
        defaultAction->setFont(QFont());

    return retVal;
}

QString MvQContextMenu::exec(QList<MvQContextItem*> lst, const std::vector<IconObject*>& objLst, QPoint pos, QWidget* parent)
{
    auto* menu = new QMenu(parent);
    QString retVal;

    foreach (MvQContextItem* item, lst) {
        bool shared = true;
        for (auto it : objLst) {
            std::set<std::string> iconCommands = it->can();
            if (!item->checkCommand(iconCommands)) {
                shared = false;
                break;
            }
        }
        if (shared == true)
            item->addToMenu(menu);
    }

    if (!menu->isEmpty()) {
        QAction* ac = menu->exec(pos);
        if (!ac || ac->isSeparator()) {
            retVal = QString();
        }
        else
            retVal = ac->data().toString();
    }

    delete menu;

    return retVal;
}

QString MvQContextMenu::exec(QList<MvQContextItem*> lst, QPoint pos, QWidget* parent,
                             QString defaultMethod, QString condition)
{
    auto* menu = new QMenu(parent);
    QString retVal;
    QAction* defaultAction = nullptr;

    foreach (MvQContextItem* item, lst) {
        if (item->checkCondition(condition))
            item->addToMenu(menu);

        if (!defaultMethod.isEmpty() &&
            defaultMethod == item->command()) {
            QFont font;
            font.setBold(true);
            defaultAction = menu->actions().back();
            defaultAction->setFont(font);
        }
    }

    if (!menu->isEmpty()) {
        QAction* ac = menu->exec(pos);

        if (ac && !ac->isSeparator())
            retVal = ac->data().toString();
    }

    delete menu;

    // Reset font for the default action
    if (defaultAction) {
        defaultAction->setFont(QFont());
    }

    return retVal;
}
