/****************************************************************************
* MeshLab                                                           o o     *
* A versatile mesh processing toolbox                             o     o   *
*                                                                _   O  _   *
* Copyright(C) 2004-2021                                           \/)\/    *
* Visual Computing Lab                                            /\/|      *
* ISTI - Italian National Research Council                           |      *
*                                                                    \      *
* All rights reserved.                                                      *
*                                                                           *
* This program is free software; you can redistribute it and/or modify      *
* it under the terms of the GNU General Public License as published by      *
* the Free Software Foundation; either version 2 of the License, or         *
* (at your option) any later version.                                       *
*                                                                           *
* This program is distributed in the hope that it will be useful,           *
* but WITHOUT ANY WARRANTY; without even the implied warranty of            *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             *
* GNU General Public License (http://www.gnu.org/licenses/gpl.txt)          *
* for more details.                                                         *
*                                                                           *
****************************************************************************/

#include "rich_parameter.h"
#include "../ml_document/mesh_document.h"
#include "../python/python_utils.h"

/**** RichParameter Class ****/

RichParameter::RichParameter(const RichParameter& rp) :
	pName(rp.pName),
	val(rp.value().clone()),
	fieldDesc(rp.fieldDesc),
	tooltip(rp.tooltip),
	advanced(rp.advanced),
	pCategory(rp.pCategory)
{
}

RichParameter::RichParameter(RichParameter&& rp) :
	pName(std::move(rp.pName)),
	fieldDesc(std::move(rp.fieldDesc)),
	tooltip(std::move(rp.tooltip)),
	pCategory(std::move(rp.pCategory))
{
	val = rp.val;
	rp.val = nullptr;
	advanced = rp.advanced;
}

RichParameter::RichParameter(
		const QString& nm,
		const Value& v,
		const QString& desc,
		const QString& tltip,
		bool isAdvanced,
		const QString& category) :
	pName(nm),
	val(v.clone()),
	fieldDesc(desc),
	tooltip(tltip),
	advanced(isAdvanced),
	pCategory(category)
{
}

RichParameter::~RichParameter()
{
	delete val;
}

const QString& RichParameter::name() const
{
	return pName;
}

const Value& RichParameter::value() const
{
	return *val;
}

const QString& RichParameter::fieldDescription() const
{
	return fieldDesc;
}

const QString& RichParameter::toolTip() const
{
	return tooltip;
}

bool RichParameter::isAdvanced() const
{
	return advanced;
}

const QString& RichParameter::category() const
{
	return pCategory;
}

void RichParameter::setName(const QString& newName)
{
	pName = newName;
}

void RichParameter::setValue(const Value& ov)
{
	assert(val->typeName() == ov.typeName());
	delete val;
	val = ov.clone();
}

QDomElement RichParameter::fillToXMLDocument(QDomDocument& doc, bool saveDescriptionAndTooltip) const
{
	QDomElement parElem = doc.createElement("Param");
	parElem.setAttribute("name", pName);
	parElem.setAttribute("type", stringType());
	if (saveDescriptionAndTooltip) {
		parElem.setAttribute("description", fieldDesc);
		parElem.setAttribute("tooltip",tooltip);
	}
	val->fillToXMLElement(parElem);
	return parElem;
}

/**
 * @brief returns the name of the parameter used in python (for pymeshlab)
 */
QString RichParameter::pythonName() const
{
	return pymeshlab::computePythonName(pName);
}

/**
 * @brief returns the type if the parameter (as a string) used in python
 * (for pymeshlab)
 */
QString RichParameter::pythonType() const
{
	return pymeshlab::computePythonTypeString(*this);
}

RichParameter& RichParameter::operator=(const RichParameter& rp)
{
	if (&rp != this){
		delete val;
		val = rp.value().clone();
		pName = rp.pName;
		fieldDesc = rp.fieldDesc;
		tooltip = rp.tooltip;
	}
	return *this;
}

