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

 Copyright 2021 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 "MvQFeatureCommand.h"

#include <QGuiApplication>
#include <QDebug>
#include <QClipboard>
#include <QLabel>
#include <QMimeData>
#include <QPlainTextEdit>
#include <QTextEdit>
#include <QUndoCommand>

#include "MvQFeatureCommandTarget.h"
#include "MvQFeatureCurveItem.h"
#include "MvQFeatureFactory.h"
#include "MvQFeatureItem.h"
#include "MvQFeatureRibbonEditor.h"
#include "MvQFeatureSelector.h"
#include "MvQFeatureTextItem.h"
#include "MvQFeatureType.h"
#include "MvQPlotView.h"
#include "PlotMod.h"

#include "MgQLayoutItem.h"
#include "MgQPlotScene.h"
#include "MgQRootItem.h"
#include "MgQSceneItem.h"

//#define FEATURECOMMAND_DEBUG_

class MvQFeatureCommand;

//==================================================
//
//  MvQFeatureMimeData
//
//==================================================

class MvQFeatureMimeData : public QMimeData
{
public:
    enum ClipboardAction
    {
        NoAction,
        CopyAction,
        CutAction
    };

    MvQFeatureMimeData(QList<MvQFeatureItem*>, ClipboardAction clipboardAction = NoAction);
    ~MvQFeatureMimeData();

    QStringList formats() const;
    QList<MvQFeatureItem*> items() const { return items_; }
    ClipboardAction clipboardAction() const { return clipboardAction_; }

protected:
    QStringList formats_;
    QList<MvQFeatureItem*> items_;
    ClipboardAction clipboardAction_;
};


MvQFeatureMimeData::MvQFeatureMimeData(QList<MvQFeatureItem*> items, ClipboardAction clipboardAction) :
    items_(items),
    clipboardAction_(clipboardAction)
{
    formats_ << "metview/feature";
    formats_ << "text/plain";
}

MvQFeatureMimeData::~MvQFeatureMimeData()
{
    for (auto item: items_) {
        delete item;
    }
}

QStringList MvQFeatureMimeData::formats() const
{
    return formats_;
}

//==================================================
//
//  The command factory
//
//==================================================

class MvQFeatureCommandFactory
{
public:
    MvQFeatureCommandFactory(QString);
    MvQFeatureCommandFactory(const MvQFeatureFactory&) = delete;
    MvQFeatureCommandFactory& operator=(const MvQFeatureFactory&) = delete;
    virtual ~MvQFeatureCommandFactory() {}

    virtual MvQFeatureCommand* make(MvQFeatureItem* item, MvQFeatureHandler* h) = 0;
    static MvQFeatureCommand* create(QString name, MvQFeatureItem* item, MvQFeatureHandler* h);
protected:
    QString name_;
};

template <class T>
class MvQFeatureCommandMaker : public MvQFeatureCommandFactory
{
public:
    MvQFeatureCommandMaker(QString name) :
        MvQFeatureCommandFactory(name) {}
private:
    MvQFeatureCommand* make(MvQFeatureItem* item, MvQFeatureHandler* h) override
    {
        return new T({item}, h, name_);
    }
};

static std::map<QString, MvQFeatureCommandFactory*>* makers = nullptr;

MvQFeatureCommandFactory::MvQFeatureCommandFactory(QString name) : name_(name)
{
    if (!makers)
        makers = new std::map<QString, MvQFeatureCommandFactory*>;

    (*makers)[name] = this;
}

MvQFeatureCommand* MvQFeatureCommandFactory::create(QString name,  MvQFeatureItem* item, MvQFeatureHandler* h)
{
    MvQFeatureCommand *cmd = nullptr;
    auto it = makers->find(name);
    if (it == makers->end()) {
    } else {
        cmd = (*it).second->make(item, h);
    }

    Q_ASSERT(cmd);
    return cmd;
}

//==================================================
//
//  MvQFeatureCommand
//
//==================================================

class MvQFeatureCommand : public QUndoCommand
{
public:
    MvQFeatureCommand(QList<MvQFeatureItem*> items, MvQFeatureHandler* handler,  QString name, QString cmdName, QUndoCommand* parent = 0);
    void undo() override;
    void redo() override;
    bool match(const QUndoCommand *other) const;

protected:
    void generateText();

    QList<MvQFeatureItem*> items_;
    QList<QGraphicsItem*> parentItems_;
    MvQFeatureHandler* handler_{nullptr};
    QString name_;
    QString cmdName_;
    bool firstCall_{true};
};

class MvQFeatureAddCommand : public MvQFeatureCommand
{
public:
    MvQFeatureAddCommand(QList<MvQFeatureItem*> items, MvQFeatureHandler* handler, QString cmdName, QUndoCommand* parent=nullptr) :
        MvQFeatureCommand(items, handler, "add", cmdName, parent) {}
    void undo() override;
    void redo() override;
};

class MvQFeatureRemoveCommand : public MvQFeatureCommand
{
public:
    MvQFeatureRemoveCommand(QList<MvQFeatureItem*> items, MvQFeatureHandler* handler, QString cmdName, QUndoCommand* parent=nullptr) :
        MvQFeatureCommand(items, handler, "remove", cmdName, parent) {}
    void undo() override;
    void redo() override;
};

class MvQFeatureDuplicateCommand : public MvQFeatureCommand
{
public:
    MvQFeatureDuplicateCommand(QList<MvQFeatureItem*> items, MvQFeatureHandler* handler, QString cmdName, QUndoCommand* parent=nullptr) :
        MvQFeatureCommand(items, handler, "duplicate", cmdName, parent) {}
    void undo() override;
    void redo() override;
protected:
    QList<MvQFeatureItem*> resItems_;
};

class MvQFeatureCopyCommand : public MvQFeatureCommand
{
public:
    MvQFeatureCopyCommand(QList<MvQFeatureItem*> items, MvQFeatureHandler* handler, QString cmdName, QUndoCommand* parent=nullptr) :
        MvQFeatureCommand(items, handler, "copy", cmdName, parent) {}
    void undo() override;
    void redo() override;
};

