/***************************************************************************
                                kstplugin.cpp
                             -------------------
    begin                : May 15 2003
    copyright            : (C) 2003 The University of Toronto
    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.                                   *
 *                                                                         *
 ***************************************************************************/

#include <stdlib.h>
#include <unistd.h>

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

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

// application specific includes
#include "kstdatacollection.h"
#include "kstdebug.h"
#include "kstplugin.h"
#include "kstplugindialog_i.h"
#include "plugincollection.h"


KstPlugin::KstPlugin() : KstDataObject() {
  _inStringCnt = 0;
  _outStringCnt = 0;
  commonConstructor();
}


KstPlugin::KstPlugin(QDomElement &e) : KstDataObject(e) {
  QString pluginName;

  _inStringCnt = 0;
  _outStringCnt = 0;
  commonConstructor();

  QDomNode n = e.firstChild();

  while (!n.isNull()) {
    QDomElement e = n.toElement();
    if (!e.isNull()) {
      if (e.tagName() == "tag") {
        setTagName(e.text());
      } else if (e.tagName() == "name") {
        pluginName = e.text();
      } else if (e.tagName() == "ivector") {
        _inputVectorLoadQueue.append(qMakePair(e.attribute("name"), e.text()));
      } else if (e.tagName() == "iscalar") {
        _inputScalarLoadQueue.append(qMakePair(e.attribute("name"), e.text()));
      } else if (e.tagName() == "ovector") {
        KstVectorPtr v;

        // FIXME: these might already exist!!
        if (e.attribute("scalarList", "0").toInt()) {
          v = new KstVector(e.text(), 0, true);
        } else {
          v = new KstVector(e.text(), 0, false);
        }
        _outputVectors.insert(e.attribute("name"), v);
        KST::addVectorToList(v);
      } else if (e.tagName() == "oscalar") {
        // FIXME: these might already exist!!
        _outputScalars.insert(e.attribute("name"), new KstScalar(e.text()));
      }
    }
    n = n.nextSibling();
  }

  _plugin = PluginCollection::self()->plugin(pluginName);

  if (!_plugin.data()) {
    KstDebug::self()->log(i18n("Unable to load plugin %1 for \"%2\".").arg(pluginName).arg(tagName()), KstDebug::Warning);
  } else {
    Plugin::countScalarsVectorsAndStrings(_plugin->data()._inputs, _inScalarCnt, _inArrayCnt, _inStringCnt, _inPid);

    const QValueList<Plugin::Data::IOValue>& otable = _plugin->data()._outputs;
    for (QValueList<Plugin::Data::IOValue>::ConstIterator it = otable.begin();
                                                           it != otable.end();
                                                                         ++it) {
      // FIXME: i18n?
      if ((*it)._type == Plugin::Data::IOValue::TableType) {
        _outArrayCnt++;
        if (!_outputVectors.contains((*it)._name)) {
          KstVectorPtr v;

          if ((*it)._subType == Plugin::Data::IOValue::FloatNonVectorSubType) {
            v = new KstVector(tagName() + " vector - " + (*it)._name, 0, true);
          } else {
            v = new KstVector(tagName() + " vector - " + (*it)._name, 0, false);
          }
          _outputVectors.insert((*it)._name, v);
          KST::addVectorToList(v);
        }
      } else if ((*it)._type == Plugin::Data::IOValue::FloatType) {
        _outScalarCnt++;
        if (!_outputScalars.contains((*it)._name)) {
          KstScalarPtr s = new KstScalar(tagName() + " scalar - " + (*it)._name);
          _outputScalars.insert((*it)._name, s);
        }
      } else if ((*it)._type == Plugin::Data::IOValue::StringType) {
        _outStringCnt++;
        if (!_outputStrings.contains((*it)._name)) {
          KstStringPtr s = new KstString(tagName() + " string - " + (*it)._name);
          _outputStrings.insert((*it)._name, s);
        }
      }
    }
    allocateParameters();
  }
}


