/***************************************************************************
    copyright            : (C) 2004-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 "freedbimporter.h"
#include "../collections/musiccollection.h"
#include "../entry.h"
#include "../field.h"
#include "../latin1literal.h"
#include "../tellico_utils.h"
#include "../tellico_debug.h"
#include "../tellico_kernel.h"
#include "../progressmanager.h"

#include <config.h>

#ifdef HAVE_KCDDB
#ifdef TQT_NO_CAST_ASCII
#define HAD_TQT_NO_CAST_ASCII
#undef TQT_NO_CAST_ASCII
#endif
#include <libkcddb/client.h>
#ifdef HAD_TQT_NO_CAST_ASCII
#define TQT_NO_CAST_ASCII
#undef HAD_TQT_NO_CAST_ASCII
#endif
#endif

#include <kcombobox.h>
#include <tdeconfig.h>
#include <tdeapplication.h>
#include <kinputdialog.h>

#include <tqfile.h>
#include <tqdir.h>
#include <tqlabel.h>
#include <tqlayout.h>
#include <tqgroupbox.h>
#include <tqwhatsthis.h>
#include <tqradiobutton.h>
#include <tqbuttongroup.h>
#include <tqhbox.h>
#include <tqcheckbox.h>

using Tellico::Import::FreeDBImporter;

FreeDBImporter::FreeDBImporter() : Tellico::Import::Importer(), m_coll(0), m_widget(0), m_cancelled(false) {
}

bool FreeDBImporter::canImport(int type) const {
  return type == Data::Collection::Album;
}

Tellico::Data::CollPtr FreeDBImporter::collection() {
  if(m_coll) {
    return m_coll;
  }

  m_cancelled = false;
  if(m_radioCDROM->isChecked()) {
    readCDROM();
  } else {
    readCache();
  }
  if(m_cancelled) {
    m_coll = 0;
  }
  return m_coll;
}

void FreeDBImporter::readCDROM() {
#ifdef HAVE_KCDDB
  TQString drivePath = m_driveCombo->currentText();
  if(drivePath.isEmpty()) {
    setStatusMessage(i18n("<qt>Tellico was unable to access the CD-ROM device - <i>%1</i>.</qt>").arg(drivePath));
    myDebug() << "FreeDBImporter::readCDROM() - no drive!" << endl;
    return;
  }

  // now it's ok to add device to saved list
  m_driveCombo->insertItem(drivePath);
  TQStringList drives;
  for(int i = 0; i < m_driveCombo->count(); ++i) {
    if(drives.findIndex(m_driveCombo->text(i)) == -1) {
      drives += m_driveCombo->text(i);
    }
  }

  {
    TDEConfigGroup config(TDEGlobal::config(), TQString::fromLatin1("ImportOptions - FreeDB"));
    config.writeEntry("CD-ROM Devices", drives);
    config.writeEntry("Last Device", drivePath);
    config.writeEntry("Cache Files Only", false);
  }

  TQCString drive = TQFile::encodeName(drivePath);
  TQValueList<uint> lengths;
  KCDDB::TrackOffsetList list;
#if 0
  // a1107d0a - Kruder & Dorfmeister - The K&D Sessions - Disc One.
/*  list
    << 150      // First track start.
    << 29462
    << 66983
    << 96785
    << 135628
    << 168676
    << 194147
    << 222158
    << 247076
    << 278203   // Last track start.
    << 10       // Disc start.
    << 316732;  // Disc end.
*/
  list
    << 150      // First track start.
    << 3296
    << 14437
    << 41279
    << 51362
    << 56253
    << 59755
    << 61324
    << 66059
    << 69073
    << 77790
    << 83214
    << 89726
    << 92078
    << 106325
    << 113117
    << 116040
    << 119877
    << 124377
    << 145466
    << 157583
    << 167208
    << 173486
    << 180120
    << 185279
    << 193270
    << 206451
    << 217303   // Last track start.
    << 10       // Disc start.
    << 224925;  // Disc end.
/*
  list
    << 150
    << 106965
    << 127220
    << 151925
    << 176085
    << 5
    << 234500;
*/
#else
  list = offsetList(drive, lengths);
#endif

  if(list.isEmpty()) {
    setStatusMessage(i18n("<qt>Tellico was unable to access the CD-ROM device - <i>%1</i>.</qt>").arg(drivePath));
    return;
  }
