/***************************************************************************
    Copyright (C) 2009 Robby Stephenson <robby@periapsis.org>
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU General Public License as        *
 *   published by the Free Software Foundation; either version 2 of        *
 *   the License or (at your option) version 3 or any later version        *
 *   accepted by the membership of KDE e.V. (or its successor approved     *
 *   by the membership of KDE e.V.), which shall act as a proxy            *
 *   defined in Section 14 of version 3 of the license.                    *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
 *                                                                         *
 ***************************************************************************/

#include "openlibraryfetcher.h"
#include "../collections/bookcollection.h"
#include "../collections/comicbookcollection.h"
#include "../images/imagefactory.h"
#include "../gui/combobox.h"
#include "../utils/isbnvalidator.h"
#include "../utils/guiproxy.h"
#include "../utils/objvalue.h"
#include "../entry.h"
#include "../core/filehandler.h"
#include "../tellico_debug.h"

#include <KLocalizedString>
#include <KIO/StoredTransferJob>
#include <KJobUiDelegate>
#include <KJobWidgets>

#include <QLabel>
#include <QFile>
#include <QTextStream>
#include <QGridLayout>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QUrlQuery>

namespace {
  static const char* OPENLIBRARY_QUERY_URL = "https://openlibrary.org/query.json";
  static const char* OPENLIBRARY_AUTHOR_QUERY_URL = "https://openlibrary.org/search/authors.json";
}

using namespace Tellico;
using Tellico::Fetch::OpenLibraryFetcher;

OpenLibraryFetcher::OpenLibraryFetcher(QObject* parent_)
    : Fetcher(parent_), m_imageSize(MediumImage), m_started(false) {
}

OpenLibraryFetcher::~OpenLibraryFetcher() {
}

QString OpenLibraryFetcher::source() const {
  return m_name.isEmpty() ? defaultName() : m_name;
}

bool OpenLibraryFetcher::canSearch(Fetch::FetchKey k) const {
  return k == Title || k == Person || k == ISBN || k == LCCN;
}

bool OpenLibraryFetcher::canFetch(int type) const {
  return type == Data::Collection::Book ||
      type == Data::Collection::Bibtex ||
      type == Data::Collection::ComicBook;
}

void OpenLibraryFetcher::readConfigHook(const KConfigGroup& config_) {
  const int imageSize = config_.readEntry("Image Size", -1);
  if(imageSize > -1) {
    m_imageSize = static_cast<ImageSize>(imageSize);
  }
}

void OpenLibraryFetcher::search() {
  m_started = true;
  // we only split ISBN and LCCN values
  QStringList searchTerms;
  if(request().key() == ISBN || request().key() == LCCN) {
    searchTerms = FieldFormat::splitValue(request().value());
  } else  {
    searchTerms += request().value();
  }
  foreach(const QString& searchTerm, searchTerms) {
    doSearch(searchTerm);
  }
  if(m_jobs.isEmpty()) {
    stop();
  }
}

void OpenLibraryFetcher::doSearch(const QString& term_) {
  QUrl u(QString::fromLatin1(OPENLIBRARY_QUERY_URL));
  QUrlQuery q;
  // books are type/edition
  q.addQueryItem(QStringLiteral("type"), QStringLiteral("/type/edition"));

  switch(request().key()) {
    case Title:
      q.addQueryItem(QStringLiteral("title"), term_);
      break;

    case Person:
      {
        QString author = getAuthorKeys(term_);
        if(author.isEmpty()) {
          myLog() << "No matching authors found";
          return;
        }
        author.prepend(QLatin1String("/authors/"));
        q.addQueryItem(QStringLiteral("authors"), author);
      }
      break;

    case ISBN:
      {
        const QString isbn = ISBNValidator::cleanValue(term_);
        if(isbn.size() > 10) {
          q.addQueryItem(QStringLiteral("isbn_13"), isbn);
        } else {
          q.addQueryItem(QStringLiteral("isbn_10"), isbn);
        }
      }
      break;

    case LCCN:
      q.addQueryItem(QStringLiteral("lccn"), term_);
      break;

    case Raw:
      {
        // raw query comes in as a query string, combine it
        QUrlQuery newQuery(term_);
        const auto newQueryItems = newQuery.queryItems();
        for(const auto& item : newQueryItems) {
          q.addQueryItem(item.first, item.second);
        }
      }
      break;

    default:
      myWarning() << source() << "- key not recognized:" << request().key();
      return;
  }
  q.addQueryItem(QStringLiteral("*"), QString());
  u.setQuery(q);
//  myDebug() << u;

  QPointer<KIO::StoredTransferJob> job = KIO::storedGet(u, KIO::NoReload, KIO::HideProgressInfo);
  KJobWidgets::setWindow(job, GUI::Proxy::widget());
  connect(job.data(), &KJob::result, this, &OpenLibraryFetcher::slotComplete);
  m_jobs << job;
}

