/* 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 <QScriptEngine>


/////////////////////// Local includes
#include <libmass/TraceJsPrototype.hpp>
#include <libmass/Trace.hpp>
#include <libmass/DataPointJs.hpp>

#include <globals/globals.hpp>

namespace msXpSlibmass
{


/*/js/ Class: Trace
 *
 *
 */


TraceJsPrototype::TraceJsPrototype(QObject *parent) : QObject(parent)
{
}


TraceJsPrototype::~TraceJsPrototype()
{
}


Trace *
TraceJsPrototype::thisTrace() const
{
  Trace *trace = qscriptvalue_cast<Trace *>(thisObject().data());

  if(trace == Q_NULLPTR)
    qFatal(
      "Fatal error at %s@%d -- %s. "
      "thisTrace() would return Q_NULLPTR."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  return trace;
}


int
TraceJsPrototype::initialize()
{
  qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";

  // Depending on the various parameters, we will choose the proper
  // initializing function.

  QScriptContext *ctx = context();
  int argCount        = ctx->argumentCount();

  qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
           << "argCount: " << argCount;

  if(argCount == 1)
    {
      return initializeOneArgument();
    }
  else if(argCount == 2)
    {
      return initializeTwoArguments();
    }
  else if(argCount == 4)
    {
      return initializeFourArguments();
    }

  return -1;
}


int
TraceJsPrototype::initializeOneArgument()
{
  QScriptContext *ctx = context();
  int argCount        = ctx->argumentCount();

  if(argCount != 1)
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "Programming error."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  // There is only one argument. We need to check the type of that argument
  // and depending on that type choose the proper initializator.

  QScriptValue arg = ctx->argument(0);

  if(arg.isString())
    {

      /*/js/
       * Trace.initialize(xyFormatString)
       *
       * Initialize this Trace object with a <String> object representing the
       * mass data in the xy text format:
       *
       * "<key><sep><val>\n<key><sep><val>\n...".
       *
       * xyFormatString: <String> object representing the <DataPoint> data
       * points of the <Trace> in the format <key> <value>, with one such
       * key-value pair per line. The separator between <key> and <value> might
       * be anything non-numerical and not a dot (decimal separator).
       *
       * 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
       */

      return thisTrace()->initialize(qscriptvalue_cast<QString>(arg));
    }
  else
    {
      // The other single-arg initialization is with another Trace.

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

      if(!variant.isValid())
        {
          qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
                   << "Variant is not valid";

          ctx->throwError("Syntax error: function takes one Trace object.");

          return -1;
        }

      if(variant.userType() == QMetaType::type("msXpSlibmass::Trace"))
        {
          Trace trace(qscriptvalue_cast<Trace>(arg));

          // We do initialize, not combine, so start by removing all the
          // DataPoint instances in this Trace.

          thisTrace()->deleteDataPoints();

          /*/js/
           * Trace.initialize(other)
           *
           * Initialize this Trace object with a <Trace> object
           *
           * other: <Trace> object
           *
           * Ex:
           * var t1 = new Trace();
           * t1.initialize("123.3321 456.654\n147.741 258.852\n");
           * var t2 = new Trace();
           * t2.initialize(t1);
           * t2[0].key; // --> 123.3321
           * t2[0].val; // --> 456.654
           * t2[1].key; // --> 147.741
           * t2[1].val; // --> 258.852
           */

          return thisTrace()->combine(trace);
        }
    }

  return -1;
}


int
TraceJsPrototype::initializeTwoArguments()
{
  QScriptContext *ctx = context();
  int argCount        = ctx->argumentCount();

  if(argCount != 2)
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "Programming error."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  QScriptValue arg1 = ctx->argument(0);
  QScriptValue arg2 = ctx->argument(1);

  // For the initialization with two arguments, these arguments might be the
  // <Array> keyList and <Array> valList. Nothing else.

  if(!arg1.isArray() || !arg2.isArray())
    {
      qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
               << "One or two arguments is/are not of the Array type.";

      ctx->throwError(
        "Syntax error: the two arguments must be of the Array type, keyArray "
        "and valArray.");

      return -1;
    }

  /*/js/
   * Trace.initialize(keyArray, valArray)
   *
   * Initialize this Trace object with two numerical <Array> entities
   *
   * keyArray: the <Array> object containing all the keys of the <Trace>
   * valArray: the <Array> object containing all the values of the <Trace>
   *
   * Ex:
   * var t1 = new Trace;
   * t1.initialize([123.321,456.654,789.987],[147.741, 258.852, 369.963]);
   * t1.length; // --> 3
   * var dp0 = t1[0];
   * dp0.key; // --> 123.321
   * dp0.val; // --> 147.741
   */

  QList<double> keyList = qscriptvalue_cast<QList<double>>(arg1);
  QList<double> valList = qscriptvalue_cast<QList<double>>(arg2);

  return thisTrace()->initialize(keyList, valList);
}


int
TraceJsPrototype::initializeFourArguments()
{
  QScriptContext *ctx = context();
  int argCount        = ctx->argumentCount();

  if(argCount != 4)
    qFatal(
      "Fatal error at %s@%d -- %s(). "
      "Programming error."
      "Program aborted.",
      __FILE__,
      __LINE__,
      __FUNCTION__);

  QScriptValue arg1 = ctx->argument(0);
  QScriptValue arg2 = ctx->argument(1);
  QScriptValue arg3 = ctx->argument(2);
  QScriptValue arg4 = ctx->argument(3);

  // For the initialization with four arguments, these arguments might be the
  // keyList and valList, the keyStart and keyEnd. Nothing else.

  if(!arg1.isArray() || !arg2.isArray())
    {
      qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
               << "One or two arguments is/are not of the Array type.";

      ctx->throwError(
        "Syntax error: the first two arguments must be of the Array type: "
        "keyArray and valArray.");

      return -1;
    }

  if(!arg3.isNumber() || !arg4.isNumber())
    {
      qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
               << "One or two arguments is/are not of the Number type.";

      ctx->throwError(
        "Syntax error: the last two arguments must be of the Number type: "
        "keyStart and valEnd.");

      return -1;
    }

  QList<double> keyList = qscriptvalue_cast<QList<double>>(arg1);
  QList<double> valList = qscriptvalue_cast<QList<double>>(arg2);
  double keyStart       = qscriptvalue_cast<double>(arg3);
  double keyEnd         = qscriptvalue_cast<double>(arg4);

  /*/js/
   * Trace.initialize(keyArray, valArray, keyStart, keyEnd)
   *
   * Initialize this Trace object with two numerical <Array> entities and two
   * <Number> entities
   *
   * keyArray: the <Array> object containing all the keys of the <Trace>
   * valArray: the <Array> object containing all the values of the <Trace>
   * keyStart: the <Number> value defining the beginning of the acceptable key
   * range keyEnd: the <Number> value defining the end of the acceptable key
   * range
   *
   * Ex:
   * var t1 = new Trace;
   * t1.initialize([123.321,456.654,789.987],[147.741, 258.852, 369.963], 124,
   * 450); t1.length; // --> 0
   *
   * var t1 = new Trace;
   * t1.initialize([123.321,456.654,789.987],[147.741, 258.852, 369.963], 124,
   * 457); t1.length; // --> 1 var dp0 = t1[0]; dp0.key; // --> 456.654 dp0.val;
   * // --> 258.852
   */

  return thisTrace()->initialize(keyList, valList, keyStart, keyEnd);
}


/*/js/
 * Trace.keyArray()
 *
 * Return an <Array> of <Number> entities corresponding to all the keys of
 * this Trace object
 *
 * Ex:
 * var t7 = new Trace;
 * t7.initialize([123.321,456.654,789.987],[147.741, 258.852, 369.963]);
 * t7.keyArray(); // --> 123.321,456.654,789.987
 */
QList<double>
TraceJsPrototype::keyArray() const
{
  return thisTrace()->keyList();
}


/*/js/
 * Trace.valArray()
 *
 * Return an <Array> of <Number> entities corresponding to all the values of
 * this Trace object
 *
 * Ex:
 * var t7 = new Trace;
 * t7.initialize([123.321,456.654,789.987],[147.741, 258.852, 369.963]);
 * t7.valArray(); // --> 147.741,258.852,369.963
 */
QList<double>
TraceJsPrototype::valArray() const
{
  return thisTrace()->valList();
}


/*/js/
 * Trace.asText()
 *
 * Return the <DataPoint> data points of this Trace object as a <String>
 * formatted according to this schema: <key> <value>, with one such pair per
 * line
 *
 * Ex:
 * var t7 = new Trace;
 * t7.initialize([123.321,456.654,789.987],[147.741, 258.852, 369.963]);
 * t7.asText(); // --> output is:
 * 123.3210000000 147.7410000000
 * 456.6540000000 258.8520000000
 * 789.9870000000 369.9630000000
 */
QString
TraceJsPrototype::asText()
{
  QScriptContext *ctx = context();
  int argCount        = ctx->argumentCount();

  int rangeStart = -1;
  int rangeEnd   = -1;

  if(argCount != 0 && argCount != 2)
    {
      ctx->throwError(
        "Syntax error: function asText takes either none or two numbers as "
        "DataPoint indexes.");

      return QString();
    }

  if(argCount == 2)
    {
      QScriptValue arg1 = ctx->argument(0);
      QScriptValue arg2 = ctx->argument(1);

      if(!arg1.isNumber() || !arg2.isNumber())
        {
          ctx->throwError(
            "Syntax error: function asText() takes zero or two numbers as "
            "DataPoint indexes.");
          return QString();
        }

      rangeStart = qscriptvalue_cast<int>(arg1);
      rangeEnd   = qscriptvalue_cast<int>(arg2);
    }

  // Finally, do the work.

  return thisTrace()->asText(rangeStart, rangeEnd);
}


/*/js/
 * Trace.exportToFile(fileName)
 *
 * Export the <DataPoint> contents of this Trace object to a file. The format
 * is according to this schema: <key> <value>, with one such pair per line
 *
 * fileName: <String> containing the path to the file in which to store the
 * data
 *
 * Ex:
 * var t7 = new Trace;
 * t7.initialize([123.321,456.654,789.987],[147.741, 258.852, 369.963]);
 * t7.exportToFile("/tmp/trial.txt"); // --> file contains:
 * 123.3210000000 147.7410000000
 * 456.6540000000 258.8520000000
 * 789.9870000000 369.9630000000
 */
bool
TraceJsPrototype::exportToFile(const QString &fileName)
{

  return thisTrace()->exportToFile(fileName);
}


/*/js/
 * Trace.valSum()
 *
 * Return the <Number> value corresponding to the sum of all the values of
 * all the <DataPoint> object in this Trace object
 *
 * Ex:
 * var t7 = new Trace;
 * t7.initialize([123.321,456.654,789.987],[147.741, 258.852, 369.963]);
 * t7.valSum(); // --> 776.556
 */
double
TraceJsPrototype::valSum() const
{
  // Either there is no argument, or there must be two arguments:
  // keyRangeStart and keyRangeEnd

  QScriptContext *ctx = context();
  int argCount        = ctx->argumentCount();

  if(argCount == 0)
    return thisTrace()->valSum();

  if(argCount == 2)
    {
      QScriptValue arg1 = ctx->argument(0);
      QScriptValue arg2 = ctx->argument(1);

      if(!arg1.isNumber() || !arg2.isNumber())
        {
          ctx->throwError(
            "Syntax error: function valSum() takes either 0 or 2 numbers as "
            "arguments.");

          return -1;
        }

      double keyRangeStart = qscriptvalue_cast<double>(arg1);
      double keyRangeEnd   = qscriptvalue_cast<double>(arg2);

      return thisTrace()->valSum(keyRangeStart, keyRangeEnd);
    }

  ctx->throwError(
    "Syntax error: function valSum() takes either 0 or 2 numbers as "
    "arguments.");

  return -1;
}


int
TraceJsPrototype::combine()
{
  QScriptContext *ctx = context();
  int argCount        = ctx->argumentCount();

  if(argCount == 1)
    {

      // The single argument can be either DataPoint or Trace

      QScriptValue arg = ctx->argument(0);

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

      if(!variant.isValid())
        {
          qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
                   << "Variant is not valid";

          ctx->throwError(
            "Syntax error: function combine takes one DataPoint object "
            "or one Trace object or two numbers (key & val) as arguments.");

          return -1;
        }

      // Test if DataPoint

      if(variant.userType() == QMetaType::type("msXpSlibmass::DataPoint"))
        {
          DataPoint dp(qscriptvalue_cast<DataPoint>(arg));

          if(!dp.isValid())
            {
              ctx->throwError(
                "Function combine takes one initialized "
                "DataPoint object as argument.");

              return -1;
            }

          /*/js/
           * Trace.combine(dataPoint)
           *
           * Combine into this Trace object the <DataPoint> object
           *
           * dataPoint: <DataPoint> object to combine into this MassSpectrum
           *
           * Ex:
           *
           * var t7 = new Trace;
           * t7.initialize([123.321,456.654,789.987],[147.741, 258.852,
           * 369.963]);
           *
           * t7.asText(); // --> output is:
           *
           * 123.3210000000 147.7410000000
           * 456.6540000000 258.8520000000
           * 789.9870000000 369.9630000000
           *
           * t7.combine(new DataPoint(200.002,1000)); // --> 1, the number of
           * added DataPoint objects
           *
           * t7.asText(); // --> output is:
           *
           * 123.3210000000 147.7410000000
           * 200.0020000000 1000.0000000000
           * 456.6540000000 258.8520000000
           * 789.9870000000 369.9630000000
           *
           * t7.combine(new DataPoint(200.002,1000)); // --> 0, no added
           * DataPoint, only increment the value
           *
           * t7.asText(); // --> output is:
           *
           * 123.3210000000 147.7410000000
           * 200.0020000000 2000.0000000000 // value increment by the same
           * original amount. 456.6540000000 258.8520000000 789.9870000000
           * 369.9630000000
           */

          return thisTrace()->combine(dp);
        }

      // Test if Trace

      if(variant.userType() == QMetaType::type("msXpSlibmass::Trace"))
        {
          Trace trace(qscriptvalue_cast<Trace>(arg));

          /*/js/
           * Trace.combine(trace)
           *
           * Combine into this Trace object the <Trace> object
           *
           * trace: <Trace> object to combine into this Trace
           *
           * Ex:
           * var t1 = new Trace;
           * t1.initialize([123.321,456.654,789.987],[147.741,
           * 258.852,369.963]); var t2 = new Trace(t1); t2.length; // --> 3
           * t2.asText(); // --> output is:
           *
           * 123.3210000000 147.7410000000
           * 456.6540000000 258.8520000000
           * 789.9870000000 369.9630000000
           *
           * t2.asText(); // --> output is:
           *
           * 123.3210000000 147.7410000000
           * 456.6540000000 258.8520000000
           * 789.9870000000 369.9630000000
           *
           * t2.combine(t1); // --> 3: 3 DataPoint objects were combined
           * t2.asText(); // --> output is:
           *
           * 123.3210000000 295.4820000000
           * 456.6540000000 517.7040000000
           * 789.9870000000 739.9260000000
           */

          return thisTrace()->combine(trace);
        }
    }
  else if(argCount == 2)
    {
      QScriptValue arg1 = ctx->argument(0);
      QScriptValue arg2 = ctx->argument(1);

      if(!arg1.isNumber() || !arg2.isNumber())
        {
          ctx->throwError(
            "Syntax error: function combine() takes two numbers (key & val) as "
            "arguments.");

          return -1;
        }

      double key = qscriptvalue_cast<double>(arg1);
      double val = qscriptvalue_cast<double>(arg2);

      /*/js/
       * Trace.combine(key, val)
       *
       * Combine into this Trace object a data point in the form of
       * two <Number> entities, key and val
       *
       * Ex:
       * var t1 = new Trace;
       * t1.initialize([123.321,456.654,789.987],[147.741, 258.852,369.963]);
       *
       * t1.length; // --> 3
       *
       * t1.asText(); // --> output is:
       *
       * 123.3210000000 147.7410000000
       * 456.6540000000 258.8520000000
       * 789.9870000000 369.9630000000
       *
       * t1.combine(456.654, 1000);
       *
       * t1.asText(); // --> output is:
       *
       * 123.3210000000 147.7410000000
       * 456.6540000000 1258.8520000000
       * 789.9870000000 369.9630000000
       *
       * t1.combine(800, 2000);
       *
       * t1.asText(); // --> output is:
       *
       * 123.3210000000 147.7410000000
       * 456.6540000000 1258.8520000000
       * 789.9870000000 369.9630000000
       * 800.0000000000 2000.0000000000
       */

      return thisTrace()->combine(DataPoint(key, val));
    }

  ctx->throwError(
    "Syntax error: function combine takes one DataPoint object "
    "or one Trace object or two numbers (key & val) as arguments.");

  return -1;
}


int
TraceJsPrototype::subtract()
{
  QScriptContext *ctx = context();
  int argCount        = ctx->argumentCount();

  if(argCount == 1)
    {

      // The single argument can be either DataPoint or Trace

      QScriptValue arg = ctx->argument(0);

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

      if(!variant.isValid())
        {
          qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
                   << "Variant is not valid";

          ctx->throwError(
            "Syntax error: function combine takes one DataPoint object "
            "or one Trace object or two numbers (key & val) as arguments.");

          return -1;
        }

      // Test if DataPoint

      if(variant.userType() == QMetaType::type("msXpSlibmass::DataPoint"))
        {
          DataPoint dp(qscriptvalue_cast<DataPoint>(arg));

          if(!dp.isValid())
            {
              ctx->throwError(
                "Function combine takes one initialized "
                "DataPoint object as argument.");

              return -1;
            }

          /*/js/
           * Trace.subtract(dataPoint)
           *
           * Subtract from this Trace object the <DataPoint> object
           *
           * dataPoint: <DataPoint> object to subtract from this MassSpectrum
           */

          return thisTrace()->subtract(dp);
        }

      // Test if Trace

      if(variant.userType() == QMetaType::type("msXpSlibmass::Trace"))
        {
          Trace trace(qscriptvalue_cast<Trace>(arg));

          /*/js/
           * Trace.subtract(trace)
           *
           * Subtract from this Trace object the <Trace> object
           *
           * trace: <Trace> object to subtract from this Trace
           */

          return thisTrace()->subtract(trace);
        }
    }
  else if(argCount == 2)
    {
      QScriptValue arg1 = ctx->argument(0);
      QScriptValue arg2 = ctx->argument(1);

      if(!arg1.isNumber() || !arg2.isNumber())
        {
          ctx->throwError(
            "Syntax error: function subtract() takes two numbers (key & val) "
            "as arguments.");

          return -1;
        }

      double key = qscriptvalue_cast<double>(arg1);
      double val = qscriptvalue_cast<double>(arg2);

      /*/js/
       * Trace.subtract(mz, i)
       *
       * Subtract from this Trace object a data point in the form of
       * two <Number> values, key and value
       */

      return thisTrace()->subtract(DataPoint(key, val));
    }

  ctx->throwError(
    "Syntax error: function subtract takes one DataPoint object "
    "or one Trace object or two numbers (key & val) as arguments.");

  return -1;
}


QScriptValue
TraceJsPrototype::valueOf() const
{
  return thisObject().data();
}


} // namespace msXpSlibmass