class MvQFeatureCutCommand : public MvQFeatureCommand
{
public:
    MvQFeatureCutCommand(QList<MvQFeatureItem*> items, MvQFeatureHandler* handler, QString cmdName, QUndoCommand* parent=nullptr) :
        MvQFeatureCommand(items, handler, "cut", cmdName, parent) {}
    void undo() override;
    void redo() override;
};

class MvQFeatureToFrontCommand : public MvQFeatureCommand
{
public:
    MvQFeatureToFrontCommand(QList<MvQFeatureItem*> items, MvQFeatureHandler* handler, QString cmdName, QUndoCommand* parent=nullptr) :
        MvQFeatureCommand(items, handler, "to_front", cmdName, parent) {}
    void undo() override;
    void redo() override;

protected:
    float oriZVal_{-1.};
};

class MvQFeatureToForwardCommand : public MvQFeatureCommand
{
public:
    MvQFeatureToForwardCommand(QList<MvQFeatureItem*> items, MvQFeatureHandler* handler, QString cmdName, QUndoCommand* parent=nullptr) :
        MvQFeatureCommand(items, handler, "to_forward", cmdName, parent) {}
    void undo() override;
    void redo() override;
};

class MvQFeatureToBackCommand : public MvQFeatureCommand
{
public:
    MvQFeatureToBackCommand(QList<MvQFeatureItem*> items, MvQFeatureHandler* handler, QString cmdName, QUndoCommand* parent=nullptr) :
        MvQFeatureCommand(items, handler, "to_back", cmdName, parent) {}
    void undo() override;
    void redo() override;

protected:
    float oriZVal_{-1.};
};

class MvQFeatureToBackwardCommand : public MvQFeatureCommand
{
public:
    MvQFeatureToBackwardCommand(QList<MvQFeatureItem*> items, MvQFeatureHandler* handler, QString cmdName, QUndoCommand* parent=nullptr) :
        MvQFeatureCommand(items, handler, "to_backward", cmdName, parent) {}
    void undo() override;
    void redo() override;
};

class MvQFeatureFlipCommand : public MvQFeatureCommand
{
public:
    MvQFeatureFlipCommand(QList<MvQFeatureItem*> items, MvQFeatureHandler* handler, QString cmdName, QUndoCommand* parent=nullptr) :
        MvQFeatureCommand(items, handler, "flip_front", cmdName, parent) {}
    void undo() override;
    void redo() override;
};

class MvQFeatureFrontSubTypeCommand : public MvQFeatureCommand
{
public:
    MvQFeatureFrontSubTypeCommand(QList<MvQFeatureItem*> items, MvQFeatureHandler* handler, QString cmdName, QUndoCommand* parent=nullptr) :
        MvQFeatureCommand(items, handler, "front_subtype", cmdName, parent) {}
    void undo() override;
    void redo() override;

protected:
    std::string targetSubType_;
    QList<std::string> oriSubTypes_;

};

class MvQFeatureStyleCommand : public MvQFeatureCommand
{
public:
    MvQFeatureStyleCommand(const MvRequest&, MvQFeatureItem::StyleEditInfo info, MvQFeatureItem* item, MvQFeatureHandler* handler,  QString cmdName, QUndoCommand* parent = 0);
    void undo() override;
    void redo() override;
    bool mergeWith(const QUndoCommand *other) override;
    int id() const override {return 1;}

protected:
    MvRequest req_;
    MvRequest oriReq_;
    MvQFeatureItem::StyleEditInfo info_;
};

class MvQFeatureTextCommand : public MvQFeatureCommand
{
public:
    MvQFeatureTextCommand(QString text, MvQFeatureItem* item, MvQFeatureHandler* handler,  QString cmdName, QUndoCommand* parent = 0);
    void undo() override;
    void redo() override;
    bool mergeWith(const QUndoCommand *other) override;
    int id() const override {return 2;}

protected:
    QString text_;
    QString oriText_;
};

class MvQFeatureAddNodeCommand : public MvQFeatureCommand
{
public:
    MvQFeatureAddNodeCommand(int nodeIndex, bool before, MvQFeatureItem* item, MvQFeatureHandler* handler,  QString cmdName, QUndoCommand* parent =0);
    void undo() override;
    void redo() override;

protected:
    int nodeIndex_{-1};
    bool before_{true};
};

class MvQFeatureRemoveNodeCommand : public MvQFeatureCommand
{
public:
    MvQFeatureRemoveNodeCommand(int nodeIndex, MvQFeatureItem* item, MvQFeatureHandler* handler,  QString cmdName, QUndoCommand* parent =0);
    void undo() override;
    void redo() override;

protected:
    int nodeIndex_{-1};
    MvQFeatureGeometry geom_;
};

class MvQFeatureMoveNodeCommand : public MvQFeatureCommand
{
public:
    MvQFeatureMoveNodeCommand(const QPointF& scenePos, int nodeIndex, bool start, MvQFeatureItem* item, MvQFeatureHandler* handler,  QString cmdName, QUndoCommand* parent =0);
    void undo() override;
    void redo() override;
    bool mergeWith(const QUndoCommand *other) override;
    int id() const override {return 3;}

protected:
    int nodeIndex_{-1};
    bool start_{false};
    MvQFeatureGeometry oriGeom_;
    MvQFeatureGeometry targetGeom_;
};

class MvQFeatureMoveByCommand : public MvQFeatureCommand
{
public:
    MvQFeatureMoveByCommand(const QPointF& delta, bool start, QList<MvQFeatureItem*> items, MvQFeatureHandler* handler,  QString cmdName, QUndoCommand* parent = 0);
    void undo() override;
    void redo() override;
    bool mergeWith(const QUndoCommand *other) override;
    int id() const override {return 4;}

protected:
    QPointF delta_;
    bool start_{false};
    QList<MvQFeatureGeometry> oriGeom_;
    QList<MvQFeatureGeometry> targetGeom_;
};

