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

 Copyright 2012 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 "MvQProperty.h"

#include <QDebug>

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

#include <cassert>

MvQProperty::MvQProperty(const std::string& name) :
    strName_(name),
    name_(QString::fromStdString(name)),
    parent_(nullptr),
    master_(nullptr),
    useMaster_(false),
    type_(StringType),
    guiType_(StringGui),
    link_(nullptr)
{
}

MvQProperty::~MvQProperty()
{
    Q_FOREACH (MvQProperty* p, children_) {
        delete p;
    }

    children_.clear();

    if (master_)
        master_->removeObserver(this);
}

QVariant MvQProperty::value() const
{
    if (master_ && useMaster_)
        return master_->value();

    return value_;
}


void MvQProperty::setDefaultValue(const std::string& val)
{
    // Colour
    if (isColour(val)) {
        defaultValue_ = toColour(val);
        type_ = ColourType;
        guiType_ = ColourGui;
    }
    // Font
    else if (isFont(val)) {
        defaultValue_ = toFont(val);
        type_ = FontType;
        guiType_ = FontGui;
    }
    // Sound
    else if (isSound(val)) {
        defaultValue_ = QString::fromStdString(val);
        type_ = SoundType;
        guiType_ = SoundGui;
    }
    // date
    else if (isDate(val)) {
        defaultValue_ = QString::fromStdString(val);
        type_ = DateType;
        guiType_ = DateGui;
    }
    // area
    else if (isArea(val)) {
        defaultValue_ = QString::fromStdString(val);
        type_ = AreaType;
        guiType_ = AreaGui;
    }
    // int
    else if (isNumber(val)) {
        defaultValue_ = toNumber(val);
        type_ = IntType;
        guiType_ = IntGui;
    }
    // bool
    else if (isBool(val)) {
        defaultValue_ = toBool(val);
        type_ = BoolType;
        guiType_ = BoolGui;
    }
    // text
    else {
        defaultValue_ = QString::fromStdString(val);
        type_ = StringType;
        guiType_ = StringGui;
    }

    if (value_.isNull())
        value_ = defaultValue_;

    // qDebug() << "Prop:" << name_ << defaultValue_ << value_.value<QColor>();
}

void MvQProperty::setValue(const std::string& val)
{
    if (master_ && useMaster_)
        return;

    bool changed = false;

    if (isColour(val)) {
        QColor col = toColour(val);
        changed = (value_.value<QColor>() != col);
        value_ = col;
    }
    else if (isFont(val)) {
        QFont font = toFont(val);
        changed = (value_.value<QFont>() != font);
        value_ = font;
    }

    else if (isNumber(val)) {
        int num = toNumber(val);
        changed = (value_.toInt() != num);
        value_ = num;
    }
    else if (isBool(val)) {
        bool b = toBool(val);
        changed = (value_.toBool() != b);
        value_ = b;
    }
    // Sound or string
    else {
        QString str = QString::fromStdString(val);
        changed = (value_ != str);
        value_ = str;
    }

    if (!defaultValue_.isNull() &&
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
        defaultValue_.typeId() != value_.typeId()) {
#else
        defaultValue_.type() != value_.type()) {
#endif
        changed = true;
        value_ = defaultValue_;
        // An error message should be shown!
    }

    if (changed)
        dispatchChange();
}

void MvQProperty::setValue(QVariant val)
{
    if (master_ && useMaster_)
        return;

    if (!defaultValue_.isNull() &&
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
        defaultValue_.typeId() != val.typeId()) {
#else
        defaultValue_.type() != val.type()) {
#endif
        return;
    }

    bool changed = (value_ != val);

    value_ = val;

    if (changed)
        dispatchChange();
}

std::string MvQProperty::valueAsString() const
{
    QString s;

    switch (type_) {
        case StringType:
            s = value().toString();
            break;
        case IntType:
            s = QString::number(value_.toInt());
            break;
        case BoolType:
            s = (value().toBool() == true) ? "true" : "false";
            break;
        case ColourType:
            s = MvQProperty::toString(value().value<QColor>());
            break;
        case FontType:
            s = MvQProperty::toString(value().value<QFont>());
            break;
        case SoundType:
            s = value().toString();
            break;
        case AreaType:
            s = value().toString();
            break;
        case DateType:
            s = value().toString();
            break;
        default:
            break;
    }

    return s.toStdString();
}

