/***************************************************************************
    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 "entryupdater.h"
#include "entry.h"
#include "collection.h"
#include "tellico_kernel.h"
#include "tellico_debug.h"
#include "progressmanager.h"
#include "statusbar.h"
#include "gui/richtextlabel.h"
#include "document.h"

#include <kdialogbase.h>
#include <tdelocale.h>
#include <tdelistview.h>
#include <kiconloader.h>

#include <tqvbox.h>
#include <tqtimer.h>

namespace {
  static const int CHECK_COLLECTION_IMAGES_STEP_SIZE = 10;
}

using Tellico::EntryUpdater;

// for each entry, we loop over all available fetchers
// then we loop over all entries
EntryUpdater::EntryUpdater(Data::CollPtr coll_, Data::EntryVec entries_, TQObject* parent_)
    : TQObject(parent_), m_coll(coll_), m_entriesToUpdate(entries_), m_cancelled(false) {
  // for now, we're assuming all entries are same collection type
  m_fetchers = Fetch::Manager::self()->createUpdateFetchers(m_coll->type());
  for(Fetch::FetcherVec::Iterator it = m_fetchers.begin(); it != m_fetchers.end(); ++it) {
    connect(it.data(), TQ_SIGNAL(signalResultFound(Tellico::Fetch::SearchResult*)),
            TQ_SLOT(slotResult(Tellico::Fetch::SearchResult*)));
    connect(it.data(), TQ_SIGNAL(signalDone(Tellico::Fetch::Fetcher::Ptr)),
            TQ_SLOT(slotDone()));
  }
  init();
}

EntryUpdater::EntryUpdater(const TQString& source_, Data::CollPtr coll_, Data::EntryVec entries_, TQObject* parent_)
    : TQObject(parent_)
    , m_coll(coll_)
    , m_entriesToUpdate(entries_)
    , m_cancelled(false) {
  // for now, we're assuming all entries are same collection type
  Fetch::Fetcher::Ptr f = Fetch::Manager::self()->createUpdateFetcher(m_coll->type(), source_);
  if(f) {
    m_fetchers.append(f);
    connect(f, TQ_SIGNAL(signalResultFound(Tellico::Fetch::SearchResult*)),
            TQ_SLOT(slotResult(Tellico::Fetch::SearchResult*)));
    connect(f, TQ_SIGNAL(signalDone(Tellico::Fetch::Fetcher::Ptr)),
            TQ_SLOT(slotDone()));
  }
  init();
}

EntryUpdater::~EntryUpdater() {
  for(ResultList::Iterator res = m_results.begin(); res != m_results.end(); ++res) {
    delete (*res).first;
  }
}

void EntryUpdater::init() {
  m_fetchIndex = 0;
  m_origEntryCount = m_entriesToUpdate.count();
  TQString label;
  if(m_entriesToUpdate.count() == 1) {
    label = i18n("Updating %1...").arg(m_entriesToUpdate.front()->title());
  } else {
    label = i18n("Updating entries...");
  }
  Kernel::self()->beginCommandGroup(i18n("Update Entries"));
  ProgressItem& item = ProgressManager::self()->newProgressItem(this, label, true /*canCancel*/);
  item.setTotalSteps(m_fetchers.count() * m_origEntryCount);
  connect(&item, TQ_SIGNAL(signalCancelled(ProgressItem*)), TQ_SLOT(slotCancel()));

  // done if no fetchers available
  if(m_fetchers.isEmpty()) {
    TQTimer::singleShot(500, this, TQ_SLOT(slotCleanup()));
  } else {
    slotStartNext(); // starts fetching
  }
}

void EntryUpdater::slotStartNext() {
  StatusBar::self()->setStatus(i18n("Updating <b>%1</b>...").arg(m_entriesToUpdate.front()->title()));
  ProgressManager::self()->setProgress(this, m_fetchers.count() * (m_origEntryCount - m_entriesToUpdate.count()) + m_fetchIndex);

  Fetch::Fetcher::Ptr f = m_fetchers[m_fetchIndex];
//  myDebug() << "EntryUpdater::slotDone() - starting " << f->source() << endl;
  f->updateEntry(m_entriesToUpdate.front());
}

void EntryUpdater::slotDone() {
  if(m_cancelled) {
    myLog() << "EntryUpdater::slotDone() - cancelled" << endl;
    TQTimer::singleShot(500, this, TQ_SLOT(slotCleanup()));
    return;
  }

  if(!m_results.isEmpty()) {
    handleResults();
  }

  m_results.clear();
  ++m_fetchIndex;
//  myDebug() << "EntryUpdater::slotDone() " << m_fetchIndex << endl;
  if(m_fetchIndex == static_cast<int>(m_fetchers.count())) {
    m_fetchIndex = 0;
    // we've gone through the loop for the first entry in the vector
    // pop it and move on
    m_entriesToUpdate.remove(m_entriesToUpdate.begin());
    // if there are no more entries, and this is the last fetcher, time to delete
    if(m_entriesToUpdate.isEmpty()) {
      TQTimer::singleShot(500, this, TQ_SLOT(slotCleanup()));
      return;
    }
  }
  kapp->processEvents();
  // so the entry updater can clean up a bit
  TQTimer::singleShot(500, this, TQ_SLOT(slotStartNext()));
}

