// This file is part of RSS Guard.
//
// Copyright (C) 2011-2015 by Martin Rotter <rotter.martinos@gmail.com>
//
// RSS Guard 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 3 of the License, or
// (at your option) any later version.
//
// RSS Guard 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 RSS Guard. If not, see <http://www.gnu.org/licenses/>.

#include "gui/feedmessageviewer.h"

#include "miscellaneous/settings.h"
#include "miscellaneous/databasefactory.h"
#include "miscellaneous/systemfactory.h"
#include "miscellaneous/iconfactory.h"
#include "miscellaneous/mutex.h"
#include "miscellaneous/databasecleaner.h"
#include "core/messagesproxymodel.h"
#include "core/feeddownloader.h"
#include "core/feedsselection.h"
#include "services/standard/standardserviceroot.h"
#include "services/standard/standardfeed.h"
#include "services/standard/standardfeedsimportexportmodel.h"
#include "network-web/webbrowser.h"
#include "gui/messagesview.h"
#include "gui/feedsview.h"
#include "gui/statusbar.h"
#include "gui/systemtrayicon.h"
#include "gui/messagebox.h"
#include "gui/messagestoolbar.h"
#include "gui/feedstoolbar.h"
#include "gui/dialogs/formdatabasecleanup.h"
#include "gui/dialogs/formmain.h"
#include "exceptions/applicationexception.h"

#include <QVBoxLayout>
#include <QSplitter>
#include <QToolBar>
#include <QDebug>
#include <QLineEdit>
#include <QAction>
#include <QToolButton>
#include <QMenu>
#include <QWidgetAction>
#include <QThread>
#include <QProgressBar>
#include <QStatusBar>
#include <QPointer>


FeedMessageViewer::FeedMessageViewer(QWidget *parent)
  : TabContent(parent),
    m_toolBarsEnabled(true),
    m_listHeadersEnabled(true),
    m_toolBarFeeds(new FeedsToolBar(tr("Toolbar for feeds"), this)),
    m_toolBarMessages(new MessagesToolBar(tr("Toolbar for messages"), this)),
    m_messagesView(new MessagesView(this)),
    m_feedsView(new FeedsView(this)),
    m_messagesBrowser(new WebBrowser(this)),
    m_feedDownloaderThread(NULL),
    m_dbCleanerThread(NULL),
    m_feedDownloader(NULL),
    m_dbCleaner(NULL) {
  initialize();
  initializeViews();
  loadMessageViewerFonts();
  createConnections();

  // Now, update all feeds if user has set it.
  m_feedsView->updateAllItemsOnStartup();
}

FeedMessageViewer::~FeedMessageViewer() {
  qDebug("Destroying FeedMessageViewer instance.");
}

DatabaseCleaner *FeedMessageViewer::databaseCleaner() {
  if (m_dbCleaner == NULL) {
    m_dbCleaner = new DatabaseCleaner();
    m_dbCleanerThread = new QThread();

    // Downloader setup.
    qRegisterMetaType<CleanerOrders>("CleanerOrders");
    m_dbCleaner->moveToThread(m_dbCleanerThread);
    connect(m_dbCleanerThread, SIGNAL(finished()), m_dbCleanerThread, SLOT(deleteLater()));

    // Connections are made, start the feed downloader thread.
    m_dbCleanerThread->start();
  }

  return m_dbCleaner;
}

void FeedMessageViewer::saveSize() {
  Settings *settings = qApp->settings();

  m_feedsView->saveExpandedStates();

  // Store offsets of splitters.
  settings->setValue(GROUP(GUI), GUI::SplitterFeeds, QString(m_feedSplitter->saveState().toBase64()));
  settings->setValue(GROUP(GUI), GUI::SplitterMessages, QString(m_messageSplitter->saveState().toBase64()));

  // States of splitters are stored, let's store
  // widths of columns.
  int width_column_author = m_messagesView->columnWidth(MSG_DB_AUTHOR_INDEX);
  int width_column_date = m_messagesView->columnWidth(MSG_DB_DCREATED_INDEX);

  if (width_column_author != 0 && width_column_date != 0) {
    settings->setValue(GROUP(GUI), KEY_MESSAGES_VIEW + QString::number(MSG_DB_AUTHOR_INDEX), width_column_author);
    settings->setValue(GROUP(GUI), KEY_MESSAGES_VIEW + QString::number(MSG_DB_DCREATED_INDEX), width_column_date);
  }

  // Store "visibility" of toolbars and list headers.
  settings->setValue(GROUP(GUI), GUI::ToolbarsVisible, m_toolBarsEnabled);
  settings->setValue(GROUP(GUI), GUI::ListHeadersVisible, m_listHeadersEnabled);
}