void OpenLibraryFetcher::endJob(KIO::StoredTransferJob* job_) {
  m_jobs.removeAll(job_);
  if(m_jobs.isEmpty())  {
    stop();
  }
}

void OpenLibraryFetcher::stop() {
  if(!m_started) {
    return;
  }
  foreach(QPointer<KIO::StoredTransferJob> job, m_jobs) {
    if(job) {
      job->kill();
    }
  }
  m_jobs.clear();
  m_started = false;
  Q_EMIT signalDone(this);
}

Tellico::Data::EntryPtr OpenLibraryFetcher::fetchEntryHook(uint uid_) {
  Data::EntryPtr entry = m_entries.value(uid_);
  if(!entry) {
    myWarning() << "no entry in dict";
    return Data::EntryPtr();
  }

  // there are several fields which may be set on the work instead of the edition
  // if any are empty, fetch the work details and update

  // https://bugs.kde.org/show_bug.cgi?id=509124
  const QString plotString(QStringLiteral("plot"));
  // see https://github.com/internetarchive/openlibrary/issues/8144
  const QString authorString = entry->collection()->type() == Data::Collection::ComicBook
                               ? QStringLiteral("writer")
                               : QStringLiteral("author");
  const QString seriesString(QStringLiteral("series"));
  if(entry->field(plotString).isEmpty() ||
     entry->field(authorString).isEmpty() ||
     entry->field(seriesString).isEmpty()) {
    const QString work = m_workLink.value(uid_);
    const QUrl workUrl(QStringLiteral("https://openlibrary.org%1.json").arg(work));
    const auto output = FileHandler::readDataFile(workUrl, true /*quiet*/);
#if 0
    myWarning() << "Remove debug openlibrary-work from openlibraryfetcher.cpp";
    QFile f(QString::fromLatin1("/tmp/openlibrary-work.json"));
    if(f.open(QIODevice::WriteOnly)) {
      QTextStream t(&f);
      t << output;
    }
    f.close();
#endif
    QJsonDocument doc = QJsonDocument::fromJson(output);
    const auto obj = doc.object();

    if(entry->field(plotString).isEmpty()) {
      QString desc = objValue(obj, "description", "value");
      if(desc.isEmpty()) {
        desc = objValue(obj, "description");
      }
      desc.replace(QLatin1Char('\n'), QLatin1String("<br/>"));
      entry->setField(plotString, desc);
    }

    if(entry->field(authorString).isEmpty()) {
      QStringList authors = getAuthorNames(obj.value(QLatin1StringView("authors")).toArray());
      if(!authors.isEmpty()) {
        entry->setField(authorString, authors.join(FieldFormat::delimiterString()));
      }
    }

    if(entry->field(seriesString).isEmpty()) {
      const auto subjArray = obj.value(QLatin1StringView("subjects")).toArray();
      for(const auto& res : subjArray) {
        QString value = res.toString();
        if(value.startsWith(QLatin1StringView("series:"))) {
          value.remove(0, 7); // remove first 7 characters
          value.replace(QLatin1Char('_'), QLatin1Char(' '));
          value = FieldFormat::capitalize(value);
          entry->setField(seriesString, value);
          break;
        }
      }
    }
  }

  const QString coverString(QStringLiteral("cover"));
  if(m_imageSize != NoImage && entry->field(coverString).isEmpty()) {
    QString imgSize;
    switch(m_imageSize) {
      case LargeImage:  imgSize = QLatin1Char('L'); break;
      case MediumImage: imgSize = QLatin1Char('M'); break;
      case SmallImage:  imgSize = QLatin1Char('S'); break;
      case NoImage: break;
    }
    QString coverId, imageUrl;

    // just want the portion after the last slash
    const QString olid = m_olidLink.value(uid_).section(QLatin1Char('/'), -1);
    if(!olid.isEmpty()) {
      coverId = olid;
      imageUrl = QStringLiteral("https://covers.openlibrary.org/b/olid/%1-%2.jpg?default=false");
    } else {
      coverId = ISBNValidator::cleanValue(entry->field(QStringLiteral("isbn")));
      imageUrl = QStringLiteral("https://covers.openlibrary.org/b/isbn/%1-%2.jpg?default=false");
    }
    if(!coverId.isEmpty()) {
      imageUrl = imageUrl.arg(coverId, imgSize);
      entry->setField(coverString, ImageFactory::addImage(QUrl(imageUrl), true));
    }
  }

  return entry;
}