class MvQFeatureResizeCommand : public MvQFeatureCommand
{
public:
    MvQFeatureResizeCommand(const QRectF& newRect, const QRectF& oriRect, bool start, QList<MvQFeatureItem*> items, MvQFeatureHandler* handler,  QString cmdName, QUndoCommand* parent = 0);
    void undo() override;
    void redo() override;
    bool mergeWith(const QUndoCommand *other) override;
    int id() const override {return 5;}

protected:
    QPointF delta_;
    bool start_{false};
    QList<MvQFeatureGeometry> oriGeom_;
    QList<MvQFeatureGeometry> targetGeom_;
};


//==================================================
//
//  MvQFeatureCommand
//
//==================================================

MvQFeatureCommand::MvQFeatureCommand(QList<MvQFeatureItem*> items, MvQFeatureHandler* handler, QString name, QString cmdName, QUndoCommand* parent) :
    QUndoCommand(parent),
    items_(items),
    handler_(handler),
    name_(name),
    cmdName_(cmdName)
{
    for (auto item: items_) {
        parentItems_ << ((item)?item->parentItem():nullptr);
    }
    generateText();
}

void MvQFeatureCommand::redo()
{
    QUndoCommand::redo();
    //view_->selection();
}

void MvQFeatureCommand::undo()
{
    QUndoCommand::undo();
    //view_->selection();
}

bool MvQFeatureCommand::match(const QUndoCommand *other) const
{
    if (id() == other->id()) {
        auto o = static_cast<const  MvQFeatureCommand*>(other);
        if (items_.size() == o->items_.size()) {
            for(int i=0; i < items_.size(); i++) {
                if (items_[i] != o->items_[i]) {
                    return false;
                }
            }
        }
        return true;
    }
    return false;
}

void MvQFeatureCommand::generateText()
{
    QString s=name_;
    if (items_.size() == 1 && items_[0]) {
        s += " " + items_[0]->typeName();
    } else {
        s += " " + QString::number(items_.size()) + " items";
    }
    return setText(s);
}

//==================================================
//
//  MvQFeatureAddCommand
//
//==================================================

void MvQFeatureAddCommand::redo()
{
    for(int i=0; i < items_.size(); i++) {
        if (items_[i]->parentItem() != parentItems_[i]) {
            items_[i]->setParentItem(parentItems_[i]);
            items_[i]->setVisible(true);
        }
    }
    handler_->notifyAdded(items_);
}

void MvQFeatureAddCommand::undo()
{
    handler_->performRemove(items_);
}

//==================================================
//
//  MvQFeatureRemoveCommand
//
//==================================================

void MvQFeatureRemoveCommand::redo()
{
    for(auto p: parentItems_) {
        Q_ASSERT(p);
    }
    handler_->performRemove(items_);
}

void MvQFeatureRemoveCommand::undo()
{
    for(int i=0; i < items_.size(); i++) {
        items_[i]->setParentItem(parentItems_[i]);
    }
    handler_->notifyAdded(items_);
}

//==================================================
//
//  MvQFeatureDuplicateCommand
//
//==================================================

void MvQFeatureDuplicateCommand::redo()
{
    if (firstCall_) {
        for(int i=0; i < items_.size(); i++) {
            auto it = items_[i]->clone();
            QPointF scenePos(0,0);
            QPointF offset(20, 20);
            scenePos = items_[i]->scenePos() + offset;
            if (handler_->add(it, scenePos)) {
                resItems_ << it;
            } else {
                delete it;
                resItems_ << nullptr;
            }
        }
        firstCall_ = false;
    }

    for(int i=0; i < items_.size(); i++) {
        if (resItems_[i] && resItems_[i]->parentItem() != parentItems_[i]) {
            resItems_[i]->setParentItem(parentItems_[i]);
        }
    }
    handler_->notifyAdded(resItems_);
}

void MvQFeatureDuplicateCommand::undo()
{
    handler_->performRemove(resItems_);
}

//==================================================
//
//  MvQFeatureCopyCommand
//
//==================================================

void MvQFeatureCopyCommand::redo()
{
    QList<MvQFeatureItem*> lst;
    for(int i=0; i < items_.size(); i++) {
        lst << items_[i]->clone();
    }
    MvQFeatureMimeData* data = new MvQFeatureMimeData(lst, MvQFeatureMimeData::CopyAction);
    QGuiApplication::clipboard()->setMimeData(data);
}

void MvQFeatureCopyCommand::undo()
{
    QGuiApplication::clipboard()->clear();
}

//==================================================
//
//  MvQFeatureCutCommand
//
//==================================================

void MvQFeatureCutCommand::redo()
{
    QList<MvQFeatureItem*> lst;
    for(auto item: items_) {
        lst << item->clone();
    }

    MvQFeatureMimeData* data = new MvQFeatureMimeData(lst, MvQFeatureMimeData::CopyAction);
    QGuiApplication::clipboard()->setMimeData(data);

    handler_->performRemove(items_);
}

void MvQFeatureCutCommand::undo()
{
    for(int i=0; i < items_.size(); i++) {
        if (items_[i]->parentItem() != parentItems_[i]) {
            items_[i]->setParentItem(parentItems_[i]);
        }
    }
    handler_->notifyAdded(items_);
    QGuiApplication::clipboard()->clear();
}

//==================================================
//
//  MvQFeatureToFrontCommand
//
//==================================================

void MvQFeatureToFrontCommand::redo()
{
    Q_ASSERT(items_.size() == 1);
    auto theItem = items_[0];

#ifdef FEATURECOMMAND_DEBUG_
    qDebug() << "MvQFeatureToFrontCommand " << theItem->typeName() << "oriZVal:" << oriZVal_;
#endif
    if (!theItem->isPoint()) {
        if (oriZVal_ < 0) {
             oriZVal_ = theItem->realZValue();
        }

        for(auto item: handler_->features()) {
            if (!item->isPoint() && item != theItem && item->realZValue() > oriZVal_) {
#ifdef FEATURECOMMAND_DEBUG_
                qDebug() << " " << item->typeName() << " " << item->realZValue() << "->" <<
                            item->realZValue()-MvQFeatureItem::zValueIncrement();
#endif
                item->setRealZValue(item->realZValue()-MvQFeatureItem::zValueIncrement());
            }
        }
#ifdef FEATURECOMMAND_DEBUG_
        qDebug() << "final z:" << MvQFeatureItem::nextZValue()-MvQFeatureItem::zValueIncrement();
#endif
        theItem->setRealZValue(MvQFeatureItem::nextZValue()-MvQFeatureItem::zValueIncrement());
#ifdef FEATURECOMMAND_DEBUG_
        qDebug() << "  ->" << theItem->realZValue() << theItem->zValue();
#endif
    }
}

