/***************************************************************************
    copyright            : (C) 2004-2007 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 <config.h>

#include "audiofileimporter.h"
#include "../collections/musiccollection.h"
#include "../entry.h"
#include "../field.h"
#include "../latin1literal.h"
#include "../imagefactory.h"
#include "../tellico_utils.h"
#include "../tellico_kernel.h"
#include "../progressmanager.h"
#include "../tellico_debug.h"

#ifdef HAVE_TAGLIB
#include <taglib/fileref.h>
#include <taglib/tag.h>
#include <taglib/id3v2tag.h>
#include <taglib/mpegfile.h>
#include <taglib/vorbisfile.h>
#include <taglib/flacfile.h>
#include <taglib/audioproperties.h>
#endif

#include <tdelocale.h>
#include <tdeapplication.h>

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

#define TStringToTQString(s) TQString::fromUtf8((s).toCString(true))


using Tellico::Import::AudioFileImporter;

AudioFileImporter::AudioFileImporter(const KURL& url_) : Tellico::Import::Importer(url_)
    , m_coll(0)
    , m_widget(0)
    , m_cancelled(false) {
}

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

Tellico::Data::CollPtr AudioFileImporter::collection() {
#ifndef HAVE_TAGLIB
  return 0;
#else

  if(m_coll) {
    return m_coll;
  }

  ProgressItem& item = ProgressManager::self()->newProgressItem(this, i18n("Scanning audio files..."), true);
  item.setTotalSteps(100);
  connect(&item, TQ_SIGNAL(signalCancelled(ProgressItem*)), TQ_SLOT(slotCancel()));
  ProgressItem::Done done(this);

  // TODO: allow remote audio file importing
  TQStringList dirs = url().path();
  if(m_recursive->isChecked()) {
    dirs += Tellico::findAllSubDirs(dirs[0]);
  }

  if(m_cancelled) {
    return 0;
  }

  const bool showProgress = options() & ImportProgress;

  TQStringList files;
  for(TQStringList::ConstIterator it = dirs.begin(); !m_cancelled && 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 += dir.absFilePath(*it2);
    }
//    kapp->processEvents(); not needed ?
  }

  if(m_cancelled) {
    return 0;
  }
  item.setTotalSteps(files.count());

  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");
  const TQString file     = TQString::fromLatin1("file");

  m_coll = new Data::MusicCollection(true);

  const bool addFile = m_addFilePath->isChecked();
  const bool addBitrate = m_addBitrate->isChecked();

  Data::FieldPtr f;
  if(addFile) {
    f = m_coll->fieldByName(file);
    if(!f) {
      f = new Data::Field(file, i18n("Files"), Data::Field::Table);
      m_coll->addField(f);
    }
    f->setProperty(TQString::fromLatin1("column1"), i18n("Files"));
    if(addBitrate) {
      f->setProperty(TQString::fromLatin1("columns"), TQChar('2'));
      f->setProperty(TQString::fromLatin1("column2"), i18n("Bitrate"));
    } else {
      f->setProperty(TQString::fromLatin1("columns"), TQChar('1'));
    }
  }

  TQMap<TQString, Data::EntryPtr> albumMap;

  TQStringList directoryFiles;
  const uint stepSize = TQMAX(static_cast<size_t>(1), files.count() / 100);

  bool changeTrackTitle = true;
  uint j = 0;
  for(TQStringList::ConstIterator it = files.begin(); !m_cancelled && it != files.end(); ++it, ++j) {
    TagLib::FileRef f(TQFile::encodeName(*it));
    if(f.isNull() || !f.tag()) {
      if((*it).endsWith(TQString::fromLatin1("/.directory"))) {
        directoryFiles += *it;
      }
      continue;
    }

    TagLib::Tag* tag = f.tag();
    TQString album = TStringToTQString(tag->album()).stripWhiteSpace();
    if(album.isEmpty()) {
      // can't do anything since tellico entries are by album
      kdWarning() << "Skipping: no album listed for " << *it << endl;
      continue;
    }
    int disc = discNumber(f);
    if(disc > 1 && !m_coll->hasField(TQString::fromLatin1("track%1").arg(disc))) {
      Data::FieldPtr f2 = new Data::Field(TQString::fromLatin1("track%1").arg(disc),
                                          i18n("Tracks (Disc %1)").arg(disc),
                                          Data::Field::Table);
      f2->setFormatFlag(Data::Field::FormatTitle);
      f2->setProperty(TQString::fromLatin1("columns"), TQChar('3'));
      f2->setProperty(TQString::fromLatin1("column1"), i18n("Title"));
      f2->setProperty(TQString::fromLatin1("column2"), i18n("Artist"));
      f2->setProperty(TQString::fromLatin1("column3"), i18n("Length"));
      m_coll->addField(f2);
      if(changeTrackTitle) {
        Data::FieldPtr newTrack = new Data::Field(*m_coll->fieldByName(track));
        newTrack->setTitle(i18n("Tracks (Disc %1)").arg(1));
        m_coll->modifyField(newTrack);
        changeTrackTitle = false;
      }
    }
    bool various = false;
    bool exists = true;
    Data::EntryPtr entry = 0;
    if(!(entry = albumMap[album.lower()])) {
      entry = new Data::Entry(m_coll);
      albumMap.insert(album.lower(), entry);
      exists = false;
    }
    // album entries use the album name as the title
    entry->setField(title, album);
    TQString a = TStringToTQString(tag->artist()).stripWhiteSpace();
    if(!a.isEmpty()) {
      if(exists && entry->field(artist).lower() != a.lower()) {
        various = true;
        entry->setField(artist, i18n("(Various)"));
      } else {
        entry->setField(artist, a);
      }
    }
    if(tag->year() > 0) {
      entry->setField(year, TQString::number(tag->year()));
    }
    if(!tag->genre().isEmpty()) {
      entry->setField(genre, TStringToTQString(tag->genre()).stripWhiteSpace());
    }

    if(!tag->title().isEmpty()) {
      int trackNum = tag->track();
      if(trackNum <= 0) { // try to figure out track number from file name
        TQFileInfo f(*it);
        TQString fileName = f.baseName();
        TQString numString;
        int i = 0;
        const int len = fileName.length();
        while(fileName[i].isNumber() && i < len) {
          i++;
        }
        if(i == 0) { // does not start with a number
          i = len - 1;
          while(i >= 0 && fileName[i].isNumber()) {
            i--;
          }
          // file name ends with a number
          if(i != len - 1) {
            numString = fileName.mid(i + 1);
          }
        } else {
          numString = fileName.mid(0, i);
        }
        bool ok;
        int number = numString.toInt(&ok);
        if(ok) {
          trackNum = number;
        }
      }
      if(trackNum > 0) {
        TQString t = TStringToTQString(tag->title()).stripWhiteSpace();
        t += "::" + a;
        const int len = f.audioProperties()->length();
        if(len > 0) {
          t += "::" + Tellico::minutes(len);
        }
        TQString realTrack = disc > 1 ? track + TQString::number(disc) : track;
        entry->setField(realTrack, insertValue(entry->field(realTrack), t, trackNum));
        if(addFile) {
          TQString fileValue = *it;
          if(addBitrate) {
            fileValue += "::" + TQString::number(f.audioProperties()->bitrate());
          }
          entry->setField(file, insertValue(entry->field(file), fileValue, trackNum));
        }
      } else {
        myDebug() << *it << " contains no track number and track number cannot be determined, so the track is not imported." << endl;
      }
    } else {
      myDebug() << *it << " has an empty title, so the track is not imported." << endl;
    }
    if(!tag->comment().stripWhiteSpace().isEmpty()) {
      TQString c = entry->field(comments);
      if(!c.isEmpty()) {
        c += TQString::fromLatin1("<br/>");
      }
      if(!tag->title().isEmpty()) {
        c += TQString::fromLatin1("<em>") + TStringToTQString(tag->title()).stripWhiteSpace() + TQString::fromLatin1("</em> - ");
      }
      c += TStringToTQString(tag->comment().stripWhiteSpace()).stripWhiteSpace();
      entry->setField(comments, c);
    }

    if(!exists) {
      m_coll->addEntries(entry);
    }
    if(showProgress && j%stepSize == 0) {
      ProgressManager::self()->setTotalSteps(this, files.count() + directoryFiles.count());
      ProgressManager::self()->setProgress(this, j);
      kapp->processEvents();
    }

/*    kdDebug() << "-- TAG --" << endl;
    kdDebug() << "title   - \"" << tag->title().to8Bit()   << "\"" << endl;
    kdDebug() << "artist  - \"" << tag->artist().to8Bit()  << "\"" << endl;
    kdDebug() << "album   - \"" << tag->album().to8Bit()   << "\"" << endl;
    kdDebug() << "year    - \"" << tag->year()             << "\"" << endl;
    kdDebug() << "comment - \"" << tag->comment().to8Bit() << "\"" << endl;
    kdDebug() << "track   - \"" << tag->track()            << "\"" << endl;
    kdDebug() << "genre   - \"" << tag->genre().to8Bit()   << "\"" << endl;*/
  }

  if(m_cancelled) {
    m_coll = 0;
    return 0;
  }

  TQTextStream ts;
  TQRegExp iconRx(TQString::fromLatin1("Icon\\s*=\\s*(.*)"));
  for(TQStringList::ConstIterator it = directoryFiles.begin(); !m_cancelled && it != directoryFiles.end(); ++it, ++j) {
    TQFile file(*it);
    if(!file.open(IO_ReadOnly)) {
      continue;
    }
    ts.unsetDevice();
    ts.setDevice(&file);
    for(TQString line = ts.readLine(); !line.isNull(); line = ts.readLine()) {
      if(!iconRx.exactMatch(line)) {
        continue;
      }
      TQDir thisDir(*it);
      thisDir.cdUp();
      TQFileInfo fi(thisDir, iconRx.cap(1));
      Data::EntryPtr entry = albumMap[thisDir.dirName()];
      if(!entry) {
        continue;
      }
      KURL u;
      u.setPath(fi.absFilePath());
      TQString id = ImageFactory::addImage(u, true);
      if(!id.isEmpty()) {
        entry->setField(TQString::fromLatin1("cover"), id);
      }
      break;
    }

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

  if(m_cancelled) {
    m_coll = 0;
    return 0;
  }

  return m_coll;
#endif
}

