// For license of this file, see <project-root-folder>/LICENSE.md.

#include "src/gui/standardfeeddetails.h"

#include "src/definitions.h"
#include "src/gui/standardfeednetworkdetails.h"

#include <librssguard/exceptions/applicationexception.h>
#include <librssguard/exceptions/networkexception.h>
#include <librssguard/exceptions/scriptexception.h>
#include <librssguard/gui/dialogs/filedialog.h>
#include <librssguard/gui/reusable/lineeditwithstatus.h>
#include <librssguard/miscellaneous/iconfactory.h>
#include <librssguard/miscellaneous/settings.h>
#include <librssguard/miscellaneous/textfactory.h>
#include <librssguard/network-web/networkfactory.h>
#include <librssguard/services/abstract/category.h>
#include <qtlinq/qtlinq.h>

#include <QClipboard>
#include <QImageReader>
#include <QInputDialog>
#include <QMenu>
#include <QMimeData>
#include <QtGlobal>

StandardFeedDetails::StandardFeedDetails(QWidget* parent) : QWidget(parent) {
  m_ui.setupUi(this);

  m_ui.m_txtPostProcessScript->textEdit()->setTabChangesFocus(true);
  m_ui.m_txtSource->textEdit()->setTabChangesFocus(true);

  m_ui.m_txtTitle->lineEdit()->setPlaceholderText(tr("Feed title"));
  m_ui.m_txtTitle->lineEdit()->setToolTip(tr("Set title for your feed."));
  m_ui.m_txtDescription->lineEdit()->setPlaceholderText(tr("Feed description"));
  m_ui.m_txtDescription->lineEdit()->setToolTip(tr("Set description for your feed."));
  m_ui.m_txtSource->textEdit()->setPlaceholderText(tr("Full feed source identifier"));
  m_ui.m_txtSource->textEdit()->setToolTip(tr("Full feed source identifier which can be URL."));
  m_ui.m_txtPostProcessScript->textEdit()->setPlaceholderText(tr("Full command to execute"));
  m_ui.m_txtPostProcessScript->textEdit()->setToolTip(tr("You can enter full command including interpreter here."));
  m_ui.m_lblFetchMetadata->textEdit()->setReadOnly(true);

  // Add source types.
  m_ui.m_cmbSourceType->addItem(StandardFeed::sourceTypeToString(StandardFeed::SourceType::Url),
                                QVariant::fromValue(StandardFeed::SourceType::Url));
  m_ui.m_cmbSourceType->addItem(StandardFeed::sourceTypeToString(StandardFeed::SourceType::Script),
                                QVariant::fromValue(StandardFeed::SourceType::Script));
  m_ui.m_cmbSourceType->addItem(StandardFeed::sourceTypeToString(StandardFeed::SourceType::LocalFile),
                                QVariant::fromValue(StandardFeed::SourceType::LocalFile));

  // Add standard feed types.
  m_ui.m_cmbType->addItem(StandardFeed::typeToString(StandardFeed::Type::Atom10),
                          QVariant::fromValue(int(StandardFeed::Type::Atom10)));
  m_ui.m_cmbType->addItem(StandardFeed::typeToString(StandardFeed::Type::Rdf),
                          QVariant::fromValue(int(StandardFeed::Type::Rdf)));
  m_ui.m_cmbType->addItem(StandardFeed::typeToString(StandardFeed::Type::Rss0X),
                          QVariant::fromValue(int(StandardFeed::Type::Rss0X)));
  m_ui.m_cmbType->addItem(StandardFeed::typeToString(StandardFeed::Type::Rss2X),
                          QVariant::fromValue(int(StandardFeed::Type::Rss2X)));
  m_ui.m_cmbType->addItem(StandardFeed::typeToString(StandardFeed::Type::iCalendar),
                          QVariant::fromValue(int(StandardFeed::Type::iCalendar)));
  m_ui.m_cmbType->addItem(StandardFeed::typeToString(StandardFeed::Type::Gemlog),
                          QVariant::fromValue(int(StandardFeed::Type::Gemlog)));
  m_ui.m_cmbType->addItem(StandardFeed::typeToString(StandardFeed::Type::Json),
                          QVariant::fromValue(int(StandardFeed::Type::Json)));
  m_ui.m_cmbType->addItem(StandardFeed::typeToString(StandardFeed::Type::Sitemap),
                          QVariant::fromValue(int(StandardFeed::Type::Sitemap)));

  // Load available encodings.
  auto encodings = TextFactory::availableEncodings();

  // Sort encodings and add them.
  std::sort(encodings.begin(), encodings.end(), [](const QString& lhs, const QString& rhs) {
    return lhs.toLower() < rhs.toLower();
  });

  m_ui.m_cmbEncoding->addItems(encodings);

  // Setup menu & actions for icon selection.
  m_actionFetchIcon =
    new QAction(qApp->icons()->fromTheme(QSL("emblem-downloads"), QSL("download")), tr("Fetch icon from feed"), this);

  m_ui.m_btnIcon->setDefaultIcon(QIcon());
  m_ui.m_btnIcon->setColorOnlyMode(false);
  m_ui.m_btnIcon->appendExtraAction(m_actionFetchIcon);

  m_ui.m_txtSource->textEdit()->setFocus(Qt::FocusReason::TabFocusReason);

  // Set feed metadata fetch label.
  m_ui.m_lblFetchMetadata->setText(WidgetWithStatus::StatusType::Information, tr("No metadata fetched so far."));

  connect(m_ui.m_txtTitle->lineEdit(), &BaseLineEdit::textChanged, this, &StandardFeedDetails::onTitleChanged);
  connect(m_ui.m_txtDescription->lineEdit(),
          &BaseLineEdit::textChanged,
          this,
          &StandardFeedDetails::onDescriptionChanged);
  connect(m_ui.m_cmbSourceType, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this]() {
    onUrlChanged(m_ui.m_txtSource->textEdit()->toPlainText());
  });
  connect(m_ui.m_txtSource->textEdit(), &QPlainTextEdit::textChanged, this, [this]() {
    onUrlChanged(m_ui.m_txtSource->textEdit()->toPlainText());
  });
  connect(m_ui.m_txtPostProcessScript->textEdit(), &QPlainTextEdit::textChanged, this, [this]() {
    onPostProcessScriptChanged(m_ui.m_txtPostProcessScript->textEdit()->toPlainText());
  });

  setTabOrder(m_ui.m_cmbParentCategory, m_ui.m_cmbType);
  setTabOrder(m_ui.m_cmbType, m_ui.m_cmbEncoding);
  setTabOrder(m_ui.m_cmbEncoding, m_ui.m_txtTitle->lineEdit());
  setTabOrder(m_ui.m_txtTitle->lineEdit(), m_ui.m_txtDescription->lineEdit());
  setTabOrder(m_ui.m_txtDescription->lineEdit(), m_ui.m_cmbSourceType);
  setTabOrder(m_ui.m_cmbSourceType, m_ui.m_txtSource->textEdit());
  setTabOrder(m_ui.m_txtSource->textEdit(), m_ui.m_txtPostProcessScript->textEdit());
  setTabOrder(m_ui.m_txtPostProcessScript->textEdit(), m_ui.m_btnFetchMetadata);
  setTabOrder(m_ui.m_btnFetchMetadata, m_ui.m_btnIcon);

  m_ui.m_lblScriptInfo->setHelpText(tr("What is post-processing script?"),
                                    tr("You can use URL as a source of your feed or you can produce your feed with "
                                       "custom script.\n\n"
                                       "Also, you can post-process generated feed data with yet "
                                       "another script if you wish. These are advanced features and make sure to "
                                       "read the documentation before your use them."),
                                    true);

  onTitleChanged({});
  onDescriptionChanged({});
  onUrlChanged({});
  onPostProcessScriptChanged({});
}