void MvQFeatureToFrontCommand::undo()
{
    Q_ASSERT(items_.size() == 1);
    auto theItem = items_[0];

    if (!theItem->isPoint()) {
       for(auto item: handler_->features()) {
           if (!item->isPoint() && item != theItem && item->realZValue() >= oriZVal_) {
               item->setRealZValue(item->realZValue()+item->MvQFeatureItem::zValueIncrement());
           }
       }
       theItem->setRealZValue(oriZVal_);
   }
}

//==================================================
//
//  MvQFeatureToForwardCommand
//
//==================================================

void MvQFeatureToForwardCommand::redo()
{
    Q_ASSERT(items_.size() == 1);
    auto theItem = items_[0];
    if (!theItem->isPoint()) {
        handler_->swapByZValue(theItem, theItem->realZValue()  + MvQFeatureItem::zValueIncrement());
    }
}

void MvQFeatureToForwardCommand::undo()
{
    Q_ASSERT(items_.size() == 1);
    auto theItem = items_[0];
    if (!theItem->isPoint()) {
       handler_->swapByZValue(theItem, theItem->realZValue()  - MvQFeatureItem::zValueIncrement());
   }
}

//==================================================
//
//  MvQFeatureToBackCommand
//
//==================================================

void MvQFeatureToBackCommand::redo()
{
    Q_ASSERT(items_.size() == 1);
    auto theItem = items_[0];
    if (!theItem->isPoint()) {
        if (oriZVal_ < 0) {
             oriZVal_ = theItem->realZValue();
        }

        for(auto item: handler_->features()) {
            if (!item->isPoint() && item != theItem && item->realZValue() < oriZVal_) {
                item->setRealZValue(item->realZValue() + MvQFeatureItem::zValueIncrement());
            }
        }
        theItem->setRealZValue(MvQFeatureItem::bottomZValue());
    }
}

void MvQFeatureToBackCommand::undo()
{
    Q_ASSERT(items_.size() == 1);
    auto theItem = items_[0];
    if (!theItem->isPoint()) {
       for(auto item: handler_->features()) {
           if (!item->isPoint() && item != theItem && item->realZValue() <= oriZVal_) {
               item->setRealZValue(item->realZValue() - item->MvQFeatureItem::zValueIncrement());
           }
       }
       theItem->setRealZValue(oriZVal_);
   }
}

//==================================================
//
//  MvQFeatureToBackwardCommand
//
//==================================================

void MvQFeatureToBackwardCommand::redo()
{
    Q_ASSERT(items_.size() == 1);
    auto theItem = items_[0];
    if (!theItem->isPoint()) {
        handler_->swapByZValue(theItem, theItem->realZValue()  - MvQFeatureItem::zValueIncrement());
    }
}

void MvQFeatureToBackwardCommand::undo()
{
    Q_ASSERT(items_.size() == 1);
    auto theItem = items_[0];
    if (!theItem->isPoint()) {
       handler_->swapByZValue(theItem, theItem->realZValue()  + MvQFeatureItem::zValueIncrement());
   }
}

//==================================================
//
//  MvQFeatureFlipCommand
//
//==================================================

void MvQFeatureFlipCommand::redo()
{
    for(auto item: items_) {
        item->flipSymbolDirection();
    }
}

void MvQFeatureFlipCommand::undo()
{
    for(auto item: items_) {
        item->flipSymbolDirection();
    }
}

//==================================================
//
//  MvQFeatureFrontSubTypeCommand
//
//==================================================

void MvQFeatureFrontSubTypeCommand::redo()
{
    if (targetSubType_.empty()) {
        targetSubType_ = cmdName_.toStdString();
        for(auto item: items_) {
            oriSubTypes_ << item->frontSubType();
        }
    }

    for(auto item: items_) {
        item->setFrontSubType(targetSubType_);
    }
}

void MvQFeatureFrontSubTypeCommand::undo()
{
    for(int i=0; i < items_.size(); i++) {
        items_[i]->setFrontSubType(oriSubTypes_[i]);
    }
}

//==================================================
//
//  MvQFeatureStyleCommand
//
//==================================================

MvQFeatureStyleCommand::MvQFeatureStyleCommand(const MvRequest& req, MvQFeatureItem::StyleEditInfo info, MvQFeatureItem* item, MvQFeatureHandler* handler,  QString cmdName, QUndoCommand* parent) :
    MvQFeatureCommand({item}, handler, "change_style", cmdName, parent),
    req_(req),
    oriReq_(item->styleRequest()),
    info_(info)
{}

void MvQFeatureStyleCommand::redo()
{
    Q_ASSERT(items_.size() == 1);
    items_[0]->setStyle(req_);
}

void MvQFeatureStyleCommand::undo()
{
    items_[0]->setStyle(oriReq_);
}

bool MvQFeatureStyleCommand::mergeWith(const QUndoCommand *other)
{
    if (match(other)) {
        auto o = static_cast<const  MvQFeatureStyleCommand*>(other);
        if (o && info_ == MvQFeatureItem::TransparencyStyleEdit && o->info_ == info_) {
            req_ = o->req_;
            return true;
        }
    }
    return false;
}

//==================================================
//
//  MvQFeatureTextCommand
//
//==================================================

MvQFeatureTextCommand::MvQFeatureTextCommand(QString text, MvQFeatureItem* item, MvQFeatureHandler* handler,  QString cmdName, QUndoCommand* parent) :
    MvQFeatureCommand({item}, handler, "edit_text", cmdName, parent),
    text_(text)
{
    Q_ASSERT(items_.size() == 1);
    oriText_ = items_[0]->text();
}

