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

 Copyright 2017 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 "PropertyLineEditor.h"

#include <QtGlobal>
#include <QComboBox>
#include <QCompleter>
#include <QFile>
#include <QGridLayout>
#include <QLabel>
#include <QLayoutItem>
#include <QLineEdit>
#include <QMouseEvent>
#include <QPainter>
#include <QRadioButton>
#include <QStyle>
#include <QToolButton>
#include <QVBoxLayout>

#include <QDebug>

#include "MvQProperty.h"
#include "MvQMethods.h"
#include "CalendarDialog.h"

EditLine* EditLineFactory::make(MvQProperty* prop, EditLineHeader* hdr)
{
    switch (prop->guiType()) {
        case MvQProperty::StringGui:
            return new EditStringLine(prop, grid_, hdr, parent_);
        case MvQProperty::DateGui:
            return new EditDateLine(prop, grid_, hdr, parent_);
        case MvQProperty::IntGui:
            return new EditStringLine(prop, grid_, hdr, parent_);
        case MvQProperty::AreaGui:
            return new EditAreaLine(prop, grid_, hdr, parent_);
        case MvQProperty::StringComboGui:
            return new EditComboLine(prop, grid_, hdr, parent_);
        case MvQProperty::AddRemoveGui:
            return new EditAddRemoveLine(prop, grid_, hdr, parent_);
        case MvQProperty::BoolGui:
            return new EditBoolLine(prop, grid_, hdr, parent_);
        case MvQProperty::ConditionGui:
            return new EditConditionLine(prop, grid_, hdr, parent_);
        case MvQProperty::MultiConditionGui:
            return new EditMultiConditionLine(prop, grid_, hdr, parent_);
        case MvQProperty::LabelGui:
            return new EditLabelLine(prop, grid_, hdr, parent_);
        default:
            return nullptr;
    }

    return nullptr;
}


EditLineHeader::EditLineHeader(QString name, QGridLayout* grid, QWidget* parent) :
    QWidget(parent),
    expanded_(true),
    needRepaint_(true),
    groupProp_(nullptr),
    title_(name)
{
    Q_ASSERT(parent);

    setProperty("lineEditHeader", "1");

    auto* hb = new QHBoxLayout(this);
    hb->setContentsMargins(0, 0, 0, 0);
    hb->setSpacing(0);

    label_ = new QLabel(this);
    label_->setObjectName("label");
    label_->setAlignment(Qt::AlignCenter);
    label_->setText(title_);
    label_->setProperty("lineEditHeader", "1");

    expandTb_ = new QToolButton(this);
    expandTb_->setObjectName("expandTb");
    expandTb_->setToolTip("Expand group");
    expandTb_->setAutoRaise(true);
    expandTb_->setProperty("lineEditHeader", "1");

    closeIcon_ = QPixmap(":/examiner/block_close.svg");
    openIcon_ = QPixmap(":/examiner/block_open.svg");

    // by default everything is open
    expandTb_->setIcon(closeIcon_);

    connect(expandTb_, SIGNAL(clicked()),
            this, SLOT(slotToggleExpand()));

    clearTb_ = new QToolButton(this);
    clearTb_->setObjectName("clearTb");
    clearTb_->setToolTip("Reset group");
    clearTb_->setAutoRaise(true);
    clearTb_->setIcon(QPixmap(":/examiner/block_reset.svg"));
    clearTb_->setProperty("lineEditHeader", "1");
    clearTb_->setEnabled(false);
    clearTb_->setVisible(false);

    connect(clearTb_, SIGNAL(clicked()),
            this, SLOT(slotClear()));

    hb->addWidget(expandTb_);
    hb->addWidget(label_, 1);
    hb->addWidget(clearTb_);

    QFont f;
    QFontMetrics fm(f);
    int h = 10;  // fm.height()-2;
    expandTb_->setIconSize(QSize(h, h));
    clearTb_->setIconSize(QSize(h, h));

    setFixedHeight(fm.height() + 4);

    int row = grid->rowCount();
    grid->addWidget(this, row, 0, 1, 4);

    connect(this, SIGNAL(needRepaint()),
            parent, SLOT(delayedForceRepaint()));

    // label_->setFixedHeight(fm.height()+4);
}

void EditLineHeader::init()
{
    // slotExpand();
}

void EditLineHeader::addLine(EditLine* line)
{
    lines_ << line;
    connect(line, SIGNAL(valueChanged(EditLine*, QString)),
            this, SLOT(lineValueChanged(EditLine*, QString)));
}

void EditLineHeader::mousePressEvent(QMouseEvent* e)
{
    if (label_->geometry().contains(e->pos())) {
        slotToggleExpand();
    }
}

void EditLineHeader::expand()
{
    if (!expanded_) {
        needRepaint_ = false;
        slotToggleExpand();
        needRepaint_ = true;
    }
}

void EditLineHeader::collapse()
{
    if (expanded_) {
        needRepaint_ = false;
        slotToggleExpand();
        needRepaint_ = true;
    }
}

void EditLineHeader::slotToggleExpand()
{
    expanded_ = !expanded_;

    if (expanded_)
        expandTb_->setIcon(closeIcon_);
    else
        expandTb_->setIcon(openIcon_);

    Q_FOREACH (EditLine* line, lines_) {
        line->setVisible(expanded_);
    }

    if (needRepaint_) {
        forceRepaint();
    }
}

void EditLineHeader::slotClear()
{
    Q_FOREACH (EditLine* line, lines_) {
        line->clear();
    }
    setChangeStatus(false);
}

