/***************************************************************************
                   kstdatasource.cpp  -  abstract data source
                             -------------------
    begin                : Thu Oct 16 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 "kstdatasource.h"

#include <assert.h>

#include <kdebug.h>
#include <klibloader.h>
#include <klocale.h>
#include <kservicetype.h>

#include <qdeepcopy.h>
#include <qfile.h>
#include <qstylesheet.h>

#include "kst.h"
#include "kstdatacollection.h"
#include "kstdebug.h"
#include "stdinsource.h"


// Eventually this will move to another file but I leave it here until then
// to avoid confusion between the function plugins and Kst applicaton plugins.
namespace KST {
  class Plugin : public KstShared {
    public:
      Plugin(KService::Ptr svc) : KstShared(), service(svc), _lib(0L) {
        assert(service);
        _plugLib = service->property("X-Kst-Plugin-Library").toString();
        //kdDebug() << "Create plugin " << (void*)this << " " << service->property("Name").toString() << endl;
      }

      virtual ~Plugin() {
        //kdDebug() << "Destroy plugin " << (void*)this << " " << service->property("Name").toString() << endl;
        if (_lib) {
          _lib->unload();
        }
      }

      KstDataSource *create(KConfig *cfg, const QString& filename, const QString& type = QString::null) const {
        KstDataSource *(*sym)(KConfig*, const QString&, const QString&) = (KstDataSource*(*)(KConfig*, const QString&, const QString&))symbol("create");
        if (sym) {
          //kdDebug() << "Trying to create " << filename << " type=" << type << " with " << service->property("Name").toString() << endl;
          KstDataSource *ds = (sym)(cfg, filename, type);
          if (ds) {
            ds->_source = service->property("Name").toString();
          }
          //kdDebug() << (ds ? "SUCCESS" : "FAILED") << endl;
          return ds;
        }

        return 0L;
      }

      QStringList fieldList(KConfig *cfg, const QString& filename, const QString& type = QString::null, QString *typeSuggestion = 0L, bool *complete = 0L) const {
        QStringList (*sym)(KConfig*, const QString&, const QString&, QString*, bool*) = (QStringList(*)(KConfig*, const QString&, const QString&, QString*, bool*))symbol("fieldList");
        if (sym) {
          return (sym)(cfg, filename, type, typeSuggestion, complete);
        }

        // fallback incase the helper isn't implemented
        //  (note: less efficient now)
        KstDataSourcePtr ds = create(cfg, filename, type);
        if (ds) {
          QStringList rc = ds->fieldList();
          if (typeSuggestion) {
            *typeSuggestion = ds->fileType();
          }
          if (complete) {
            *complete = ds->fieldListIsComplete();
          }
          return rc;
        }

        return QStringList();
      }

      int understands(KConfig *cfg, const QString& filename) const {
        int (*sym)(KConfig*, const QString&) = (int(*)(KConfig*, const QString&))symbol("understands");
        if (sym) {
          //kdDebug() << "Checking if " << service->property("Name").toString() << " understands " << filename << endl;
          int rc = (sym)(cfg, filename);
          //kdDebug() << "result: " << rc << endl;
          return rc;
        }

        return false;
      }

      bool provides(const QString& type) const {
        return provides().contains(type);
      }

      QStringList provides() const {
        QStringList (*sym)() = (QStringList(*)())symbol("provides");
        if (sym) {
          //kdDebug() << "Checking if " << service->property("Name").toString() << " provides " << type << endl;
          return (sym)();
        }

        return QStringList();
      }

      KstDataSourceConfigWidget *configWidget(KConfig *cfg, const QString& filename) const {
        QWidget *(*sym)(const QString&) = (QWidget *(*)(const QString&))symbol("widget");
        if (sym) {
          QWidget *rc = (sym)(filename);
          KstDataSourceConfigWidget *cw = dynamic_cast<KstDataSourceConfigWidget*>(rc);
          if (cw) {
            cw->setConfig(cfg);
            return cw;
          }
          KstDebug::self()->log(i18n("Error in plugin %1: Configuration widget is of the wrong type.").arg(service->property("Name").toString()), KstDebug::Error);
          delete rc;
        }

        return 0L;
      }

      KService::Ptr service;

    private:
      void *symbol(const QString& sym) const {
        if (!loadLibrary()) {
          return 0L;
        }

        // FIXME: might be a good idea to cache this per-symbol

        QCString s = QFile::encodeName(sym + "_" + _plugLib);
        if (_lib->hasSymbol(s)) {
          return _lib->symbol(s);
        }
        return 0L;
      }

      bool loadLibrary() const {
        assert(service);
        if (_lib) {
          return true;
        }

        QCString libname = QFile::encodeName(QString("kstdata_") + _plugLib);
        _lib = KLibLoader::self()->library(libname);
        if (!_lib) {
          KstDebug::self()->log(i18n("Error loading data-source plugin [%1]: %2").arg(libname).arg(KLibLoader::self()->lastErrorMessage()), KstDebug::Error);
        }
        return _lib != 0L;
      }

      QString _plugLib;
      // mutable so we can lazy load the library, but at the same time
      // use const iterators and provide a nice const interface
      mutable KLibrary *_lib;
  };

  typedef QValueList<KstSharedPtr<KST::Plugin> > PluginInfoList;
}


static KConfig *kConfigObject = 0L;
void KstDataSource::setupOnStartup(KConfig *cfg) {
  kConfigObject = cfg;
}


static KST::PluginInfoList pluginInfo;
void KstDataSource::cleanupForExit() {
  pluginInfo.clear();
  kConfigObject = 0L;
}


// Scans for plugins and stores the information for them in "pluginInfo"
static void scanPlugins() {
  KST::PluginInfoList tmpList;

  KstDebug::self()->log(i18n("Scanning for data-source plugins."));

  KService::List sl = KServiceType::offers("Kst Data Source");
  for (KService::List::ConstIterator it = sl.begin(); it != sl.end(); ++it) {
    for (KST::PluginInfoList::ConstIterator i2 = pluginInfo.begin(); i2 != pluginInfo.end(); ++i2) {
      if ((*i2)->service == *it) {
        tmpList.append(*i2);
        continue;
      }
    }

    KstSharedPtr<KST::Plugin> p = new KST::Plugin(*it);
    tmpList.append(p);
  }

  // This cleans up plugins that have been uninstalled and adds in new ones.
  // Since it is a shared pointer it can't dangle anywhere.
  pluginInfo.clear();
  pluginInfo = QDeepCopy<KST::PluginInfoList>(tmpList);
}


QStringList KstDataSource::pluginList() {
  QStringList rc;

  if (pluginInfo.isEmpty()) {
    scanPlugins();
  }

  for (KST::PluginInfoList::ConstIterator it = pluginInfo.begin(); it != pluginInfo.end(); ++it) {
    rc += (*it)->service->property("Name").toString();
  }

  return rc;
}


namespace {
class PluginSortContainer {
  public:
    KstSharedPtr<KST::Plugin> plugin;
    int match;
    int operator<(const PluginSortContainer& x) const {
      return match > x.match; // yes, this is by design.  biggest go first
    }
    int operator==(const PluginSortContainer& x) const {
      return match == x.match;
    }
};
}


static QValueList<PluginSortContainer> bestPluginsForSource(const QString& filename, const QString& type) {
  QValueList<PluginSortContainer> bestPlugins;
  scanPlugins();

  KST::PluginInfoList info = QDeepCopy<KST::PluginInfoList>(pluginInfo);

  if (!type.isEmpty()) {
    for (KST::PluginInfoList::ConstIterator it = info.begin(); it != info.end(); ++it) {
      if ((*it)->provides(type)) {
        PluginSortContainer psc;
        psc.match = 100;
        psc.plugin = *it;
        bestPlugins.append(psc);
        return bestPlugins;
      }
    }
  }

  for (KST::PluginInfoList::ConstIterator it = info.begin(); it != info.end(); ++it) {
    PluginSortContainer psc;
    if ((psc.match = (*it)->understands(kConfigObject, filename)) > 0) {
      psc.plugin = *it;
      bestPlugins.append(psc);
    }
  }

  qHeapSort(bestPlugins);
  return bestPlugins;
}


static KstDataSourcePtr findPluginFor(const QString& filename, const QString& type) {

  QValueList<PluginSortContainer> bestPlugins = bestPluginsForSource(filename, type);

  for (QValueList<PluginSortContainer>::Iterator i = bestPlugins.begin(); i != bestPlugins.end(); ++i) {
    KstDataSourcePtr plugin = (*i).plugin->create(kConfigObject, filename);
    if (plugin) {
      return plugin;
    }
  }

  return 0L;
}


KstDataSourcePtr KstDataSource::loadSource(const QString& filename, const QString& type) {
  if (filename == "stdin" || filename == "-") {
    return new KstStdinSource(kConfigObject);
  }

  if (filename.isEmpty()) {
    return 0L;
  }

  return findPluginFor(filename, type);
}


KstDataSourceConfigWidget* KstDataSource::configWidget() const {
  return configWidgetForSource(_filename, fileType());
}


KstDataSourceConfigWidget* KstDataSource::configWidgetForPlugin(const QString& plugin) {
  scanPlugins();

  KST::PluginInfoList info = QDeepCopy<KST::PluginInfoList>(pluginInfo);

  for (KST::PluginInfoList::ConstIterator it = info.begin(); it != info.end(); ++it) {
    if ((*it)->service->property("Name").toString() == plugin) {
      return (*it)->configWidget(kConfigObject, QString::null);
    }
  }

  return 0L;
}


KstDataSourceConfigWidget* KstDataSource::configWidgetForSource(const QString& filename, const QString& type) {
  if (filename.isEmpty() || filename == "stdin" || filename == "-") {
    return 0L;
  }

  QValueList<PluginSortContainer> bestPlugins = bestPluginsForSource(filename, type);
  for (QValueList<PluginSortContainer>::Iterator i = bestPlugins.begin(); i != bestPlugins.end(); ++i) {
    KstDataSourceConfigWidget *w = (*i).plugin->configWidget(kConfigObject, filename);
    // Don't iterate.
    return w;
  }

  KstDebug::self()->log(i18n("Could not find a datasource for '%1'(%2), but we found one just prior.  Something is wrong with Kst.").arg(filename).arg(type), KstDebug::Error);
  return 0L;
}


QStringList KstDataSource::fieldListForSource(const QString& filename, const QString& type, QString *outType, bool *complete) {
  if (filename.isEmpty() || filename == "stdin" || filename == "-") {
    return QStringList();
  }

  QValueList<PluginSortContainer> bestPlugins = bestPluginsForSource(filename, type);
  QStringList rc;
  for (QValueList<PluginSortContainer>::Iterator i = bestPlugins.begin(); i != bestPlugins.end(); ++i) {
    QString typeSuggestion;
    rc = (*i).plugin->fieldList(kConfigObject, filename, QString::null, &typeSuggestion, complete);
    if (!rc.isEmpty()) {
      if (outType) {
        if (typeSuggestion.isEmpty()) {
          *outType = (*i).plugin->provides()[0];
        } else {
          *outType = typeSuggestion;
        }
      }
      break;
    }
  }

  return rc;
}


KstDataSourcePtr KstDataSource::loadSource(QDomElement& e) {
  QString filename, type;

  QDomNode n = e.firstChild();
  while (!n.isNull()) {
    QDomElement e = n.toElement();
    if (!e.isNull()) {
      if (e.tagName() == "filename") {
        filename = e.text();
      } else if (e.tagName() == "type") {
        type = e.text();
      }
    }
    n = n.nextSibling();
  }

  if (filename.isEmpty()) {
    return 0L;
  }

  if (filename == "stdin" || filename == "-") {
    return new KstStdinSource(kConfigObject);
  }

  return findPluginFor(filename, type);
}


class KstDataSourcePrivate {
  public:
    KstDataSourcePrivate() {}
    ~KstDataSourcePrivate() {}
};


KstDataSource::KstDataSource(KConfig *cfg, const QString& filename, const QString& type)
: KstObject(), _filename(filename), _cfg(cfg) {
  Q_UNUSED(type)
  _valid = false;
  //d = new KstDataSourcePrivate;
  _numFramesScalar = new KstScalar(filename + i18n("-frames"));
}


KstDataSource::~KstDataSource() {
  KST::scalarList.lock().writeLock();
  KST::scalarList.remove(_numFramesScalar);
  KST::scalarList.lock().writeUnlock();
  _numFramesScalar = 0L;
  //delete d;
}


KstObject::UpdateType KstDataSource::update(int u) {
  Q_UNUSED(u)
  return KstObject::NO_CHANGE;
}


void KstDataSource::updateNumFramesScalar() {
  _numFramesScalar->setValue(frameCount());
}


int KstDataSource::readField(double *v, const QString& field, int s, int n, int skip, int *lastFrameRead) {
  Q_UNUSED(v)
  Q_UNUSED(field)
  Q_UNUSED(s)
  Q_UNUSED(n)
  Q_UNUSED(skip)
  Q_UNUSED(lastFrameRead)
  return -9999; // unsupported
}


int KstDataSource::readField(double *v, const QString& field, int s, int n) {
  Q_UNUSED(v)
  Q_UNUSED(field)
  Q_UNUSED(s)
  Q_UNUSED(n)
  return -1;
}


bool KstDataSource::isValid() const {
  return _valid;
}


bool KstDataSource::isValidField(const QString& field) const {
  Q_UNUSED(field)
  return false;
}


int KstDataSource::samplesPerFrame(const QString &field) {
  Q_UNUSED(field)
  return 0;
}


int KstDataSource::frameCount(const QString& field) const {
  Q_UNUSED(field)
  return 0;
}


QString KstDataSource::fileName() const {
  return _filename;
}


QStringList KstDataSource::fieldList() const {
  return _fieldList;
}


QString KstDataSource::fileType() const {
  return QString::null;
}


void KstDataSource::save(QTextStream &ts, const QString& indent) {
  ts << indent << "<filename>" << QStyleSheet::escape(_filename) << "</filename>" << endl;
  ts << indent << "<type>" << QStyleSheet::escape(fileType()) << "</type>" << endl;
}


void KstDataSource::virtual_hook(int id, void *data) {
  Q_UNUSED(id)
  Q_UNUSED(data)
  //KstObject::virtual_hook(id, data);
}


void *KstDataSource::bufferMalloc(size_t size) {
  return KST::malloc(size);
}


void KstDataSource::bufferFree(void *ptr) {
  return ::free(ptr);
}


void *KstDataSource::bufferRealloc(void *ptr, size_t size) {
  return KST::realloc(ptr, size);
}


bool KstDataSource::fieldListIsComplete() const {
  return true;
}


bool KstDataSource::isEmpty() const {
  return true;
}


bool KstDataSource::reset() {
  return false;
}


const QMap<QString, QString>& KstDataSource::metaData() const {
  return _metaData;
}


const QString& KstDataSource::metaData(const QString& key) const {
  if (_metaData.contains(key)) {
    return _metaData[key];
  } else {
    return QString::null;
  }
}


bool KstDataSource::hasMetaData() const {
  return !_metaData.isEmpty();
}


bool KstDataSource::hasMetaData(const QString& key) const {
  return _metaData.contains(key);
}


bool KstDataSource::supportsTimeConversions() const {
  return false;
}


int KstDataSource::sampleForTime(const QDateTime& time, bool *ok) {
  Q_UNUSED(time)
  if (ok) {
    *ok = false;
  }
  return 0;
}



int KstDataSource::sampleForTime(int milliseconds, bool *ok) {
  Q_UNUSED(milliseconds)
  if (ok) {
    *ok = false;
  }
  return 0;
}



QDateTime KstDataSource::timeForSample(int sample, bool *ok) {
  Q_UNUSED(sample)
  if (ok) {
    *ok = false;
  }
  return QDateTime::currentDateTime();
}



int KstDataSource::relativeTimeForSample(int sample, bool *ok) {
  Q_UNUSED(sample)
  if (ok) {
    *ok = false;
  }
  return 0;
}


/////////////////////////////////////////////////////////////////////////////
KstDataSourceConfigWidget::KstDataSourceConfigWidget()
: QWidget(0L), _cfg(0L) {
}


KstDataSourceConfigWidget::~KstDataSourceConfigWidget() {
}


void KstDataSourceConfigWidget::save() {
}


void KstDataSourceConfigWidget::setConfig(KConfig *cfg) {
  _cfg = cfg;
}

#include "kstdatasource.moc"
// vim: ts=2 sw=2 et
