/* 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>
#include <QScriptString>


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

#include <minexpert/nongui/SavGolFilter.hpp>
#include <minexpert/nongui/SavGolFilterJs.hpp>
#include <minexpert/nongui/SavGolFilterJsPrototype.hpp>


#include <stdlib.h>


Q_DECLARE_METATYPE(msXpSmineXpert::SavGolFilterJs *);


namespace msXpSmineXpert
{


/*/js/ Class: SavGolFilter
 * <comment>This class handles the parameterization of the Savitzky-Golay
 * filter. This filter is an efficient way to remove noise from noisy mass
 * spectra and can be automatically applied after any mass spectrum
 * combination processing.
 *
 * A SavGolFilter object contains the following member data as members of a
 * <SavGolParams> object (see documentation for SavGolParams):
 * - nl: <Number> of points on the left of the filtered point;
 * - nR: <Number of points on the right of the filtered point;
 * - m:	 <Number> (order) of the polynomial to use in the regression analysis
 *   leading to the Savitzky-Golay coefficients (typically between 2 and 6);
 * - lD: <Number> specifying the order of the derivative to extract from the
 *   Savitzky-Golay smoothing algorithm (for regular smoothing, use 0)
 * - convolveWithNr <Boolean>  Set to false for best results
 * </comment>
 */


SavGolFilterJs::SavGolFilterJs(QScriptEngine *engine)
  : QObject(engine), QScriptClass(engine)
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";

  qScriptRegisterMetaType<SavGolFilter>(engine, toScriptValue, fromScriptValue);
  qScriptRegisterMetaType<SavGolParams>(engine, toScriptValue, fromScriptValue);

  nL             = engine->toStringHandle(QLatin1String("nL"));
  nR             = engine->toStringHandle(QLatin1String("nR"));
  m              = engine->toStringHandle(QLatin1String("m"));
  lD             = engine->toStringHandle(QLatin1String("lD"));
  convolveWithNr = engine->toStringHandle(QLatin1String("convolveWithNr"));
  savGolParams   = engine->toStringHandle(QLatin1String("savGolParams"));

  samples = engine->toStringHandle(QLatin1String("samples"));

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


SavGolFilterJs::~SavGolFilterJs()
{
}


QString
SavGolFilterJs::name() const
{
  return QLatin1String("SavGolFilter");
}


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


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


/*/js/
 * SavGolFilter()
 *
 * Constructor of a SavGolFilter object
 *
 * Ex:
 * var sgf1 = new SavGolFilter();
 * sgf1.nL; // --> 15
 * sgf1.nR; // --> 15
 * sgf1.m; // --> 4
 * sgf1.lD; // --> 0
 * sgf1.convolveWithNr; // --> false
 */
QScriptValue
SavGolFilterJs::newInstance()
{
  SavGolFilter savGolFilter;

  return newInstance(savGolFilter);
}


/*/js/
 * SavGolFilter(savGolParams)
 *
 * Constructor of a SavGolFilter object initialized using a <SavGolParams>
 * object
 *
 * Ex:
 *
 * var sgp1 = new SavGolParams();
 *
 * sgp1.nL = 10; --> 10
 * sgp1.nR = 10; --> 10
 * sgp1.lD = 0; --> 0
 * sgp1.m = 5; --> 5
 *
 * var sgf1 = new SavGolFilter(sgp1);
 * sgf1.asText(); --> output is:
 *
 * Savitzy-Golay filter parameters:
 * nL: 10 ; nR: 10 ; m: 5 ; lD: 0 ; convolveWithNr : false
 * savGolParams: <SavGolParams> object to be used to initialize this object's
 * SavGolParams
 */
QScriptValue
SavGolFilterJs::newInstance(const SavGolParams &savGolParams)
{
  SavGolFilter savGolFilter(savGolParams);

  return newInstance(savGolFilter);
}