void EditLineHeader::lineValueChanged(EditLine*, QString)
{
    Q_FOREACH (EditLine* line, lines_) {
        if (line->isSet()) {
            setChangeStatus(true);
            return;
        }
    }
    setChangeStatus(false);
}

void EditLineHeader::adjustLabel(bool hasEdited)
{
    Q_ASSERT(label_);
    if (hasEdited) {
        label_->setText("<b>" + title_ + "</b>");
    }
    else {
        label_->setText(title_);
    }
}

void EditLineHeader::setChangeStatus(bool b)
{
    bool changed = (clearTb_->isEnabled() != b);

    clearTb_->setEnabled(b);
    clearTb_->setVisible(b);
    adjustLabel(hasEdited());

    if (changed && needRepaint_) {
        forceRepaint();
    }
}

bool EditLineHeader::hasEdited() const
{
    return clearTb_->isEnabled();
}

void EditLineHeader::forceRepaint()
{
    Q_EMIT needRepaint();
}

void EditLineHeader::paintEvent(QPaintEvent*)
{
    QStyleOption opt;
    opt.initFrom(this);
    QPainter p(this);
    style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
}

//=============================================
//
// EditLine
//
//=============================================

EditLine::EditLine(MvQProperty* prop, QGridLayout* grid, EditLineHeader* header, QWidget* parent) :
    prop_(prop),
    parentWidget_(parent),
    resetTb_(nullptr),
    nameLabel_(nullptr),
    helpTb_(nullptr),
    suffixLabel_(nullptr),
    row_(-1),
    visible_(true),
    hiddenByRule_(false)
{
    Q_ASSERT(prop_);

    QString tooltip = prop->param("tooltip");
    name_ = prop->param("label");
    if (!name_.isEmpty()) {
        nameLabel_ = new QLabel(name_, parent);
        nameLabel_->setProperty("lineEdit", "1");
        nameLabel_->setToolTip(tooltip);
    }

    if (prop->guiType() != MvQProperty::LabelGui) {
        resetTb_ = new QToolButton(parent);
        resetTb_->setAutoRaise(true);
        resetTb_->setIcon(QPixmap(":/examiner/reset_line.svg"));
        resetTb_->setToolTip(tr("Reset to default"));
        resetTb_->setVisible(false);

        connect(resetTb_, SIGNAL(clicked()),
                this, SLOT(slotReset()));
    }

    row_ = grid->rowCount();
    // QSpacerItem *spItem=new QSpacerItem(2,0,
    //       QSizePolicy::Fixed,QSizePolicy::Preferred);
    // grid->addItem(spItem,row_,SpacerColumn);

    if (resetTb_)
        grid->addWidget(resetTb_, row_, ResetColumn);

    if (nameLabel_)
        grid->addWidget(nameLabel_, row_, NameColumn);

    if (header)
        header->addLine(this);
}

void EditLine::setVisible(bool b)
{
    visible_ = b;

    if (!hiddenByRule_ || !visible_) {
        show(visible_);
    }
}

void EditLine::show(bool b)
{
    if (resetTb_) {
        if (!b)
            resetTb_->setVisible(b);
        else
            resetTb_->setVisible(isSet());
    }

    if (nameLabel_)
        nameLabel_->setVisible(b);
    if (helpTb_)
        helpTb_->setVisible(b);
    if (widget())
        widget()->setVisible(b);
    if (suffixLabel_)
        suffixLabel_->setVisible(b);
}

void EditLine::setHiddenByRule(bool b)
{
    hiddenByRule_ = b;
    if (!hiddenByRule_) {
        if (visible_)
            show(true);
    }
    else
        show(false);
}

bool EditLine::isSet() const
{
    return oriVal_ != value();
}

void EditLine::setLabelBold(bool b)
{
    // return;
    if (!nameLabel_)
        return;

    if (b) {
        nameLabel_->setText("<b>" + name_ + "</b>");
        // nameLabel_->setStyleSheet(boldNameLabelSh_);
    }
    else {
        nameLabel_->setText(name_);
        // nameLabel_->setStyleSheet(normalNameLabelSh_);
    }
}

void EditLine::checkState()
{
    if (resetTb_)
        resetTb_->setVisible(isSet());

    if (!nameLabel_)
        return;

    setLabelBold(isSet());
}

void EditLine::slotReset()
{
    clear();
}

QString EditLine::editorValue(QLineEdit* le) const
{
    QString pht = le->placeholderText();
    if (!pht.isEmpty() && le->text().isEmpty())
        return pht;

    return le->text();
}

void EditLine::setCompleterForLe(const std::set<std::string>& coVals, QLineEdit* le)
{
    Q_ASSERT(le);

    QStringList lst;
    for (const auto& coVal : coVals) {
        lst << QString::fromStdString(coVal);
    }
    le->setCompleter(nullptr);
    le->setCompleter(new QCompleter(lst, le));
}

void EditLine::updateCompleterForLe(const std::set<std::string>& coVals,
                                    QLineEdit* le)
{
    Q_ASSERT(le);

    QStringList lst;
    for (const auto& coVal : coVals) {
        lst << QString::fromStdString(coVal);
    }

    if (QCompleter* c = le->completer()) {
        if (QAbstractItemModel* m = c->model()) {
            for (int i = 0; i < m->rowCount(); i++) {
                QString s = m->data(m->index(i, 0)).toString();
                if (!lst.contains(s))
                    lst << s;
            }
        }
    }

    le->setCompleter(nullptr);
    le->setCompleter(new QCompleter(lst, le));
}


//=============================================
//
// EditStringLine
//
//=============================================