void KstPlugin::commonConstructor() {
  _inArrayLens = 0L;
  _outArrayLens = 0L;
  _inScalars = 0L;
  _outScalars = 0L;
  _inVectors = 0L;
  _outVectors = 0L;
  _inStrings = 0L;
  _outStrings = 0L;

  _inScalarCnt = 0;
  _outScalarCnt = 0;
  _inArrayCnt = 0;
  _outArrayCnt = 0;
  _typeString = i18n("Plugin");
  _plugin = 0L;
  _localData = 0L;
  //kdDebug() << "Creating KSTPlugin: " << long(this) << endl;
}


KstPlugin::~KstPlugin() {
  freeParameters();
  if (_localData) {
    if (!_plugin || !_plugin->freeLocalData(&_localData)) {
      free(_localData);
    }
    _localData = 0L;
  }
  //kdDebug() << "Destroying KSTPlugin: " << long(this) << endl;
}



void KstPlugin::allocateParameters() {
  if (_inArrayCnt > 0) {
    _inArrayLens = new int[_inArrayCnt];
    _inVectors = new double*[_inArrayCnt];
  }

  if (_outArrayCnt > 0) {
    _outArrayLens = new int[_outArrayCnt];
    _outVectors = new double*[_outArrayCnt];
  }

  if (_inScalarCnt > 0) {
    _inScalars = new double[_inScalarCnt];
  }

  if (_outScalarCnt > 0) {
    _outScalars = new double[_outScalarCnt];
  }

  if (_inStringCnt > 0) {
    _inStrings = new char*[_inStringCnt];
    memset(_inStrings, 0, sizeof(char*)*_inStringCnt);
  }

  if (_outStringCnt > 0) {
    _outStrings = new char*[_outStringCnt];
    memset(_outStrings, 0, sizeof(char*)*_outStringCnt);
  }
}


void KstPlugin::freeParameters() {
  delete[] _inVectors;
  _inVectors = 0L;
  delete[] _outVectors;
  _outVectors = 0L;
  delete[] _outArrayLens;
  _outArrayLens = 0L;
  delete[] _inArrayLens;
  _inArrayLens = 0L;
  delete[] _outScalars;
  _outScalars = 0L;
  delete[] _inScalars;
  _inScalars = 0L;
  if (_outStrings) {
    for (unsigned i = 0; i < _outStringCnt; ++i) {
      if (_outStrings[i]) {
        free(_outStrings[i]);
        _outStrings[i] = 0L;
      }
    }
  }
  delete[] _outStrings;
  _outStrings = 0L;
  if (_inStrings) {
    for (unsigned i = 0; i < _inStringCnt; ++i) {
      if (_inStrings[i]) {
        free(_inStrings[i]);
        _inStrings[i] = 0L;
      }
    }
  }
  delete[] _inStrings;
  _inStrings = 0L;
}