void StandardFeedDetails::guessIconOnly(StandardFeed::SourceType source_type,
                                        const QString& source,
                                        const QString& post_process_script,
                                        StandardServiceRoot* account,
                                        NetworkFactory::NetworkAuthentication protection,
                                        const QString& username,
                                        const QString& password,
                                        const QList<QPair<QByteArray, QByteArray>>& headers,
                                        const QNetworkProxy& custom_proxy) {
  try {
    auto metadata = StandardFeed::guessFeed(source_type,
                                            source,
                                            post_process_script,
                                            account,
                                            protection,
                                            true,
                                            username,
                                            password,
                                            {},
                                            custom_proxy);

    // Icon or whole feed was guessed.
    m_ui.m_btnIcon->setIcon(metadata.first->icon());
    m_ui.m_lblFetchMetadata->setText(WidgetWithStatus::StatusType::Ok, tr("Icon fetched successfully."));

    // Remove temporary feed object.
    metadata.first->deleteLater();
  }
  catch (const ScriptException& ex) {
    m_ui.m_lblFetchMetadata->setText(WidgetWithStatus::StatusType::Error, tr("Script failed: %1").arg(ex.message()));
  }
  catch (const NetworkException& ex) {
    m_ui.m_lblFetchMetadata->setText(WidgetWithStatus::StatusType::Error, tr("Network error: %1").arg(ex.message()));
  }
  catch (const ApplicationException& ex) {
    m_ui.m_lblFetchMetadata->setText(WidgetWithStatus::StatusType::Error, tr("Error: %1").arg(ex.message()));
  }
}