TQWidget* AudioFileImporter::widget(TQWidget* parent_, const char* name_) {
  if(m_widget) {
    return m_widget;
  }

  m_widget = new TQWidget(parent_, name_);
  TQVBoxLayout* l = new TQVBoxLayout(m_widget);

  TQVGroupBox* box = new TQVGroupBox(i18n("Audio File Options"), m_widget);

  m_recursive = new TQCheckBox(i18n("Recursive &folder search"), box);
  TQWhatsThis::add(m_recursive, i18n("If checked, folders are recursively searched for audio files."));
  // by default, make it checked
  m_recursive->setChecked(true);

  m_addFilePath = new TQCheckBox(i18n("Include file &location"), box);
  TQWhatsThis::add(m_addFilePath, i18n("If checked, the file names for each track are added to the entries."));
  m_addFilePath->setChecked(false);
  connect(m_addFilePath, TQ_SIGNAL(toggled(bool)), TQ_SLOT(slotAddFileToggled(bool)));

  m_addBitrate = new TQCheckBox(i18n("Include &bitrate"), box);
  TQWhatsThis::add(m_addBitrate, i18n("If checked, the bitrate for each track is added to the entries."));
  m_addBitrate->setChecked(false);
  m_addBitrate->setEnabled(false);

  l->addWidget(box);
  l->addStretch(1);
  return m_widget;
}