EditStringLine::EditStringLine(MvQProperty* prop, QGridLayout* grid, EditLineHeader* header, QWidget* parent) :
    EditLine(prop, grid, header, parent)
{
    le_ = new QLineEdit(parent);
    if (prop_->defaultValue().isValid()) {
        QString v = prop_->defaultValue().toString();
        le_->setPlaceholderText(v);
        oriVal_ = v;
    }

    QString tooltip = prop->param("tooltip");
    le_->setToolTip(tooltip);

#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)
    le_->setClearButtonEnabled(true);
#endif

    connect(le_, SIGNAL(textChanged(QString)),
            this, SLOT(currentChanged(QString)));

    Q_ASSERT(row_ > -1);
    grid->addWidget(le_, row_, WidgetColumn);
}

QWidget* EditStringLine::widget()
{
    return le_;
}

QString EditStringLine::value() const
{
    Q_ASSERT(le_);
    return editorValue(le_);
}

void EditStringLine::clear()
{
    Q_ASSERT(le_);
    le_->clear();
}

void EditStringLine::currentChanged(QString s)
{
    checkState();
    Q_EMIT valueChanged(this, s);
}

void EditStringLine::setCompleter(const std::set<std::string>& coVals)
{
    setCompleterForLe(coVals, le_);
}

void EditStringLine::updateCompleter(const std::set<std::string>& coVals)
{
    updateCompleterForLe(coVals, le_);
}

void EditStringLine::applyChange()
{
    Q_ASSERT(prop_);

    QString v = value();
    if (prop_->type() == MvQProperty::IntType)
        prop_->setValue(QVariant(v.toInt()));
    else
        prop_->setValue(v);
}

//=============================================
//
// EditLabelLine
//
//=============================================

EditLabelLine::EditLabelLine(MvQProperty* prop, QGridLayout* grid, EditLineHeader* header, QWidget* parent) :
    EditLine(prop, grid, header, parent)
{
    label_ = new QLabel(parent);

    QString v = prop_->param("text");
    label_->setText(v);
    label_->setAlignment(Qt::AlignCenter);
    Q_ASSERT(row_ > -1);
    grid->addWidget(label_, row_, ResetColumn, 1, 4);
}

QWidget* EditLabelLine::widget()
{
    return label_;
}


//=============================================
//
// EditDateLine
//
//=============================================

EditDateLine::EditDateLine(MvQProperty* prop, QGridLayout* grid, EditLineHeader* header, QWidget* parent) :
    EditLine(prop, grid, header, parent),
    cal_(nullptr)
{
    helpTb_ = new QToolButton(parent);
    helpTb_->setToolTip("Choose date from calendar");
    helpTb_->setIcon(QPixmap(":/examiner/calendar.svg"));
    helpTb_->setAutoRaise(true);

    connect(helpTb_, SIGNAL(clicked()),
            this, SLOT(slotHelp()));

    le_ = new QLineEdit(parent);
    if (prop_->defaultValue().isValid()) {
        QString v = prop_->defaultValue().toString();
        if (v == "date(ANY)") {
            le_->setPlaceholderText("ANY");
            oriVal_ = "ANY";
        }
        else {
            le_->setPlaceholderText(v);
            oriVal_ = v;
        }
    }

    QString tooltip = prop->param("tooltip");
    le_->setToolTip(tooltip);

    // le_->setPlaceholderText("ANY  (format YYYYMMDD)");
#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)
    le_->setClearButtonEnabled(true);
#endif

    connect(le_, SIGNAL(textChanged(QString)),
            this, SLOT(currentChanged(QString)));

    Q_ASSERT(row_ > -1);
    grid->addWidget(helpTb_, row_, HelperColumn);
    grid->addWidget(le_, row_, WidgetColumn);
}

QWidget* EditDateLine::widget()
{
    return le_;
}

QString EditDateLine::value() const
{
    Q_ASSERT(le_);
    return editorValue(le_);
}

void EditDateLine::clear()
{
    Q_ASSERT(le_);
    le_->clear();
}

void EditDateLine::currentChanged(QString s)
{
    checkState();
    Q_EMIT valueChanged(this, s);
}

void EditDateLine::slotHelp()
{
    if (!cal_) {
        cal_ = new CalendarDialog(parentWidget_);
        // cal_->move(helpTb_->pos()+QPoint(helpTb_->size().width(),helpTb_->size().height()));
        // cal_->resize(QSize(300,300));
        cal_->setModal(true);
        connect(cal_, SIGNAL(dateSelected(QDate)),
                this, SLOT(slotDateSelected(QDate)));
    }

    if (value().isEmpty() || value() == "ANY") {
        if (!helper_.isEmpty()) {
            cal_->setSelectedDate(QDate::fromString(helper_, "yyyyMMdd"));
        }
    }

    cal_->show();
    cal_->raise();
}

void EditDateLine::hideCalendar()
{
    if (cal_)
        cal_->hide();
}

void EditDateLine::slotDateSelected(QDate d)
{
    le_->setText(d.toString("yyyyMMdd"));
}

void EditDateLine::applyChange()
{
    Q_ASSERT(prop_);
    prop_->setValue(value());
}

//===========================================
//
// EditTimeLine
//
//===========================================

EditTimeLine::EditTimeLine(MvQProperty* prop, QGridLayout* grid, EditLineHeader* header, QWidget* parent) :
    EditLine(prop, grid, header, parent)
{
    le_ = new QLineEdit(parent);
    // le_->setPlaceholderText("ANY  (format hh[:mm[:ss])");
#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)
    le_->setClearButtonEnabled(true);
#endif

    Q_ASSERT(row_ > -1);
    grid->addWidget(le_, row_, WidgetColumn);
}

QWidget* EditTimeLine::widget()
{
    return le_;
}