/*/js/
 * SavGolFilter(nL, nR, m, lD, convolveWithNr)
 *
 * Constructor of a SavGolFilter object initialized using parameters that
 * initialize the SavGolParams member object
 *
 * nL: number of data points on the left of the point being filtered (default *
 * 15)
 *
 * nR: number of data points on the right of the point being filtered (default *
 * 15)
 *
 * m: order of the polynomial to use in the regression analysis
 * leading to the Savitzky-Golay coefficients (typicall [2-6], default 4)
 *
 * lD: order of the derivative to extract from the Savitzky-Golay
 * smoothing algorithm (for regular smoothing, use 0, the default);
 *
 * Ex:
 *
 * var sgf1 = new SavGolFilter(10, 10, 6, 0, true);
 *
 * sgf1.asText(); --> output is:
 *
 * Savitzy-Golay filter parameters:
 * nL: 10 ; nR: 10 ; m: 6 ; lD: 0 ; convolveWithNr : true
 *
 * convolveWithNr: set to false for best results (default false)
 */
QScriptValue
SavGolFilterJs::newInstance(int nL, int nR, int m, int lD, bool convolveWithNr)
{
  SavGolFilter savGolFilter(nL, nR, m, lD, convolveWithNr);

  return newInstance(savGolFilter);
}


/*/js/
 * SavGolFilter(savGolFilter)
 *
 * Constructor of a SavGolFilter object
 *
 * Ex:
 *
 * var sgf1 = new SavGolFilter();
 * sgf1.asText(); --> output is:
 *
 * Savitzy-Golay filter parameters:
 * nL: 15 ; nR: 15 ; m: 4 ; lD: 0 ; convolveWithNr : false*
 *
 * sgf1.nL = 10;
 * sgf1.nR = 10;
 * sgf1.m= 6;
 *
 * var sgf2 = new SavGolFilter(sgf1);
 * sgf2.asText(); --> output is:
 *
 * Savitzy-Golay filter parameters:
 * nL: 10 ; nR: 10 ; m: 6 ; lD: 0 ; convolveWithNr : false
 *
 * savGolFilter: <SavGolFilter> object to be used to initialize this object
 */
QScriptValue
SavGolFilterJs::newInstance(const SavGolFilter &other)
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "Right before getting QScriptValue as
  //newVariant(QVariant::fromValue(SavGolFilter))";

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

  // 'this', below, is because the JS object (data) needs to be linked to
  // the corresponding JS class that must match the class of the object.
  // 'data' is an object of SavGolFilter class and thus needs to be
  // associated to the SavGolFilterJs class (that is, this).

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "Right before returning QScriptValue as newObject(this, data);";

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


QScriptValue::PropertyFlags
SavGolFilterJs::propertyFlags(const QScriptValue & /*object*/,
                              const QScriptString &name,
                              uint /*id*/)
{
  return QScriptValue::Undeletable;
}


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

  if(savGolFilter == Q_NULLPTR)
    {
      qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
               << "savGolFilter is Q_NULLPTR";

      return 0;
    }

  if(name == nL)
    {
      return flags;
    }
  else if(name == nR)
    {
      return flags;
    }
  else if(name == m)
    {
      return flags;
    }
  else if(name == lD)
    {
      return flags;
    }
  else if(name == convolveWithNr)
    {
      return flags;
    }
  else if(name == savGolParams)
    {
      return flags;
    }
  else if(name == samples)
    {
      return flags;
    }

  // It is essential to return 0 here, otherwise the prototype will never
  // get called.
  return 0;
}