// pos_ is NOT zero-indexed!
TQString AudioFileImporter::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();
  }
  if(!list[pos_-1].isNull()) {
    myDebug() << "AudioFileImporter::insertValue() - overwriting track " << pos_ << endl;
    myDebug() << "*** Old value: " << list[pos_-1] << endl;
    myDebug() << "*** New value: " << value_ << endl;
  }
  list[pos_-1] = value_;
  return list.join(TQString::fromLatin1("; "));
}

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

void AudioFileImporter::slotAddFileToggled(bool on_) {
  m_addBitrate->setEnabled(on_);
  if(!on_) {
    m_addBitrate->setChecked(false);
  }
}

int AudioFileImporter::discNumber(const TagLib::FileRef& ref_) const {
  // default to 1 unless otherwise
  int num = 1;
#ifdef HAVE_TAGLIB
  TQString disc;
  if(TagLib::MPEG::File* file = dynamic_cast<TagLib::MPEG::File*>(ref_.file())) {
    if(file->ID3v2Tag() && !file->ID3v2Tag()->frameListMap()["TPOS"].isEmpty()) {
      disc = TStringToTQString(file->ID3v2Tag()->frameListMap()["TPOS"].front()->toString()).stripWhiteSpace();
    }
  } else if(TagLib::Ogg::Vorbis::File* file = dynamic_cast<TagLib::Ogg::Vorbis::File*>(ref_.file())) {
    if(file->tag() && !file->tag()->fieldListMap()["DISCNUMBER"].isEmpty()) {
      disc = TStringToTQString(file->tag()->fieldListMap()["DISCNUMBER"].front()).stripWhiteSpace();
    }
  } else if(TagLib::FLAC::File* file = dynamic_cast<TagLib::FLAC::File*>(ref_.file())) {
    if(file->xiphComment() && !file->xiphComment()->fieldListMap()["DISCNUMBER"].isEmpty()) {
      disc = TStringToTQString(file->xiphComment()->fieldListMap()["DISCNUMBER"].front()).stripWhiteSpace();
    }
  }

  if(!disc.isEmpty()) {
    int pos = disc.find('/');
    int n;
    bool ok;
    if(pos == -1) {
      n = disc.toInt(&ok);
    } else {
      n = disc.left(pos).toInt(&ok);
    }
    if(ok && n > 0) {
      num = n;
    }
  }
#endif
  return num;
}

#include "audiofileimporter.moc"
