/***************************************************************************
    copyright            : (C) 2005-2006 by Robby Stephenson
    email                : robby@periapsis.org
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of version 2 of the GNU General Public License as  *
 *   published by the Free Software Foundation;                            *
 *                                                                         *
 ***************************************************************************/

#include "gcstarpluginfetcher.h"
#include "messagehandler.h"
#include "fetchmanager.h"
#include "../collection.h"
#include "../entry.h"
#include "../translators/tellicoimporter.h"
#include "../gui/combobox.h"
#include "../gui/collectiontypecombo.h"
#include "../filehandler.h"
#include "../tellico_kernel.h"
#include "../tellico_debug.h"
#include "../latin1literal.h"
#include "../tellico_utils.h"

#include <kconfig.h>
#include <kprocess.h>
#include <kprocio.h>
#include <kstandarddirs.h>
#include <kaccelmanager.h>

#include <tqdir.h>
#include <tqlayout.h>
#include <tqlabel.h>
#include <tqwhatsthis.h>

using Tellico::Fetch::GCstarPluginFetcher;

GCstarPluginFetcher::PluginMap GCstarPluginFetcher::pluginMap;
GCstarPluginFetcher::PluginParse GCstarPluginFetcher::pluginParse = NotYet;

//static
GCstarPluginFetcher::PluginList GCstarPluginFetcher::plugins(int collType_) {
  if(!pluginMap.contains(collType_)) {
    GUI::CursorSaver cs;
    TQString gcstar = KStandardDirs::findExe(TQString::fromLatin1("gcstar"));

    if(pluginParse == NotYet) {
      KProcIO proc;
      proc << gcstar << TQString::fromLatin1("--version");
      // wait 5 seconds at most, just a sanity thing, never want to block completely
      if(proc.start(KProcess::Block) && proc.wait(5)) {
        TQString output;
        proc.readln(output);
        if(!output.isEmpty()) {
          // always going to be x.y[.z] ?
          TQRegExp versionRx(TQString::fromLatin1("(\\d+)\\.(\\d+)(?:\\.(\\d+))?"));
          if(versionRx.search(output) > -1) {
            int x = versionRx.cap(1).toInt();
            int y = versionRx.cap(2).toInt();
            int z = versionRx.cap(3).toInt(); // ok to be empty
            myDebug() << TQString::fromLatin1("GCstarPluginFetcher() - found %1.%2.%3").arg(x).arg(y).arg(z) << endl;
            // --list-plugins argument was added for 1.3 release
            pluginParse = (x >= 1 && y >=3) ? New : Old;
          }
        }
      }
      // if still zero, then we should use old in future
      if(pluginParse == NotYet) {
        pluginParse = Old;
      }
    }

    if(pluginParse == New) {
      readPluginsNew(collType_, gcstar);
    } else {
      readPluginsOld(collType_, gcstar);
    }
  }

  return pluginMap.contains(collType_) ? pluginMap[collType_] : GCstarPluginFetcher::PluginList();
}

void GCstarPluginFetcher::readPluginsNew(int collType_, const TQString& gcstar_) {
  PluginList plugins;

  TQString gcstarCollection = gcstarType(collType_);
  if(gcstarCollection.isEmpty()) {
    pluginMap.insert(collType_, plugins);
    return;
  }

  KProcIO proc;
  proc << gcstar_
        << TQString::fromLatin1("-x")
        << TQString::fromLatin1("--list-plugins")
        << TQString::fromLatin1("--collection") << gcstarCollection;

  if(!proc.start(KProcess::Block)) {
    myWarning() << "GCstarPluginFetcher::readPluginsNew() - can't start" << endl;
    return;
  }

  bool hasName = false;
  PluginInfo info;
  TQString line;
  for(int length = 0; length > -1; length = proc.readln(line)) {
    if(line.isEmpty()) {
      if(hasName) {
        plugins << info;
      }
      hasName = false;
      info.clear();
    } else {
      // authors have \t at beginning
      line = line.stripWhiteSpace();
      if(!hasName) {
        info.insert(TQString::fromLatin1("name"), line);
        hasName = true;
      } else {
        info.insert(TQString::fromLatin1("author"), line);
      }
//      myDebug() << line << endl;
    }
  }

  pluginMap.insert(collType_, plugins);
}