void MvQFeatureTextCommand::redo()
{
    Q_ASSERT(items_.size() == 1);
    items_[0]->setText(text_);
}

void MvQFeatureTextCommand::undo()
{
    Q_ASSERT(items_.size() == 1);
    items_[0]->setText(oriText_);
}

bool MvQFeatureTextCommand::mergeWith(const QUndoCommand *other)
{
    if (match(other)) {
        auto o = static_cast<const  MvQFeatureTextCommand*>(other);
        if (o) {
            if ((text_.endsWith(" ") && o->text_.endsWith(" ")) ||
                (!text_.endsWith(" ") && !o->text_.endsWith(" ")))  {
                text_ = o->text_;
                return true;
             }
        }
    }
    return false;
}

//==================================================
//
//  MvQFeatureAddNodeCommand
//
//==================================================

MvQFeatureAddNodeCommand::MvQFeatureAddNodeCommand(int nodeIndex, bool before, MvQFeatureItem* item, MvQFeatureHandler* handler,  QString cmdName, QUndoCommand* parent) :
    MvQFeatureCommand({item}, handler, "add_node", cmdName, parent),
    nodeIndex_(nodeIndex),
    before_(before)
{}

void MvQFeatureAddNodeCommand::redo()
{
    Q_ASSERT(items_.size() == 1);
    items_[0]->addNode(nodeIndex_, before_);
}

void MvQFeatureAddNodeCommand::undo()
{
    items_[0]->removeNode(nodeIndex_);
}

//==================================================
//
//  MvQFeatureRemoveNodeCommand
//
//==================================================

MvQFeatureRemoveNodeCommand::MvQFeatureRemoveNodeCommand(int nodeIndex,  MvQFeatureItem* item, MvQFeatureHandler* handler,  QString cmdName, QUndoCommand* parent) :
    MvQFeatureCommand({item}, handler, "remove_node", cmdName, parent),
    nodeIndex_(nodeIndex),
    geom_(items_[0]->getGeometry())
{}

void MvQFeatureRemoveNodeCommand::redo()
{
    Q_ASSERT(items_.size() == 1);
    items_[0]->removeNode(nodeIndex_);
}

void MvQFeatureRemoveNodeCommand::undo()
{
    items_[0]->addNode(nodeIndex_, geom_.pointsScenePos_[nodeIndex_]);
}

//==================================================
//
//  MvQFeatureRemoveNodeCommand
//
//==================================================

MvQFeatureMoveNodeCommand::MvQFeatureMoveNodeCommand(const QPointF& scenePos, int nodeIndex, bool start, MvQFeatureItem* item, MvQFeatureHandler* handler,  QString cmdName, QUndoCommand* parent) :
    MvQFeatureCommand({item}, handler, "move_node", cmdName, parent),
    nodeIndex_(nodeIndex),
    start_(start)
{
    Q_ASSERT(nodeIndex_ != -1);
    oriGeom_=items_[0]->getGeometry();
    item->moveNodeTo(nodeIndex_, scenePos);
    targetGeom_=items_[0]->getGeometry();
}

void MvQFeatureMoveNodeCommand::redo()
{
    if (firstCall_) {
        firstCall_ = false;
    } else {
        Q_ASSERT(items_.size() == 1);
        items_[0]->moveNodeTo(nodeIndex_, targetGeom_.pointsScenePos_[nodeIndex_]);
    }
}

void MvQFeatureMoveNodeCommand::undo()
{
    items_[0]->moveNodeTo(nodeIndex_, oriGeom_.pointsScenePos_[nodeIndex_]);
}

bool MvQFeatureMoveNodeCommand::mergeWith(const QUndoCommand *other)
{
    if (match(other)) {
        auto o = static_cast<const  MvQFeatureMoveNodeCommand*>(other);
        if (o && nodeIndex_ == o->nodeIndex_ && !o->start_) {
            targetGeom_ = o->targetGeom_;
            return true;
        }
    }
    return false;
}

//==================================================
//
//  MvQFeatureMoveByCommand
//
//==================================================

MvQFeatureMoveByCommand::MvQFeatureMoveByCommand(const QPointF& delta, bool start, QList<MvQFeatureItem*> items, MvQFeatureHandler* handler,  QString cmdName, QUndoCommand* parent) :
    MvQFeatureCommand(items, handler, "move", cmdName, parent),
    start_(start)
{
    bool validDelta = fabs(delta.x()) > 1E-10 || fabs(delta.y()) > 1E-10;
    for (auto item: items_) {
        oriGeom_ << item->getGeometry();
        if (validDelta) {
            item->moveBy(delta);
        }
        targetGeom_ << item->getGeometry();
     }
}

void MvQFeatureMoveByCommand::redo()
{
    if (firstCall_) {
        firstCall_ = false;
    } else {
        for (int i=0; i < items_.size(); i++) {
            items_[i]->moveTo(targetGeom_[i]);
        }
        handler_->adjustSelection();
    }
}

void MvQFeatureMoveByCommand::undo()
{
    for (int i=0; i < items_.size(); i++) {
        items_[i]->moveTo(oriGeom_[i]);
    }
    handler_->adjustSelection();
}

bool MvQFeatureMoveByCommand::mergeWith(const QUndoCommand *other)
{
    if (match(other)) {
       auto o = static_cast<const  MvQFeatureMoveByCommand*>(other);
       if (o && !o->start_) {
            for (int i=0; i < items_.size(); i++) {
                targetGeom_[i] = o->targetGeom_[i];
            }
            return true;
        }
    }
    return false;
}

//==================================================
//
//  MvQFeatureResizeCommand
//
//==================================================

MvQFeatureResizeCommand::MvQFeatureResizeCommand(const QRectF& newRect, const QRectF& oriRect, bool start, QList<MvQFeatureItem*> items, MvQFeatureHandler* handler,  QString cmdName, QUndoCommand* parent) :
    MvQFeatureCommand(items, handler, "resize", cmdName, parent),
    start_(start)
{
    //bool validDelta = fabs(delta.x()) > 1E-10 || fabs(delta.y()) > 1E-10;
    for (auto item: items_) {
        oriGeom_ << item->getGeometry();
        item->adjustSizeToSelector(newRect, oriRect);
        targetGeom_ << item->getGeometry();
     }
}

