/***************************************************************************
                          kstequation.cpp: Equations for KST
                             -------------------
    begin                : Fri Feb 10 2002
    copyright            : (C) 2002 by C. Barth Netterfield
    email                :
 ***************************************************************************/

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

/** A class for handling equations for kst
 *@author C. Barth Netterfield
 */

#include <assert.h>
#include <math.h>

// include files for Qt
#include <qstylesheet.h>

// include files for KDE
#include <kdebug.h>
#include <klocale.h>

// application specific includes
#include "enodes.h"
#include "kstdatacollection.h"
#include "kstequation.h"
#include "ksteqdialog_i.h"

extern "C" int yyparse();
extern "C" void *ParsedEquation;
extern "C" struct yy_buffer_state *yy_scan_string(const char*);

const QString KstEquation::XVECTOR = "X";
const QString KstEquation::OUTVECTOR = "O"; // Output (slave) vector


KstEquation::KstEquation(const QString& in_tag, const QString& equation, double x0, double x1, int nx)
: KstDataObject() {
  _staticX = true;
  _doInterp = false;
  _xVector = _inputVectors.insert(XVECTOR, KstVector::generateVector(x0, x1, nx, QString::null));
  commonConstructor(in_tag, equation);
  update();
}


KstEquation::KstEquation(const QString& in_tag, const QString& equation, KstVectorPtr xvector, bool do_interp)
: KstDataObject() {
  _staticX = false;
  _doInterp = do_interp; //false;
  _xVector = _inputVectors.insert(XVECTOR, xvector);

  commonConstructor(in_tag, equation);
  update();
}


KstEquation::KstEquation(QDomElement &e)
: KstDataObject(e) {
  QString in_tag, equation;

  int ns = -1;
  double x0 = 0.0, x1 = 1.0;
  QString xvtag;

  _staticX = false;
  _doInterp = false;

  /* parse the DOM tree */
  QDomNode n = e.firstChild();
  while (!n.isNull()) {
    QDomElement e = n.toElement(); // try to convert the node to an element.
    if (!e.isNull()) { // the node was really an element.
      if (e.tagName() == "tag") {
        in_tag = e.text();
      } else if (e.tagName() == "equation") {
        equation = e.text();
      } else if (e.tagName() == "x0") {
        x0 = e.text().toDouble();
      } else if (e.tagName() == "x1") {
        x1 = e.text().toDouble();
      } else if (e.tagName() == "ns") {
        ns = e.text().toInt();
      } else if (e.tagName() == "xvtag") {
        xvtag = e.text();
      } else if (e.tagName() == "xvector") {
        _inputVectorLoadQueue.append(qMakePair(XVECTOR, e.text()));
      } else if (e.tagName() == "interpolate") {
        _doInterp = true;
      }
    }
    n = n.nextSibling();
  }

  if (_inputVectorLoadQueue.isEmpty()) {
    if (ns < 0) {
      ns = 2;
    }
    if (x0 == x1) {
      x1 = x0 + 2;
    }
    _staticX = true;
    _doInterp = false;
    _xVector = _inputVectors.insert(XVECTOR, KstVector::generateVector(x0, x1, ns, xvtag));
  } else {
    _xVector = _inputVectors.end();
  }
  commonConstructor(in_tag, equation);
}


KstEquation::~KstEquation() {
  if (_staticX) {
    _xVector = _inputVectors.end();
    KST::vectorList.lock().writeLock();
    KST::vectorList.remove(_inputVectors[XVECTOR]);
    KST::vectorList.lock().writeUnlock();
  }

  delete _pe;
  _pe = 0L;
}


bool KstEquation::loadInputs() {
  if (!_staticX) {
    return KstDataObject::loadInputs();
  } else {
    update();
    return true;
  }
}


void KstEquation::commonConstructor(const QString& in_tag, const QString& in_equation) {
  _ns = 2;
  _pe = 0L;
  _typeString = i18n("Equation");
  KstObject::setTagName(in_tag);

  KstVectorPtr yv = new KstVector(in_tag + "-sv", 2);
  KST::addVectorToList(yv);
  _yVector = _outputVectors.insert(OUTVECTOR, yv);
  yv->setProvider(this);
  yv->zero();

  _isValid = false;
  _numNew = _numShifted = 0;

  setEquation(in_equation);

  if (_xVector == _inputVectors.end()) {
    _curveHints->append(new KstCurveHint(i18n("Equation Curve"), (*_inputVectorLoadQueue.begin()).second, (*_yVector)->tagName()));
  } else {
    _curveHints->append(new KstCurveHint(i18n("Equation Curve"), (*_xVector)->tagName(), (*_yVector)->tagName()));
  }
}