void MvQProperty::setParam(QString name, QString value)
{
    /*if(name == "values")
    {
        if(type_ == SoundType)
            guiType_=SoundComboGui;
        else
            guiType_=StringComboGui;
    }

    if(name == "multi" && value == "true")
    {
        if(type_ == StringType || guiType_ == StringComboGui)
            guiType_ == MultiStringComboGui;
    }*/

    params_[name] = value;
}

QString MvQProperty::param(QString name)
{
    QMap<QString, QString>::const_iterator it = params_.find(name);
    if (it != params_.end())
        return it.value();

    return {};
}

void MvQProperty::adjustAfterLoad()
{
    QString vals = param("values");
    QString multi = param("multi");

    if (!vals.isEmpty()) {
        if (type_ == SoundType)
            guiType_ = SoundComboGui;
        else {
            if (multi == "true") {
                guiType_ = MultiStringComboGui;
            }
            else if (vals == "on/off") {
                guiType_ = BoolGui;
            }
            else {
                guiType_ = StringComboGui;
            }
        }
    }

    QString gui = param("gui");
    if (gui == "add_remove")
        guiType_ = AddRemoveGui;
    else if (gui == "condition")
        guiType_ = ConditionGui;
    else if (gui == "multi_condition")
        guiType_ = MultiConditionGui;
    else if (gui == "separator")
        guiType_ = SeparatorGui;
    else if (gui == "label")
        guiType_ = LabelGui;
}


void MvQProperty::addChild(MvQProperty* prop)
{
    children_ << prop;
    prop->setParent(this);
}

void MvQProperty::addObserver(MvQPropertyObserver* obs)
{
    observers_ << obs;
}

void MvQProperty::removeObserver(MvQPropertyObserver* obs)
{
    observers_.removeAll(obs);
}

void MvQProperty::dispatchChange()
{
    Q_FOREACH (MvQPropertyObserver* obs, observers_) {
        obs->notifyChange(this);
    }
}

MvQProperty* MvQProperty::findChild(QString name)
{
    Q_FOREACH (MvQProperty* p, children_) {
        if (p->name() == name)
            return p;
    }

    return nullptr;
}

MvQProperty* MvQProperty::find(const std::string& /*fullPath*/)
{
    // TODO: implement it without using boost
    return nullptr;
#if 0
    if(fullPath.empty())
		return nullptr;

	if(fullPath == strName_)
		return this;

	std::vector<std::string> pathVec;
	boost::split(pathVec,fullPath,boost::is_any_of("."));

	if(pathVec.size() > 0)
	{
		if(pathVec.at(0) != strName_)
			return nullptr;
	}

    return MvQProperty::find(pathVec);
#endif
}

MvQProperty* MvQProperty::find(const std::vector<std::string>& pathVec)
{
    if (pathVec.size() == 0) {
        return nullptr;
    }

    if (pathVec.size() == 1) {
        return this;
    }

    // The vec size  >=2

    std::vector<std::string> rest(pathVec.begin() + 1, pathVec.end());
    MvQProperty* n = findChild(QString::fromStdString(pathVec.at(1)));

    return n ? n->find(rest) : nullptr;
}

void MvQProperty::collectChildren(std::vector<MvQProperty*>& chVec) const
{
    Q_FOREACH (MvQProperty* p, children_) {
        chVec.push_back(p);
        p->collectChildren(chVec);
    }
}

bool MvQProperty::changed() const
{
    return value() != defaultValue_;
}

void MvQProperty::collectLinks(std::vector<MvQProperty*>& linkVec)
{
    if (link_)
        linkVec.push_back(link_);

    Q_FOREACH (MvQProperty* p, children_) {
        p->collectLinks(linkVec);
    }
}

std::string MvQProperty::path()
{
    if (parent_)
        return parent_->path() + "." + strName_;

    return strName_;
}

void MvQProperty::setMaster(MvQProperty* m, bool useMaster)
{
    if (master_)
        master_->removeObserver(this);

    master_ = m;
    master_->addObserver(this);

    setUseMaster(useMaster);
}