void FeedMessageViewer::loadSize() {
  Settings *settings = qApp->settings();
  int default_msg_section_size = m_messagesView->header()->defaultSectionSize();

  m_feedsView->loadExpandedStates();

  // Restore offsets of splitters.
  m_feedSplitter->restoreState(QByteArray::fromBase64(settings->value(GROUP(GUI), SETTING(GUI::SplitterFeeds)).toString().toLocal8Bit()));
  m_messageSplitter->restoreState(QByteArray::fromBase64(settings->value(GROUP(GUI), SETTING(GUI::SplitterMessages)).toString().toLocal8Bit()));

  // Splitters are restored, now, restore widths of columns.
  m_messagesView->setColumnWidth(MSG_DB_AUTHOR_INDEX, settings->value(GROUP(GUI),
                                                                      KEY_MESSAGES_VIEW + QString::number(MSG_DB_AUTHOR_INDEX),
                                                                      default_msg_section_size).toInt());
  m_messagesView->setColumnWidth(MSG_DB_DCREATED_INDEX, settings->value(GROUP(GUI),
                                                                        KEY_MESSAGES_VIEW + QString::number(MSG_DB_DCREATED_INDEX),
                                                                        default_msg_section_size).toInt());
}

void FeedMessageViewer::loadMessageViewerFonts() {
  Settings *settings = qApp->settings();
  QWebSettings *view_settings = m_messagesBrowser->view()->settings();

  view_settings->setFontFamily(QWebSettings::StandardFont, settings->value(GROUP(Messages),
                                                                           SETTING(Messages::PreviewerFontStandard)).toString());
}

void FeedMessageViewer::quit() {
  // Quit the feeds model (stops auto-update timer etc.).
  m_feedsView->sourceModel()->quit();

  // Close worker threads.
  if (m_feedDownloaderThread != NULL && m_feedDownloaderThread->isRunning()) {
    qDebug("Quitting feed downloader thread.");
    m_feedDownloaderThread->quit();

    if (!m_feedDownloaderThread->wait(CLOSE_LOCK_TIMEOUT)) {
      qCritical("Feed downloader thread is running despite it was told to quit. Terminating it.");
      m_feedDownloaderThread->terminate();
    }
  }

  if (m_dbCleanerThread != NULL && m_dbCleanerThread->isRunning()) {
    qDebug("Quitting database cleaner thread.");
    m_dbCleanerThread->quit();

    if (!m_dbCleanerThread->wait(CLOSE_LOCK_TIMEOUT)) {
      qCritical("Database cleaner thread is running despite it was told to quit. Terminating it.");
      m_dbCleanerThread->terminate();
    }
  }

  // Close workers.
  if (m_feedDownloader != NULL) {
    qDebug("Feed downloader exists. Deleting it from memory.");
    m_feedDownloader->deleteLater();
  }

  if (m_dbCleaner != NULL) {
    qDebug("Database cleaner exists. Deleting it from memory.");
    m_dbCleaner->deleteLater();
  }

  if (qApp->settings()->value(GROUP(Messages), SETTING(Messages::ClearReadOnExit)).toBool()) {
    m_feedsView->clearAllReadMessages();
  }
}

bool FeedMessageViewer::areToolBarsEnabled() const {
  return m_toolBarsEnabled;
}

bool FeedMessageViewer::areListHeadersEnabled() const {
  return m_listHeadersEnabled;
}