bool KstEquation::isValid() const {
  return _isValid;
}


KstObject::UpdateType KstEquation::update(int update_counter) {
  bool force = false;

  if (KstObject::checkUpdateCounter(update_counter)) {
    return NO_CHANGE;
  }

  if (update_counter <= 0) {
    force = true;
  } else {
    if (*_xVector) {
      Equation::Context ctx;
      ctx.sampleCount = _ns;
      ctx.xVector = *_xVector;
      _pe->update(update_counter, &ctx);
    }
  }

  if (_xVector == _inputVectors.end()) {
    _xVector = _inputVectors.find(XVECTOR);
    if (!*_xVector) { // This is technically sort of fatal
      return NO_CHANGE;
    }
  }

  KstVectorPtr v = *_xVector;
  _isValid = FillY(force);
  v = *_yVector;
  v->update(update_counter);

  return UPDATE;
}


void KstEquation::save(QTextStream &ts, const QString& indent) {
  QString l2 = indent + "  ";
  ts << indent << "<equationobject>" << endl;
  ts << l2 << "<tag>" << QStyleSheet::escape(tagName()) << "</tag>" << endl;
  ts << l2 << "<equation>" << QStyleSheet::escape(_equation) << "</equation>" << endl;
  if (_staticX) {
    ts << l2 << "<x0>" << QString::number((*_xVector)->min()) << "</x0>" << endl;
    ts << l2 << "<x1>" << QString::number((*_xVector)->max()) << "</x1>" << endl;
    ts << l2 << "<ns>" << QString::number((*_xVector)->sampleCount()) << "</ns>" << endl;
    ts << l2 << "<xvtag>" << QStyleSheet::escape((*_xVector)->tagName()) << "</xvtag>" << endl;
  } else {
    ts << l2 << "<xvector>" << QStyleSheet::escape((*_xVector)->tagName()) << "</xvector>" << endl;
    if (_doInterp) {
      ts << l2 << "<interpolate/>" << endl;
    }
  }

  ts << indent << "</equationobject>" << endl;
}


void KstEquation::setEquation(const QString& in_fn) {
  // assert(*_xVector); - ugly, we have to allow this here due to
  // document loading with vector lazy-loading
  _equation = in_fn;
  VectorsUsed.clear();
  _ns = 2; // reset the updating
  delete _pe;
  _pe = 0L;
  if (!_equation.isEmpty()) {
    yy_scan_string(_equation.latin1());
    int rc = yyparse();
    if (rc == 0) {
      _pe = static_cast<Equation::Node*>(ParsedEquation);
      Equation::Context ctx;
      ctx.sampleCount = _ns;
      ctx.xVector = *_xVector;
      Equation::FoldVisitor vis(&ctx, _pe);
      _pe->collectVectors(VectorsUsed);
      _pe->update(-1, &ctx);
      ParsedEquation = 0L;
    } else {
      delete (Equation::Node*)ParsedEquation;
      ParsedEquation = 0L;
    }
  }
  _isValid = _pe != 0L;
}


void KstEquation::setExistingXVector(KstVectorPtr in_xv, bool do_interp) {
  KstVectorPtr v = _inputVectors[XVECTOR];
  if (v) {
    if (v == in_xv) {
      return;
    }
    v->writeUnlock();
  }

  if (_staticX) {
    _xVector = _inputVectors.end();
    KST::vectorList.lock().writeLock();
    KST::vectorList.remove(_inputVectors[XVECTOR]);
    KST::vectorList.lock().writeUnlock();
  }

  _inputVectors.erase(XVECTOR);
  in_xv->writeLock();
  _xVector = _inputVectors.insert(XVECTOR, in_xv);

  _ns = 2; // reset the updating
  _staticX = false;
  _doInterp = do_interp;
}


void KstEquation::setStaticXVector(KstVectorPtr vp) {
  KstVectorPtr v = _inputVectors[XVECTOR];
  if (v) {
    v->writeUnlock();
  }

  if (_staticX) {
    _xVector = _inputVectors.end();
    KST::vectorList.lock().writeLock();
    KST::vectorList.remove(_inputVectors[XVECTOR]);
    KST::vectorList.lock().writeUnlock();
  }

  _inputVectors.erase(XVECTOR);
  _staticX = true;
  vp->writeLock();
  _xVector = _inputVectors.insert(XVECTOR, vp);
}