Tellico::Fetch::FetchRequest OpenLibraryFetcher::updateRequest(Data::EntryPtr entry_) {
  QString olid = entry_->field(QStringLiteral("openlibrary")).section(QLatin1Char('/'), -2);
  if(!olid.isEmpty()) {
    return FetchRequest(Raw, QStringLiteral("key=/") + olid);
  }
  const QString isbn = entry_->field(QStringLiteral("isbn"));
  if(!isbn.isEmpty()) {
    return FetchRequest(ISBN, isbn);
  }
  const QString lccn = entry_->field(QStringLiteral("lccn"));
  if(!lccn.isEmpty()) {
    return FetchRequest(LCCN, lccn);
  }
  const QString title = entry_->field(QStringLiteral("title"));
  if(title.isEmpty()) {
    return FetchRequest();
  }

  // can't search by authors, for now, since the author value is a key reference
  // can't search by pub year since many of the publish_date fields are a full month, day, year

  const QString pub = entry_->field(QStringLiteral("publisher"));
  auto publishers = FieldFormat::splitValue(pub);
  if(!publishers.isEmpty()) {
    QUrlQuery q;
    q.addQueryItem(QStringLiteral("title"), title);
    q.addQueryItem(QStringLiteral("publishers"), publishers.first());
    return FetchRequest(Raw, q.query());
  }

  // fallback to just title search
  return FetchRequest(Title, title);
}

void OpenLibraryFetcher::slotComplete(KJob* job_) {
  KIO::StoredTransferJob* job = static_cast<KIO::StoredTransferJob*>(job_);

  if(job->error()) {
    job->uiDelegate()->showErrorMessage();
    endJob(job);
    return;
  }

  const QByteArray data = job->data();
  if(data.isEmpty()) {
    endJob(job);
    return;
  }

#if 0
  myWarning() << "Remove debug from openlibraryfetcher.cpp";
  QFile f(QString::fromLatin1("/tmp/openlibrary-result.json"));
  if(f.open(QIODevice::WriteOnly)) {
    QTextStream t(&f);
    t << data;
  }
  f.close();
#endif

  QJsonDocument doc = QJsonDocument::fromJson(data);
  const auto array = doc.array();
  if(array.isEmpty()) {
    endJob(job);
    return;
  }

  Data::CollPtr coll;
  if(request().collectionType() == Data::Collection::ComicBook) {
    coll = new Data::ComicBookCollection(true);
  } else {
    coll = new Data::BookCollection(true);
  }

  for(const auto& result : array) {
    // be sure to check that the fetcher has not been stopped
    // crashes can occur if not
    if(!m_started) {
      break;
    }

    const auto resObj = result.toObject();
    if(coll->type() == Data::Collection::ComicBook) {
      const auto binding = objValue(resObj, "physical_format");
      if(!binding.isEmpty() && binding != QLatin1String("comic")) {
        myLog() << "Skipping non-comic result:" << objValue(resObj, "title");
        continue;
      }
    }

    Data::EntryPtr entry(new Data::Entry(coll));
    populate(entry, resObj);

    FetchResult* r = new FetchResult(this, entry);
    m_entries.insert(r->uid, entry);

    // retain olid and link to parent work
    m_olidLink.insert(r->uid, objValue(resObj, "key"));
    const auto worksArray = resObj[QLatin1StringView("works")].toArray();
    if(!worksArray.isEmpty()) {
      const auto workObj = worksArray.first().toObject();
      const QString key = objValue(workObj, "key");
      if(!key.isEmpty()) {
        m_workLink.insert(r->uid, key);
      }
    }

    Q_EMIT signalResultFound(r);
  }

//  m_start = m_entries.count();
//  m_hasMoreResults = m_start <= m_total;
  m_hasMoreResults = false; // for now, no continued searches
  endJob(job);
}