void GCstarPluginFetcher::readPluginsOld(int collType_, const TQString& gcstar_) {
  TQDir dir(gcstar_, TQString::fromLatin1("GC*.pm"));
  dir.cd(TQString::fromLatin1("../../lib/gcstar/GCPlugins/"));

  TQRegExp rx(TQString::fromLatin1("get(Name|Author|Lang)\\s*\\{\\s*return\\s+['\"](.+)['\"]"));
  rx.setMinimal(true);

  PluginList plugins;

  TQString dirName = gcstarType(collType_);
  if(dirName.isEmpty()) {
    pluginMap.insert(collType_, plugins);
    return;
  }

  TQStringList files = dir.entryList();
  for(TQStringList::ConstIterator file = files.begin(); file != files.end(); ++file) {
    KURL u;
    u.setPath(dir.filePath(*file));
    PluginInfo info;
    TQString text = FileHandler::readTextFile(u);
    for(int pos = rx.search(text);
        pos > -1;
        pos = rx.search(text, pos+rx.matchedLength())) {
      info.insert(rx.cap(1).lower(), rx.cap(2));
    }
    // only add if it has a name
    if(info.contains(TQString::fromLatin1("name"))) {
      plugins << info;
    }
  }
  // inserting empty map is ok
  pluginMap.insert(collType_, plugins);
}

TQString GCstarPluginFetcher::gcstarType(int collType_) {
  switch(collType_) {
    case Data::Collection::Book:      return TQString::fromLatin1("GCbooks");
    case Data::Collection::Video:     return TQString::fromLatin1("GCfilms");
    case Data::Collection::Game:      return TQString::fromLatin1("GCgames");
    case Data::Collection::Album:     return TQString::fromLatin1("GCmusics");
    case Data::Collection::Coin:      return TQString::fromLatin1("GCcoins");
    case Data::Collection::Wine:      return TQString::fromLatin1("GCwines");
    case Data::Collection::BoardGame: return TQString::fromLatin1("GCboardgames");
    default: break;
  }
  return TQString();
}

GCstarPluginFetcher::GCstarPluginFetcher(TQObject* parent_, const char* name_/*=0*/) : Fetcher(parent_, name_),
    m_started(false), m_collType(-1), m_process(0) {
}

GCstarPluginFetcher::~GCstarPluginFetcher() {
  stop();
}

TQString GCstarPluginFetcher::defaultName() {
  return i18n("GCstar Plugin");
}

TQString GCstarPluginFetcher::source() const {
  return m_name;
}

bool GCstarPluginFetcher::canFetch(int type_) const {
  return m_collType == -1 ? false : m_collType == type_;
}

void GCstarPluginFetcher::readConfigHook(const KConfigGroup& config_) {
  m_collType = config_.readNumEntry("CollectionType", -1);
  m_plugin = config_.readEntry("Plugin");
}

void GCstarPluginFetcher::search(FetchKey key_, const TQString& value_) {
  m_started = true;
  m_data.truncate(0);

  if(key_ != Fetch::Title) {
    myDebug() << "GCstarPluginFetcher::search() - only Title searches are supported" << endl;
    stop();
    return;
  }

  TQString gcstar = KStandardDirs::findExe(TQString::fromLatin1("gcstar"));
  if(gcstar.isEmpty()) {
    myWarning() << "GCstarPluginFetcher::search() - gcstar not found!" << endl;
    stop();
    return;
  }

  TQString gcstarCollection = gcstarType(m_collType);

  if(m_plugin.isEmpty()) {
    myWarning() << "GCstarPluginFetcher::search() - no plugin name! " << endl;
    stop();
    return;
  }

  m_process = new KProcess();
  connect(m_process, TQT_SIGNAL(receivedStdout(KProcess*, char*, int)), TQT_SLOT(slotData(KProcess*, char*, int)));
  connect(m_process, TQT_SIGNAL(receivedStderr(KProcess*, char*, int)), TQT_SLOT(slotError(KProcess*, char*, int)));
  connect(m_process, TQT_SIGNAL(processExited(KProcess*)), TQT_SLOT(slotProcessExited(KProcess*)));
  TQStringList args;
  args << gcstar << TQString::fromLatin1("-x")
       << TQString::fromLatin1("--collection") << gcstarCollection
       << TQString::fromLatin1("--export")     << TQString::fromLatin1("Tellico")
       << TQString::fromLatin1("--website")    << m_plugin
       << TQString::fromLatin1("--download")   << KProcess::quote(value_);
  myLog() << "GCstarPluginFetcher::search() - " << args.join(TQChar(' ')) << endl;
  *m_process << args;
  if(!m_process->start(KProcess::NotifyOnExit, KProcess::AllOutput)) {
    myDebug() << "GCstarPluginFetcher::startSearch() - process failed to start" << endl;
    stop();
  }
}

void GCstarPluginFetcher::stop() {
  if(!m_started) {
    return;
  }
  if(m_process) {
    m_process->kill();
    delete m_process;
    m_process = 0;
  }
  m_data.truncate(0);
  m_started = false;
  m_errors.clear();
  emit signalDone(this);
}