void StandardFeedDetails::guessFeed(StandardFeed::SourceType source_type,
                                    const QString& source,
                                    const QString& post_process_script,
                                    StandardServiceRoot* account,
                                    NetworkFactory::NetworkAuthentication protection,
                                    const QString& username,
                                    const QString& password,
                                    const QList<QPair<QByteArray, QByteArray>>& headers,
                                    const QNetworkProxy& custom_proxy,
                                    NetworkFactory::Http2Status http2_status) {
  try {
    auto metadata = StandardFeed::guessFeed(source_type,
                                            source,
                                            post_process_script,
                                            account,
                                            protection,
                                            true,
                                            username,
                                            password,
                                            headers,
                                            custom_proxy,
                                            http2_status);

    // Icon or whole feed was guessed.
    m_ui.m_btnIcon->setIcon(metadata.first->icon());
    m_ui.m_txtTitle->lineEdit()->setText(metadata.first->sanitizedTitle());
    m_ui.m_txtDescription->lineEdit()->setText(metadata.first->description());
    m_ui.m_cmbType->setCurrentIndex(m_ui.m_cmbType->findData(QVariant::fromValue((int)metadata.first->type())));

    if (metadata.second.m_url.isValid()) {
      m_ui.m_txtSource->textEdit()->setPlainText(metadata.second.m_url.toString());
    }

    int encoding_index = m_ui.m_cmbEncoding->findText(metadata.first->encoding(), Qt::MatchFlag::MatchFixedString);

    if (encoding_index >= 0) {
      m_ui.m_cmbEncoding->setCurrentIndex(encoding_index);
    }
    else {
      m_ui.m_cmbEncoding->setCurrentIndex(m_ui.m_cmbEncoding->findText(QSL(DEFAULT_FEED_ENCODING),
                                                                       Qt::MatchFlag::MatchFixedString));
    }

    m_ui.m_lblFetchMetadata->setText(WidgetWithStatus::StatusType::Ok, tr("All metadata fetched successfully."));

    // Remove temporary feed object.
    metadata.first->deleteLater();
  }
  catch (const ScriptException& ex) {
    m_ui.m_lblFetchMetadata->setText(WidgetWithStatus::StatusType::Error, tr("Script failed: %1").arg(ex.message()));
  }
  catch (const NetworkException& ex) {
    m_ui.m_lblFetchMetadata->setText(WidgetWithStatus::StatusType::Error, tr("Network error: %1").arg(ex.message()));
  }
  catch (const ApplicationException& ex) {
    m_ui.m_lblFetchMetadata->setText(WidgetWithStatus::StatusType::Error, tr("Error: %1").arg(ex.message()));
  }
}