void MvQFeatureResizeCommand::redo()
{
    if (firstCall_) {
        firstCall_ = false;
    } else {
        for (int i=0; i < items_.size(); i++) {
            items_[i]->resizeTo(targetGeom_[i]);
        }
        handler_->adjustSelection();
    }
}

void MvQFeatureResizeCommand::undo()
{
    for (int i=0; i < items_.size(); i++) {
        items_[i]->resizeTo(oriGeom_[i]);
    }
    handler_->adjustSelection();
}

bool MvQFeatureResizeCommand::mergeWith(const QUndoCommand *other)
{
    if (match(other)) {
       auto o = static_cast<const  MvQFeatureResizeCommand*>(other);
       if (o && !o->start_) {
            for (int i=0; i < items_.size(); i++) {
                targetGeom_[i] = o->targetGeom_[i];
            }
            return true;
        }
    }
    return false;
}

//==================================================
//
//  MvQFeatureHandler
//
//==================================================

MvQFeatureHandler::MvQFeatureHandler(MvQPlotView* view) : view_(view)
{

}

void MvQFeatureHandler::setUndoStack(QUndoStack *s)
{
    undoStack_=s;
}

void MvQFeatureHandler::setRibbonEditor(MvQFeatureRibbonEditor* e)
{
    ribbonEditor_ = e;
}

void MvQFeatureHandler::setInfoWidget(QPlainTextEdit* w)
{
    infoWidget_ = w;
}

void MvQFeatureHandler::updateFeatureInfo(MvQFeatureItem* item)
{
    if (infoWidget_ && item) {
        infoWidget_->clear();
        infoWidget_->appendHtml(item->describe());
    }

}

QGraphicsItem* MvQFeatureHandler::rootItem() const
{
    return view_->plotScene_->annotationRootItem();
}

void MvQFeatureHandler::makeSelector()
{
    if (!selector_) {
        selector_ = new MvQFeatureSelector(
                     view_, this, rootItem());
    }
}

void MvQFeatureHandler::clearSelection()
{
    if (selector_) {
        selector_->clear();
    }
}

void MvQFeatureHandler::checkSelection()
{
    if (selector_) {
        selector_->selectionChanged();
        if (ribbonEditor_) {
            if (auto item = selector_->firstItem()) {
                ribbonEditor_->edit(item);
            } else if (!textEditor_) {
                ribbonEditor_->finishEdit();
            }
        }
        if (infoWidget_) {
            updateFeatureInfo(selector_->firstItem());
        }
    }
}

void MvQFeatureHandler::adjustSelection()
{
    if (selector_) {
        selector_->adjustToItems();
    }
}

MgQLayoutItem* MvQFeatureHandler::layoutItemAt(const QPointF& scPos) const
{
    MgQSceneItem *sceneItem = nullptr;
    MgQLayoutItem* layoutItem = nullptr;
    if (view_->plotScene_->identifyPos(scPos, &sceneItem, &layoutItem)) {
        layoutItem = sceneItem->firstProjectorItem();
    } else if(view_->plotScene_->sceneItems().size() == 1) {
        layoutItem = view_->plotScene_->sceneItems()[0]->firstProjectorItem();
    }
    return layoutItem;
}

bool MvQFeatureHandler::isOutOfView(const QPointF& scPos, MgQLayoutItem *layoutItem) const
{
    bool outOfView = true;
    if (layoutItem) {
        QPointF coord;
        layoutItem->mapFromSceneToGeoCoords(scPos, coord);
        QPointF scPosA = scPos;
        if(layoutItem->containsSceneCoords(scPosA) && MvQFeatureItem::isValidPoint(coord)) {
            outOfView = false;
        }
    }
    return outOfView;
}

void MvQFeatureHandler::currentLayout(MvQFeatureItem* item, MgQLayoutItem **layoutItem) const
{
    QPointF scPos = item->scenePos();
    MgQSceneItem *sceneItem = nullptr;
    *layoutItem = nullptr;
    if (view_->plotScene_->identifyPos(scPos, &sceneItem, layoutItem)) {
        *layoutItem = sceneItem->firstProjectorItem();
    } else if(view_->plotScene_->sceneItems().size() == 1) {
        *layoutItem = view_->plotScene_->sceneItems()[0]->firstProjectorItem();
    }
}

bool MvQFeatureHandler::canBeAddedToScene(MvQFeatureItem* item, const QPointF& scPos, MgQLayoutItem **layoutItem) const
{
    *layoutItem = layoutItemAt(scPos);
    if ((!*layoutItem || isOutOfView(scPos, *layoutItem)) &&
        !item->canBeCreatedOutOfView()) {
        return false;
    }
    return true;
}

bool MvQFeatureHandler::canBePastedToScene(MvQFeatureItem* item, const QPointF& scPos, MgQLayoutItem **layoutItem) const
{
    *layoutItem = layoutItemAt(scPos);
    if ((!*layoutItem || isOutOfView(scPos, *layoutItem)) &&
        !item->canBePastedOutOfView()) {
        return false;
    }
    return true;
}

// Add a feature from the "library"
void MvQFeatureHandler::add(QString name, QPoint pos)
{
    makeSelector();

    if (MvQFeatureType *feature = MvQFeatureType::find(name)) {
        QPointF scPos = view_->mapToScene(pos);
        if (MvQFeatureItem *item = MvQFeatureFactory::create(feature, view_)) {
            MgQLayoutItem *layoutItem = nullptr;
            if (!canBeAddedToScene(item, scPos, &layoutItem)) {
                PlotMod::Instance().UserWarningMessage("Cannot create " + item->typeName().toStdString() +
                                                " items outside the view area!");
                delete item;
                return;
            }
            item->setParentItem(rootItem());
            item->initScenePosition(layoutItem, scPos);
            undoStack_->push(new MvQFeatureAddCommand(
                                 {item}, this, "add"));
            item->initContents();
        }
    }
}