KstObject::UpdateType KstPlugin::update(int update_counter) {
  if (!isValid()) {
    return NO_CHANGE;
  }

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

  if (update_counter != -1) {
    //bool force = false;
    // FIXME: we should only update if one of our dependencies was updated
  }

#define CLEANUP() do {\
  for (unsigned i = 0; i < _outStringCnt; ++i) { \
    if (_outStrings[i]) { \
      free(_outStrings[i]); \
      _outStrings[i] = 0L; \
    } \
  } \
  for (unsigned i = 0; i < _inStringCnt; ++i) { \
    if (_inStrings[i]) { \
      free(_inStrings[i]); \
      _inStrings[i] = 0L; \
    } \
  } \
  } while(0)


  const QValueList<Plugin::Data::IOValue>& itable = _plugin->data()._inputs;
  const QValueList<Plugin::Data::IOValue>& otable = _plugin->data()._outputs;
  bool bNew = false; // what does this variable name mean?  That's why we don't
                    // use HN in Kst!!
  int itcnt = 0, vitcnt = 0, sitcnt = 0;

  // Populate the input scalars and vectors
  for (QValueList<Plugin::Data::IOValue>::ConstIterator it = itable.begin();
                                                         it != itable.end();
                                                                        ++it) {
    if ((*it)._type == Plugin::Data::IOValue::TableType) {
      if (!_inputVectors.contains((*it)._name)) {
        KstDebug::self()->log(i18n("Input vector [%1] for plugin %2 not found.  Unable to continue.").arg((*it)._name).arg(tagName()), KstDebug::Error);
        CLEANUP();
        return NO_CHANGE;
      }
      if (_inputVectors[(*it)._name]->sampleCount() == _inputVectors[(*it)._name]->numNew()) {
        bNew = true;
      }
      _inVectors[vitcnt] = _inputVectors[(*it)._name]->value();
      _inArrayLens[vitcnt++] = _inputVectors[(*it)._name]->sampleCount();
    } else if ((*it)._type == Plugin::Data::IOValue::FloatType) {
      _inScalars[itcnt++] = _inputScalars[(*it)._name]->value();
    } else if ((*it)._type == Plugin::Data::IOValue::StringType) {
      _inStrings[sitcnt++] = strdup(_inputStrings[(*it)._name]->value().latin1());
    } else if ((*it)._type == Plugin::Data::IOValue::PidType) {
      _inScalars[itcnt++] = getpid();
    }
  }

  vitcnt = 0;
  // Populate the output vectors
  for (QValueList<Plugin::Data::IOValue>::ConstIterator it = otable.begin();
                                                         it != otable.end();
                                                                        ++it) {
    if ((*it)._type == Plugin::Data::IOValue::TableType) {
      if (!_outputVectors.contains((*it)._name)) {
        KstDebug::self()->log(i18n("Output vector [%1] for plugin %2 not found.  Unable to continue.").arg((*it)._name).arg(tagName()), KstDebug::Error);
        CLEANUP();
        return NO_CHANGE;
      }
      _outVectors[vitcnt] = _outputVectors[(*it)._name]->value();
      _outArrayLens[vitcnt++] = _outputVectors[(*it)._name]->length();
    }
  }

  if (_outStringCnt > 0) {
    memset(_outStrings, 0, _outStringCnt*sizeof(char *));
  }

  int rc;
  if (_inStringCnt > 0 || _outStringCnt > 0) {
    if (_plugin->data()._localdata) {
      rc = _plugin->call(_inVectors, _inArrayLens, _inScalars,
          _outVectors, _outArrayLens, _outScalars,
          const_cast<const char**>(_inStrings), _outStrings, &_localData);
    } else {
      rc = _plugin->call(_inVectors, _inArrayLens, _inScalars,
          _outVectors, _outArrayLens, _outScalars,
          const_cast<const char**>(_inStrings), _outStrings);
    }
  } else {
    if (_plugin->data()._localdata) {
      rc = _plugin->call(_inVectors, _inArrayLens, _inScalars,
          _outVectors, _outArrayLens, _outScalars, &_localData);
    } else {
      rc = _plugin->call(_inVectors, _inArrayLens, _inScalars,
          _outVectors, _outArrayLens, _outScalars);
    }
  }

  if (rc == 0) {
    itcnt = 0;
    vitcnt = 0;
    sitcnt = 0;
    // Read back the output vectors and scalars
    for (QValueList<Plugin::Data::IOValue>::ConstIterator it = otable.begin();
        it != otable.end();
        ++it) {
      if ((*it)._type == Plugin::Data::IOValue::TableType) {
        vectorRealloced(_outputVectors[(*it)._name], _outVectors[vitcnt], _outArrayLens[vitcnt], bNew);
        vitcnt++;
      } else if ((*it)._type == Plugin::Data::IOValue::FloatType) {
        _outputScalars[(*it)._name]->setValue(_outScalars[itcnt++]);
      } else if ((*it)._type == Plugin::Data::IOValue::StringType) {
        _outputStrings[(*it)._name]->setValue(_outStrings[sitcnt++]);
      }
    }

    // if we have a fit plugin then create the necessary scalars from the parameter vector...
    if (_plugin->data()._isFit) {
      KstVectorPtr vectorParam = _outputVectors["Parameters"];
      if (vectorParam) {
        for (int i = 0; i < vectorParam->length(); i++) {
          const QString& paramName = _plugin->parameterName(i);
          if (!paramName.isEmpty()) {
            QString scalarName = i18n("%1-%2").arg(tagName()).arg(paramName);
            double scalarValue = vectorParam->value(i);
            if (!_outputScalars.contains(paramName)) {
              KstScalarPtr s = new KstScalar(scalarName);
              _outputScalars.insert(paramName, s);
            }
            _outputScalars[paramName]->setValue(scalarValue);
          }
        }
      }
    }
    _lastError = QString::null;
  } else if (rc > 0) {
    if (_lastError.isEmpty()) {
      const char *err = _plugin->errorCode(rc);
      if (err && *err) {
        _lastError = err;
        KstDebug::self()->log(i18n("Plugin %1 produced error: %2.").arg(tagName()).arg(_lastError), KstDebug::Error);
      } else {
        _lastError = QString::null;
      }
    }
  } else {
    bool doSend = _lastError.isEmpty() ? true : false;

    switch (rc) {
      case -1:
        _lastError = i18n("Generic Error");
        break;
      case -2:
        _lastError = i18n("Input Error");
        break;
      case -3:
        _lastError = i18n("Memory Error");
        break;
      default:
        _lastError = i18n("Unknown Error");
        break;
    }

    if (doSend) {
      KstDebug::self()->log(i18n("Plugin %2 produced error: %1.").arg(_lastError).arg(tagName()), KstDebug::Error);
    }
  }
  
  CLEANUP();
