/* BEGIN software license
 *
 * msXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright(C) 2009,...,2018 Filippo Rusconi
 *
 * http://www.msxpertsuite.org
 *
 * This file is part of the msXpertSuite project.
 *
 * The msXpertSuite project is the successor of the massXpert project. This
 * project now includes various independent modules:
 *
 * - massXpert, model polymer chemistries and simulate mass spectrometric data;
 * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner;
 *
 * 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 3 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 for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 * END software license
 */


/////////////////////// Qt includes
#include <QDebug>
#include <QRegularExpressionMatch>
#include <QScriptEngine>
#include <QScriptString>
#include <QScriptClassPropertyIterator>


/////////////////////// Local includes
#include <globals/globals.hpp>

#include <libmass/DataPoint.hpp>
#include <libmass/DataPointJs.hpp>
#include <libmass/DataPointJsPrototype.hpp>


#include <stdlib.h>


Q_DECLARE_METATYPE(msXpSlibmass::DataPoint *);
Q_DECLARE_METATYPE(msXpSlibmass::DataPointJs *);


namespace msXpSlibmass
{


/*/js/ Class: DataPoint
 * <comment>The DataPoint class describes a data point. Its members are:
 * - key: the X-axis coordinate
 * - val: the Y-axis coordinate</comment>
 */


DataPointJs::DataPointJs(QScriptEngine *engine)
  : QObject(engine), QScriptClass(engine)
{
  qScriptRegisterMetaType<DataPoint>(engine, toScriptValue, fromScriptValue);

  key = engine->toStringHandle(QLatin1String("key"));
  val = engine->toStringHandle(QLatin1String("val"));

  proto = engine->newQObject(new DataPointJsPrototype(this),
                             QScriptEngine::QtOwnership,
                             QScriptEngine::SkipMethodsInEnumeration |
                               QScriptEngine::ExcludeSuperClassMethods |
                               QScriptEngine::ExcludeSuperClassProperties);

  QScriptValue global = engine->globalObject();

  proto.setPrototype(global.property("Object").property("prototype"));

  ctor = engine->newFunction(construct, proto);
  ctor.setData(engine->toScriptValue(this));
}


DataPointJs::~DataPointJs()
{
}


QString
DataPointJs::name() const
{
  return QLatin1String("DataPoint");
}


QScriptValue
DataPointJs::prototype() const
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__;
  return proto;
}


QScriptValue
DataPointJs::constructor()
{
  return ctor;
}


/*/js/
 * DataPoint()
 *
 * Constructor of a DataPoint object
 *
 * Ex:
 * var dp = new DataPoint;
 * dp.key; // --> NaN
 * dp.val; // --> NaN
 * dp.isValid(); // --> false
 */
QScriptValue
DataPointJs::newInstance()
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "Allocating supporting DataPoint";

  DataPoint dataPoint;

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "Right before return QScriptValue with newInstance(DataPoint)";

  return newInstance(dataPoint);
}


/*/js/
 * DataPoint(xyLine)
 *
 * Constructor of a DataPoint object initialized using the xy-formatted line.
 *
 * xyLine: <String> holding the key,val pair in the format key<sep>val, with
 * <sep> being any character not a cipher nor a decimal point.
 *
 * Ex:
 * var dp = new DataPoint("123.321,456.654");
 * dp.key; // --> 123.321
 * dp.val; // --> 456.654
 * dp.isValid(); // --> true
 */
QScriptValue
DataPointJs::newInstance(const QString &xyLine)
{
  QRegularExpressionMatch regExpMatch;

  regExpMatch = msXpS::gXyFormatMassDataRegExp.match(xyLine);

  if(!regExpMatch.hasMatch())
    {
      return false;
    }

  bool ok = false;

  double key = regExpMatch.captured(1).toDouble(&ok);

  if(!ok)
    return QScriptValue();

  // Note that group 2 is the separator group.

  double val = regExpMatch.captured(3).toDouble(&ok);

  if(!ok)
    return QScriptValue();

  DataPoint dataPoint(key, val);

  return newInstance(dataPoint);
}


/*/js/
 * DataPoint(dataPoint)
 *
 * Constructor of a DataPoint object initialized with another DataPoint object
 *
 * dataPoint: <DataPoint> object used to initialize this object.
 *
 * Ex:
 * var dp1 = new DataPoint(dp);
 */
QScriptValue
DataPointJs::newInstance(const DataPoint &dataPoint)
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "Right before QScriptValue data =
  //engine()->newVariant(QVariant::fromValue(dataPoint));";

  QScriptValue data = engine()->newVariant(QVariant::fromValue(dataPoint));

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "Right before return engine()->newObject(this, data)";

  return engine()->newObject(this, data);
}


/*/js/
 * DataPoint(key, val)
 *
 * Constructor of a DataPoint object initialized with the numerical
 * parameters.
 *
 * key: key (X-value)
 * val: value (Y-value)
 *
 * Ex:
 * var dp = DataPoint(123.321, 456.654);
 */
QScriptValue
DataPointJs::newInstance(double key, double val)
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "Right before DataPoint dataPoint(key, val);";

  DataPoint dataPoint(key, val);

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "Right before return newInstance(dataPoint);";

  return newInstance(dataPoint);
}