// add a cloned item
bool MvQFeatureHandler::add(MvQFeatureItem* item, const QPointF& scPos)
{
    Q_ASSERT(selector_);

    if (item) {
        MgQLayoutItem *layoutItem = nullptr;
        if (!canBePastedToScene(item, scPos, &layoutItem)) {
            return false;
        }
        clearSelection();
        item->setParentItem(rootItem());
        item->initScenePosition(layoutItem, scPos);
    }
    return true;
}

void MvQFeatureHandler::paste(QList<MvQFeatureItem*> items, const QPointF& scPos)
{
    Q_ASSERT(selector_);

    if (items.size() > 0) {
        clearSelection();
        QList<MvQFeatureItem*> clItems;
        QPointF firstScenePos;
        for (auto item: items) {
            MgQLayoutItem *layoutItem = nullptr;
            if (canBePastedToScene(item, scPos, &layoutItem)) {
                if (clItems.size() == 0) {
                    firstScenePos = item->copiedScenePos();
                }
                QPointF delta = item->copiedScenePos() - firstScenePos;
                auto cl = item->clone();
                cl->setParentItem(rootItem());
                cl->initScenePosition(layoutItem, scPos + delta);
                clItems << cl;
            }
            if (clItems.size() > 0) {
                undoStack_->push(new MvQFeatureAddCommand(
                             clItems, this, "add"));
            }
        }
    }
}

void MvQFeatureHandler::notifyAdded(QList<MvQFeatureItem*> items)
{
    for(auto item: items) {
        if (item) {
            features_ << item;
        }
    }
}

void MvQFeatureHandler::performRemove(QList<MvQFeatureItem*> items)
{
    Q_ASSERT(selector_);
    selector_->clear();
    selector_->suspend(true);
    for (auto item: items) {
        if (item) {
            features_.removeOne(item);
            // NOTE: after calling removeItem the item is still selected!
            view_->scene()->removeItem(item);
        }
    }
    selector_->suspend(false);
    // TODO: do we need to call it?
    checkSelection();
}

void MvQFeatureHandler::fromClipboard(const QPointF& scenePos)
{
    auto m = QGuiApplication::clipboard()->mimeData();
    if (m && m->formats().contains("metview/feature")) {
        const MvQFeatureMimeData* mimeData =
            static_cast<const MvQFeatureMimeData*>(m);
        if (mimeData) {
            if (mimeData->clipboardAction() == MvQFeatureMimeData::CopyAction) {
                paste(mimeData->items(), scenePos);
            }
        }
    }
}

void MvQFeatureHandler::runCommand(const MvQFeatureCommandTarget& sel, QString cmd)
{
#ifdef FEATURECOMMAND_DEBUG_
    qDebug() << "MvQFeatureHandler::runCommand:" << cmd << sel.scenePos();
#endif
    if (cmd.startsWith("extra:")) {
//        int idx = cmd.mid(6).toInt();
        //return (idx >=0 && idx < topLevelActions.size())?topLevelActions[idx]:nullptr;
    } else if (!cmd.isEmpty()) {
        if (cmd == "paste") {
            fromClipboard(sel.scenePos());
        } else  {
#ifdef FEATURECOMMAND_DEBUG_
            qDebug() << " tagetItem:" << sel.targetItem();
#endif
            if (sel.targetItem()) {
                handleItemCommand(sel.targetItem(), cmd, sel.nodeIndex(), sel.scenePos());
            } else if (sel.inSelector()) {
                Q_ASSERT(selector_);
#ifdef FEATURECOMMAND_DEBUG_
                qDebug() << " selector handles command!";
#endif
                handleSelectorCommand(cmd, sel.scenePos());
            }
        }
    }
}

void MvQFeatureHandler::handleItemCommand(MvQFeatureItem *targetItem, QString cmd, int nodeIndex, const QPointF& /*scenePos*/)
{
    // item commands
    if (nodeIndex == -1) {
        if (cmd == "edit") {
            targetItem->editStyle();
        } else {
            auto obj =  MvQFeatureCommandFactory::create(cmd, targetItem, this);
            if (obj) {
                undoStack_->push(obj);
            }
        }
    // (curve) node commands
    } else {
        if (cmd == "add_before" || cmd == "add_after") {
            undoStack_->push(new MvQFeatureAddNodeCommand(
                                 nodeIndex, cmd == "add_before", targetItem, this, cmd));
        } else if (cmd == "delete") {
            undoStack_->push(new MvQFeatureRemoveNodeCommand(
                                 nodeIndex, targetItem, this, cmd));
        }
    }

    updateFeatureInfo(targetItem);
}

void MvQFeatureHandler::handleSelectorCommand(QString cmd, const QPointF& /*scenePos*/)
{
    if (cmd == "copy") {
        undoStack_->push(new MvQFeatureCopyCommand(selector_->allItems(), this, "copy"));
    } else if (cmd == "cut") {
        undoStack_->push(new MvQFeatureCutCommand(selector_->allItems(), this, "cut"));
    } else if (cmd == "delete") {
        undoStack_->push(new MvQFeatureRemoveCommand(selector_->allItems(), this, "delete"));
    }
}

void MvQFeatureHandler::swapByZValue(MvQFeatureItem *item, float z)
{
    if (!item->isPoint()) {
        const float eps = MvQFeatureItem::zValueIncrement()/1000;
        for(auto v: features_) {
            if (!v->isPoint() && fabs(v->realZValue() - z) < eps) {
                auto zThis = item->realZValue();
                v->setRealZValue(zThis);
                item->setRealZValue(z);
                return;
             }
        }
        item->setRealZValue(z);
    }
}

void MvQFeatureHandler::updateStyle(MvQFeatureItem* item, const MvRequest& req, MvQFeatureItem::StyleEditInfo info)
{
    undoStack_->push(new MvQFeatureStyleCommand(
                         req, info, item, this, "style"));
}