void EntryUpdater::slotResult(Fetch::SearchResult* result_) {
  if(!result_ || m_cancelled) {
    return;
  }

//  myDebug() << "EntryUpdater::slotResult() - " << result_->title << " [" << result_->fetcher->source() << "]" << endl;
  m_results.append(UpdateResult(result_, m_fetchers[m_fetchIndex]->updateOverwrite()));
  Data::EntryPtr e = result_->fetchEntry();
  if(e) {
    m_fetchedEntries.append(e);
    int match = m_coll->sameEntry(m_entriesToUpdate.front(), e);
    if(match > Data::Collection::ENTRY_PERFECT_MATCH) {
      result_->fetcher->stop();
    }
  }
  kapp->processEvents();
}

void EntryUpdater::slotCancel() {
//  myDebug() << "EntryUpdater::slotCancel()" << endl;
  m_cancelled = true;
  Fetch::Fetcher::Ptr f = m_fetchers[m_fetchIndex];
  if(f) {
    f->stop(); // ends up calling slotDone();
  } else {
    slotDone();
  }
}

void EntryUpdater::handleResults() {
  Data::EntryPtr entry = m_entriesToUpdate.front();
  int best = 0;
  ResultList matches;
  for(ResultList::Iterator res = m_results.begin(); res != m_results.end(); ++res) {
    Data::EntryPtr e = (*res).first->fetchEntry();
    if(!e) {
      continue;
    }
    m_fetchedEntries.append(e);
    int match = m_coll->sameEntry(entry, e);
    if(match) {
//      myDebug() << e->title() << " matches by " << match << endl;
    }
    if(match > best) {
      best = match;
      matches.clear();
      matches.append(*res);
    } else if(match == best && best > 0) {
      matches.append(*res);
    }
  }
  if(best < Data::Collection::ENTRY_GOOD_MATCH) {
    if(best > 0) {
      myDebug() << "no good match (score > 10), best match = " << best << " (" << matches.count() << " matches)" << endl;
    }
    return;
  }
//  myDebug() << "best match = " << best << " (" << matches.count() << " matches)" << endl;
  UpdateResult match(0, true);
  if(matches.count() == 1) {
    match = matches.front();
  } else if(matches.count() > 1) {
    match = askUser(matches);
  }
  // askUser() could come back with nil
  if(match.first) {
    mergeCurrent(match.first->fetchEntry(), match.second);
  }
}

Tellico::EntryUpdater::UpdateResult EntryUpdater::askUser(ResultList results) {
  KDialogBase dlg(Kernel::self()->widget(), "entry updater dialog",
                  true, i18n("Select Match"), KDialogBase::Ok|KDialogBase::Cancel);
  TQVBox* box = new TQVBox(&dlg);
  box->setSpacing(10);

  TQHBox* hbox = new TQHBox(box);
  hbox->setSpacing(10);
  TQLabel* icon = new TQLabel(hbox);
  icon->setPixmap(TDEGlobal::iconLoader()->loadIcon(TQString::fromLatin1("network"), TDEIcon::Panel, 64));
  TQString s = i18n("<qt><b>%1</b> returned multiple results which could match <b>%2</b>, "
                   "the entry currently in the collection. Please select the correct match.</qt>")
              .arg(m_fetchers[m_fetchIndex]->source())
              .arg(m_entriesToUpdate.front()->field(TQString::fromLatin1("title")));
  GUI::RichTextLabel* l = new GUI::RichTextLabel(s, hbox);
  hbox->setStretchFactor(l, 100);

  TDEListView* view = new TDEListView(box);
  view->setShowSortIndicator(true);
  view->setAllColumnsShowFocus(true);
  view->setResizeMode(TQListView::AllColumns);
  view->setMinimumWidth(640);
  view->addColumn(i18n("Title"));
  view->addColumn(i18n("Description"));
  TQMap<TDEListViewItem*, UpdateResult> map;
  for(ResultList::Iterator res = results.begin(); res != results.end(); ++res) {
    map.insert(new TDEListViewItem(view, (*res).first->fetchEntry()->title(), (*res).first->desc), *res);
  }

  dlg.setMainWidget(box);
  if(dlg.exec() != TQDialog::Accepted) {
    return UpdateResult(0, false);
  }
  TDEListViewItem* item = static_cast<TDEListViewItem*>(view->selectedItem());
  if(!item) {
    return UpdateResult(0, false);
  }
  return map[item];
}

void EntryUpdater::mergeCurrent(Data::EntryPtr entry_, bool overWrite_) {
  Data::EntryPtr currEntry = m_entriesToUpdate.front();
  if(entry_) {
    m_matchedEntries.append(entry_);
    Kernel::self()->updateEntry(currEntry, entry_, overWrite_);
    if(m_entriesToUpdate.count() % CHECK_COLLECTION_IMAGES_STEP_SIZE == 1) {
      // I don't want to remove any images in the entries that are getting
      // updated since they'll reference them later and the command isn't
      // executed until the command history group is finished
      // so remove pointers to matched entries
      Data::EntryVec nonUpdatedEntries = m_fetchedEntries;
      for(Data::EntryVecIt match = m_matchedEntries.begin(); match != m_matchedEntries.end(); ++match) {
        nonUpdatedEntries.remove(match);
      }
      Data::Document::self()->removeImagesNotInCollection(nonUpdatedEntries, m_matchedEntries);
    }
  }
}

void EntryUpdater::slotCleanup() {
  StatusBar::self()->clearStatus();
  ProgressManager::self()->setDone(this);
  Kernel::self()->endCommandGroup();
  deleteLater();
}

#include "entryupdater.moc"