RichParameter& RichParameter::operator=(RichParameter&& rp)
{
	assert(&rp != this);
	val = rp.val;
	rp.val = nullptr;
	pName = std::move(rp.pName);
	fieldDesc = std::move(rp.fieldDesc);
	tooltip = std::move(rp.tooltip);
	return *this;
}

/**** RichBool Class ****/

RichBool::RichBool(
		const QString& nm,
		const bool defval,
		const QString& desc,
		const QString& tltip,
		bool hidden,
		const QString& category) :
	RichParameter(nm, BoolValue(defval), desc, tltip, hidden, category)
{
}

RichBool::~RichBool()
{
}

QString RichBool::stringType() const
{
	return "RichBool";
}

RichBool* RichBool::clone() const
{
	return new RichBool(*this);
}

bool RichBool::operator==( const RichParameter& rb )
{
	return (rb.value().isBool() && (pName == rb.name()) && (value().getBool() == rb.value().getBool()));
}

/**** RichInt Class ****/

RichInt::RichInt(
		const QString& nm,
		const int defval,
		const QString& desc,
		const QString& tltip,
		bool hidden,
		const QString& category) :
	RichParameter(nm, IntValue(defval),desc, tltip, hidden, category)
{
}

RichInt::~RichInt()
{
}

QString RichInt::stringType() const
{
	return "RichInt";
}

RichInt* RichInt::clone() const
{
	return new RichInt(*this);
}

bool RichInt::operator==( const RichParameter& rb )
{
	return (rb.value().isInt() &&(pName == rb.name()) && (value().getInt() == rb.value().getInt()));
}

/**** RichFloat Class ****/

RichFloat::RichFloat(
		const QString& nm,
		const Scalarm defval,
		const QString& desc,
		const QString& tltip,
		bool hidden,
		const QString& category) :
	RichParameter(nm, FloatValue(defval),desc, tltip, hidden, category)
{
}

RichFloat::~RichFloat()
{
}

QString RichFloat::stringType() const
{
	return "RichFloat";
}

RichFloat* RichFloat::clone() const
{
	return new RichFloat(*this);
}

bool RichFloat::operator==( const RichParameter& rb )
{
	return (rb.value().isFloat() &&(pName == rb.name()) && (value().getFloat() == rb.value().getFloat()));
}

/**** RichString Class ****/

RichString::RichString(
		const QString& nm,
		const QString& defval,
		const QString& desc,
		const QString& tltip,
		bool hidden,
		const QString& category) :
	RichParameter(nm, StringValue(defval),desc,tltip, hidden, category)
{
}

RichString::~RichString()
{
}

QString RichString::stringType() const
{
	return "RichString";
}

RichString* RichString::clone() const
{
	return new RichString(*this);
}

bool RichString::operator==( const RichParameter& rb )
{
	return (rb.value().isString() &&(pName == rb.name()) && (value().getString() == rb.value().getString()));
}

/**** RichMatrix44f Class ****/

RichMatrix44f::RichMatrix44f(
		const QString& nm,
		const Matrix44m& defval,
		const QString& desc,
		const QString& tltip,
		bool hidden,
		const QString& category) :
	RichParameter(nm, Matrix44fValue(defval),desc, tltip, hidden, category)
{
}

RichMatrix44f::~RichMatrix44f()
{
}

QString RichMatrix44f::stringType() const
{
	return "RichMatrix44f";
}

RichMatrix44f* RichMatrix44f::clone() const
{
	return new RichMatrix44f(*this);
}

bool RichMatrix44f::operator==( const RichParameter& rb )
{
	return (rb.value().isMatrix44f() &&(pName == rb.name()) && (value().getMatrix44f() == rb.value().getMatrix44f()));
}

/**** RichPosition Class ****/

RichPosition::RichPosition(
		const QString& nm,
		const Point3m& defval,
		const QString& desc,
		const QString& tltip,
		bool hidden,
		const QString& category) :
	RichParameter(nm, Point3fValue(defval),desc, tltip, hidden, category)
{
}

