/***************************************************************************
    copyright            : (C) 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 "yahoofetcher.h"
#include "messagehandler.h"
#include "../translators/xslthandler.h"
#include "../translators/tellicoimporter.h"
#include "../imagefactory.h"
#include "../tellico_kernel.h"
#include "../tellico_utils.h"
#include "../collection.h"
#include "../entry.h"
#include "../tellico_debug.h"

#include <tdelocale.h>
#include <tdestandarddirs.h>
#include <tdeconfig.h>
#include <tdeio/job.h>

#include <tqdom.h>
#include <tqlabel.h>
#include <tqlayout.h>
#include <tqfile.h>

namespace {
  static const int YAHOO_MAX_RETURNS_TOTAL = 20;
  static const char* YAHOO_BASE_URL = "http://search.yahooapis.com/AudioSearchService/V1/albumSearch";
  static const char* YAHOO_APP_ID = "tellico-robby";
}

using Tellico::Fetch::YahooFetcher;

YahooFetcher::YahooFetcher(TQObject* parent_, const char* name_)
    : Fetcher(parent_, name_), m_xsltHandler(0),
      m_limit(YAHOO_MAX_RETURNS_TOTAL), m_job(0), m_started(false) {
}

YahooFetcher::~YahooFetcher() {
  delete m_xsltHandler;
  m_xsltHandler = 0;
}

TQString YahooFetcher::defaultName() {
  return i18n("Yahoo! Audio Search");
}

TQString YahooFetcher::source() const {
  return m_name.isEmpty() ? defaultName() : m_name;
}

bool YahooFetcher::canFetch(int type) const {
  return type == Data::Collection::Album;
}

void YahooFetcher::readConfigHook(const TDEConfigGroup& config_) {
  Q_UNUSED(config_);
}

void YahooFetcher::search(FetchKey key_, const TQString& value_) {
  m_key = key_;
  m_value = value_;
  m_started = true;
  m_start = 1;
  m_total = -1;
  doSearch();
}

void YahooFetcher::continueSearch() {
  m_started = true;
  doSearch();
}

void YahooFetcher::doSearch() {
//  myDebug() << "YahooFetcher::search() - value = " << value_ << endl;

  KURL u(TQString::fromLatin1(YAHOO_BASE_URL));
  u.addQueryItem(TQString::fromLatin1("appid"),   TQString::fromLatin1(YAHOO_APP_ID));
  u.addQueryItem(TQString::fromLatin1("type"),    TQString::fromLatin1("all"));
  u.addQueryItem(TQString::fromLatin1("output"),  TQString::fromLatin1("xml"));
  u.addQueryItem(TQString::fromLatin1("start"),   TQString::number(m_start));
  u.addQueryItem(TQString::fromLatin1("results"), TQString::number(YAHOO_MAX_RETURNS_TOTAL));

  if(!canFetch(Kernel::self()->collectionType())) {
    message(i18n("%1 does not allow searching for this collection type.").arg(source()), MessageHandler::Warning);
    stop();
    return;
  }

  switch(m_key) {
    case Title:
      u.addQueryItem(TQString::fromLatin1("album"), m_value);
      break;

    case Person:
      u.addQueryItem(TQString::fromLatin1("artist"), m_value);
      break;

    // raw is used for the entry updates
    case Raw:
//      u.removeQueryItem(TQString::fromLatin1("type"));
//      u.addQueryItem(TQString::fromLatin1("type"), TQString::fromLatin1("phrase"));
      u.setQuery(u.query() + '&' + m_value);
      break;

    default:
      kdWarning() << "YahooFetcher::search() - key not recognized: " << m_key << endl;
      stop();
      return;
  }
//  myDebug() << "YahooFetcher::search() - url: " << u.url() << endl;

  m_job = TDEIO::get(u, false, false);
  connect(m_job, TQ_SIGNAL(data(TDEIO::Job*, const TQByteArray&)),
          TQ_SLOT(slotData(TDEIO::Job*, const TQByteArray&)));
  connect(m_job, TQ_SIGNAL(result(TDEIO::Job*)),
          TQ_SLOT(slotComplete(TDEIO::Job*)));
}

void YahooFetcher::stop() {
  if(!m_started) {
    return;
  }
  if(m_job) {
    m_job->kill();
    m_job = 0;
  }
  m_data.truncate(0);
  m_started = false;
  emit signalDone(this);
}

void YahooFetcher::slotData(TDEIO::Job*, const TQByteArray& data_) {
  TQDataStream stream(m_data, IO_WriteOnly | IO_Append);
  stream.writeRawBytes(data_.data(), data_.size());
}

void YahooFetcher::slotComplete(TDEIO::Job* job_) {
//  myDebug() << "YahooFetcher::slotComplete()" << endl;
  // since the fetch is done, don't worry about holding the job pointer
  m_job = 0;

  if(job_->error()) {
    job_->showErrorDialog(Kernel::self()->widget());
    stop();
    return;
  }

  if(m_data.isEmpty()) {
    myDebug() << "YahooFetcher::slotComplete() - no data" << endl;
    stop();
    return;
  }

#if 0
  kdWarning() << "Remove debug from yahoofetcher.cpp" << endl;
  TQFile f(TQString::fromLatin1("/tmp/test.xml"));
  if(f.open(IO_WriteOnly)) {
    TQTextStream t(&f);
    t.setEncoding(TQTextStream::UnicodeUTF8);
    t << TQCString(m_data, m_data.size()+1);
  }
  f.close();
#endif

  if(!m_xsltHandler) {
    initXSLTHandler();
    if(!m_xsltHandler) { // probably an error somewhere in the stylesheet loading
      stop();
      return;
    }
  }

  if(m_total == -1) {
    TQDomDocument dom;
    if(!dom.setContent(m_data, false)) {
      kdWarning() << "YahooFetcher::slotComplete() - server did not return valid XML." << endl;
      return;
    }
    // total is top level element, with attribute totalResultsAvailable
    TQDomElement e = dom.documentElement();
    if(!e.isNull()) {
      m_total = e.attribute(TQString::fromLatin1("totalResultsAvailable")).toInt();
    }
  }

  // assume yahoo is always utf-8
  TQString str = m_xsltHandler->applyStylesheet(TQString::fromUtf8(m_data, m_data.size()));
  Import::TellicoImporter imp(str);
  Data::CollPtr coll = imp.collection();
  if(!coll) {
    myDebug() << "YahooFetcher::slotComplete() - no collection pointer" << endl;
    stop();
    return;
  }

  int count = 0;
  Data::EntryVec entries = coll->entries();
  for(Data::EntryVec::Iterator entry = entries.begin(); count < m_limit && entry != entries.end(); ++entry, ++count) {
    if(!m_started) {
      // might get aborted
      break;
    }
    TQString desc = entry->field(TQString::fromLatin1("artist"))
                 + TQChar('/')
                 + entry->field(TQString::fromLatin1("label"))
                 + TQChar('/')
                 + entry->field(TQString::fromLatin1("year"));

    SearchResult* r = new SearchResult(this, entry->title(), desc, entry->field(TQString::fromLatin1("isbn")));
    m_entries.insert(r->uid, Data::EntryPtr(entry));
    emit signalResultFound(r);
  }
  m_start = m_entries.count() + 1;
  m_hasMoreResults = m_start <= m_total;
  stop(); // required
}

Tellico::Data::EntryPtr YahooFetcher::fetchEntry(uint uid_) {
  Data::EntryPtr entry = m_entries[uid_];
  if(!entry) {
    kdWarning() << "YahooFetcher::fetchEntry() - no entry in dict" << endl;
    return 0;
  }

  KURL imageURL = entry->field(TQString::fromLatin1("image"));
  if(!imageURL.isEmpty()) {
    TQString id = ImageFactory::addImage(imageURL, true);
    if(id.isEmpty()) {
    // rich text causes layout issues
//      emit signalStatus(i18n("<qt>The cover image for <i>%1</i> could not be loaded.</qt>").arg(
//                              entry->field(TQString::fromLatin1("title"))));
      message(i18n("The cover image could not be loaded."), MessageHandler::Warning);
    } else {
      entry->setField(TQString::fromLatin1("cover"), id);
    }
  }

  getTracks(entry);

  // don't want to show image urls in the fetch dialog
  entry->setField(TQString::fromLatin1("image"),  TQString());
  // no need for album id now ?
  entry->setField(TQString::fromLatin1("yahoo"),  TQString());
  return entry;
}

void YahooFetcher::initXSLTHandler() {
  TQString xsltfile = locate("appdata", TQString::fromLatin1("yahoo2tellico.xsl"));
  if(xsltfile.isEmpty()) {
    kdWarning() << "YahooFetcher::initXSLTHandler() - can not locate yahoo2tellico.xsl." << endl;
    return;
  }

  KURL u;
  u.setPath(xsltfile);

  delete m_xsltHandler;
  m_xsltHandler = new XSLTHandler(u);
  if(!m_xsltHandler->isValid()) {
    kdWarning() << "YahooFetcher::initXSLTHandler() - error in yahoo2tellico.xsl." << endl;
    delete m_xsltHandler;
    m_xsltHandler = 0;
    return;
  }
}

void YahooFetcher::getTracks(Data::EntryPtr entry_) {
  // get album id
  if(!entry_ || entry_->field(TQString::fromLatin1("yahoo")).isEmpty()) {
    return;
  }

  const TQString albumid = entry_->field(TQString::fromLatin1("yahoo"));

  KURL u(TQString::fromLatin1(YAHOO_BASE_URL));
  u.setFileName(TQString::fromLatin1("songSearch"));
  u.addQueryItem(TQString::fromLatin1("appid"),   TQString::fromLatin1(YAHOO_APP_ID));
  u.addQueryItem(TQString::fromLatin1("type"),    TQString::fromLatin1("all"));
  u.addQueryItem(TQString::fromLatin1("output"),  TQString::fromLatin1("xml"));
  // go ahesad and ask for all results, since there might well be more than 10 songs on the CD
  u.addQueryItem(TQString::fromLatin1("results"), TQString::number(50));
  u.addQueryItem(TQString::fromLatin1("albumid"), albumid);

//  myDebug() << "YahooFetcher::getTracks() - url: " << u.url() << endl;
  TQDomDocument dom = FileHandler::readXMLFile(u, false /*no namespace*/, true /*quiet*/);
  if(dom.isNull()) {
    myDebug() << "YahooFetcher::getTracks() - null dom returned" << endl;
    return;
  }

