AutomatedRssDownloader::AutomatedRssDownloader(const QWeakPointer<RssManager>& manager, QWidget *parent) :
  QDialog(parent),
  ui(new Ui::AutomatedRssDownloader),
  m_manager(manager), m_editedRule(0)
{
  ui->setupUi(this);
  // Icons
  ui->removeRuleBtn->setIcon(IconProvider::instance()->getIcon("list-remove"));
  ui->addRuleBtn->setIcon(IconProvider::instance()->getIcon("list-add"));

  // Ui Settings
  ui->listRules->setSortingEnabled(true);
  ui->listRules->setSelectionMode(QAbstractItemView::ExtendedSelection);
  ui->treeMatchingArticles->setSortingEnabled(true);
  ui->hsplitter->setCollapsible(0, false);
  ui->hsplitter->setCollapsible(1, false);
  ui->hsplitter->setCollapsible(2, true); // Only the preview list is collapsible
  bool ok; Q_UNUSED(ok);
  ok = connect(ui->checkRegex, SIGNAL(toggled(bool)), SLOT(updateFieldsToolTips(bool)));
  Q_ASSERT(ok);
  ok = connect(ui->listRules, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(displayRulesListMenu(const QPoint&)));
  Q_ASSERT(ok);
  m_ruleList = manager.toStrongRef()->downloadRules();
  m_editableRuleList = new RssDownloadRuleList; // Read rule list from disk
  initLabelCombobox();
  loadFeedList();
  loadSettings();
  ok = connect(ui->listRules, SIGNAL(itemSelectionChanged()), SLOT(updateRuleDefinitionBox()));
  Q_ASSERT(ok);
  ok = connect(ui->listRules, SIGNAL(itemSelectionChanged()), SLOT(updateFeedList()));
  Q_ASSERT(ok);
  ok = connect(ui->listFeeds, SIGNAL(itemChanged(QListWidgetItem*)), SLOT(handleFeedCheckStateChange(QListWidgetItem*)));
  Q_ASSERT(ok);
  // Update matching articles when necessary
  ok = connect(ui->lineContains, SIGNAL(textEdited(QString)), SLOT(updateMatchingArticles()));
  Q_ASSERT(ok);
  ok = connect(ui->lineContains, SIGNAL(textEdited(QString)), SLOT(updateMustLineValidity()));
  Q_ASSERT(ok);
  ok = connect(ui->lineNotContains, SIGNAL(textEdited(QString)), SLOT(updateMatchingArticles()));
  Q_ASSERT(ok);
  ok = connect(ui->lineNotContains, SIGNAL(textEdited(QString)), SLOT(updateMustNotLineValidity()));
  Q_ASSERT(ok);
  ok = connect(ui->checkRegex, SIGNAL(stateChanged(int)), SLOT(updateMatchingArticles()));
  Q_ASSERT(ok);
  ok = connect(ui->checkRegex, SIGNAL(stateChanged(int)), SLOT(updateMustLineValidity()));
  Q_ASSERT(ok);
  ok = connect(ui->checkRegex, SIGNAL(stateChanged(int)), SLOT(updateMustNotLineValidity()));
  Q_ASSERT(ok);
  ok = connect(this, SIGNAL(finished(int)), SLOT(on_finished(int)));
  Q_ASSERT(ok);
  updateRuleDefinitionBox();
  updateFeedList();
}
AutomatedRssDownloader::AutomatedRssDownloader(QWidget *parent)
    : QDialog(parent)
    , m_formatFilterJSON(QString("%1 (*%2)").arg(tr("Rules"), EXT_JSON))
    , m_formatFilterLegacy(QString("%1 (*%2)").arg(tr("Rules (legacy)"), EXT_LEGACY))
    , m_ui(new Ui::AutomatedRssDownloader)
    , m_currentRuleItem(nullptr)
{
    m_ui->setupUi(this);
    // Icons
    m_ui->removeRuleBtn->setIcon(GuiIconProvider::instance()->getIcon("list-remove"));
    m_ui->addRuleBtn->setIcon(GuiIconProvider::instance()->getIcon("list-add"));

    // Ui Settings
    m_ui->listRules->setSortingEnabled(true);
    m_ui->listRules->setSelectionMode(QAbstractItemView::ExtendedSelection);
    m_ui->treeMatchingArticles->setSortingEnabled(true);
    m_ui->treeMatchingArticles->sortByColumn(0, Qt::AscendingOrder);
    m_ui->hsplitter->setCollapsible(0, false);
    m_ui->hsplitter->setCollapsible(1, false);
    m_ui->hsplitter->setCollapsible(2, true); // Only the preview list is collapsible

    connect(m_ui->checkRegex, &QAbstractButton::toggled, this, &AutomatedRssDownloader::updateFieldsToolTips);
    connect(m_ui->listRules, &QWidget::customContextMenuRequested, this, &AutomatedRssDownloader::displayRulesListMenu);

    m_episodeRegex = new QRegularExpression("^(^\\d{1,4}x(\\d{1,4}(-(\\d{1,4})?)?;){1,}){1,1}"
                                            , QRegularExpression::CaseInsensitiveOption);
    QString tip = "<p>" + tr("Matches articles based on episode filter.") + "</p><p><b>" + tr("Example: ")
                  + "1x2;8-15;5;30-;</b>" + tr(" will match 2, 5, 8 through 15, 30 and onward episodes of season one", "example X will match") + "</p>";
    tip += "<p>" + tr("Episode filter rules: ") + "</p><ul><li>" + tr("Season number is a mandatory non-zero value") + "</li>"
           + "<li>" + tr("Episode number is a mandatory positive value") + "</li>"
           + "<li>" + tr("Filter must end with semicolon") + "</li>"
           + "<li>" + tr("Three range types for episodes are supported: ") + "</li>" + "<li><ul>"
           + "<li>" + tr("Single number: <b>1x25;</b> matches episode 25 of season one") + "</li>"
           + "<li>" + tr("Normal range: <b>1x25-40;</b> matches episodes 25 through 40 of season one") + "</li>"
           + "<li>" + tr("Infinite range: <b>1x25-;</b> matches episodes 25 and upward of season one, and all episodes of later seasons") + "</li>" + "</ul></li></ul>";
    m_ui->lineEFilter->setToolTip(tip);

    initCategoryCombobox();
    loadSettings();

    connect(RSS::AutoDownloader::instance(), &RSS::AutoDownloader::ruleAdded, this, &AutomatedRssDownloader::handleRuleAdded);
    connect(RSS::AutoDownloader::instance(), &RSS::AutoDownloader::ruleRenamed, this, &AutomatedRssDownloader::handleRuleRenamed);
    connect(RSS::AutoDownloader::instance(), &RSS::AutoDownloader::ruleChanged, this, &AutomatedRssDownloader::handleRuleChanged);
    connect(RSS::AutoDownloader::instance(), &RSS::AutoDownloader::ruleAboutToBeRemoved, this, &AutomatedRssDownloader::handleRuleAboutToBeRemoved);

    // Update matching articles when necessary
    connect(m_ui->lineContains, &QLineEdit::textEdited, this, &AutomatedRssDownloader::handleRuleDefinitionChanged);
    connect(m_ui->lineContains, &QLineEdit::textEdited, this, &AutomatedRssDownloader::updateMustLineValidity);
    connect(m_ui->lineNotContains, &QLineEdit::textEdited, this, &AutomatedRssDownloader::handleRuleDefinitionChanged);
    connect(m_ui->lineNotContains, &QLineEdit::textEdited, this, &AutomatedRssDownloader::updateMustNotLineValidity);
    connect(m_ui->lineEFilter, &QLineEdit::textEdited, this, &AutomatedRssDownloader::handleRuleDefinitionChanged);
    connect(m_ui->lineEFilter, &QLineEdit::textEdited, this, &AutomatedRssDownloader::updateEpisodeFilterValidity);
    connect(m_ui->checkRegex, &QCheckBox::stateChanged, this, &AutomatedRssDownloader::handleRuleDefinitionChanged);
    connect(m_ui->checkRegex, &QCheckBox::stateChanged, this, &AutomatedRssDownloader::updateMustLineValidity);
    connect(m_ui->checkRegex, &QCheckBox::stateChanged, this, &AutomatedRssDownloader::updateMustNotLineValidity);
    connect(m_ui->checkSmart, &QCheckBox::stateChanged, this, &AutomatedRssDownloader::handleRuleDefinitionChanged);
    connect(m_ui->spinIgnorePeriod, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged)
            , this, &AutomatedRssDownloader::handleRuleDefinitionChanged);

    connect(m_ui->listFeeds, &QListWidget::itemChanged, this, &AutomatedRssDownloader::handleFeedCheckStateChange);

    connect(m_ui->listRules, &QListWidget::itemSelectionChanged, this, &AutomatedRssDownloader::updateRuleDefinitionBox);
    connect(m_ui->listRules, &QListWidget::itemChanged, this, &AutomatedRssDownloader::handleRuleCheckStateChange);

    m_editHotkey = new QShortcut(Qt::Key_F2, m_ui->listRules, nullptr, nullptr, Qt::WidgetShortcut);
    connect(m_editHotkey, &QShortcut::activated, this, &AutomatedRssDownloader::renameSelectedRule);
    connect(m_ui->listRules, &QAbstractItemView::doubleClicked, this, &AutomatedRssDownloader::renameSelectedRule);

    m_deleteHotkey = new QShortcut(QKeySequence::Delete, m_ui->listRules, nullptr, nullptr, Qt::WidgetShortcut);
    connect(m_deleteHotkey, &QShortcut::activated, this, &AutomatedRssDownloader::on_removeRuleBtn_clicked);

    loadFeedList();

    m_ui->listRules->blockSignals(true);
    for (const RSS::AutoDownloadRule &rule : asConst(RSS::AutoDownloader::instance()->rules()))
        createRuleItem(rule);
    m_ui->listRules->blockSignals(false);

    updateRuleDefinitionBox();

    if (RSS::AutoDownloader::instance()->isProcessingEnabled())
        m_ui->labelWarn->hide();
    connect(RSS::AutoDownloader::instance(), &RSS::AutoDownloader::processingStateChanged
            , this, &AutomatedRssDownloader::handleProcessingStateChanged);
}