RichPosition::~RichPosition()
{
}

QString RichPosition::stringType() const
{
	return "RichPosition";
}

RichPosition* RichPosition::clone() const
{
	return new RichPosition(*this);
}

bool RichPosition::operator==( const RichParameter& rb )
{
	return (rb.value().isPoint3f() &&(pName == rb.name()) && (value().getPoint3f() == rb.value().getPoint3f()));
}

/**** RichDirection Class ****/

RichDirection::RichDirection(
	const QString& nm,
	const Point3m& defval,
	const QString& desc,
	const QString& tltip,
	bool           hidden,
	const QString& category) :
	RichParameter(nm, Point3fValue(defval), desc, tltip, hidden, category)
{
}

RichDirection::~RichDirection()
{
}

QString RichDirection::stringType() const
{
	return "RichDirection";
}

RichDirection* RichDirection::clone() const
{
	return new RichDirection(*this);
}

bool RichDirection::operator==(const RichParameter& rb)
{
	return (rb.value().isPoint3f() &&(pName == rb.name()) && (value().getPoint3f() == rb.value().getPoint3f()));
}

/**** RichShotf Class ****/

RichShotf::RichShotf(
		const QString& nm,
		const Shotm& defval,
		const QString& desc,
		const QString& tltip,
		bool hidden,
		const QString& category) :
	RichParameter(nm, ShotfValue(defval),desc, tltip, hidden, category)
{
}

RichShotf::~RichShotf()
{
}

QString RichShotf::stringType() const
{
	return "RichShotf";
}

RichShotf* RichShotf::clone() const
{
	return new RichShotf(*this);
}

bool RichShotf::operator==( const RichParameter& rb )
{
	return (rb.value().isShotf() &&(pName == rb.name()) );
	// TODO REAL TEST OF EQUALITY // && (value().getShotf() == rb.value().getShotf()));
}

/**** RichColor Class ****/

RichColor::RichColor(
		const QString& nm,
		const QColor& defval,
		const QString& desc,
		const QString& tltip,
		bool hidden,
		const QString& category) :
	RichParameter(nm, ColorValue(defval),desc, tltip, hidden, category)
{
}

RichColor::~RichColor()
{
}

QString RichColor::stringType() const
{
	return "RichColor";
}

RichColor* RichColor::clone() const
{
	return new RichColor(*this);
}

bool RichColor::operator==( const RichParameter& rb )
{
	return (rb.value().isColor() &&(pName == rb.name()) && (value().getColor() == rb.value().getColor()));
}

/**** RichAbsPerc Class ****/

RichAbsPerc::RichAbsPerc(
		const QString& nm,
		const Scalarm defval,
		const Scalarm minval,
		const Scalarm maxval,
		const QString& desc,
		const QString& tltip,
		bool hidden,
		const QString& category) :
	RichParameter(nm, FloatValue(defval), desc, tltip, hidden, category), min(minval), max(maxval)
{
}

RichAbsPerc::~RichAbsPerc()
{
}

QString RichAbsPerc::stringType() const
{
	return "RichAbsPerc";
}

QDomElement RichAbsPerc::fillToXMLDocument(QDomDocument& doc, bool saveDescriptionAndTooltip) const
{
	QDomElement parElem = RichParameter::fillToXMLDocument(doc, saveDescriptionAndTooltip);
	parElem.setAttribute("min",QString::number(min));
	parElem.setAttribute("max",QString::number(max));
	return parElem;
}

RichAbsPerc* RichAbsPerc::clone() const
{
	return new RichAbsPerc(*this);
}

bool RichAbsPerc::operator==( const RichParameter& rb )
{
	return (rb.isOfType<RichAbsPerc>() &&(pName == rb.name()) && (value().getFloat() == rb.value().getFloat()));
}

/**** RichEnum Class ****/