#undef CLEANUP
  return UPDATE;
}


void KstPlugin::save(QTextStream &ts, const QString& indent) {
  if (!_plugin) {
    return;
  }

  QString l2 = indent + "  ";
  ts << indent << "<plugin>" << endl;
  ts << l2 << "<tag>" << QStyleSheet::escape(tagName()) << "</tag>" << endl;
  ts << l2 << "<name>" << QStyleSheet::escape(_plugin->data()._name) << "</name>" << endl;
  for (KstVectorMap::Iterator i = _inputVectors.begin(); i != _inputVectors.end(); ++i) {
    ts << l2 << "<ivector name=\"" << QStyleSheet::escape(i.key()) << "\">"
      << QStyleSheet::escape(i.data()->tagName())
      << "</ivector>" << endl;
  }
  for (KstScalarMap::Iterator i = _inputScalars.begin(); i != _inputScalars.end(); ++i) {
    ts << l2 << "<iscalar name=\"" << QStyleSheet::escape(i.key()) << "\">"
      << QStyleSheet::escape(i.data()->tagName())
      << "</iscalar>" << endl;
  }
  for (KstStringMap::Iterator i = _inputStrings.begin(); i != _inputStrings.end(); ++i) {
    ts << l2 << "<istring name=\"" << QStyleSheet::escape(i.key()) << "\">"
      << QStyleSheet::escape(i.data()->tagName())
      << "</istring>" << endl;
  }
  for (KstVectorMap::Iterator i = _outputVectors.begin(); i != _outputVectors.end(); ++i) {
    ts << l2 << "<ovector name=\"" << QStyleSheet::escape(i.key());
    if (i.data()->isScalarList()) {
      ts << "\" scalarList=\"1";
    }
    ts << "\">" << QStyleSheet::escape(i.data()->tagName())
      << "</ovector>" << endl;
  }
  for (KstScalarMap::Iterator i = _outputScalars.begin(); i != _outputScalars.end(); ++i) {
    ts << l2 << "<oscalar name=\"" << QStyleSheet::escape(i.key()) << "\">"
      << QStyleSheet::escape(i.data()->tagName())
      << "</oscalar>" << endl;
  }
  for (KstStringMap::Iterator i = _outputStrings.begin(); i != _outputStrings.end(); ++i) {
    ts << l2 << "<ostring name=\"" << QStyleSheet::escape(i.key()) << "\">"
      << QStyleSheet::escape(i.data()->tagName())
      << "</ostring>" << endl;
  }
  ts << indent << "</plugin>" << endl;
}