void GCstarPluginFetcher::slotData(KProcess*, char* buffer_, int len_) {
  TQDataStream stream(m_data, IO_WriteOnly | IO_Append);
  stream.writeRawBytes(buffer_, len_);
}

void GCstarPluginFetcher::slotError(KProcess*, char* buffer_, int len_) {
  TQString msg = TQString::fromLocal8Bit(buffer_, len_);
  msg.prepend(source() + TQString::fromLatin1(": "));
  myDebug() << "GCstarPluginFetcher::slotError() - " << msg << endl;
  m_errors << msg;
}

void GCstarPluginFetcher::slotProcessExited(KProcess*) {
//  myDebug() << "GCstarPluginFetcher::slotProcessExited()" << endl;
  if(!m_process->normalExit() || m_process->exitStatus()) {
    myDebug() << "GCstarPluginFetcher::slotProcessExited() - "<< source() << ": process did not exit successfully" << endl;
    if(!m_errors.isEmpty()) {
      message(m_errors.join(TQChar('\n')), MessageHandler::Error);
    }
    stop();
    return;
  }
  if(!m_errors.isEmpty()) {
    message(m_errors.join(TQChar('\n')), MessageHandler::Warning);
  }

  if(m_data.isEmpty()) {
    myDebug() << "GCstarPluginFetcher::slotProcessExited() - "<< source() << ": no data" << endl;
    stop();
    return;
  }

  Import::TellicoImporter imp(TQString::fromUtf8(m_data, m_data.size()));

  Data::CollPtr coll = imp.collection();
  if(!coll) {
    if(!imp.statusMessage().isEmpty()) {
      message(imp.statusMessage(), MessageHandler::Status);
    }
    myDebug() << "GCstarPluginFetcher::slotProcessExited() - "<< source() << ": no collection pointer" << endl;
    stop();
    return;
  }

  Data::EntryVec entries = coll->entries();
  for(Data::EntryVec::Iterator entry = entries.begin(); entry != entries.end(); ++entry) {
    TQString desc;
    switch(coll->type()) {
      case Data::Collection::Book:
      case Data::Collection::Bibtex:
        desc = entry->field(TQString::fromLatin1("author"))
               + TQChar('/')
               + entry->field(TQString::fromLatin1("publisher"));
        if(!entry->field(TQString::fromLatin1("cr_year")).isEmpty()) {
          desc += TQChar('/') + entry->field(TQString::fromLatin1("cr_year"));
        } else if(!entry->field(TQString::fromLatin1("pub_year")).isEmpty()){
          desc += TQChar('/') + entry->field(TQString::fromLatin1("pub_year"));
        }
        break;

      case Data::Collection::Video:
        desc = entry->field(TQString::fromLatin1("studio"))
               + TQChar('/')
               + entry->field(TQString::fromLatin1("director"))
               + TQChar('/')
               + entry->field(TQString::fromLatin1("year"))
               + TQChar('/')
               + entry->field(TQString::fromLatin1("medium"));
        break;

      case Data::Collection::Album:
        desc = entry->field(TQString::fromLatin1("artist"))
               + TQChar('/')
               + entry->field(TQString::fromLatin1("label"))
               + TQChar('/')
               + entry->field(TQString::fromLatin1("year"));
        break;

      case Data::Collection::Game:
        desc = entry->field(TQString::fromLatin1("platform"));
        break;

      case Data::Collection::ComicBook:
        desc = entry->field(TQString::fromLatin1("publisher"))
               + TQChar('/')
               + entry->field(TQString::fromLatin1("pub_year"));
        break;

     case Data::Collection::BoardGame:
       desc = entry->field(TQString::fromLatin1("designer"))
              + TQChar('/')
              + entry->field(TQString::fromLatin1("publisher"))
              + TQChar('/')
              + entry->field(TQString::fromLatin1("year"));
       break;

      default:
        break;
    }
    SearchResult* r = new SearchResult(this, entry->title(), desc, entry->field(TQString::fromLatin1("isbn")));
    m_entries.insert(r->uid, entry);
    emit signalResultFound(r);
  }
  stop(); // be sure to call this
}

Tellico::Data::EntryPtr GCstarPluginFetcher::fetchEntry(uint uid_) {
  return m_entries[uid_];
}

void GCstarPluginFetcher::updateEntry(Data::EntryPtr entry_) {
  // ry searching for title and rely on Collection::sameEntry() to figure things out
  TQString t = entry_->field(TQString::fromLatin1("title"));
  if(!t.isEmpty()) {
    search(Fetch::Title, t);
    return;
  }

  myDebug() << "GCstarPluginFetcher::updateEntry() - insufficient info to search" << endl;
  emit signalDone(this); // always need to emit this if not continuing with the search
}