//  myDebug() << KCDDB::CDDB::trackOffsetListToId(list) << endl;
//  for(KCDDB::TrackOffsetList::iterator it = list.begin(); it != list.end(); ++it) {
//    myDebug() << *it << endl;
//  }

  // the result info, could be multiple ones
  KCDDB::CDInfo info;
  KCDDB::Client client;
  client.setBlockingMode(true);
  KCDDB::CDDB::Result r = client.lookup(list);
  // KCDDB doesn't return MultipleRecordFound properly, so check outselves
  if(r == KCDDB::CDDB::MultipleRecordFound || client.lookupResponse().count() > 1) {
    TQStringList list;
    KCDDB::CDInfoList infoList = client.lookupResponse();
    for(KCDDB::CDInfoList::iterator it = infoList.begin(); it != infoList.end(); ++it) {
      list.append(TQString::fromLatin1("%1, %2, %3").arg((*it).artist)
                                                   .arg((*it).title)
                                                   .arg((*it).genre));
    }

    // switch back to pointer cursor
    GUI::CursorSaver cs(TQt::arrowCursor);
    bool ok;
    TQString res = KInputDialog::getItem(i18n("Select CDDB Entry"),
                                        i18n("Select a CDDB entry:"),
                                        list, 0, false, &ok,
                                        Kernel::self()->widget());
    if(ok) {
      uint i = 0;
      for(TQStringList::ConstIterator it = list.begin(); it != list.end(); ++it, ++i) {
        if(*it == res) {
          break;
        }
      }
      if(i < infoList.size()) {
        info = infoList[i];
      }
    } else { // cancelled dialog
      m_cancelled = true;
    }
  } else if(r == KCDDB::CDDB::Success) {
    info = client.bestLookupResponse();
  } else {
//    myDebug() << "FreeDBImporter::readCDROM() - no success! Return value = " << r << endl;
    TQString s;
    switch(r) {
      case KCDDB::CDDB::NoRecordFound:
        s = i18n("<qt>No records were found to match the CD.</qt>");
        break;
      case KCDDB::CDDB::ServerError:
        myDebug() << "Server Error" << endl;
        break;
      case KCDDB::CDDB::HostNotFound:
        myDebug() << "Host Not Found" << endl;
        break;
      case KCDDB::CDDB::NoResponse:
        myDebug() << "No Response" << endl;
        break;
      case KCDDB::CDDB::UnknownError:
        myDebug() << "Unknown Error" << endl;
        break;
      default:
        break;
    }
    if(s.isEmpty()) {
      s = i18n("<qt>Tellico was unable to complete the CD lookup.</qt>");
    }
    setStatusMessage(s);
    return;
  }

  if(!info.isValid()) {
    // go ahead and try to read cd-text if we weren't cancelled
    // could be the case we don't have net access
    if(!m_cancelled) {
      readCDText(drive);
    }
    return;
  }

  m_coll = new Data::MusicCollection(true);

  Data::EntryPtr entry = new Data::Entry(m_coll);
  // obviously a CD
  entry->setField(TQString::fromLatin1("medium"), i18n("Compact Disc"));
  entry->setField(TQString::fromLatin1("title"),  info.title);
  entry->setField(TQString::fromLatin1("artist"), info.artist);
  entry->setField(TQString::fromLatin1("genre"),  info.genre);
  if(info.year > 0) {
    entry->setField(TQString::fromLatin1("year"), TQString::number(info.year));
  }
  entry->setField(TQString::fromLatin1("keyword"), info.category);
  TQString extd = info.extd;
  extd.replace('\n', TQString::fromLatin1("<br/>"));
  entry->setField(TQString::fromLatin1("comments"), extd);

  TQStringList trackList;
  KCDDB::TrackInfoList t = info.trackInfoList;
  for(uint i = 0; i < t.count(); ++i) {
    TQString s = t[i].get(TQString::fromLatin1("title")).toString() + "::" + info.artist;
    if(i < lengths.count()) {
      s += "::" + Tellico::minutes(lengths[i]);
    }
    trackList << s;
    // TODO: KDE4 will probably have track length too
  }
  entry->setField(TQString::fromLatin1("track"), trackList.join(TQString::fromLatin1("; ")));

  m_coll->addEntries(entry);
  readCDText(drive);
#endif
}