QString EditTimeLine::value() const
{
    Q_ASSERT(le_);
    return le_->text();
}

void EditTimeLine::clear()
{
    Q_ASSERT(le_);
    le_->clear();
}

void EditTimeLine::applyChange()
{
    Q_ASSERT(prop_);
    prop_->setValue(value());
}

//===========================================
//
// EditAreaLine
//
//===========================================

EditAreaLine::EditAreaLine(MvQProperty* prop, QGridLayout* grid, EditLineHeader* header, QWidget* parent) :
    EditLine(prop, grid, header, parent)
{
    w_ = new QWidget(parent);
    nLe_ = new QLineEdit(w_);
    wLe_ = new QLineEdit(w_);
    sLe_ = new QLineEdit(w_);
    eLe_ = new QLineEdit(w_);

    if (prop_->defaultValue().isValid()) {
        QString v = prop_->defaultValue().toString();
        QString n, w, e, s;
        MvQProperty::parseArea(v, w, n, e, s);
        if (!w.isEmpty())
            wLe_->setPlaceholderText(w);
        if (!n.isEmpty())
            nLe_->setPlaceholderText(n);
        if (!e.isEmpty())
            eLe_->setPlaceholderText(e);
        if (!s.isEmpty())
            sLe_->setPlaceholderText(s);
        oriVal_ = w + "/" + n + "/" + e + "/" + s;
    }

#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)
    nLe_->setClearButtonEnabled(true);
    wLe_->setClearButtonEnabled(true);
    sLe_->setClearButtonEnabled(true);
    eLe_->setClearButtonEnabled(true);
#endif

    connect(wLe_, SIGNAL(textChanged(QString)),
            this, SLOT(currentChanged(QString)));
    connect(nLe_, SIGNAL(textChanged(QString)),
            this, SLOT(currentChanged(QString)));
    connect(eLe_, SIGNAL(textChanged(QString)),
            this, SLOT(currentChanged(QString)));
    connect(sLe_, SIGNAL(textChanged(QString)),
            this, SLOT(currentChanged(QString)));

    auto* gr = new QGridLayout(w_);
    gr->addWidget(new QLabel("N:", w_), 0, 2);
    gr->addWidget(nLe_, 0, 3);
    gr->addWidget(new QLabel("W:", w_), 1, 0);
    gr->addWidget(wLe_, 1, 1);
    gr->addWidget(new QLabel("E:", w_), 1, 4);
    gr->addWidget(eLe_, 1, 5);
    gr->addWidget(new QLabel("S:", w_), 2, 2);
    gr->addWidget(sLe_, 2, 3);

    Q_ASSERT(row_ > -1);
    grid->addWidget(w_, row_, WidgetColumn);
}

QWidget* EditAreaLine::widget()
{
    return w_;
}

QString EditAreaLine::value() const
{
    return editorValue(wLe_) + "/" + editorValue(nLe_) + "/" +
           editorValue(eLe_) + "/" + editorValue(sLe_);
}

void EditAreaLine::clear()
{
    wLe_->clear();
    nLe_->clear();
    eLe_->clear();
    sLe_->clear();
}

void EditAreaLine::currentChanged(QString /*s*/)
{
    checkState();
    Q_EMIT valueChanged(this, value());
}

void EditAreaLine::applyChange()
{
    Q_ASSERT(prop_);
    prop_->setValue(value());
}

//===========================================
//
// EditComboLine
//
//===========================================

EditComboLine::EditComboLine(MvQProperty* prop, QGridLayout* grid, EditLineHeader* header, QWidget* parent) :
    EditLine(prop, grid, header, parent)
{
    Q_ASSERT(prop_);

    cb_ = new QComboBox(parent);

    if (prop_->param("editable").simplified() == "1")
        cb_->setEditable(true);

    Q_ASSERT(row_ > -1);
    grid->addWidget(cb_, row_, WidgetColumn);

    QStringList lst = prop_->param("values_label").split("/");
    QStringList lstData = prop_->param("values").split("/");
    if (prop_->param("values_label").simplified().isEmpty())
        lst = lstData;

    Q_ASSERT(lst.count() == lstData.count());
    for (int i = 0; i < lst.count(); i++) {
        cb_->addItem(lst[i], lstData[i]);
    }

    // Add separators
    QStringList sepLst = prop_->param("separator").split("/");
    for (int i = 0; i < sepLst.count(); i++) {
        QString sepKey = sepLst[i].simplified();
        if (!sepKey.isEmpty()) {
            int idx = cb_->findData(sepKey);
            if (idx >= 0) {
                cb_->insertSeparator(idx + 1);
            }
        }
    }

    // Set combo to default value
    if (prop_->defaultValue().isValid()) {
        int idx = cb_->findData(prop_->defaultValue().toString());
        if (idx >= 0) {
            cb_->setCurrentIndex(idx);
            oriVal_ = cb_->itemData(idx).toString();
        }
    }

    // le_->setPlaceholderText(prop_->defaultValue().toString());

    connect(cb_, SIGNAL(currentTextChanged(QString)),
            this, SLOT(currentChanged(QString)));
}

QWidget* EditComboLine::widget()
{
    return cb_;
}

void EditComboLine::init(QStringList)
{
}

QString EditComboLine::value() const
{
    Q_ASSERT(cb_);
    int idx = cb_->currentIndex();
    if (idx != -1) {
        return cb_->itemData(idx).toString();
    }
    return {};
}

void EditComboLine::currentChanged(QString)
{
    checkState();
    Q_EMIT valueChanged(this, value());
}

void EditComboLine::clear()
{
    Q_ASSERT(cb_);
    int idx = cb_->findData(oriVal_);
    if (idx < 0)
        idx = 0;
    cb_->setCurrentIndex(idx);
}