QScriptValue::PropertyFlags
DataPointJs::propertyFlags(const QScriptValue & /*object*/,
                           const QScriptString &name,
                           uint /*id*/)
{
  if(name == key)
    {
      return QScriptValue::Undeletable;
    }
  else if(name == val)
    {
      return QScriptValue::Undeletable;
    }

  return QScriptValue::Undeletable;
}


QScriptClass::QueryFlags
DataPointJs::queryProperty(const QScriptValue &object,
                           const QScriptString &name,
                           QueryFlags flags,
                           uint *id)
{
  DataPoint *dataPoint = qscriptvalue_cast<DataPoint *>(object.data());

  if(dataPoint == Q_NULLPTR)
    return 0;

  if(name == key)
    {
      return flags;
    }
  else if(name == val)
    {
      return flags;
    }

  flags &= ~HandlesReadAccess;

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__;
  return flags;
}


QScriptValue
DataPointJs::property(const QScriptValue &object,
                      const QScriptString &name,
                      uint id)
{
  DataPoint *dataPoint = qscriptvalue_cast<DataPoint *>(object.data());
  if(!dataPoint)
    return QScriptValue();

  if(name == key)
    {
      // qDebug() << __FUNCTION__;

      /*/js/
       * <DataPoint>.key
       *
       * Return the key <Number> property.
       *
       * Ex:
       * var dp = new DataPoint(123.321, 456.654);
       * var mz = dp.key;
       * mz // --> 123.321
       */
      return QScriptValue(dataPoint->m_key);
    }
  else if(name == val)
    {
      // qDebug() << __FUNCTION__;

      /*/js/
       * <DataPoint>.val
       *
       * Return the val <Number> property.
       *
       * Ex:
       * var dp = new DataPoint(123.321, 456.654);
       * var i = dp.val;
       * i // --> 456.654
       */
      return QScriptValue(dataPoint->m_val);
    }

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__;
  return QScriptValue();
}


void
DataPointJs::setProperty(QScriptValue &object,
                         const QScriptString &name,
                         uint id,
                         const QScriptValue &value)
{
  DataPoint *dataPoint = qscriptvalue_cast<DataPoint *>(object.data());

  if(name == key)
    {
      dataPoint->m_key = qscriptvalue_cast<double>(value);
    }
  else if(name == val)
    {
      dataPoint->m_val = qscriptvalue_cast<double>(value);
    }

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__;
  return;
}


QScriptClassPropertyIterator *
DataPointJs::newIterator(const QScriptValue &object)
{
  return Q_NULLPTR;
}


QScriptValue
DataPointJs::construct(QScriptContext *ctx, QScriptEngine *)
{
  DataPointJs *cls = qscriptvalue_cast<DataPointJs *>(ctx->callee().data());

  if(!cls)
    return QScriptValue();

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__
  //<< "Found the DataPointJs class.";

  int argCount = ctx->argumentCount();

  if(argCount == 2)
    {
      QScriptValue keyJsValue = ctx->argument(0);
      QScriptValue valJsValue = ctx->argument(1);

      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
      //<< "Right before cls->newInstance(keyJsValue.toNumber(),
      //valJsValue.toNumber());";

      return cls->newInstance(keyJsValue.toNumber(), valJsValue.toNumber());
    }
  else if(argCount == 1)
    {
      QScriptValue arg = ctx->argument(0);

      if(arg.instanceOf(ctx->callee()))
        {
          // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
          //<< "Right before return
          //cls->newInstance(qscriptvalue_cast<DataPoint>(arg));";

          return cls->newInstance(qscriptvalue_cast<DataPoint>(arg));
        }

      if(arg.isString())
        {
          // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
          //<< "Right before return
          //cls->newInstance(qscriptvalue_cast<QString>(arg));";
          return cls->newInstance(qscriptvalue_cast<QString>(arg));
        }

      return QScriptValue();
    }

  // Finally, by default, return an empty DataPoint:
  return cls->newInstance();
}


QScriptValue
DataPointJs::toScriptValue(QScriptEngine *eng, const DataPoint &dataPoint)
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "Right before QScriptValue ctor =
  //eng->globalObject().property(\"DataPoint\");";

  QScriptValue ctor = eng->globalObject().property("DataPoint");

  DataPointJs *cls = qscriptvalue_cast<DataPointJs *>(ctor.data());

  if(!cls)
    return eng->newVariant(QVariant::fromValue(dataPoint));

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "Right before return cls->newInstance(dataPoint)"
  //<< "key, val:" << dataPoint.key() << "," << dataPoint.i();

  return cls->newInstance(dataPoint);
}


void
DataPointJs::fromScriptValue(const QScriptValue &obj, DataPoint &dataPoint)
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "Right before dataPoint =
  //qvariant_cast<DataPoint>(obj.data().toVariant())"
  //<< "to fill-in DataPoint" << &dataPoint << "key, val:" << dataPoint.key() <<
  //"," << dataPoint.i();

  QVariant variant = obj.data().toVariant();

  qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
           << "Variant type:" << variant.userType();

  dataPoint = qvariant_cast<DataPoint>(variant);

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "Right after dataPoint =
  //qvariant_cast<DataPoint>(obj.data().toVariant())"
  //<< "to fill-in DataPoint" << &dataPoint << "key, val:" << dataPoint.key() <<
  //"," << dataPoint.i();
}


} // namespace msXpSlibmass