void StandardFeedDetails::onTitleChanged(const QString& new_title) {
  if (!new_title.simplified().isEmpty()) {
    m_ui.m_txtTitle->setStatus(LineEditWithStatus::StatusType::Ok, tr("Feed name is ok."));
  }
  else {
    m_ui.m_txtTitle->setStatus(LineEditWithStatus::StatusType::Error, tr("Feed name is too short."));
  }
}

void StandardFeedDetails::onDescriptionChanged(const QString& new_description) {
  if (new_description.simplified().isEmpty()) {
    m_ui.m_txtDescription->setStatus(LineEditWithStatus::StatusType::Warning, tr("Description is empty."));
  }
  else {
    m_ui.m_txtDescription->setStatus(LineEditWithStatus::StatusType::Ok, tr("The description is ok."));
  }
}

void StandardFeedDetails::onUrlChanged(const QString& new_url) {
  switch (sourceType()) {
    case StandardFeed::SourceType::Url: {
      if (QUrl(new_url).isValid()) {
        m_ui.m_txtSource->setStatus(LineEditWithStatus::StatusType::Ok, tr("The URL is ok."));
      }
      else if (!new_url.simplified().isEmpty()) {
        m_ui.m_txtSource->setStatus(LineEditWithStatus::StatusType::Warning,
                                    tr("The URL does not meet standard pattern. "
                                       "Does your URL start with \"http://\" or \"https://\" prefix."));
      }
      else {
        m_ui.m_txtSource->setStatus(LineEditWithStatus::StatusType::Error, tr("The URL is empty."));
      }

      break;
    }

    case StandardFeed::SourceType::Script: {
      try {
        TextFactory::tokenizeProcessArguments(new_url);
        m_ui.m_txtSource->setStatus(LineEditWithStatus::StatusType::Ok, tr("Source is ok."));
      }
      catch (const ApplicationException& ex) {
        m_ui.m_txtSource->setStatus(LineEditWithStatus::StatusType::Error, tr("Error: %1").arg(ex.message()));
      }

      break;
    }
    case StandardFeed::SourceType::LocalFile: {
      if (QFile::exists(new_url)) {
        m_ui.m_txtSource->setStatus(LineEditWithStatus::StatusType::Ok, tr("File exists."));
      }
      else {
        m_ui.m_txtSource->setStatus(LineEditWithStatus::StatusType::Error, tr("File does not exist."));
      }

      break;
    }

    default:
      m_ui.m_txtSource->setStatus(LineEditWithStatus::StatusType::Ok, tr("The source is ok."));
  }
}

void StandardFeedDetails::onPostProcessScriptChanged(const QString& new_pp) {
  try {
    TextFactory::tokenizeProcessArguments(new_pp);
    m_ui.m_txtPostProcessScript->setStatus(LineEditWithStatus::StatusType::Ok, tr("Command is ok."));
  }
  catch (const ApplicationException& ex) {
    m_ui.m_txtPostProcessScript->setStatus(LineEditWithStatus::StatusType::Error, tr("Error: %1").arg(ex.message()));
  }
}

StandardFeed::SourceType StandardFeedDetails::sourceType() const {
  return m_ui.m_cmbSourceType->currentData().value<StandardFeed::SourceType>();
}

void StandardFeedDetails::setNetworkDetails(StandardFeedNetworkDetails* network_details) {
  m_networkDetails = network_details;
}