RichEnum::RichEnum(
		const QString& nm,
		const int defval,
		const QStringList& values,
		const QString& desc,
		const QString& tltip,
		bool hidden,
		const QString& category) :
	RichParameter(nm, IntValue(defval),desc, tltip, hidden, category), enumvalues(values)
{
}

RichEnum::~RichEnum()
{
}

QString RichEnum::stringType() const
{
	return "RichEnum";
}

QDomElement RichEnum::fillToXMLDocument(QDomDocument& doc, bool saveDescriptionAndTooltip) const
{
	QDomElement parElem = RichParameter::fillToXMLDocument(doc, saveDescriptionAndTooltip);
	parElem.setAttribute("enum_cardinality", enumvalues.size());
	for(int ii = 0; ii < enumvalues.size(); ++ii)
		parElem.setAttribute(QString("enum_val")+QString::number(ii), enumvalues.at(ii));
	return parElem;
}

RichEnum* RichEnum::clone() const
{
	return new RichEnum(*this);
}

bool RichEnum::operator==( const RichParameter& rb )
{
	return (rb.isOfType<RichEnum>() &&(pName == rb.name()) && (value().getInt() == rb.value().getInt()));
}

/**** RichDynamicFloat Class ****/

RichDynamicFloat::RichDynamicFloat(
		const QString& nm,
		const Scalarm defval,
		const Scalarm minval,
		const Scalarm maxval,
		const QString& desc,
		const QString& tltip,
		bool hidden,
		const QString& category) :
	RichParameter(nm, FloatValue(defval),desc, tltip, hidden, category), min(minval), max(maxval)
{
}

RichDynamicFloat::~RichDynamicFloat()
{
}

QString RichDynamicFloat::stringType() const
{
	return "RichDynamicFloat";
}

QDomElement RichDynamicFloat::fillToXMLDocument(QDomDocument& doc, bool saveDescriptionAndTooltip) const
{
	QDomElement parElem = RichParameter::fillToXMLDocument(doc, saveDescriptionAndTooltip);
	parElem.setAttribute("min",QString::number(min));
	parElem.setAttribute("max",QString::number(max));
	return parElem;
}

RichDynamicFloat* RichDynamicFloat::clone() const
{
	return new RichDynamicFloat(*this);
}

bool RichDynamicFloat::operator==( const RichParameter& rb )
{
	return (rb.isOfType<RichDynamicFloat>() &&(pName == rb.name()) && (value().getFloat() == rb.value().getFloat()));
}

/**** RichOpenFile Class ****/

RichOpenFile::RichOpenFile(
		const QString& nm,
		const QString& directorydefval,
		const QStringList& exts,
		const QString& desc,
		const QString& tltip,
		bool hidden,
		const QString& category) :
	RichParameter(nm, StringValue(directorydefval), desc, tltip, hidden, category), exts(exts)
{
}

RichOpenFile::~RichOpenFile()
{
}

QString RichOpenFile::stringType() const
{
	return "RichOpenFile";
}

QDomElement RichOpenFile::fillToXMLDocument(QDomDocument& doc, bool saveDescriptionAndTooltip) const
{
	QDomElement parElem = RichParameter::fillToXMLDocument(doc, saveDescriptionAndTooltip);
	parElem.setAttribute("exts_cardinality", exts.size());
	for(int ii = 0; ii < exts.size(); ++ii)
		parElem.setAttribute(QString("ext_val")+QString::number(ii), exts[ii]);
	return parElem;
}

RichOpenFile* RichOpenFile::clone() const
{
	return new RichOpenFile(*this);
}

bool RichOpenFile::operator==( const RichParameter& rb )
{
	return (rb.isOfType<RichOpenFile>() &&(pName == rb.name()) && (value().getString() == rb.value().getString()));
}

/**** RichSaveFile Class ****/

RichSaveFile::RichSaveFile(
		const QString& nm,
		const QString& filedefval,
		const QString& ext,
		const QString& desc,
		const QString& tltip,
		bool hidden,
		const QString& category) :
	RichParameter(nm, StringValue(filedefval), desc, tltip, hidden, category), ext(ext)
{
}