void FreeDBImporter::readCache() {
#ifdef HAVE_KCDDB
  {
    // remember the import options
    TDEConfigGroup config(TDEGlobal::config(), TQString::fromLatin1("ImportOptions - FreeDB"));
    config.writeEntry("Cache Files Only", true);
  }

  KCDDB::Config cfg;
  cfg.readConfig();

  TQStringList dirs = cfg.cacheLocations();
  for(TQStringList::ConstIterator it = dirs.begin(); it != dirs.end(); ++it) {
    dirs += Tellico::findAllSubDirs(*it);
  }

  // using a TQMap is a lazy man's way of getting unique keys
  // the cddb info may be in multiple files, all with the same filename, the cddb id
  TQMap<TQString, TQString> files;
  for(TQStringList::ConstIterator it = dirs.begin(); it != dirs.end(); ++it) {
    if((*it).isEmpty()) {
      continue;
    }

    TQDir dir(*it);
    dir.setFilter(TQDir::Files | TQDir::Readable | TQDir::Hidden); // hidden since I want directory files
    const TQStringList list = dir.entryList();
    for(TQStringList::ConstIterator it2 = list.begin(); it2 != list.end(); ++it2) {
      files.insert(*it2, dir.absFilePath(*it2), false);
    }
//    kapp->processEvents(); // really needed ?
  }

  const TQString title    = TQString::fromLatin1("title");
  const TQString artist   = TQString::fromLatin1("artist");
  const TQString year     = TQString::fromLatin1("year");
  const TQString genre    = TQString::fromLatin1("genre");
  const TQString track    = TQString::fromLatin1("track");
  const TQString comments = TQString::fromLatin1("comments");
  uint numFiles = files.count();

  if(numFiles == 0) {
    myDebug() << "FreeDBImporter::readCache() - no files found" << endl;
    return;
  }

  m_coll = new Data::MusicCollection(true);

  const uint stepSize = TQMAX(1, numFiles / 100);
  const bool showProgress = options() & ImportProgress;

  ProgressItem& item = ProgressManager::self()->newProgressItem(this, progressLabel(), true);
  item.setTotalSteps(numFiles);
  connect(&item, TQ_SIGNAL(signalCancelled(ProgressItem*)), TQ_SLOT(slotCancel()));
  ProgressItem::Done done(this);

  uint step = 1;

  KCDDB::CDInfo info;
  for(TQMap<TQString, TQString>::Iterator it = files.begin(); !m_cancelled && it != files.end(); ++it, ++step) {
    // open file and read content
    TQFileInfo fileinfo(it.data()); // skip files larger than 10 kB
    if(!fileinfo.exists() || !fileinfo.isReadable() || fileinfo.size() > 10*1024) {
      myDebug() << "FreeDBImporter::readCache() - skipping " << it.data() << endl;
      continue;
    }
    TQFile file(it.data());
    if(!file.open(IO_ReadOnly)) {
      continue;
    }
    TQTextStream ts(&file);
    // libkcddb always writes the cache files in utf-8
    ts.setEncoding(TQTextStream::UnicodeUTF8);
    TQString cddbData = ts.read();
    file.close();

    if(cddbData.isEmpty() || !info.load(cddbData) || !info.isValid()) {
      myDebug() << "FreeDBImporter::readCache() - Error - CDDB record is not valid" << endl;
      myDebug() << "FreeDBImporter::readCache() - File = " << it.data() << endl;
      continue;
    }

    // create a new entry and set fields
    Data::EntryPtr entry = new Data::Entry(m_coll);
    // obviously a CD
    entry->setField(TQString::fromLatin1("medium"), i18n("Compact Disc"));
    entry->setField(title,  info.title);
    entry->setField(artist, info.artist);
    entry->setField(genre,  info.genre);
    if(info.year > 0) {
      entry->setField(TQString::fromLatin1("year"), TQString::number(info.year));
    }
    entry->setField(TQString::fromLatin1("keyword"), info.category);
    TQString extd = info.extd;
    extd.replace('\n', TQString::fromLatin1("<br/>"));
    entry->setField(TQString::fromLatin1("comments"), extd);

    // step through trackList
    TQStringList trackList;
    KCDDB::TrackInfoList t = info.trackInfoList;
    for(uint i = 0; i < t.count(); ++i) {
      trackList << t[i].get(TQString::fromLatin1("title")).toString();
    }
    entry->setField(track, trackList.join(TQString::fromLatin1("; ")));

#if 0
    // add CDDB info
    const TQString br = TQString::fromLatin1("<br/>");
    TQString comment;
    if(!info.extd.isEmpty()) {
      comment.append(info.extd + br);
    }
    if(!info.id.isEmpty()) {
      comment.append(TQString::fromLatin1("CDDB-ID: ") + info.id + br);
    }
    if(info.length > 0) {
      comment.append("Length: " + TQString::number(info.length) + br);
    }
    if(info.revision > 0) {
      comment.append("Revision: " + TQString::number(info.revision) + br);
    }
    entry->setField(comments, comment);
#endif

    // add this entry to the music collection
    m_coll->addEntries(entry);

    if(showProgress && step%stepSize == 0) {
      ProgressManager::self()->setProgress(this, step);
      kapp->processEvents();
    }
  }
#endif
}

#define SETFIELD(name,value) \
  if(entry->field(TQString::fromLatin1(name)).isEmpty()) { \
    entry->setField(TQString::fromLatin1(name), value); \
  }