void EditComboLine::applyChange()
{
    Q_ASSERT(prop_);
    prop_->setValue(value());
}

//===========================================
//
// EditAddRemoveLine
//
//===========================================

EditAddRemoveLine::EditAddRemoveLine(MvQProperty* prop, QGridLayout* grid, EditLineHeader* header, QWidget* parent) :
    EditLine(prop, grid, header, parent),
    minimum_(1),
    maximum_(1),
    current_(1)
{
    Q_ASSERT(prop_);

    // Minimum
    QString sval = prop_->param("minimum").simplified();
    if (!sval.isEmpty()) {
        int ival = sval.toInt();
        if (ival >= 0)
            minimum_ = ival;
    }

    // Maximum
    sval = prop_->param("maximum").simplified();
    if (!sval.isEmpty()) {
        int ival = sval.toInt();
        if (ival >= 0 && ival >= maximum_)
            maximum_ = ival;
    }

    Q_ASSERT(minimum_ <= maximum_);

    // Default
    sval = prop_->defaultValue().toString();
    if (!sval.isEmpty()) {
        int ival = sval.toInt();
        if (ival >= minimum_ && ival <= maximum_)
            current_ = ival;
    }

    oriVal_ = QString::number(current_);

    w_ = new QWidget(parent);
    auto* hb = new QHBoxLayout(w_);
    hb->setContentsMargins(0, 0, 0, 0);
    hb->setSpacing(0);

    // label
    cntLabel_ = new QLabel(w_);
    cntLabel_->setText(oriVal_);

    // Buttons
    addTb_ = new QToolButton(w_);
    addTb_->setAutoRaise(true);
    // addTb_->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
    addTb_->setIcon(QPixmap(":/examiner/plus_black.svg"));
    addTb_->setIconSize(QSize(16, 16));
    // addTb_->setText(tr("Add"));

    removeTb_ = new QToolButton(w_);
    removeTb_->setAutoRaise(true);
    // removeTb_->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
    removeTb_->setIcon(QPixmap(":/examiner/minus_black.svg"));
    removeTb_->setIconSize(QSize(16, 16));
    // removeTb_->setText(tr("Remove"));

    connect(addTb_, SIGNAL(clicked()),
            this, SLOT(slotAdd()));

    connect(removeTb_, SIGNAL(clicked()),
            this, SLOT(slotRemove()));

    checkButtonStatus();

    // Layout
    hb->addSpacing(4);
    hb->addWidget(cntLabel_);
    hb->addSpacing(6);
    hb->addWidget(addTb_);
    // hb->addSpacing(2);
    hb->addWidget(removeTb_);
    hb->addStretch(1);

    Q_ASSERT(row_ > -1);
    grid->addWidget(w_, row_, WidgetColumn);
}

QWidget* EditAddRemoveLine::widget()
{
    return w_;
}

QString EditAddRemoveLine::value() const
{
    return QString::number(current_);
}

void EditAddRemoveLine::currentChanged()
{
    cntLabel_->setText(value());
    checkState();
    checkButtonStatus();
    Q_EMIT valueChanged(this, value());
}

void EditAddRemoveLine::slotAdd()
{
    if (current_ < maximum_) {
        current_++;
        currentChanged();
    }
}

void EditAddRemoveLine::slotRemove()
{
    if (current_ > minimum_) {
        current_--;
        currentChanged();
    }
}

void EditAddRemoveLine::checkButtonStatus()
{
    bool addSt = true;
    bool removeSt = true;
    if (current_ == minimum_) {
        removeSt = false;
    }
    if (current_ == maximum_) {
        addSt = false;
    }
    addTb_->setEnabled(addSt);
    removeTb_->setEnabled(removeSt);
}

void EditAddRemoveLine::clear()
{
    current_ = oriVal_.toInt();
    Q_ASSERT(current_ >= minimum_ && current_ <= maximum_);
    currentChanged();
}

void EditAddRemoveLine::applyChange()
{
    Q_ASSERT(prop_);
    Q_ASSERT(prop_->type() == MvQProperty::IntType);
    prop_->setValue(current_);
}

//===========================================
//
// EditBoolLine
//
//===========================================

EditBoolLine::EditBoolLine(MvQProperty* prop, QGridLayout* grid, EditLineHeader* header, QWidget* parent) :
    EditLine(prop, grid, header, parent)
{
    Q_ASSERT(prop_);

    // Default
    oriVal_ = prop_->defaultValue().toString();
    Q_ASSERT(!oriVal_.isEmpty());

    w_ = new QWidget(parent);
    auto* hb = new QHBoxLayout(w_);
    hb->setContentsMargins(0, 0, 0, 0);
    hb->setSpacing(0);

    QString strOnVal;
    QString strOffVal;
    if (prop_->type() == MvQProperty::BoolType) {
        strOnVal = "True";
        strOffVal = "False";
        strOnData_ = "true";
        strOffData_ = "false";
    }
    else {
        QStringList lst = prop_->param("values_label").split("/");
        QStringList lstData = prop_->param("values").split("/");
        if (prop_->param("values_label").simplified().isEmpty())
            lst = lstData;

        Q_ASSERT(lst.count() == lstData.count());
        Q_ASSERT(lst.count() == 2);

        strOnVal = lst[0];
        strOffVal = lst[1];
        strOnData_ = lstData[0];
        strOffData_ = lstData[1];
    }

    // Radio buttons
    radioOn_ = new QRadioButton(strOnVal, w_);
    radioOff_ = new QRadioButton(strOffVal, w_);
    radioOn_->setProperty("data", strOnData_);
    radioOff_->setProperty("data", strOffData_);

    radioOn_->setChecked(strOnData_ == oriVal_);
    radioOff_->setChecked(strOnData_ != oriVal_);

    hb->addWidget(radioOn_);
    hb->addWidget(radioOff_, 1);

    connect(radioOn_, SIGNAL(toggled(bool)),
            this, SLOT(slotStatusChanged(bool)));

    Q_ASSERT(row_ > -1);
    grid->addWidget(w_, row_, WidgetColumn);
}