void OpenLibraryFetcher::populate(Data::EntryPtr entry_, const QJsonObject& obj_) {
  static const QRegularExpression yearRx(QStringLiteral("\\d{4}"));

  entry_->setField(QStringLiteral("title"), objValue(obj_, "title"));

  // only allow comic format for comic book collections
  QString binding = objValue(obj_, "physical_format");
  const auto bindingLower = binding.toLower();
  if(bindingLower == QLatin1StringView("hardcover")) {
    binding = QStringLiteral("Hardback");
  } else if(bindingLower == QLatin1StringView("ebook")) {
    binding = QStringLiteral("E-Book");
  } else if(bindingLower.contains(QLatin1StringView("paperback"))) {
    binding = QStringLiteral("Paperback");
  }
  if(!binding.isEmpty()) {
    entry_->setField(QStringLiteral("binding"), i18n(binding.toUtf8().constData()));
  }

  entry_->setField(QStringLiteral("subtitle"), objValue(obj_, "subtitle"));
  auto yearMatch = yearRx.match(objValue(obj_, "publish_date"));
  if(yearMatch.hasMatch()) {
    entry_->setField(QStringLiteral("pub_year"), yearMatch.captured());
  }
  yearMatch = yearRx.match(objValue(obj_, "copyright_date"));
  if(yearMatch.hasMatch()) {
    entry_->setField(QStringLiteral("cr_year"), yearMatch.captured());
  }
  QString isbn = objValue(obj_, "isbn_10");
  if(isbn.isEmpty()) {
    isbn = objValue(obj_, "isbn_13");
  }
  const QString isbnName(QStringLiteral("isbn"));
  if(!isbn.isEmpty()) {
    if(!entry_->collection()->hasField(isbnName)) {
      entry_->collection()->addField(Data::Field::createDefaultField(Data::Field::IsbnField));
    }
    ISBNValidator val(this);
    val.fixup(isbn);
    entry_->setField(isbnName, isbn);
  }
  const QString lccnName(QStringLiteral("lccn"));
  const QString lccn = objValue(obj_, "lccn");
  if(!lccn.isEmpty()) {
    if(!entry_->collection()->hasField(lccnName)) {
      entry_->collection()->addField(Data::Field::createDefaultField(Data::Field::LccnField));
    }
    entry_->setField(lccnName, lccn);
  }
  entry_->setField(QStringLiteral("genre"), objValue(obj_, "genres"));
  entry_->setField(QStringLiteral("keyword"), objValue(obj_, "subjects"));
  entry_->setField(QStringLiteral("edition"), objValue(obj_, "edition_name"));
  entry_->setField(QStringLiteral("publisher"), objValue(obj_, "publishers"));
  entry_->setField(QStringLiteral("series"), objValue(obj_, "series"));
  entry_->setField(QStringLiteral("pages"), objValue(obj_, "number_of_pages"));

  QString desc = objValue(obj_, "description", "value");
  if(desc.isEmpty()) {
    desc = objValue(obj_, "description");
  }
  desc.replace(QLatin1Char('\n'), QLatin1String("<br/>"));
  entry_->setField(QStringLiteral("plot"), desc);

  QString notes = objValue(obj_, "notes", "value");
  if(notes.isEmpty()) {
    notes = objValue(obj_, "notes");
  }
  notes.replace(QLatin1Char('\n'), QLatin1String("<br/>"));
  entry_->setField(QStringLiteral("comments"), notes);

  const QString openlibraryString(QStringLiteral("openlibrary"));
  if(optionalFields().contains(openlibraryString)) {
    if(!entry_->collection()->hasField(openlibraryString)) {
      Data::FieldPtr field(new Data::Field(openlibraryString, i18n("OpenLibrary Link"), Data::Field::URL));
      field->setCategory(i18n("General"));
      entry_->collection()->addField(field);
    }
    entry_->setField(openlibraryString, QLatin1String("https://openlibrary.org") + objValue(obj_, "key"));
  }

  QStringList authors = getAuthorNames(obj_[QLatin1StringView("authors")].toArray());
  if(!authors.isEmpty()) {
    const QString authorString = entry_->collection()->type() == Data::Collection::ComicBook
                                 ? QStringLiteral("writer")
                                 : QStringLiteral("author");
    entry_->setField(authorString, authors.join(FieldFormat::delimiterString()));
  }

  QStringList translators;
  const auto contribsArray = obj_[QLatin1StringView("contributors")].toArray();
  for(const auto& contrib : contribsArray) {
    const auto contribObj = contrib.toObject();
    const auto role = objValue(contribObj, "role");
    if(role == QLatin1StringView("Translator")) {
      translators += objValue(contribObj, "name");
    }
  }
  if(!translators.isEmpty()) {
    entry_->setField(QStringLiteral("translator"), translators.join(FieldFormat::delimiterString()));
  }

  QStringList langs;
  const auto langArray = obj_[QLatin1String("languages")].toArray();
  for(const auto& lang : langArray) {
    const auto langObj = lang.toObject();
    const QString key = objValue(langObj, "key");
    if(m_langHash.contains(key)) {
      langs += m_langHash.value(key);
    } else if(!key.isEmpty()) {
      QUrl langUrl(QString::fromLatin1(OPENLIBRARY_QUERY_URL));
      QUrlQuery q;
      q.addQueryItem(QStringLiteral("type"), QStringLiteral("/type/language"));
      q.addQueryItem(QStringLiteral("key"), key);
      q.addQueryItem(QStringLiteral("name"), QString());
      langUrl.setQuery(q);

      const auto output = FileHandler::readDataFile(langUrl, true /*quiet*/);
      QJsonDocument doc2 = QJsonDocument::fromJson(output);
      const auto langArray = doc2.array();
      if(!langArray.isEmpty()) {
        const QString name = objValue(langArray.at(0).toObject(), "name");
        if(!name.isEmpty()) {
          langs << i18n(name.toUtf8().constData());
          m_langHash.insert(key, langs.last());
        }
      }
    }
  }
  if(!langs.isEmpty()) {
    entry_->setField(QStringLiteral("language"), langs.join(FieldFormat::delimiterString()));
  }
}