QScriptValue
SavGolFilterJs::property(const QScriptValue &object,
                         const QScriptString &name,
                         uint id)
{
  SavGolFilter *savGolFilter = qscriptvalue_cast<SavGolFilter *>(object.data());

  if(!savGolFilter)
    return QScriptValue();

  if(name == nL)
    {
      /*js/
       * SavGolFilter.nL
       *
       * Return the <Number> property nL.
       *
       * Ex:
       *
       * var sgf1 = new SavGolFilter();
       *
       * sgf1.nL; // --> 15
       */
      return QScriptValue(savGolFilter->m_savGolParams.nL);
    }
  else if(name == nR)
    {
      /*js/
       * SavGolFilter.nR
       *
       * Return the <Number> property nR.
       *
       * Ex:
       *
       * var sgf1 = new SavGolFilter();
       *
       * sgf1.nR; // --> 15
       */
      return QScriptValue(savGolFilter->m_savGolParams.nR);
    }
  else if(name == m)
    {
      /*js/
       * SavGolFilter.m
       *
       * Return the <Number> property m.
       *
       * Ex:
       *
       * var sgf1 = new SavGolFilter();
       *
       * sgf1.m; // --> 4
       */
      return QScriptValue(savGolFilter->m_savGolParams.m);
    }
  else if(name == lD)
    {
      /*js/
       * SavGolFilter.lD
       *
       * Return the <Number> property lD.
       *
       * Ex:
       *
       * var sgf1 = new SavGolFilter();
       *
       * sgf1.lD; // --> 0
       */
      return QScriptValue(savGolFilter->m_savGolParams.lD);
    }
  else if(name == convolveWithNr)
    {
      /*js/
       * SavGolFilter.convolveWithNr
       *
       * Return the <Boolean> property convolveWithNr.
       *
       * Ex:
       *
       * var sgf1 = new SavGolFilter();
       *
       * sgf1.convolveWithNr; // --> false;
       */
      return QScriptValue(savGolFilter->m_savGolParams.convolveWithNr);
    }
  else if(name == savGolParams)
    {
      /*js/
       * SavGolFilter.savGolParams
       *
       * Return a <SavGolParams> object containing this object's SavGolParams
       * values
       *
       * Ex:
       *
       * var sgf1 = new SavGolFilter();
       * sgf1.nL; --> 15
       * sgf1.nR; --> 15
       * sgf1.m; --> 4
       * sgf1.lD; --> 0
       * sgf1.convolveWithNr; --> false
       * sgf1.nL =1;
       * sgf1.nR =1;
       * sgf1.m =3;
       * sgf1.lD =1;
       * sgf1.convolveWithNr = true;
       * sgf1.asText();
       *
       * Savitzy-Golay filter parameters:
       * nL: 1 ; nR: 1 ; m: 3 ; lD: 1 ; convolveWithNr : true
       *
       * var sgp1 = sgf1.savGolParams;
       * sgp1.nL; --> 1
       * sgp1.nR; --> 1
       * sgp1.m; --> 3
       * sgp1.lD; --> 1
       * sgp1.convolveWithNr; -> true
       */
      return toScriptValue(engine(), savGolFilter->m_savGolParams);
    }
  else if(name == samples)
    {
      /*js/
       *
       * Return the number of samples to filter
       *
       * Ex:
       *
       * var sgf1 = new SavGolFilter();
       * sgf1.samples; --> 0
       */
      return QScriptValue(savGolFilter->m_samples);
    }

  return QScriptValue();
}


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

  SavGolFilter *savGolFilter = qscriptvalue_cast<SavGolFilter *>(object.data());

  if(name == nL)
    {
      /*js/
       * SavGolFilter.nL = <Number>
       *
       * Set the <Number> property nL.
       *
       * Ex:
       *
       * var sgf1 = new SavGolFilter();
       *
       * sgf1.nL = 14;
       *
       * sgf1.nL; // --> 14
       */
      savGolFilter->m_savGolParams.nL = qscriptvalue_cast<int>(value);
    }
  else if(name == nR)
    {
      /*js/
       * SavGolFilter.nL = <Number>
       *
       * Set the <Number> property nL.
       *
       * Ex:
       *
       * var sgf1 = new SavGolFilter();
       *
       * sgf1.nL = 14;
       *
       * sgf1.nL; // --> 14
       */
      savGolFilter->m_savGolParams.nR = qscriptvalue_cast<int>(value);
    }
  else if(name == m)
    {
      /*js/
       * SavGolFilter.m = <Number>
       *
       * Set the <Number> property m.
       *
       * Ex:
       *
       * var sgf1 = new SavGolFilter();
       *
       * sgf1.m = 6;
       *
       * sgf1.m; // --> 6
       */
      savGolFilter->m_savGolParams.m = qscriptvalue_cast<int>(value);
    }
  else if(name == lD)
    {
      /*js/
       * SavGolFilter.lD = <Number>
       *
       * Set the <Number> property lD.
       *
       * Ex:
       *
       * var sgf1 = new SavGolFilter();
       *
       * sgf1.lD = 1;
       *
       * sgf1.lD; // --> 1
       */
      savGolFilter->m_savGolParams.lD = qscriptvalue_cast<int>(value);
    }
  else if(name == convolveWithNr)
    {
      /*js/
       * SavGolFilter.convolveWithNr = <Number>
       *
       * Set the <Boolean> property convolveWithNr.
       *
       * Ex:
       *
       * var sgf1 = new SavGolFilter();
       *
       * sgf1.convolveWithNr = true;
       *
       * sgf1.convolveWithNr; // --> true
       */
      savGolFilter->m_savGolParams.convolveWithNr =
        qscriptvalue_cast<bool>(value);
    }
  else if(name == savGolParams)
    {
      /*js/
       *
       * Set the SavGolParams values from a <SavGolParams> object
       *
       * Ex:
       *
       * var sgp1 =  new SavGolParams;
       * sgp1.nL = 10;
       * sgp1.nR = 10;
       * sgp1.m = 6;
       * sgp1.lD = 1;
       * sgp1.convolveWithNr = true;
       *
       * var sgf1 = new SavGolFilter();
       * sgf1.savGolParams = sgp1;
       * sgf1.asText(); --> output is:
       *
       * Savitzy-Golay filter parameters:
       * nL: 10 ; nR: 10 ; m: 6 ; lD: 1 ; convolveWithNr : true
       */
      savGolFilter->m_savGolParams = qscriptvalue_cast<SavGolParams>(value);
    }

  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "Right before returning void";
  return;
}


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

  SavGolFilterJs *cls =
    qscriptvalue_cast<SavGolFilterJs *>(ctx->callee().data());

  if(!cls)
    return QScriptValue();

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

  int argCount = ctx->argumentCount();

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

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

      if(!variant.isValid())
        {
          variant = arg.toVariant();

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

          if(!variant.isValid())
            {
              qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
                       << "Variant is not valid, returning default-constructed "
                          "object.";

              return cls->newInstance();
            }

          // Let's check if the object passed as parameter in the script
          // environment is actually a SavGolParams object.

          QScriptValue nLValue = arg.property("nL", QScriptValue::ResolveLocal);

          if(nLValue.isValid())
            {

              // Finally, we can take the constructor parameter as a
              // SavGolParams argument.
              SavGolParams params = qscriptvalue_cast<SavGolParams>(arg);

              // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
              //<< "We are constructing from SavGolParams.";

              return cls->newInstance(params);
            }

          return cls->newInstance();
        }

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

          return cls->newInstance(qscriptvalue_cast<SavGolFilter>(arg));
        }
      else if(variant.userType() ==
              QMetaType::type("msXpSmineXpert::SavGolParams"))
        {
          // qDebug() << __FILE__ << __LINE__ << __FUNCTION__<< "()"
          //<< "The argument passed to the constructor is a SavGolParams
          //structure.";

          return cls->newInstance(qscriptvalue_cast<SavGolParams>(arg));
        }
      else
        {
          // By default return an empty mass spectrum.

          qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
                   << "Default constructor, single argument not recognized";

          return cls->newInstance();
        }
    }
  else if(argCount == 5)
    {
      // We are constructing from raw values.

      QScriptValue nLJSValue             = ctx->argument(0);
      QScriptValue nRJSValue             = ctx->argument(1);
      QScriptValue mJSValue              = ctx->argument(2);
      QScriptValue lDJSValue             = ctx->argument(3);
      QScriptValue convolveWithNrJSValue = ctx->argument(4);

      return cls->newInstance(nLJSValue.toNumber(),
                              nRJSValue.toNumber(),
                              mJSValue.toNumber(),
                              lDJSValue.toNumber(),
                              convolveWithNrJSValue.toBoolean());
    }

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