QWidget* EditBoolLine::widget()
{
    return w_;
}

QString EditBoolLine::value() const
{
    return (radioOn_->isChecked()) ? strOnData_ : strOffData_;
}

void EditBoolLine::slotStatusChanged(bool)
{
    checkState();
    Q_EMIT valueChanged(this, value());
}

void EditBoolLine::clear()
{
    if (oriVal_ == strOnData_)
        radioOn_->toggle();
    else
        radioOff_->toggle();
}

void EditBoolLine::applyChange()
{
    Q_ASSERT(prop_);
    Q_ASSERT(prop_->type() == MvQProperty::BoolType || prop_->type() == MvQProperty::StringType);
    prop_->setValue(value());
}

//===========================================
//
// EditConditionLine
//
//===========================================

EditConditionLine::EditConditionLine(MvQProperty* prop, QGridLayout* grid, EditLineHeader* header, QWidget* parent) :
    EditLine(prop, grid, header, parent),
    condOperCb_(nullptr),
    condValueLe_(nullptr),
    condOperProp_(nullptr),
    broadcastChange_(true)
{
    Q_ASSERT(prop_);
    Q_ASSERT(header);
    MvQProperty* grProp = header->groupProp();
    Q_ASSERT(grProp);

    // The holder widget
    w_ = new QWidget(parent);

    // Q_ASSERT(prop_->guiType() == MvQProperty::StringGui);

    // The main editor
    condValueLe_ = new QLineEdit(w_);
    if (prop_->defaultValue().isValid()) {
        QString v = prop_->defaultValue().toString();
        condValueLe_->setPlaceholderText(v);
        oriVal_ = v;
    }

    QString tooltip = prop->param("tooltip");
    condValueLe_->setToolTip(tooltip);

#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)
    condValueLe_->setClearButtonEnabled(true);
#endif

    connect(condValueLe_, SIGNAL(textChanged(QString)),
            this, SLOT(condValueChanged(QString)));

    // The additonal editors
    // First, check if the properties are available
    QStringList wLst = prop_->param("with").simplified().split("/");
    Q_FOREACH (QString t, wLst) {
        QStringList opLst = t.simplified().split("=");
        if (opLst.count() == 2) {
            if (opLst[0] == "oper") {
                MvQProperty* p = grProp->findChild(opLst[1]);
                if (p && p->guiType() == MvQProperty::StringComboGui)
                    condOperProp_ = p;
            }
        }
    }

    //---------------------
    // Operator
    //---------------------

    if (condOperProp_) {
        Q_ASSERT(condOperProp_->guiType() == MvQProperty::StringComboGui);
        condOperCb_ = new QComboBox(w_);

        QStringList lst = condOperProp_->param("values_label").split("/");
        QStringList lstData = condOperProp_->param("values").split("/");
        if (condOperProp_->param("values_label").simplified().isEmpty())
            lst = lstData;

        Q_ASSERT(lst.count() == lstData.count());
        for (int i = 0; i < lst.count(); i++)
            condOperCb_->addItem(lst[i], lstData[i]);

        // Set combo to default value
        if (condOperProp_->defaultValue().isValid()) {
            int idx = lstData.indexOf(condOperProp_->defaultValue().toString());
            if (idx >= 0) {
                condOperCb_->setCurrentIndex(idx);
                condOperOriVal_ = condOperCb_->itemData(idx).toString();
            }
        }

        connect(condOperCb_, SIGNAL(currentIndexChanged(int)),
                this, SLOT(condOperChanged(int)));
    }

    // The layout
    auto* hb = new QHBoxLayout(w_);
    hb->setContentsMargins(0, 0, 0, 0);
    hb->setSpacing(0);

    if (condOperCb_) {
        hb->addWidget(condOperCb_);
    }

    hb->addWidget(condValueLe_, 1);

    Q_ASSERT(row_ > -1);
    grid->addWidget(w_, row_, WidgetColumn);
}

QWidget* EditConditionLine::widget()
{
    return w_;
}

QString EditConditionLine::value() const
{
    Q_ASSERT(condValueLe_);
    return editorValue(condValueLe_);
}

void EditConditionLine::currentChanged()
{
    checkState();
    if (broadcastChange_)
        Q_EMIT valueChanged(this, value());
}

void EditConditionLine::condOperChanged(int)
{
    currentChanged();
}

void EditConditionLine::condValueChanged(QString)
{
    currentChanged();
}

void EditConditionLine::clear()
{
    broadcastChange_ = false;

    condValueLe_->clear();

    if (condOperCb_) {
        int idx = -1;
        if ((idx = condOperCb_->findData(condOperOriVal_)) != -1)
            condOperCb_->setCurrentIndex(idx);
    }
    broadcastChange_ = true;

    currentChanged();
}

void EditConditionLine::applyChange()
{
    Q_ASSERT(prop_);
    prop_->setValue(editorValue(condValueLe_));

    if (condOperProp_) {
        int idx = condOperCb_->currentIndex();
        if (idx >= 0)
            condOperProp_->setValue(condOperCb_->itemData(idx).toString());
    }
}


//===========================================
//
// EditConditionLine
//
//===========================================