QStringList OpenLibraryFetcher::getAuthorNames(const QJsonArray& keys_) {
  QStringList authors;
  if(keys_.isEmpty()) {
    return authors;
  }

  bool emptyQuery = true;
  QString authorQuery(QStringLiteral("key:("));
  for(int i = 0; i < keys_.count(); i++) {
    const auto obj = keys_.at(i).toObject();
    QString key = objValue(obj, "author", "key");
    if(key.isEmpty()) key = objValue(obj, "key");
    if(key.isEmpty()) {
      myDebug() << "Failed to pull key from" << obj;
      continue;
    }
    if(!key.startsWith(QLatin1String("/authors/"))) {
      key = QLatin1String("/authors/") + key;
    }
    if(m_authorHash.contains(key)) {
      authors += m_authorHash.value(key);
      continue;
    }
    authorQuery += key + QLatin1String(" OR ");
    emptyQuery = false;
  }
  if(emptyQuery) {
    // no need to search further
    return authors;
  }
  // remove final OR and add )
  authorQuery.chop(4); // " OR "
  authorQuery += QLatin1Char(')');

  QUrlQuery q;
  q.addQueryItem(QStringLiteral("q"), authorQuery);
  q.addQueryItem(QStringLiteral("fields"), QStringLiteral("key,name"));
  QUrl u(QString::fromLatin1(OPENLIBRARY_AUTHOR_QUERY_URL));
  u.setQuery(q);

  const auto output = FileHandler::readDataFile(u, true /*quiet*/);
  const QJsonDocument doc = QJsonDocument::fromJson(output);
  const auto array = doc.object().value(QLatin1StringView("docs")).toArray();
  for(int i = 0; i < array.count(); i++) {
    const auto obj = array.at(i).toObject();
    QString key = objValue(obj, "key");
    if(!key.startsWith(QLatin1StringView("/authors/"))) {
      key = QLatin1String("/authors/") + key;
    }
    const QString author = objValue(obj, "name");
    if(!author.isEmpty()) {
      m_authorHash.insert(key, author);
      authors += author;
    }
  }
  return authors;
}