void FreeDBImporter::readCDText(const TQCString& drive_) {
#ifdef USE_CDTEXT
  Data::EntryPtr entry;
  if(m_coll) {
    if(m_coll->entryCount() > 0) {
      entry = m_coll->entries().front();
    }
  } else {
    m_coll = new Data::MusicCollection(true);
  }
  if(!entry) {
    entry = new Data::Entry(m_coll);
    entry->setField(TQString::fromLatin1("medium"), i18n("Compact Disc"));
    m_coll->addEntries(entry);
  }

  CDText cdtext = getCDText(drive_);
/*
  myDebug() << "CDText - title: " << cdtext.title << endl;
  myDebug() << "CDText - title: " << cdtext.artist << endl;
  for(int i = 0; i < cdtext.trackTitles.size(); ++i) {
    myDebug() << i << "::" << cdtext.trackTitles[i] << " - " << cdtext.trackArtists[i] << endl;
  }
*/

  TQString artist = cdtext.artist;
  SETFIELD("title",    cdtext.title);
  SETFIELD("artist",   artist);
  SETFIELD("comments", cdtext.message);
  TQStringList tracks;
  for(uint i = 0; i < cdtext.trackTitles.size(); ++i) {
    tracks << cdtext.trackTitles[i] + "::" + cdtext.trackArtists[i];
    if(artist.isEmpty()) {
      artist = cdtext.trackArtists[i];
    }
    if(!artist.isEmpty() && artist.lower() != cdtext.trackArtists[i].lower()) {
      artist = i18n("Various");
    }
  }
  SETFIELD("track", tracks.join(TQString::fromLatin1("; ")));

  // something special for compilations and such
  SETFIELD("title",  i18n(Data::Collection::s_emptyGroupTitle));
  SETFIELD("artist", artist);
#endif
}
#undef SETFIELD

TQWidget* FreeDBImporter::widget(TQWidget* parent_, const char* name_/*=0*/) {
  if(m_widget) {
    return m_widget;
  }
  m_widget = new TQWidget(parent_, name_);
  TQVBoxLayout* l = new TQVBoxLayout(m_widget);

  TQGroupBox* bigbox = new TQGroupBox(1, TQt::Horizontal, i18n("Audio CD Options"), m_widget);

  // cdrom stuff
  TQHBox* box = new TQHBox(bigbox);
  m_radioCDROM = new TQRadioButton(i18n("Read data from CD-ROM device"), box);
  m_driveCombo = new KComboBox(true, box);
  m_driveCombo->setDuplicatesEnabled(false);
  TQString w = i18n("Select or input the CD-ROM device location.");
  TQWhatsThis::add(m_radioCDROM, w);
  TQWhatsThis::add(m_driveCombo, w);

  /********************************************************************************/

  m_radioCache = new TQRadioButton(i18n("Read all CDDB cache files only"), bigbox);
  TQWhatsThis::add(m_radioCache, i18n("Read data recursively from all the CDDB cache files "
                                     "contained in the default cache folders."));

  // cddb cache stuff
  m_buttonGroup = new TQButtonGroup(m_widget);
  m_buttonGroup->hide(); // only use as button parent
  m_buttonGroup->setExclusive(true);
  m_buttonGroup->insert(m_radioCDROM);
  m_buttonGroup->insert(m_radioCache);
  connect(m_buttonGroup, TQ_SIGNAL(clicked(int)), TQ_SLOT(slotClicked(int)));

  l->addWidget(bigbox);
  l->addStretch(1);

  // now read config options
  TDEConfigGroup config(TDEGlobal::config(), TQString::fromLatin1("ImportOptions - FreeDB"));
  TQStringList devices = config.readListEntry("CD-ROM Devices");
  if(devices.isEmpty()) {
#if defined(__OpenBSD__)
    devices += TQString::fromLatin1("/dev/rcd0c");
#endif
    devices += TQString::fromLatin1("/dev/cdrom");
    devices += TQString::fromLatin1("/dev/dvd");
  }
  m_driveCombo->insertStringList(devices);
  TQString device = config.readEntry("Last Device");
  if(!device.isEmpty()) {
    m_driveCombo->setCurrentText(device);
  }
  if(config.readBoolEntry("Cache Files Only", false)) {
    m_radioCache->setChecked(true);
  } else {
    m_radioCDROM->setChecked(true);
  }
  // set enabled widgets
  slotClicked(m_buttonGroup->selectedId());

  return m_widget;
}

void FreeDBImporter::slotClicked(int id_) {
  TQButton* button = m_buttonGroup->find(id_);
  if(!button) {
    return;
  }

  m_driveCombo->setEnabled(button == m_radioCDROM);
}

void FreeDBImporter::slotCancel() {
  m_cancelled = true;
}

#include "freedbimporter.moc"
