/* 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/Trace.hpp>
#include <libmass/DataPointJs.hpp>
#include <libmass/TraceJs.hpp>
#include <libmass/TraceJsPrototype.hpp>
#include <libmass/TraceJsPropertyIterator.hpp>


#include <stdlib.h>


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


namespace msXpSlibmass
{


/*/js/ Class: Trace
 * <comment>The Trace class is fundamentally an <Array> of DataPoint
 * objects.</comment>
 */


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

  title         = engine->toStringHandle(QLatin1String("title"));
  length        = engine->toStringHandle(QLatin1String("length"));
  decimalPlaces = engine->toStringHandle(QLatin1String("decimalPlaces"));
  lastModifIdx  = engine->toStringHandle(QLatin1String("lastModifIdx"));
  removeZeroValDataPoints =
    engine->toStringHandle(QLatin1String("removeZeroValDataPoints"));

  proto = engine->newQObject(new TraceJsPrototype(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));
}


TraceJs::~TraceJs()
{
}


QString
TraceJs::name() const
{
  return QLatin1String("Trace");
}


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


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


/*/js/
 * Trace()
 *
 * Constructor of a Trace object
 *
 * Ex:
 * var t = new Trace;
 * t.length; // --> 0
 */
QScriptValue
TraceJs::newInstance()
{
  Trace trace;

  return newInstance(trace);
}


/*/js/
 * Trace(title)
 *
 * Constructor of a Trace object
 *
 * title: <String> containing the title of the Trace object
 *
 * Ex:
 * var t = new Trace("Trace title");
 * t.title; // --> Trace title
 */
QScriptValue
TraceJs::newInstance(const QString &title)
{
  Trace trace(title);

  return newInstance(trace);
}


/*/js/
 * Trace(other)
 *
 * Constructor of a Trace object
 *
 * other: <Trace> object to be used to initialize this object
 *
 * Ex:
 * var t2 = new Trace();
 * t2.initialize("123.3321 456.654\n147.741 258.852\n");
 * t2[0].key; // --> 123.3321
 * t2[0].val; // --> 456.654
 * t2[1].key; // --> 147.741
 * t2[1].val; // --> 258.852
 * var t3 = new Trace(t2);
 * t3[0].key; // --> 123.3321
 * t3[0].val; // --> 456.654
 * t3[1].key; // --> 147.741
 * t3[1].val; // --> 258.852
 */