bool KstPlugin::slaveVectorsUsed() const {
  return true;
}


bool KstPlugin::isValid() const {
  return _inputVectors.count() == _inArrayCnt &&
         _inputScalars.count() == _inScalarCnt - _inPid &&
         _inputStrings.count() == _inStringCnt &&
         _plugin.data() != 0L;
}


QString KstPlugin::propertyString() const {
  if (!isValid()) {
    return i18n("Invalid plugin.");
  }
  return plugin()->data()._name;
}


bool KstPlugin::setPlugin(KstSharedPtr<Plugin> plugin) {
  // Assumes that this is called with a write lock in place on this object
  if (plugin == _plugin) {
    return true;
  }

  freeParameters();

  if (_localData) {
    if (!_plugin || !_plugin->freeLocalData(&_localData)) {
      free(_localData);
    }
    _localData = 0L;
  }

  if (!plugin) {
    _inputVectors.clear();
    _inputScalars.clear();
    _inputStrings.clear();
    _outputVectors.clear();
    _outputScalars.clear();
    _outputStrings.clear();
    _plugin = 0L;
    return true;
  }

  Plugin::countScalarsVectorsAndStrings(plugin->data()._inputs, _inScalarCnt, _inArrayCnt, _inStringCnt, _inPid);

  if (_inputVectors.count() != _inArrayCnt ||
      _inputScalars.count() != _inScalarCnt - _inPid ||
      _inputStrings.count() != _inStringCnt) {
    _plugin = 0L;
    return false;
  }

  _outScalarCnt = 0;
  _outArrayCnt = 0;
  _outStringCnt = 0;
  _outputVectors.clear();
  _outputScalars.clear();
  _outputStrings.clear();

  const QValueList<Plugin::Data::IOValue>& otable = plugin->data()._outputs;
  for (QValueList<Plugin::Data::IOValue>::ConstIterator it = otable.begin();
                                                         it != otable.end();
                                                                        ++it) {
    if ((*it)._type == Plugin::Data::IOValue::TableType) {
      KstVectorPtr v;

      if ((*it)._subType == Plugin::Data::IOValue::FloatNonVectorSubType) {
        v = new KstVector(QString::null, 0, true);
      } else {
        v = new KstVector;
      }
      v->writeLock();
      _outputVectors.insert((*it)._name, v);
      _outArrayCnt++;
      KST::addVectorToList(v);
    } else if ((*it)._type == Plugin::Data::IOValue::FloatType) {
      KstScalarPtr s = new KstScalar;
      s->writeLock();
      _outputScalars.insert((*it)._name, s);
      _outScalarCnt++;
    } else if ((*it)._type == Plugin::Data::IOValue::StringType) {
      KstStringPtr s = new KstString;
      s->writeLock();
      _outputStrings.insert((*it)._name, s);
      _outStringCnt++;
    }
  }

  allocateParameters();
  _plugin = plugin;
  return true;
}


KstSharedPtr<Plugin> KstPlugin::plugin() const {
  return _plugin;
}


void KstPlugin::_showDialog() {
  KstPluginDialogI::globalInstance()->show_Edit(tagName());
}


const KstCurveHintList* KstPlugin::curveHints() const {
  _curveHints->clear();
  if (_plugin) {
    for (QValueList<Plugin::Data::CurveHint>::ConstIterator i = _plugin->data()._hints.begin(); i != _plugin->data()._hints.end(); ++i) {
      _curveHints->append(new KstCurveHint((*i).name, _outputVectors[(*i).x]->tagName(), _outputVectors[(*i).y]->tagName()));
    }
  }
  return KstDataObject::curveHints();
}


QString KstPlugin::lastError() const {
  return _lastError;
}

// vim: ts=2 sw=2 et