void FeedMessageViewer::switchMessageSplitterOrientation() {
  if (m_messageSplitter->orientation() == Qt::Vertical) {
    m_messageSplitter->setOrientation(Qt::Horizontal);
  }
  else {
    m_messageSplitter->setOrientation(Qt::Vertical);
  }
}

void FeedMessageViewer::setToolBarsEnabled(bool enable) {
  m_toolBarsEnabled = enable;
  m_toolBarFeeds->setVisible(enable);
  m_toolBarMessages->setVisible(enable);
}

void FeedMessageViewer::setListHeadersEnabled(bool enable) {
  m_listHeadersEnabled = enable;
  m_feedsView->header()->setVisible(enable);
  m_messagesView->header()->setVisible(enable);
}

void FeedMessageViewer::updateTrayIconStatus(int unread_messages, int total_messages, bool any_unread_messages) {
  Q_UNUSED(total_messages)

  if (SystemTrayIcon::isSystemTrayActivated()) {
    qApp->trayIcon()->setNumber(unread_messages, any_unread_messages);
  }
}

void FeedMessageViewer::onFeedUpdatesStarted() {
  //: Text display in status bar when feed update is started.
  qApp->mainForm()->statusBar()->showProgressFeeds(0, tr("Feed update started"));
}

void FeedMessageViewer::onFeedUpdatesProgress(Feed *feed, int current, int total) {
  // Some feed got updated.
  m_feedsView->updateCountsOfParticularFeed(feed, true);
  qApp->mainForm()->statusBar()->showProgressFeeds((current * 100.0) / total,
                                                   //: Text display in status bar when particular feed is updated.
                                                   tr("Updated feed '%1'").arg(feed->title()));
}

void FeedMessageViewer::onFeedUpdatesFinished(FeedDownloadResults results) {
  qApp->feedUpdateLock()->unlock();
  qApp->mainForm()->statusBar()->clearProgressFeeds();
  m_messagesView->reloadSelections(true);

  if (!results.m_updatedFeeds.isEmpty()) {
    // Now, inform about results via GUI message/notification.
    qApp->showGuiMessage(tr("New messages downloaded"), results.getOverview(10), QSystemTrayIcon::NoIcon,
                         0, false, qApp->icons()->fromTheme(QSL("item-update-all")));
  }
}

void FeedMessageViewer::switchFeedComponentVisibility() {
  m_feedsWidget->setVisible(!m_feedsWidget->isVisible());
}

void FeedMessageViewer::toggleShowOnlyUnreadFeeds() {
  QAction *origin = qobject_cast<QAction*>(sender());

  if (origin == NULL) {
    m_feedsView->invalidateReadFeedsFilter(true, false);
  }
  else {
    m_feedsView->invalidateReadFeedsFilter(true, origin->isChecked());
  }
}

void FeedMessageViewer::updateMessageButtonsAvailability() {
  bool one_message_selected = m_messagesView->selectionModel()->selectedRows().size() == 1;
  bool atleast_one_message_selected = !m_messagesView->selectionModel()->selectedRows().isEmpty();
  FormMain *form_main = qApp->mainForm();

  form_main->m_ui->m_actionDeleteSelectedMessages->setEnabled(atleast_one_message_selected);
  form_main->m_ui->m_actionMarkSelectedMessagesAsRead->setEnabled(atleast_one_message_selected);
  form_main->m_ui->m_actionMarkSelectedMessagesAsUnread->setEnabled(atleast_one_message_selected);
  form_main->m_ui->m_actionOpenSelectedMessagesInternally->setEnabled(atleast_one_message_selected);
  form_main->m_ui->m_actionOpenSelectedSourceArticlesExternally->setEnabled(atleast_one_message_selected);
  form_main->m_ui->m_actionOpenSelectedSourceArticlesInternally->setEnabled(atleast_one_message_selected);
  form_main->m_ui->m_actionSendMessageViaEmail->setEnabled(one_message_selected);
  form_main->m_ui->m_actionSwitchImportanceOfSelectedMessages->setEnabled(atleast_one_message_selected);
}