QScriptValue
SavGolFilterJs::toScriptValue(QScriptEngine *eng,
                              const SavGolFilter &savGolFilter)
{
  QScriptValue ctor = eng->globalObject().property("SavGolFilter");

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

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

  return cls->newInstance(savGolFilter);
}


void
SavGolFilterJs::fromScriptValue(const QScriptValue &obj,
                                SavGolFilter &savGolFilter)
{
  savGolFilter = qvariant_cast<SavGolFilter>(obj.data().toVariant());
}


QScriptValue
SavGolFilterJs::toScriptValue(QScriptEngine *eng, const SavGolParams &params)
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";
  QScriptValue obj = eng->newObject();

  obj.setProperty("nL", params.nL);
  obj.setProperty("nR", params.nR);
  obj.setProperty("m", params.m);
  obj.setProperty("lD", params.lD);
  obj.setProperty("convolveWithNr", params.convolveWithNr);

  return obj;
}


void
SavGolFilterJs::fromScriptValue(const QScriptValue &obj, SavGolParams &params)
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()";
  params.nL             = obj.property("nL").toInt32();
  params.nR             = obj.property("nR").toInt32();
  params.m              = obj.property("m").toInt32();
  params.lD             = obj.property("lD").toInt32();
  params.convolveWithNr = obj.property("convolveWithNr").toBoolean();
}