void MvQProperty::setUseMaster(bool b)
{
    assert(master_);

    if (useMaster_ != b) {
        useMaster_ = b;

        if (useMaster_) {
            value_ = master_->value_;
        }
    }
}

MvQProperty* MvQProperty::clone(bool addLink, bool setMaster, bool useMaster)
{
    auto* cp = new MvQProperty(strName_);

    cp->value_ = value_;
    cp->defaultValue_ = defaultValue_;
    cp->type_ = type_;
    cp->guiType_ = guiType_;

    if (addLink) {
        cp->link_ = link_;
    }

    cp->params_ = params_;

    if (setMaster) {
        cp->setMaster(this, useMaster);
    }

    Q_FOREACH (MvQProperty* p, children_) {
        MvQProperty* ch = p->clone(addLink, setMaster, useMaster);
        cp->addChild(ch);
    }

    return cp;
}

void MvQProperty::notifyChange(MvQProperty* p)
{
    if (master_ && p == master_ && useMaster_) {
        value_ = master_->value_;
        dispatchChange();
    }
}

//=============================
//
// Static methods
//
//=============================

bool MvQProperty::isColour(const std::string& val)
{
    return QString::fromStdString(val).simplified().startsWith("rgb(");
}

bool MvQProperty::isFont(const std::string& val)
{
    return QString::fromStdString(val).simplified().startsWith("font(");
}

bool MvQProperty::isSound(const std::string& /*val*/)
{
    // TODO: implement it
    return false;
#if 0
    return Sound::instance()->isSoundFile(val);
#endif
}

bool MvQProperty::isDate(const std::string& val)
{
    return QString::fromStdString(val).simplified().startsWith("date(");
}

bool MvQProperty::isArea(const std::string& val)
{
    return QString::fromStdString(val).simplified().startsWith("area(");
}

bool MvQProperty::isNumber(const std::string& val)
{
    QString str = QString::fromStdString(val);
    QRegExp re("\\d*");
    return (re.exactMatch(str));
}

bool MvQProperty::isBool(const std::string& val)
{
    return (val == "true" || val == "false");
}

QColor MvQProperty::toColour(const std::string& name)
{
    QString qn = QString::fromStdString(name);
    QColor col;
    QRegExp rx(R"(rgb\((\d+),(\d+),(\d+))");

    if (rx.indexIn(qn) > -1 && rx.captureCount() == 3) {
        col = QColor(rx.cap(1).toInt(),
                     rx.cap(2).toInt(),
                     rx.cap(3).toInt());
    }
    return col;
}

QFont MvQProperty::toFont(const std::string& name)
{
    QString qn = QString::fromStdString(name);
    QFont f;
    QRegExp rx("font\\((.*),(.*)\\)");
    if (rx.indexIn(qn) > -1 && rx.captureCount() == 2) {
        QString family = rx.cap(1);
        int size = rx.cap(2).toInt();

        if (!family.isEmpty())
            f.setFamily(family);

        if (size >= 1 && size < 200)
            f.setPointSize(size);

        // qDebug() << family << size
        // f.fromString(rx.cap(1));
    }

    return f;
}

QDate MvQProperty::toDate(const std::string& /*name*/)
{
    // TODO: implement it
    return {};
}

int MvQProperty::toNumber(const std::string& name)
{
    QString qn = QString::fromStdString(name);
    return qn.toInt();
}

bool MvQProperty::toBool(const std::string& name)
{
    return (name == "true") ? true : false;
}

QString MvQProperty::toString(QColor col)
{
    return "rgb(" + QString::number(col.red()) + "," +
           QString::number(col.green()) + "," +
           QString::number(col.blue()) + ")";
}

QString MvQProperty::toString(QFont f)
{
    return "font(" + f.family() + "," + QString::number(f.pointSize()) + ")";
}

void MvQProperty::parseArea(QString area, QString& w, QString& n, QString& e, QString& s)
{
    QRegExp rx(R"(area\((\S+),(\S+),(\S+),(\S+)\))");
    if (rx.indexIn(area) > -1 && rx.captureCount() == 4) {
        w = rx.cap(1);
        n = rx.cap(2);
        e = rx.cap(3);
        s = rx.cap(4);
    }
}