QScriptValue
TraceJs::newInstance(const Trace &other)
{
  QScriptValue data = engine()->newVariant(QVariant::fromValue(other));

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


QScriptValue::PropertyFlags
TraceJs::propertyFlags(const QScriptValue & /*object*/,
                       const QScriptString &name,
                       uint /*id*/)
{
  // For the time being, no property is considered deletable.
  return QScriptValue::Undeletable;
}


QScriptClass::QueryFlags
TraceJs::queryProperty(const QScriptValue &object,
                       const QScriptString &name,
                       QueryFlags flags,
                       uint *id)
{
  Trace *trace = qscriptvalue_cast<Trace *>(object.data());
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "initial flags:" << flags;

  if(trace == Q_NULLPTR)
    return 0;

  if(name == title)
    {
      return flags;
    }
  else if(name == length)
    {
      // We do not want to set the length of the Trace, but we may want to
      // know what is the length (size) of the Trace, that is, the number of
      // DataPoint instances in it.

      if(flags & HandlesWriteAccess)
        {
          flags &= ~HandlesWriteAccess;
        }

      return flags;
    }
  else if(name == decimalPlaces)
    {
      // We are fine with either querying or setting the m_decimalPlaces of
      // Trace-C++.
      return flags;
    }
  else if(name == lastModifIdx)
    {
      // We only want to accept read queries.
      if(flags & HandlesWriteAccess)
        {
          flags &= ~HandlesWriteAccess;
        }
      return flags;
    }
  else if(name == removeZeroValDataPoints)
    {
      return flags;
    }
  else
    {
      // Treat situation as if the name param actually contained "22", that is
      // the index of the DataPoint in Trace: anyTrace["22"].
      bool isArrayIndex;

      qint32 index = name.toArrayIndex(&isArrayIndex);

      if(!isArrayIndex)
        return 0;

      // We do not want to grant write access with the DataPoint at index.
      if(flags & HandlesWriteAccess)
        flags &= ~HandlesWriteAccess;

      // We do not want to grant read access to the list of DataPoint instances
      // if there will be an out of bound error later... So we remove the read
      // access.

      if(index >= trace->size())
        {
          if(flags & HandlesReadAccess)
            {
              flags &= ~HandlesReadAccess;
            }
        }
      else
        // The index has an in-bound list value, set it.
        *id = index;

      // Finally return the flags.
      return flags;
    }
}


QScriptValue
TraceJs::property(const QScriptValue &object,
                  const QScriptString &name,
                  uint id)
{
  Trace *trace = qscriptvalue_cast<Trace *>(object.data());

  if(!trace)
    {
      return QScriptValue();
    }

  if(name == title)
    {
      return QScriptValue(trace->m_title);
    }
  else if(name == length)
    {
      return QScriptValue(trace->size());
    }
  else
    {
      qint32 index = id;

      if((index < 0) || (index >= trace->size()))
        return QScriptValue();

      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__
      //<< "The index is:" << index
      //<< "and the variant:" << QVariant::fromValue(*(trace->at(index)));

      // Create a new script value of type msXpSlibmass::DataPoint.
      QScriptValue data =
        engine()->newVariant(QVariant::fromValue(trace->at(index)));
      // QVariant variant = data.toVariant();
      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
      //<< "Variant type:" << variant.userType() << "name:" <<
      //variant.typeName();

      // We need to allocate the proper JS proxy for the object class: the
      // class of the native object is DataPoint, so we need to allocate a
      // DataPointJs instance to feed to newObject() below. Only if we do this,
      // will we have a properly behaving DataPoint JS object when accessing
      // the individual members of the mass spectrum.

      DataPointJs *dataPointJs = new DataPointJs(engine());

      /*/js/
       * Trace[index]
       *
       * Return the <DataPoint> object at index.
       *
       * Ex:
       * var t1 = new Trace();
       * t1.initialize([123.321,456.654,789.987],[147.741, 258.852, 369.963]);
       * t1.asText();
       *
       * 123.3210000000 147.7410000000
       * 456.6540000000 258.8520000000
       * 789.9870000000 369.9630000000
       *
       * var dp0 = t1[0];
       * dp0.key; // --> 123.321
       * dp0.val; // --> 147.741
       *
       * t1[0].key; // --> 123.321
       * t1[0].val; // --> 147.741
       */

      // Finally actually create a JS object of type DataPoint.
      return engine()->newObject(dataPointJs, data);
    }

  return QScriptValue();
}


void
TraceJs::setProperty(QScriptValue &object,
                     const QScriptString &name,
                     uint id,
                     const QScriptValue &value)
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";

  Trace *trace = qscriptvalue_cast<Trace *>(object.data());

  if(trace == Q_NULLPTR)
    return;

  if(name == title)
    {
      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";
      trace->m_title = qscriptvalue_cast<QString>(value);
    }
  else if(name == length)
    {
      qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";
      return;
    }
  else if(name == decimalPlaces)
    {
      trace->m_decimalPlaces = qscriptvalue_cast<int>(value);
    }
  else if(name == removeZeroValDataPoints)
    {
      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";
      trace->m_removeZeroValDataPoints = qscriptvalue_cast<bool>(value);
    }

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


QScriptClassPropertyIterator *
TraceJs::newIterator(const QScriptValue &object)
{
  return new TraceJsPropertyIterator(object);
}


QScriptValue
TraceJs::construct(QScriptContext *ctx, QScriptEngine *)
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()" ;

  TraceJs *msJs = qscriptvalue_cast<TraceJs *>(ctx->callee().data());

  if(!msJs)
    return QScriptValue();

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

  int argCount = ctx->argumentCount();

  if(argCount == 1)
    {
      QScriptValue arg = ctx->argument(0);

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

      if(variant.userType() == QMetaType::type("msXpSlibmass::Trace"))
        {
          // qDebug() << __FILE__ << __LINE__ << __FUNCTION__<< "()"
          //<< "The argument passed to the constructor is a Trace.";

          return msJs->newInstance(qscriptvalue_cast<Trace>(arg));
        }
      else if(arg.isString())
        {
          // The string is the title of the trace.
          return msJs->newInstance(Trace(qscriptvalue_cast<QString>(arg)));
        }
    }

  // By default return an empty mass spectrum.
  return msJs->newInstance();
}


QScriptValue
TraceJs::toScriptValue(QScriptEngine *eng, const Trace &trace)
{
  QScriptValue ctor = eng->globalObject().property("Trace");

  TraceJs *msJs = qscriptvalue_cast<TraceJs *>(ctor.data());

  if(!msJs)
    return eng->newVariant(QVariant::fromValue(trace));

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "Right before newInstance(Trace-C++)";

  return msJs->newInstance(trace);
}


void
TraceJs::fromScriptValue(const QScriptValue &obj, Trace &trace)
{
  trace = qvariant_cast<Trace>(obj.data().toVariant());
}


} // namespace msXpSlibmass