void MvQFeatureHandler::setText(MvQFeatureItem* item, QString text)
{
    undoStack_->push(new MvQFeatureTextCommand(
                         text, item, this, "text"));
}

void MvQFeatureHandler::editText(MvQFeatureTextItem* item)
{
    if (item && !textEditor_) {
#ifdef FEATURECOMMAND_DEBUG_
        qDebug() << "create editor";
#endif
        textEditor_ = new MvQFeatureTextEditor(item);
        view_->scene()->addItem(textEditor_);
#ifdef FEATURECOMMAND_DEBUG_
        qDebug() << "editor pos=" << item->pos();
        qDebug() << "item pos:" << item->pos() << "textpos:" << item->textPos();
#endif
        textEditor_->initPos();
        textEditor_->grabMouse();
        view_->scene()->setFocusItem(textEditor_, Qt::MouseFocusReason);
        textEditor_->editor()->setFocus(Qt::MouseFocusReason);
        ribbonEditor_->edit(item);
    }
}

void MvQFeatureHandler::removeTextEditor()
{
    if (textEditor_) {
        textEditor_->finish();
        view_->scene()->removeItem(textEditor_);
        textEditor_->deleteLater();
        textEditor_ = nullptr;
        ribbonEditor_->finishEdit();
    }
}

// close feature text editor if we click outside the editor
bool MvQFeatureHandler::checkMousePressForTextEditor(const QPointF& scenePos)
{
    if (textEditor_) {
        if (!textEditor_->containsScenePos(scenePos)) {
           removeTextEditor();
           return true;
        }
    }
    return false;
}

void MvQFeatureHandler::checkMouseMove(QMouseEvent* viewEvent)
{
    if (textEditor_ && view_) {
        textEditor_->adjustCursorToMouseLeave(viewEvent);
    }
}

void MvQFeatureHandler::dragTextEditor(MvQFeatureTextEditor* editor)
{
    Q_ASSERT(textEditor_ == editor);
    if (textEditor_ ==  editor) {
        if (textEditor_->item()) {
            auto item = textEditor_->item();
            removeTextEditor();
            item->setSelected(true);
        }
    }
}

void MvQFeatureHandler::moveNode(MvQFeatureCurveBaseItem* curve, const QPointF& pp, int nodeIndex, bool start)
{
    undoStack_->push(new MvQFeatureMoveNodeCommand(
                         pp, nodeIndex, start, curve, this, "move"));
    updateFeatureInfo(curve);
}

void MvQFeatureHandler::moveBy(QList<MvQFeatureItem*> items, const QPointF& delta, bool start)
{
    undoStack_->push(new MvQFeatureMoveByCommand(
                         delta, start, items, this, "moveby"));
    if (items.size() > 0) {
        updateFeatureInfo(items[0]);
    }
}

void MvQFeatureHandler::resize(QList<MvQFeatureItem*> items, const QRectF& newRect, const QRectF& oriRect, bool start)
{
    undoStack_->push(new MvQFeatureResizeCommand(
                         newRect, oriRect, start, items, this, "resize"));

    if (items.size() > 0) {
        updateFeatureInfo(items[0]);
    }
}

void MvQFeatureHandler::resetBegin()
{
     clearSelection();
     // TODO: this should only be done on zoom or when the projection
     // changes.
     if(undoStack_) {
         undoStack_->clear();
     }
     for(auto sym: features_) {
        sym->prepareForProjectionChange();
    }
}

void MvQFeatureHandler::resetEnd()
{
    foreach(MvQFeatureItem *sym, features_) {
        foreach (MgQSceneItem* item, view_->plotScene_->sceneItems()) {
            MgQLayoutItem* projItem = item->firstProjectorItem();
            if (item->layout().id() == sym->sceneId()) {
                sym->projectionChanged(projItem);
                break;
            } else if (item->layout().id().empty() && projItem &&
                     sym->sceneId() == projItem->layout().id()) {
                sym->projectionChanged(item);
                break;
            }
        }
    }
}

bool MvQFeatureHandler::canAcceptMouseEvent() const
{
    return (view_)?(!view_->hasFeatureTypeToAdd()):false;
}

void MvQFeatureHandler::setDragCursor() const
{
    if (view_ && canAcceptMouseEvent()) {
        view_->viewport()->setCursor(QCursor(Qt::SizeAllCursor));
    }
}

void MvQFeatureHandler::setCurvePointCursor() const
{
    if (view_ && canAcceptMouseEvent()) {
        view_->viewport()->setCursor(QCursor(Qt::CrossCursor));
    }
}

void MvQFeatureHandler::resetCursor() const
{
    if (view_ && canAcceptMouseEvent()) {
        view_->viewport()->setCursor(QCursor());
    }
}

static MvQFeatureCommandMaker<MvQFeatureRemoveCommand> cmdMaker1("delete");
static MvQFeatureCommandMaker<MvQFeatureDuplicateCommand> cmdMaker2("duplicate");
static MvQFeatureCommandMaker<MvQFeatureCopyCommand> cmdMaker3("copy");
static MvQFeatureCommandMaker<MvQFeatureCutCommand> cmdMaker4("cut");
static MvQFeatureCommandMaker<MvQFeatureToFrontCommand> cmdMaker5("to_front");
static MvQFeatureCommandMaker<MvQFeatureToForwardCommand> cmdMaker6("to_forward");
static MvQFeatureCommandMaker<MvQFeatureToBackCommand> cmdMaker7("to_back");
static MvQFeatureCommandMaker<MvQFeatureToBackwardCommand> cmdMaker8("to_backward");
static MvQFeatureCommandMaker<MvQFeatureFlipCommand> cmdMaker9("flip");
static MvQFeatureCommandMaker<MvQFeatureFrontSubTypeCommand> cmdMaker10("surface");
static MvQFeatureCommandMaker<MvQFeatureFrontSubTypeCommand> cmdMaker11("upper");
static MvQFeatureCommandMaker<MvQFeatureFrontSubTypeCommand> cmdMaker12("frontogenesis");
static MvQFeatureCommandMaker<MvQFeatureFrontSubTypeCommand> cmdMaker13("frontolysis");