void KstEquation::setTagName(const QString &in_tag) {
  KstObject::setTagName(in_tag);
  (*_yVector)->setTagName(in_tag+"-sv");
  if (_staticX) {
    (*_xVector)->setTagName(in_tag);
  }
}


bool KstEquation::slaveVectorsUsed() const {
  return _staticX;
}


/************************************************************************/
/*                                                                      */
/*                     	Fill Y: Evaluates the equation                 	*/
/*                                                                     	*/
/************************************************************************/
bool KstEquation::FillY(bool force) {
  unsigned i_v;
  int v_shift, v_new;
  int i0;
  int ns;

  // determine value of Interp
  if (_doInterp) {
    ns = (*_xVector)->sampleCount();
    for (i_v = 0; i_v < VectorsUsed.count(); i_v++) {
      if (VectorsUsed[i_v]->sampleCount() > ns) {
        ns = VectorsUsed[i_v]->sampleCount();
      }
    }
  } else {
    ns = (*_xVector)->sampleCount();
  }

  if (_ns != (*_xVector)->sampleCount() || (ns != (*_xVector)->sampleCount()) ||
      (*_xVector)->numShift() != (*_xVector)->numNew()) {
    _ns = ns;

    KstVectorPtr yv = *_yVector;
    if (!yv->resize(_ns)) {
      // FIXME: handle error?
      return false;
    }
    yv->zero();
    i0 = 0; // other vectors may have diffent lengths, so start over
    v_shift = _ns;
  } else {
    // calculate shift and new samples
    // only do shift optimization if all used vectors are same size and shift
    v_shift = (*_xVector)->numShift();
    v_new = (*_xVector)->numNew();

    for (i_v = 0; i_v < VectorsUsed.count(); i_v++) {
      if (v_shift != VectorsUsed[i_v]->numShift()) {
        v_shift = _ns;
      }
      if (v_new != VectorsUsed[i_v]->numNew()) {
        v_shift = _ns;
      }
      if (_ns != VectorsUsed[i_v]->sampleCount()) {
        v_shift = _ns;
      }
    }

    if (v_shift > _ns/2 || force) {
      i0 = 0;
      v_shift = _ns;
    } else {
      KstVectorPtr yv = *_yVector;
      for (int i = v_shift; i < _ns; i++) {
        yv->value()[i - v_shift] = yv->value()[i];
      }
      i0 = _ns - v_shift;
    }
  }

  _numShifted = (*_yVector)->numShift() + v_shift;
  if (_numShifted > _ns) {
    _numShifted = _ns;
  }

  _numNew = _ns - i0 + (*_yVector)->numNew();
  if (_numNew > _ns) {
    _numNew = _ns;
  }

  (*_yVector)->setNewAndShift(_numNew, _numShifted);

  double *rawv = (*_yVector)->value();
  KstVectorPtr iv = (*_xVector);

  Equation::Context ctx;
  ctx.sampleCount = _ns;
  ctx.xVector = iv;

  if (!_pe) {
    if (_equation.isEmpty()) {
      return true;
    }

    yy_scan_string(_equation.latin1());
    int rc = yyparse();
    if (rc == 0) {
      _pe = static_cast<Equation::Node*>(ParsedEquation);
      Equation::FoldVisitor vis(&ctx, _pe);
      _pe->collectVectors(VectorsUsed);
      ParsedEquation = 0L;
    } else {
      delete (Equation::Node*)ParsedEquation;
      ParsedEquation = 0L;
      return false;
    }
  }

  for (ctx.i = i0; ctx.i < _ns; ++ctx.i) {
    ctx.x = iv->interpolate(ctx.i, _ns);
    rawv[ctx.i] = _pe->value(&ctx);
  }

  return true;
}


QString KstEquation::propertyString() const {
  return equation();
}


void KstEquation::_showDialog() {
  KstEqDialogI::globalInstance()->show_Edit(tagName());
}


bool KstEquation::uses(KstObjectPtr p) const {
  return KstDataObject::uses(p) || VectorsUsed.contains(kst_cast<KstVector>(p));
}

// vim: ts=2 sw=2 et