void StandardFeedDetails::prepareForNewFeed(ServiceRoot* account, RootItem* parent_to_select, const QString& url) {
  m_account = account;

  m_ui.m_btnIcon->setProxy(m_networkDetails->m_ui.m_wdgNetworkProxy->useAccountProxy()
                             ? m_account->networkProxy()
                             : m_networkDetails->m_ui.m_wdgNetworkProxy->proxy());
  m_ui.m_btnIcon->setAdditionalIcons(account->getSubTreeIcons());
  m_ui.m_btnIcon->setIcon(QIcon());

  int default_encoding_index = m_ui.m_cmbEncoding->findText(QSL(DEFAULT_FEED_ENCODING));

  if (default_encoding_index >= 0) {
    m_ui.m_cmbEncoding->setCurrentIndex(default_encoding_index);
  }

  if (parent_to_select != nullptr) {
    if (parent_to_select->kind() == RootItem::Kind::Category) {
      m_ui.m_cmbParentCategory
        ->setCurrentIndex(m_ui.m_cmbParentCategory->findData(QVariant::fromValue(parent_to_select)));
    }
    else if (parent_to_select->kind() == RootItem::Kind::Feed) {
      int target_item = m_ui.m_cmbParentCategory->findData(QVariant::fromValue(parent_to_select->parent()));

      if (target_item >= 0) {
        m_ui.m_cmbParentCategory->setCurrentIndex(target_item);
      }
    }
    else {
      m_ui.m_cmbParentCategory->setCurrentIndex(0);
    }
  }

  if (!url.isEmpty()) {
    m_ui.m_txtSource->textEdit()->setPlainText(url);
  }

  m_ui.m_txtSource->setFocus();
  m_ui.m_txtSource->textEdit()->selectAll();
}

void StandardFeedDetails::setExistingFeed(ServiceRoot* account, StandardFeed* feed) {
  m_account = account;

  m_ui.m_btnIcon->setProxy(m_networkDetails->m_ui.m_wdgNetworkProxy->useAccountProxy()
                             ? m_account->networkProxy()
                             : m_networkDetails->m_ui.m_wdgNetworkProxy->proxy());
  m_ui.m_btnIcon->setAdditionalIcons(account->getSubTreeIcons());

  m_ui.m_cmbSourceType->setCurrentIndex(m_ui.m_cmbSourceType->findData(QVariant::fromValue(feed->sourceType())));
  m_ui.m_cmbParentCategory->setCurrentIndex(m_ui.m_cmbParentCategory->findData(QVariant::fromValue(feed->parent())));
  m_ui.m_txtTitle->lineEdit()->setText(feed->title());
  m_ui.m_txtDescription->lineEdit()->setText(feed->description());
  m_ui.m_btnIcon->setIcon(feed->icon());
  m_ui.m_txtSource->textEdit()->setPlainText(feed->source());
  m_ui.m_txtPostProcessScript->textEdit()->setPlainText(feed->postProcessScript());
  m_ui.m_cmbType->setCurrentIndex(m_ui.m_cmbType->findData(QVariant::fromValue(int(feed->type()))));
  m_ui.m_cmbEncoding->setCurrentIndex(m_ui.m_cmbEncoding->findData(feed->encoding(),
                                                                   Qt::ItemDataRole::DisplayRole,
                                                                   Qt::MatchFlag::MatchFixedString));
}

void StandardFeedDetails::loadCategories(const QList<Category*>& categories, RootItem* root_item) {
  QList<Category*> cats;

  if (qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::SortAlphabetically)).toBool()) {
    cats = qlinq::from(categories)
             .orderBy([](Category* cat) {
               return cat->title().toLower();
             })
             .toList();
  }
  else {
    cats = categories;
  }

  m_ui.m_cmbParentCategory->addItem(root_item->fullIcon(),
                                    TextFactory::shorten(root_item->title()),
                                    QVariant::fromValue(root_item));

  for (Category* category : std::as_const(cats)) {
    m_ui.m_cmbParentCategory->addItem(category->fullIcon(),
                                      TextFactory::shorten(category->title()),
                                      QVariant::fromValue(category));
  }
}