void FeedMessageViewer::updateFeedButtonsAvailability() {
  bool critical_action_running = qApp->feedUpdateLock()->isLocked();
  RootItem *selected_item = feedsView()->selectedItem();
  bool anything_selected = selected_item != NULL;
  bool feed_selected = anything_selected && selected_item->kind() == RootItemKind::Feed;
  bool category_selected = anything_selected && selected_item->kind() == RootItemKind::Category;
  bool service_selected = anything_selected && selected_item->kind() == RootItemKind::ServiceRoot;
  FormMain *form_main = qApp->mainForm();

  form_main->m_ui->m_actionServiceEdit->setEnabled(!critical_action_running && service_selected);
  form_main->m_ui->m_actionServiceDelete->setEnabled(!critical_action_running && service_selected);
  form_main->m_ui->m_actionBackupDatabaseSettings->setEnabled(!critical_action_running);
  form_main->m_ui->m_actionCleanupDatabase->setEnabled(!critical_action_running);
  form_main->m_ui->m_actionClearSelectedItems->setEnabled(feed_selected || category_selected || service_selected);
  form_main->m_ui->m_actionDeleteSelectedItem->setEnabled(!critical_action_running && anything_selected);
  form_main->m_ui->m_actionEditSelectedItem->setEnabled(!critical_action_running && anything_selected);
  form_main->m_ui->m_actionMarkSelectedItemsAsRead->setEnabled(feed_selected || category_selected || service_selected);
  form_main->m_ui->m_actionMarkSelectedItemsAsUnread->setEnabled(feed_selected || category_selected || service_selected);
  form_main->m_ui->m_actionUpdateAllItems->setEnabled(!critical_action_running);
  form_main->m_ui->m_actionUpdateSelectedItems->setEnabled(!critical_action_running && (feed_selected || category_selected || service_selected));
  form_main->m_ui->m_actionViewSelectedItemsNewspaperMode->setEnabled(feed_selected || category_selected || service_selected);
  form_main->m_ui->m_actionExpandCollapseItem->setEnabled(feed_selected || category_selected || service_selected);
  form_main->m_ui->m_menuAddItem->setEnabled(!critical_action_running);
}