#if 0
  kdWarning() << "Remove debug from yahoofetcher.cpp" << endl;
  TQFile f(TQString::fromLatin1("/tmp/test.xml"));
  if(f.open(IO_WriteOnly)) {
    TQTextStream t(&f);
    t.setEncoding(TQTextStream::UnicodeUTF8);
    t << dom.toString();
  }
  f.close();
#endif

  const TQString track = TQString::fromLatin1("track");

  TQDomNodeList nodes = dom.documentElement().childNodes();
  for(uint i = 0; i < nodes.count(); ++i) {
    TQDomElement e = nodes.item(i).toElement();
    if(e.isNull()) {
      continue;
    }
    TQString t = e.namedItem(TQString::fromLatin1("Title")).toElement().text();
    TQString n = e.namedItem(TQString::fromLatin1("Track")).toElement().text();
    bool ok;
    int trackNum = Tellico::toUInt(n, &ok);
    // trackNum might be 0
    if(t.isEmpty() || !ok || trackNum < 1) {
      continue;
    }
    TQString a = e.namedItem(TQString::fromLatin1("Artist")).toElement().text();
    TQString l = e.namedItem(TQString::fromLatin1("Length")).toElement().text();

    int len = Tellico::toUInt(l, &ok);
    TQString value = t + "::" + a;
    if(ok && len > 0) {
      value += + "::" + Tellico::minutes(len);
    }
    entry_->setField(track, insertValue(entry_->field(track), value, trackNum));
  }
}