EditMultiConditionLine::EditMultiConditionLine(MvQProperty* prop, QGridLayout* grid, EditLineHeader* header, QWidget* parent) :
    EditLine(prop, grid, header, parent),
    condRankTb_(nullptr),
    condRankLabel_(nullptr),
    condRankLe_(nullptr),
    condControlTb_(nullptr),
    condControlLabel_(nullptr),
    condOperCb_(nullptr),
    condValueLe_(nullptr),
    condRankProp_(nullptr),
    condOperProp_(nullptr),
    condValueProp_(nullptr),
    broadcastChange_(true)
{
    Q_ASSERT(prop_);
    Q_ASSERT(header);
    MvQProperty* grProp = header->groupProp();
    Q_ASSERT(grProp);

    // The holder widget
    w_ = new QWidget(parent);

    // The main editor
    paramLe_ = new QLineEdit(w_);
    if (prop_->defaultValue().isValid()) {
        QString v = prop_->defaultValue().toString();
        paramLe_->setPlaceholderText(v);
        oriVal_ = v;
    }

    QString tooltip = prop->param("tooltip");
    paramLe_->setToolTip(tooltip);

#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)
    paramLe_->setClearButtonEnabled(true);
#endif

    connect(paramLe_, SIGNAL(textChanged(QString)),
            this, SLOT(paramChanged(QString)));

    // The additonal editors
    // First, check if the properties are available
    QStringList wLst = prop_->param("with").simplified().split("/");
    Q_FOREACH (QString t, wLst) {
        QStringList opLst = t.simplified().split("=");
        if (opLst.count() == 2) {
            if (opLst[0] == "rank") {
                MvQProperty* p = grProp->findChild(opLst[1]);
                if (p &&
                    (p->guiType() == MvQProperty::StringGui ||
                     p->guiType() == MvQProperty::IntGui))
                    condRankProp_ = p;
            }
            else if (opLst[0] == "oper") {
                MvQProperty* p = grProp->findChild(opLst[1]);
                if (p && p->guiType() == MvQProperty::StringComboGui)
                    condOperProp_ = p;
            }
            else if (opLst[0] == "value") {
                MvQProperty* p = grProp->findChild(opLst[1]);
                if (p && p->guiType() == MvQProperty::StringGui)
                    condValueProp_ = p;
            }
        }
    }
    // Next, create the editors

    QFont smallFont;
    smallFont.setPointSize(smallFont.pointSize() - 1);

    //---------------------
    // Rank condition
    //---------------------
    if (condRankProp_) {
        Q_ASSERT(condRankProp_->guiType() == MvQProperty::StringGui ||
                 condRankProp_->guiType() == MvQProperty::IntGui);

        // Linedit for rank
        condRankLe_ = new QLineEdit(w_);
        QFont f;
        QFontMetrics fm(f);
        condRankLe_->setFixedWidth(MvQ::textWidth(fm, "ANY AN"));

        if (condRankProp_->defaultValue().isValid()) {
            QString v = condRankProp_->defaultValue().toString();
            condRankLe_->setPlaceholderText(v);
            condRankOriVal_ = v;
        }

        QString tooltip = prop->param("tooltip");
        condRankLe_->setToolTip(tooltip);

#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)
        // condRankLe_->setClearButtonEnabled(true);
#endif
        connect(condRankLe_, SIGNAL(textChanged(QString)),
                this, SLOT(condRankChanged(QString)));

        QStringList condCnt = prop_->param("control").split("/");

        // If rank can be enabled/disabled
        if (condCnt.contains("rank")) {
            condRankTb_ = new QToolButton(w_);
            condRankTb_->setAutoRaise(false);
            condRankTb_->setText(tr("Rank"));
            condRankTb_->setCheckable(true);
            condRankTb_->setChecked(false);
            condRankTb_->setToolTip(tr("Set rank for parameter"));
            condRankTb_->setFont(smallFont);

            connect(condRankTb_, SIGNAL(clicked(bool)),
                    this, SLOT(condRankTbChanged(bool)));

            // off by default
            condRankTbChanged(false);
        }
        // rank is always visible
        else {
            condRankLabel_ = new QLabel(w_);
            condRankLabel_->setText(" Rank:");
            condRankLabel_->setFont(smallFont);
        }
    }

    //---------------------
    // Value condition
    //---------------------
    if (condOperProp_ && condValueProp_) {
        //---------------------
        // operator
        //---------------------
        Q_ASSERT(condOperProp_->guiType() == MvQProperty::StringComboGui);
        condOperCb_ = new QComboBox(w_);

        QStringList lst = condOperProp_->param("values_label").split("/");
        QStringList lstData = condOperProp_->param("values").split("/");
        if (condOperProp_->param("values_label").simplified().isEmpty())
            lst = lstData;

        Q_ASSERT(lst.count() == lstData.count());
        for (int i = 0; i < lst.count(); i++)
            condOperCb_->addItem(lst[i], lstData[i]);

        // Set combo to default value
        if (condOperProp_->defaultValue().isValid()) {
            int idx = lstData.indexOf(condOperProp_->defaultValue().toString());
            if (idx >= 0) {
                condOperCb_->setCurrentIndex(idx);
                condOperOriVal_ = condOperCb_->itemData(idx).toString();
            }
        }

        connect(condOperCb_, SIGNAL(currentIndexChanged(int)),
                this, SLOT(condOperChanged(int)));

        //---------------------
        // value
        //---------------------
        Q_ASSERT(condValueProp_->guiType() == MvQProperty::StringGui);

        condValueLe_ = new QLineEdit(w_);

        if (condValueProp_->defaultValue().isValid()) {
            QString v = condValueProp_->defaultValue().toString();
            condValueLe_->setPlaceholderText(v);
            condValueOriVal_ = v;
        }

        QString tooltip = prop->param("tooltip");
        condValueLe_->setToolTip(tooltip);

#if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0)
        condValueLe_->setClearButtonEnabled(true);
#endif
        connect(condValueLe_, SIGNAL(textChanged(QString)),
                this, SLOT(condValueChanged(QString)));


        QStringList condCnt = prop_->param("control").split("/");

        // If value filter can be enabled/disabled
        if (condCnt.contains("value")) {
            condControlTb_ = new QToolButton(w_);
            condControlTb_->setAutoRaise(false);
            // condControlTb_->setIcon(QPixmap(":/examiner/filter.svg"));
            condControlTb_->setText(tr("Value"));
            condControlTb_->setCheckable(true);
            condControlTb_->setChecked(false);
            condControlTb_->setToolTip(tr("Enable value filter for parameter"));
            condControlTb_->setFont(smallFont);

            connect(condControlTb_, SIGNAL(clicked(bool)),
                    this, SLOT(condControlChanged(bool)));

            // Disabled by defaut
            condControlChanged(false);
        }
        // value filter is always visible
        else {
            condControlLabel_ = new QLabel(w_);
            condControlLabel_->setText(" Value:");
            condControlLabel_->setFont(smallFont);
        }
    }
    else {
        condOperProp_ = nullptr;
        condValueProp_ = nullptr;

        condOperOriVal_.clear();
        condValueOriVal_.clear();
    }

    // The layout
    auto* hb = new QHBoxLayout(w_);
    hb->setContentsMargins(0, 0, 0, 0);
    hb->setSpacing(0);
    hb->addWidget(paramLe_, 2);

    if (condRankLe_) {
        if (condRankTb_) {
            Q_ASSERT(condRankLabel_ == nullptr);
            hb->addWidget(condRankTb_);
        }
        else if (condRankLabel_) {
            hb->addWidget(condRankLabel_);
        }

        hb->addWidget(condRankLe_);
    }

    if (condOperCb_ && condValueLe_) {
        if (condControlTb_) {
            Q_ASSERT(condControlLabel_ == nullptr);
            hb->addWidget(condControlTb_);
        }
        else if (condControlLabel_)
            hb->addWidget(condControlLabel_);

        hb->addWidget(condOperCb_);
        hb->addWidget(condValueLe_, 1);
    }

    Q_ASSERT(row_ > -1);
    grid->addWidget(w_, row_, WidgetColumn);
}