void FeedMessageViewer::createConnections() {
  FormMain *form_main = qApp->mainForm();

  // Filtering & searching.
  connect(m_toolBarMessages, SIGNAL(messageSearchPatternChanged(QString)), m_messagesView, SLOT(searchMessages(QString)));
  connect(m_toolBarMessages, SIGNAL(messageFilterChanged(MessagesModel::MessageFilter)), m_messagesView, SLOT(filterMessages(MessagesModel::MessageFilter)));

  // Message changers.
  connect(m_messagesView, SIGNAL(currentMessagesRemoved()), m_messagesBrowser, SLOT(clear()));
  connect(m_messagesView, SIGNAL(currentMessagesChanged(QList<Message>)), m_messagesBrowser, SLOT(navigateToMessages(QList<Message>)));
  connect(m_messagesView, SIGNAL(currentMessagesRemoved()), this, SLOT(updateMessageButtonsAvailability()));
  connect(m_messagesView, SIGNAL(currentMessagesChanged(QList<Message>)), this, SLOT(updateMessageButtonsAvailability()));

  connect(m_feedsView, SIGNAL(feedsSelected(FeedsSelection)), this, SLOT(updateFeedButtonsAvailability()));
  connect(qApp->feedUpdateLock(), SIGNAL(locked()), this, SLOT(updateFeedButtonsAvailability()));
  connect(qApp->feedUpdateLock(), SIGNAL(unlocked()), this, SLOT(updateFeedButtonsAvailability()));

  // If user selects feeds, load their messages.
  connect(m_feedsView, SIGNAL(feedsSelected(FeedsSelection)), m_messagesView, SLOT(loadFeeds(FeedsSelection)));

  // If user changes status of some messages, recalculate message counts.
  connect(m_messagesView->sourceModel(), SIGNAL(messageCountsChanged(FeedsSelection::SelectionMode,bool,bool)),
          m_feedsView, SLOT(receiveMessageCountsChange(FeedsSelection::SelectionMode,bool,bool)));

  // State of many messages is changed, then we need
  // to reload selections.
  connect(m_feedsView, SIGNAL(feedsNeedToBeReloaded(bool)), m_messagesView, SLOT(reloadSelections(bool)));

  // If counts of unread/all messages change, update the tray icon.
  connect(m_feedsView->sourceModel(), SIGNAL(messageCountsChanged(int,int,bool)), this, SLOT(updateTrayIconStatus(int,int,bool)));

  // Message openers.
  connect(m_messagesView, SIGNAL(openLinkMiniBrowser(QString)), m_messagesBrowser, SLOT(navigateToUrl(QString)));
  connect(m_messagesView, SIGNAL(openMessagesInNewspaperView(QList<Message>)),
          form_main->m_ui->m_tabWidget, SLOT(addBrowserWithMessages(QList<Message>)));
  connect(m_messagesView, SIGNAL(openLinkNewTab(QString)),
          form_main->m_ui->m_tabWidget, SLOT(addLinkedBrowser(QString)));
  connect(m_feedsView, SIGNAL(openMessagesInNewspaperView(QList<Message>)),
          form_main->m_ui->m_tabWidget, SLOT(addBrowserWithMessages(QList<Message>)));

  // Downloader connections.
  connect(m_feedsView, SIGNAL(feedsUpdateRequested(QList<Feed*>)), this, SLOT(updateFeeds(QList<Feed*>)));

  // Toolbar forwardings.
  connect(form_main->m_ui->m_actionCleanupDatabase,
          SIGNAL(triggered()), this, SLOT(showDbCleanupAssistant()));
  connect(form_main->m_ui->m_actionSwitchImportanceOfSelectedMessages,
          SIGNAL(triggered()), m_messagesView, SLOT(switchSelectedMessagesImportance()));
  connect(form_main->m_ui->m_actionDeleteSelectedMessages,
          SIGNAL(triggered()), m_messagesView, SLOT(deleteSelectedMessages()));
  connect(form_main->m_ui->m_actionMarkSelectedMessagesAsRead,
          SIGNAL(triggered()), m_messagesView, SLOT(markSelectedMessagesRead()));
  connect(form_main->m_ui->m_actionMarkSelectedMessagesAsUnread,
          SIGNAL(triggered()), m_messagesView, SLOT(markSelectedMessagesUnread()));
  connect(form_main->m_ui->m_actionOpenSelectedSourceArticlesExternally,
          SIGNAL(triggered()), m_messagesView, SLOT(openSelectedSourceMessagesExternally()));
  connect(form_main->m_ui->m_actionOpenSelectedSourceArticlesInternally,
          SIGNAL(triggered()), m_messagesView, SLOT(openSelectedSourceMessagesInternally()));
  connect(form_main->m_ui->m_actionOpenSelectedMessagesInternally,
          SIGNAL(triggered()), m_messagesView, SLOT(openSelectedMessagesInternally()));
  connect(form_main->m_ui->m_actionSendMessageViaEmail,
          SIGNAL(triggered()), m_messagesView, SLOT(sendSelectedMessageViaEmail()));
  connect(form_main->m_ui->m_actionMarkAllItemsRead,
          SIGNAL(triggered()), m_feedsView, SLOT(markAllItemsRead()));
  connect(form_main->m_ui->m_actionMarkSelectedItemsAsRead,
          SIGNAL(triggered()), m_feedsView, SLOT(markSelectedItemsRead()));
  connect(form_main->m_ui->m_actionExpandCollapseItem,
          SIGNAL(triggered()), m_feedsView, SLOT(expandCollapseCurrentItem()));
  connect(form_main->m_ui->m_actionMarkSelectedItemsAsUnread,
          SIGNAL(triggered()), m_feedsView, SLOT(markSelectedItemsUnread()));
  connect(form_main->m_ui->m_actionClearSelectedItems,
          SIGNAL(triggered()), m_feedsView, SLOT(clearSelectedFeeds()));
  connect(form_main->m_ui->m_actionClearAllItems,
          SIGNAL(triggered()), m_feedsView, SLOT(clearAllFeeds()));
  connect(form_main->m_ui->m_actionUpdateSelectedItems,
          SIGNAL(triggered()), m_feedsView, SLOT(updateSelectedItems()));
  connect(form_main->m_ui->m_actionUpdateAllItems,
          SIGNAL(triggered()), m_feedsView, SLOT(updateAllItems()));
  connect(form_main->m_ui->m_actionEditSelectedItem,
          SIGNAL(triggered()), m_feedsView, SLOT(editSelectedItem()));
  connect(form_main->m_ui->m_actionViewSelectedItemsNewspaperMode,
          SIGNAL(triggered()), m_feedsView, SLOT(openSelectedItemsInNewspaperMode()));
  connect(form_main->m_ui->m_actionDeleteSelectedItem,
          SIGNAL(triggered()), m_feedsView, SLOT(deleteSelectedItem()));
  connect(form_main->m_ui->m_actionSwitchFeedsList,
          SIGNAL(triggered()), this, SLOT(switchFeedComponentVisibility()));
  connect(form_main->m_ui->m_actionSelectNextItem,
          SIGNAL(triggered()), m_feedsView, SLOT(selectNextItem()));
  connect(form_main->m_ui->m_actionSwitchToolBars,
          SIGNAL(toggled(bool)), this, SLOT(setToolBarsEnabled(bool)));
  connect(form_main->m_ui->m_actionSwitchListHeaders,
          SIGNAL(toggled(bool)), this, SLOT(setListHeadersEnabled(bool)));
  connect(form_main->m_ui->m_actionSelectPreviousItem,
          SIGNAL(triggered()), m_feedsView, SLOT(selectPreviousItem()));
  connect(form_main->m_ui->m_actionSelectNextMessage,
          SIGNAL(triggered()), m_messagesView, SLOT(selectNextItem()));
  connect(form_main->m_ui->m_actionSelectPreviousMessage,
          SIGNAL(triggered()), m_messagesView, SLOT(selectPreviousItem()));
  connect(form_main->m_ui->m_actionSwitchMessageListOrientation, SIGNAL(triggered()),
          this, SLOT(switchMessageSplitterOrientation()));
  connect(form_main->m_ui->m_actionShowOnlyUnreadItems, SIGNAL(toggled(bool)),
          this, SLOT(toggleShowOnlyUnreadFeeds()));
}