RichSaveFile::~RichSaveFile()
{
}

QString RichSaveFile::stringType() const
{
	return "RichSaveFile";
}

QDomElement RichSaveFile::fillToXMLDocument(QDomDocument& doc, bool saveDescriptionAndTooltip) const
{
	QDomElement parElem = RichParameter::fillToXMLDocument(doc, saveDescriptionAndTooltip);
	parElem.setAttribute("ext", ext);
	return parElem;
}

RichSaveFile* RichSaveFile::clone() const
{
	return new RichSaveFile(*this);
}

bool RichSaveFile::operator==( const RichParameter& rb )
{
	return (rb.isOfType<RichSaveFile>() &&(pName == rb.name()) && (value().getString() == rb.value().getString()));
}

/**** RichMesh Class ****/

RichMesh::RichMesh(
		const QString& nm,
		unsigned int meshind,
		const MeshDocument* doc,
		const QString& desc,
		const QString& tltip,
		bool hidden,
		const QString& category):
	RichParameter(nm,IntValue(meshind), desc, tltip, hidden, category), meshdoc(doc)
{
}

RichMesh::~RichMesh()
{
}

QString RichMesh::stringType() const
{
	return "RichMesh";
}

RichMesh* RichMesh::clone() const
{
	return new RichMesh(*this);
}

bool RichMesh::operator==( const RichParameter& rb )
{
	return (rb.isOfType<RichMesh>() &&(pName == rb.name()) && (value().getInt() == rb.value().getInt()));
}

/**** RichParameterAdapter Class ****/