QWidget* EditMultiConditionLine::widget()
{
    return w_;
}

QString EditMultiConditionLine::value() const
{
    Q_ASSERT(paramLe_);
    return editorValue(paramLe_);
}

void EditMultiConditionLine::currentChanged()
{
    checkState();
    if (broadcastChange_)
        Q_EMIT valueChanged(this, value());
}

void EditMultiConditionLine::paramChanged(QString)
{
    currentChanged();
}

void EditMultiConditionLine::condControlChanged(bool)
{
    Q_ASSERT(condControlTb_);
    Q_ASSERT(condOperCb_);
    Q_ASSERT(condValueLe_);
    bool b = condControlTb_->isChecked();
    if (!b) {
        int idx = -1;
        if ((idx = condOperCb_->findData(condOperOriVal_)) != -1)
            condOperCb_->setCurrentIndex(idx);

        condValueLe_->clear();
    }

    condOperCb_->setVisible(b);
    condValueLe_->setVisible(b);
}

void EditMultiConditionLine::condRankChanged(QString)
{
    currentChanged();
}

void EditMultiConditionLine::condRankTbChanged(bool)
{
    Q_ASSERT(condRankTb_);
    Q_ASSERT(condRankLe_);
    bool b = condRankTb_->isChecked();
    if (!b)
        condRankLe_->clear();

    condRankLe_->setVisible(b);
}

void EditMultiConditionLine::condOperChanged(int)
{
    currentChanged();
}

void EditMultiConditionLine::condValueChanged(QString)
{
    currentChanged();
}

void EditMultiConditionLine::clear()
{
    broadcastChange_ = false;

    paramLe_->clear();
    if (condRankLe_) {
        condRankLe_->clear();
        if (condRankTb_)
            condRankTb_->setChecked(false);
    }

    if (condOperCb_ && condValueLe_) {
        if (condControlTb_) {
            condControlTb_->setChecked(false);
        }

        int idx = -1;
        if ((idx = condOperCb_->findData(condOperOriVal_)) != -1)
            condOperCb_->setCurrentIndex(idx);

        condValueLe_->clear();
    }
    broadcastChange_ = true;

    currentChanged();
}

void EditMultiConditionLine::setCompleter(const std::set<std::string>& coVals)
{
    setCompleterForLe(coVals, paramLe_);
}

void EditMultiConditionLine::updateCompleter(const std::set<std::string>& coVals)
{
    updateCompleterForLe(coVals, paramLe_);
}

void EditMultiConditionLine::applyChange()
{
    Q_ASSERT(prop_);
    prop_->setValue(editorValue(paramLe_));

    if (condRankProp_) {
        QString v = editorValue(condRankLe_);
        if (condRankProp_->type() == MvQProperty::IntType)
            condRankProp_->setValue(QVariant(v.toInt()));
        else
            condRankProp_->setValue(v);
    }

    if (condOperProp_ && condValueProp_) {
        int idx = condOperCb_->currentIndex();
        if (idx >= 0)
            condOperProp_->setValue(condOperCb_->itemData(idx).toString());

        condValueProp_->setValue(editorValue(condValueLe_));
    }
}