void FeedMessageViewer::initialize() {
  // Initialize/populate toolbars.
  m_toolBarFeeds->setFloatable(false);
  m_toolBarFeeds->setMovable(false);
  m_toolBarFeeds->setAllowedAreas(Qt::TopToolBarArea);
  m_toolBarFeeds->loadChangeableActions();

  m_toolBarMessages->setFloatable(false);
  m_toolBarMessages->setMovable(false);
  m_toolBarMessages->setAllowedAreas(Qt::TopToolBarArea);
  m_toolBarMessages->loadChangeableActions();

  // Finish web/message browser setup.
  m_messagesBrowser->setNavigationBarVisible(false);

  // Now refresh visual setup.
  refreshVisualProperties();
}

void FeedMessageViewer::initializeViews() {
  m_feedsWidget = new QWidget(this);
  m_messagesWidget = new QWidget(this);
  m_feedSplitter = new QSplitter(Qt::Horizontal, this);
  m_messageSplitter = new QSplitter(Qt::Vertical, this);

  // Instantiate needed components.
  QVBoxLayout *central_layout = new QVBoxLayout(this);
  QVBoxLayout *feed_layout = new QVBoxLayout(m_feedsWidget);
  QVBoxLayout *message_layout = new QVBoxLayout(m_messagesWidget);

  // Set layout properties.
  central_layout->setMargin(0);
  central_layout->setSpacing(0);
  feed_layout->setMargin(0);
  feed_layout->setSpacing(0);
  message_layout->setMargin(0);
  message_layout->setSpacing(0);

  // Set views.
  m_feedsView->setFrameStyle(QFrame::NoFrame);
  m_messagesView->setFrameStyle(QFrame::NoFrame);

  // Setup message splitter.
  m_messageSplitter->setObjectName(QSL("MessageSplitter"));
  m_messageSplitter->setHandleWidth(1);
  m_messageSplitter->setOpaqueResize(false);
  m_messageSplitter->setChildrenCollapsible(false);
  m_messageSplitter->addWidget(m_messagesView);
  m_messageSplitter->addWidget(m_messagesBrowser);

  // Assemble message-related components to single widget.
  message_layout->addWidget(m_toolBarMessages);
  message_layout->addWidget(m_messageSplitter);

  // Assemble feed-related components to another widget.
  feed_layout->addWidget(m_toolBarFeeds);
  feed_layout->addWidget(m_feedsView);

  // Assembler everything together.
  m_feedSplitter->setHandleWidth(1);
  m_feedSplitter->setOpaqueResize(false);
  m_feedSplitter->setChildrenCollapsible(false);
  m_feedSplitter->addWidget(m_feedsWidget);
  m_feedSplitter->addWidget(m_messagesWidget);

  // Add toolbar and main feeds/messages widget to main layout.
  central_layout->addWidget(m_feedSplitter);

  setTabOrder(m_feedsView, m_messagesView);
  setTabOrder(m_messagesView, m_toolBarFeeds);
  setTabOrder(m_toolBarFeeds, m_toolBarMessages);
  setTabOrder(m_toolBarMessages, m_messagesBrowser);

  updateMessageButtonsAvailability();
  updateFeedButtonsAvailability();
}