// See, for the JS-doc the SavGolParamsJSDoc.cpp file
QScriptValue
SavGolFilterJs::SavGolParamsCtor(QScriptContext *ctx, QScriptEngine *engine)
{
  // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
  //<< "with backtrace:" << ctx->backtrace();

  int argCount = ctx->argumentCount();

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

      if(!arg.isValid())
        {
          // Return a default-initialized SavGolParams object.
          SavGolParams params;
          return engine->toScriptValue(params);
        }

      // Let's check if the object passed as parameter in the script
      // environment is actually a SavGolParams object.

      QScriptValue nLValue = arg.property("nL", QScriptValue::ResolveLocal);

      if(nLValue.isValid())
        {

          // Finally, we can take the constructor parameter as a SavGolParams
          // argument.
          SavGolParams params = qscriptvalue_cast<SavGolParams>(arg);

          // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
          //<< "We are constructing from SavGolParams.";

          return engine->toScriptValue(params);
        }
      else
        {
          // Check if the object is a SavGolFilter object

          QScriptValue samplesValue =
            arg.property("samples", QScriptValue::ResolveLocal);

          if(samplesValue.isValid())
            {

              // Finally, we can take the constructor parameter as a
              // SavGolFilter argument.
              SavGolFilter filter = qscriptvalue_cast<SavGolFilter>(arg);

              // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
              //<< "We are constructing from SavGolFilter.";

              return engine->toScriptValue(filter);
            }
          else
            {
              // Nothing to do, we do not have anything to base a construction
              // on. Return a default-initialized SavGolParams object.
              SavGolParams params;
              return engine->toScriptValue(params);
            }
        }
    }
  // End of
  // if(argCount == 1)
  else if(argCount == 5)
    {
      // We are constructing from raw values.

      // qDebug() << __FILE__ << __LINE__ << __FUNCTION__ << "()"
      //<< "We are constructing from raw values.";

      QScriptValue nLJSValue             = ctx->argument(0);
      QScriptValue nRJSValue             = ctx->argument(1);
      QScriptValue mJSValue              = ctx->argument(2);
      QScriptValue lDJSValue             = ctx->argument(3);
      QScriptValue convolveWithNrJSValue = ctx->argument(4);

      SavGolParams params;

      params.nL             = static_cast<int>(nLJSValue.toNumber());
      params.nR             = static_cast<int>(nRJSValue.toNumber());
      params.m              = static_cast<int>(mJSValue.toNumber());
      params.lD             = static_cast<int>(lDJSValue.toNumber());
      params.convolveWithNr = convolveWithNrJSValue.toBoolean();

      return engine->toScriptValue(params);
    }

  SavGolParams params;
  return engine->toScriptValue(params);
}


} // namespace msXpSmineXpert