bool RichParameterAdapter::create( const QDomElement& np,RichParameter*& par )
{
	QString name=np.attribute("name");
	QString type=np.attribute("type");
	QString desc=np.attribute("description");
	QString tooltip=np.attribute("tooltip");

	// qDebug("    Reading Param with name %s : %s", qUtf8Printable(name), qUtf8Printable(type));

	bool corrconv = false;
	if(type=="RichBool") {
		QString val = np.attribute("value").toLower();
		if ((val != QString("true")) && (val != QString("false")))
			return false;
		par = new RichBool(name,np.attribute("value")!=QString("false"),desc,tooltip);
		return true;
	}

	if(type=="RichInt") {
		int val = np.attribute("value").toInt(&corrconv);
		if (!corrconv)
			return false;
		par = new RichInt(name,val,desc,tooltip);
		return true;
	}

	if(type=="RichFloat") {
		float val = np.attribute("value").toFloat(&corrconv);
		if (!corrconv)
			return false;
		par = new RichFloat(name,val,desc,tooltip);
		return true;
	}

	if(type=="RichString") {
		par = new RichString(name,np.attribute("value"),desc,tooltip);
		return true;
	}

	if(type=="RichAbsPerc") {
		float val = np.attribute("value").toFloat(&corrconv);
		if ((!corrconv) && (val >= 0.0f) && (val <= 100.0f))
			return false;
		float min = np.attribute("min").toFloat(&corrconv);
		if (!corrconv)
			return false;
		float max = np.attribute("max").toFloat(&corrconv);
		if (!corrconv)
			return false;
		par = new RichAbsPerc(name,val,min,max,desc,tooltip);
		return true;
	}

	if(type=="RichColor") {
		unsigned int r = np.attribute("r").toUInt(&corrconv);
		if ((!corrconv) && (r <= 255))
			return false;
		unsigned int g = np.attribute("g").toUInt(&corrconv);
		if ((!corrconv) && (g <= 255))
			return false;
		unsigned int b = np.attribute("b").toUInt(&corrconv);
		if ((!corrconv) && (b <= 255))
			return false;
		unsigned int a = np.attribute("a").toUInt(&corrconv);
		if ((!corrconv) && (a <= 255))
			return false;
		QColor col(r,g,b,a);
		par= new RichColor(name,col,desc,tooltip);
		return true;
	}

	if(type=="RichMatrix44f") {
		Matrix44m mm;
		for(int i=0;i<16;++i)
		{
			Scalarm val = np.attribute(QString("val")+QString::number(i)).toFloat(&corrconv);
			if (!corrconv)
				return false;
			mm.V()[i]=val;
		}
		par = new RichMatrix44f(name,mm,desc,tooltip);
		return true;
	}

	if(type=="RichEnum") {
		QStringList list;
		int enum_card = np.attribute(QString("enum_cardinality")).toUInt(&corrconv);
		if (!corrconv)
			return false;

		for(int i=0;i<enum_card;++i)
			list<<np.attribute(QString("enum_val")+QString::number(i));

		int val = np.attribute("value").toInt(&corrconv);
		if ((!corrconv) && (val >=0) && (val < enum_card))
			return false;
		par = new RichEnum(name,val,list,desc,tooltip);
		return true;
	}

	if(type == "RichMesh") {
		int val = np.attribute("value").toInt(&corrconv);

		if (!corrconv)
			return false;

		par = new RichMesh(name, val, nullptr, desc,tooltip);
		return true;
	}

	if(type == "RichDynamicFloat") {
		float min = np.attribute("min").toFloat(&corrconv);
		if (!corrconv)
			return false;
		float max = np.attribute("max").toFloat(&corrconv);
		if (!corrconv)
			return false;

		float val = np.attribute("value").toFloat(&corrconv);
		if ((!corrconv) && (val >= min) && (val <= max))
			return false;

		par = new RichDynamicFloat(name, val, min, max, desc, tooltip);
		return true;
	}

	if(type == "RichOpenFile") {
		QStringList list;
		int exts_card = np.attribute(QString("exts_cardinality")).toUInt(&corrconv);
		if (!corrconv)
			return false;

		for(int i=0;i<exts_card;++i)
			list<<np.attribute(QString("exts_val")+QString::number(i));

		QString defdir = np.attribute("value");

		par = new RichOpenFile(name,defdir,list,desc,tooltip);
		return true;
	}

	if(type == "RichSaveFile") {
		QString deffile = np.attribute("value");
		QString ext = np.attribute("ext");
		par = new RichSaveFile(name,deffile,ext,desc,tooltip);
		return true;
	}

	if(type=="RichPoint3f") { // for backward compatibility
		vcg::Point3f val;
		val[0]=np.attribute("x").toFloat(&corrconv);
		if (!corrconv)
			return false;
		val[1]=np.attribute("y").toFloat(&corrconv);
		if (!corrconv)
			return false;
		val[2]=np.attribute("z").toFloat(&corrconv);
		if (!corrconv)
			return false;

		par = new RichPosition(name, val,desc,tooltip);
		return true;
	}
	if(type=="RichPosition") { // for backward compatibility
		vcg::Point3f val;
		val[0]=np.attribute("x").toFloat(&corrconv);
		if (!corrconv)
			return false;
		val[1]=np.attribute("y").toFloat(&corrconv);
		if (!corrconv)
			return false;
		val[2]=np.attribute("z").toFloat(&corrconv);
		if (!corrconv)
			return false;

		par = new RichPosition(name, val,desc,tooltip);
		return true;
	}
	if(type=="RichDirection") { // for backward compatibility
		vcg::Point3f val;
		val[0]=np.attribute("x").toFloat(&corrconv);
		if (!corrconv)
			return false;
		val[1]=np.attribute("y").toFloat(&corrconv);
		if (!corrconv)
			return false;
		val[2]=np.attribute("z").toFloat(&corrconv);
		if (!corrconv)
			return false;

		par = new RichDirection(name, val,desc,tooltip);
		return true;
	}
	if(type=="RichShotf") {
		Shotm val;
		assert(0); //TODO!!!!
		par = new RichShotf(name, val,desc,tooltip);
		return true;
	}

	return false;
}