void FeedMessageViewer::showDbCleanupAssistant() {
  if (qApp->feedUpdateLock()->tryLock()) {
    QPointer<FormDatabaseCleanup> form_pointer = new FormDatabaseCleanup(this);
    form_pointer.data()->setCleaner(databaseCleaner());
    form_pointer.data()->exec();

    delete form_pointer.data();
    qApp->feedUpdateLock()->unlock();

    m_messagesView->reloadSelections(false);
    m_feedsView->updateCountsOfAllFeeds(true);
  }
  else {
    qApp->showGuiMessage(tr("Cannot cleanup database"),
                         tr("Cannot cleanup database, because another critical action is running."),
                         QSystemTrayIcon::Warning, qApp->mainForm(), true);
  }
}

void FeedMessageViewer::refreshVisualProperties() {
  Qt::ToolButtonStyle button_style = static_cast<Qt::ToolButtonStyle>(qApp->settings()->value(GROUP(GUI),
                                                                                              SETTING(GUI::ToolbarStyle)).toInt());

  m_toolBarFeeds->setToolButtonStyle(button_style);
  m_toolBarMessages->setToolButtonStyle(button_style);
}

void FeedMessageViewer::updateFeeds(QList<Feed*> feeds) {
  if (!qApp->feedUpdateLock()->tryLock()) {
    qApp->showGuiMessage(tr("Cannot update all items"),
                         tr("You cannot update all items because another another critical operation is ongoing."),
                         QSystemTrayIcon::Warning, qApp->mainForm(), true);
    return;
  }

  if (m_feedDownloader == NULL) {
    m_feedDownloader = new FeedDownloader();
    m_feedDownloaderThread = new QThread();

    // Downloader setup.
    qRegisterMetaType<QList<Feed*> >("QList<Feed*>");
    m_feedDownloader->moveToThread(m_feedDownloaderThread);

    connect(this, SIGNAL(feedsUpdateRequested(QList<Feed*>)), m_feedDownloader, SLOT(updateFeeds(QList<Feed*>)));
    connect(m_feedDownloaderThread, SIGNAL(finished()), m_feedDownloaderThread, SLOT(deleteLater()));
    connect(m_feedDownloader, SIGNAL(finished(FeedDownloadResults)), this, SLOT(onFeedUpdatesFinished(FeedDownloadResults)));
    connect(m_feedDownloader, SIGNAL(started()), this, SLOT(onFeedUpdatesStarted()));
    connect(m_feedDownloader, SIGNAL(progress(Feed*,int,int)), this, SLOT(onFeedUpdatesProgress(Feed*,int,int)));

    // Connections are made, start the feed downloader thread.
    m_feedDownloaderThread->start();
  }

  emit feedsUpdateRequested(feeds);
}