QString OpenLibraryFetcher::getAuthorKeys(const QString& term_) {
  QUrl u(QString::fromLatin1(OPENLIBRARY_AUTHOR_QUERY_URL));
  QUrlQuery q;
  q.addQueryItem(QStringLiteral("q"), term_);
  q.addQueryItem(QStringLiteral("fields"), QStringLiteral("key,name"));
  u.setQuery(q);

//  myLog() << "Searching for authors:" << u.toDisplayString();
  const auto output = FileHandler::readDataFile(u, true /*quiet*/);
#if 0
  myWarning() << "Remove author debug from openlibraryfetcher.cpp";
  QFile f(QString::fromLatin1("/tmp/test-openlibraryauthor.json"));
  if(f.open(QIODevice::WriteOnly)) {
    QTextStream t(&f);
    t << output;
  }
  f.close();
#endif
  const QJsonDocument doc = QJsonDocument::fromJson(output);
  const auto array = doc.object().value(QLatin1StringView("docs")).toArray();
  if(array.isEmpty()) {
    return QString();
  }
  // right now, only use the first to search on
  const auto obj1 = array.at(0).toObject();
  const auto key = obj1.value(QLatin1StringView("key")).toString();
  const auto name = obj1.value(QLatin1StringView("name")).toString();
  m_authorHash.insert(key, name);
  return key;
}

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

QString OpenLibraryFetcher::defaultName() {
  return QStringLiteral("Open Library"); // no translation
}

QString OpenLibraryFetcher::defaultIcon() {
  return favIcon("https://openlibrary.org");
}

Tellico::StringHash OpenLibraryFetcher::allOptionalFields() {
  StringHash hash;
  hash[QStringLiteral("openlibrary")] = i18n("OpenLibrary Link");
  return hash;
}

OpenLibraryFetcher::ConfigWidget::ConfigWidget(QWidget* parent_, const OpenLibraryFetcher* fetcher_)
    : Fetch::ConfigWidget(parent_) {
  QGridLayout* l = new QGridLayout(optionsWidget());
  l->setSpacing(4);
  l->setColumnStretch(1, 10);

  int row = -1;
  auto label = new QLabel(i18n("&Image size: "), optionsWidget());
  l->addWidget(label, ++row, 0);
  m_imageCombo = new GUI::ComboBox(optionsWidget());
  m_imageCombo->addItem(i18n("Small Image"), SmallImage);
  m_imageCombo->addItem(i18n("Medium Image"), MediumImage);
  m_imageCombo->addItem(i18n("Large Image"), LargeImage);
  m_imageCombo->addItem(i18n("No Image"), NoImage);
  void (GUI::ComboBox::* activatedInt)(int) = &GUI::ComboBox::activated;
  connect(m_imageCombo, activatedInt, this, &ConfigWidget::slotSetModified);
  l->addWidget(m_imageCombo, row, 1);
  QString w = i18n("The cover image may be downloaded as well. However, too many large images in the "
                   "collection may degrade performance.");
  label->setWhatsThis(w);
  m_imageCombo->setWhatsThis(w);
  label->setBuddy(m_imageCombo);

  l->setRowStretch(++row, 10);

  if(fetcher_) {
    m_imageCombo->setCurrentData(fetcher_->m_imageSize);
  } else { // defaults
    m_imageCombo->setCurrentData(MediumImage);
  }

  // now add additional fields widget
  addFieldsWidget(OpenLibraryFetcher::allOptionalFields(), fetcher_ ? fetcher_->optionalFields() : QStringList());
}

void OpenLibraryFetcher::ConfigWidget::saveConfigHook(KConfigGroup& config_) {
  const int n = m_imageCombo->currentData().toInt();
  config_.writeEntry("Image Size", n);
}

QString OpenLibraryFetcher::ConfigWidget::preferredName() const {
  return OpenLibraryFetcher::defaultName();
}