// not zero-based
TQString YahooFetcher::insertValue(const TQString& str_, const TQString& value_, uint pos_) {
  TQStringList list = Data::Field::split(str_, true);
  for(uint i = list.count(); i < pos_; ++i) {
    list += TQString();
  }
  bool write = true;
  if(!list[pos_-1].isNull()) {
    // for some reason, some songs are repeated from yahoo, with 0 length, don't overwrite that
    if(value_.contains(TQString::fromLatin1("::")) < 2) { // means no length value
      write = false;
    }
  }
  if(!value_.isEmpty() && write) {
    list[pos_-1] = value_;
  }
  return list.join(TQString::fromLatin1("; "));
}

void YahooFetcher::updateEntry(Data::EntryPtr entry_) {
//  myDebug() << "YahooFetcher::updateEntry()" << endl;
  // limit to top 5 results
  m_limit = 5;

  TQString value;
  TQString title = entry_->field(TQString::fromLatin1("title"));
  if(!title.isEmpty()) {
    value += TQString::fromLatin1("album=") + title;
  }
  TQString artist = entry_->field(TQString::fromLatin1("artist"));
  if(!artist.isEmpty()) {
    if(!value.isEmpty()) {
      value += '&';
    }
    value += TQString::fromLatin1("artist=") + artist;
  }
  if(!value.isEmpty()) {
    search(Fetch::Raw, value);
    return;
  }

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

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

YahooFetcher::ConfigWidget::ConfigWidget(TQWidget* parent_, const YahooFetcher*/*=0*/)
    : Fetch::ConfigWidget(parent_) {
  TQVBoxLayout* l = new TQVBoxLayout(optionsWidget());
  l->addWidget(new TQLabel(i18n("This source has no options."), optionsWidget()));
  l->addStretch();
}

TQString YahooFetcher::ConfigWidget::preferredName() const {
  return YahooFetcher::defaultName();
}

#include "yahoofetcher.moc"