Tellico::Fetch::ConfigWidget* GCstarPluginFetcher::configWidget(TQWidget* parent_) const {
  return new GCstarPluginFetcher::ConfigWidget(parent_, this);
}

GCstarPluginFetcher::ConfigWidget::ConfigWidget(TQWidget* parent_, const GCstarPluginFetcher* fetcher_/*=0*/)
    : Fetch::ConfigWidget(parent_), m_needPluginList(true) {
  TQGridLayout* l = new TQGridLayout(optionsWidget(), 3, 4);
  l->setSpacing(4);
  l->setColStretch(1, 10);

  int row = -1;

  TQLabel* label = new TQLabel(i18n("Collection &type:"), optionsWidget());
  l->addWidget(label, ++row, 0);
  m_collCombo = new GUI::CollectionTypeCombo(optionsWidget());
  connect(m_collCombo, TQT_SIGNAL(activated(int)), TQT_SLOT(slotSetModified()));
  connect(m_collCombo, TQT_SIGNAL(activated(int)), TQT_SLOT(slotTypeChanged()));
  l->addMultiCellWidget(m_collCombo, row, row, 1, 3);
  TQString w = i18n("Set the collection type of the data returned from the plugin.");
  TQWhatsThis::add(label, w);
  TQWhatsThis::add(m_collCombo, w);
  label->setBuddy(m_collCombo);

  label = new TQLabel(i18n("&Plugin: "), optionsWidget());
  l->addWidget(label, ++row, 0);
  m_pluginCombo = new GUI::ComboBox(optionsWidget());
  connect(m_pluginCombo, TQT_SIGNAL(activated(int)), TQT_SLOT(slotSetModified()));
  connect(m_pluginCombo, TQT_SIGNAL(activated(int)), TQT_SLOT(slotPluginChanged()));
  l->addMultiCellWidget(m_pluginCombo, row, row, 1, 3);
  w = i18n("Select the GCstar plugin used for the data source.");
  TQWhatsThis::add(label, w);
  TQWhatsThis::add(m_pluginCombo, w);
  label->setBuddy(m_pluginCombo);

  label = new TQLabel(i18n("Author: "), optionsWidget());
  l->addWidget(label, ++row, 0);
  m_authorLabel = new TQLabel(optionsWidget());
  l->addWidget(m_authorLabel, row, 1);

//  label = new TQLabel(i18n("Language: "), optionsWidget());
//  l->addWidget(label, row, 2);
//  m_langLabel = new TQLabel(optionsWidget());
//  l->addWidget(m_langLabel, row, 3);

  if(fetcher_ && fetcher_->m_collType > -1) {
    m_collCombo->setCurrentType(fetcher_->m_collType);
  } else {
    m_collCombo->setCurrentType(Kernel::self()->collectionType());
  }

  if(fetcher_) {
    m_originalPluginName = fetcher_->m_plugin;
  }

  KAcceleratorManager::manage(optionsWidget());
}

GCstarPluginFetcher::ConfigWidget::~ConfigWidget() {
}

void GCstarPluginFetcher::ConfigWidget::saveConfig(KConfigGroup& config_) {
  config_.writeEntry("CollectionType", m_collCombo->currentType());
  config_.writeEntry("Plugin", m_pluginCombo->currentText());
}

TQString GCstarPluginFetcher::ConfigWidget::preferredName() const {
  return TQString::fromLatin1("GCstar - ") + m_pluginCombo->currentText();
}

void GCstarPluginFetcher::ConfigWidget::slotTypeChanged() {
  int collType = m_collCombo->currentType();
  m_pluginCombo->clear();
  TQStringList pluginNames;
  GCstarPluginFetcher::PluginList list = GCstarPluginFetcher::plugins(collType);
  for(GCstarPluginFetcher::PluginList::ConstIterator it = list.begin(); it != list.end(); ++it) {
    pluginNames << (*it)[TQString::fromLatin1("name")].toString();
    m_pluginCombo->insertItem(pluginNames.last(), *it);
  }
  slotPluginChanged();
  emit signalName(preferredName());
}

void GCstarPluginFetcher::ConfigWidget::slotPluginChanged() {
  PluginInfo info = m_pluginCombo->currentData().toMap();
  m_authorLabel->setText(info[TQString::fromLatin1("author")].toString());
//  m_langLabel->setText(info[TQString::fromLatin1("lang")].toString());
  emit signalName(preferredName());
}

void GCstarPluginFetcher::ConfigWidget::showEvent(TQShowEvent*) {
  if(m_needPluginList) {
    m_needPluginList = false;
    slotTypeChanged(); // update plugin combo box
    if(!m_originalPluginName.isEmpty()) {
      m_pluginCombo->setCurrentText(m_originalPluginName);
      slotPluginChanged();
    }
  }
}

#include "gcstarpluginfetcher.moc"