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

#include "services/abstract/rootitem.h"

#include "3rd-party/boolinq/boolinq.h"
#include "miscellaneous/application.h"
#include "miscellaneous/iconfactory.h"
#include "services/abstract/category.h"
#include "services/abstract/feed.h"
#include "services/abstract/label.h"
#include "services/abstract/recyclebin.h"
#include "services/abstract/serviceroot.h"

#include <QVariant>

RootItem::RootItem(RootItem* parent_item)
  : QObject(nullptr), m_kind(RootItem::Kind::Root), m_id(NO_PARENT_CATEGORY), m_customId(QL1S("")),
  m_title(QString()), m_description(QString()), m_creationDate(QDateTime::currentDateTimeUtc()),
  m_keepOnTop(false), m_childItems(QList<RootItem*>()), m_parentItem(parent_item) {}

RootItem::RootItem(const RootItem& other) : RootItem(nullptr) {
  setTitle(other.title());
  setId(other.id());
  setCustomId(other.customId());
  setIcon(other.icon());
  setKeepOnTop(other.keepOnTop());

  // NOTE: We do not need to clone childs, because that would mean that
  // either source or target item tree would get corrupted.
  //setChildItems(other.childItems());

  setParent(other.parent());
  setCreationDate(other.creationDate());
  setDescription(other.description());
}

RootItem::~RootItem() {
  qDeleteAll(m_childItems);
}

QString RootItem::hashCode() const {
  ServiceRoot* root = getParentServiceRoot();
  int acc_id = root == nullptr ? 0 : root->accountId();

  return
    QString::number(acc_id) + QL1S("-") +
    QString::number(int(kind())) + QL1S("-") +
    QString::number(id());
}

QString RootItem::additionalTooltip() const {
  return QString();
}

QList<QAction*> RootItem::contextMenuFeedsList() {
  return QList<QAction*>();
}

bool RootItem::canBeEdited() const {
  return false;
}

bool RootItem::editViaGui() {
  return false;
}

bool RootItem::canBeDeleted() const {
  return false;
}

bool RootItem::deleteViaGui() {
  return false;
}

bool RootItem::markAsReadUnread(ReadStatus status) {
  bool result = true;

  for (RootItem* child : qAsConst(m_childItems)) {
    result &= child->markAsReadUnread(status);
  }

  return result;
}

QList<Message> RootItem::undeletedMessages() const {
  QList<Message> messages;

  for (RootItem* child : qAsConst(m_childItems)) {
    if (child->kind() != Kind::Bin && child->kind() != Kind::Labels && child->kind() != Kind::Label) {
      messages.append(child->undeletedMessages());
    }
  }

  return messages;
}

bool RootItem::cleanMessages(bool clear_only_read) {
  bool result = true;

  for (RootItem* child : qAsConst(m_childItems)) {
    if (child->kind() != RootItem::Kind::Bin) {
      result &= child->cleanMessages(clear_only_read);
    }
  }

  return result;
}

void RootItem::updateCounts(bool including_total_count) {
  for (RootItem* child : qAsConst(m_childItems)) {
    child->updateCounts(including_total_count);
  }
}

int RootItem::row() const {
  if (m_parentItem != nullptr) {
    return m_parentItem->m_childItems.indexOf(const_cast<RootItem*>(this));
  }
  else {
    // This item has no parent. Therefore, its row index is 0.
    return 0;
  }
}

QVariant RootItem::data(int column, int role) const {
  switch (role) {
    case Qt::ItemDataRole::ToolTipRole:
      if (column == FDS_MODEL_TITLE_INDEX) {
        QString tool_tip = m_title;

        if (!m_description.isEmpty()) {
          tool_tip += QL1S("\n") + m_description;
        }

        QString extra_tooltip = additionalTooltip();

        if (!extra_tooltip.isEmpty()) {
          tool_tip += QL1S("\n\n") + extra_tooltip;
        }

        return tool_tip;
      }
      else if (column == FDS_MODEL_COUNTS_INDEX) {
        //: Tooltip for "unread" column of feed list.
        return tr("%n unread article(s).", nullptr, countOfUnreadMessages());
      }
      else {
        return QVariant();
      }

    case LOWER_TITLE_ROLE:
      return m_title.toLower();

    case Qt::ItemDataRole::EditRole:
      if (column == FDS_MODEL_TITLE_INDEX) {
        return m_title;
      }
      else if (column == FDS_MODEL_COUNTS_INDEX) {
        return countOfUnreadMessages();
      }
      else {
        return QVariant();
      }

    case Qt::ItemDataRole::DisplayRole:
      if (column == FDS_MODEL_TITLE_INDEX) {
        return m_title;
      }
      else if (column == FDS_MODEL_COUNTS_INDEX) {
        int count_unread = countOfUnreadMessages();

        if (count_unread <= 0 && qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::HideCountsIfNoUnread)).toBool()) {
          return QString();
        }
        else {
          int count_all = countOfAllMessages();

          return qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::CountFormat)).toString()
                 .replace(QSL(PLACEHOLDER_UNREAD_COUNTS), count_unread < 0 ? QSL("-") : QString::number(count_unread))
                 .replace(QSL(PLACEHOLDER_ALL_COUNTS), count_all < 0 ? QSL("-") : QString::number(count_all));
        }
      }
      else {
        return QVariant();
      }

    case Qt::ItemDataRole::DecorationRole:
      if (column == FDS_MODEL_TITLE_INDEX) {
        return fullIcon();
      }
      else {
        return QVariant();
      }

    case Qt::ItemDataRole::TextAlignmentRole:
      if (column == FDS_MODEL_COUNTS_INDEX) {
        return Qt::AlignmentFlag::AlignCenter;
      }
      else {
        return QVariant();
      }

    default:
      return QVariant();
  }
}

Qt::ItemFlags RootItem::additionalFlags() const {
  return Qt::ItemFlag::NoItemFlags;
}

bool RootItem::performDragDropChange(RootItem* target_item) {
  Q_UNUSED(target_item)
  return false;
}

int RootItem::countOfUnreadMessages() const {
  return boolinq::from(m_childItems).sum([](RootItem* it) {
    return (it->kind() == RootItem::Kind::Important ||
            it->kind() == RootItem::Kind::Unread ||
            it->kind() == RootItem::Kind::Labels)
        ? 0
        : it->countOfUnreadMessages();
  });
}

int RootItem::countOfAllMessages() const {
  return boolinq::from(m_childItems).sum([](RootItem* it) {
    return (it->kind() == RootItem::Kind::Important ||
            it->kind() == RootItem::Kind::Unread ||
            it->kind() == RootItem::Kind::Labels)
        ? 0
        : it->countOfAllMessages();
  });
}

bool RootItem::isChildOf(const RootItem* root) const {
  if (root == nullptr) {
    return false;
  }

  const RootItem* this_item = this;

  while (this_item->kind() != RootItem::Kind::Root) {
    if (root->childItems().contains(const_cast<RootItem* const>(this_item))) {
      return true;
    }
    else {
      this_item = this_item->parent();
    }
  }

  return false;
}

bool RootItem::isParentOf(const RootItem* child) const {
  if (child == nullptr) {
    return false;
  }
  else {
    return child->isChildOf(this);
  }
}

QList<RootItem*> RootItem::getSubTree() const {
  QList<RootItem*> children;
  QList<RootItem*> traversable_items;

  traversable_items.append(const_cast<RootItem* const>(this));

  // Iterate all nested items.
  while (!traversable_items.isEmpty()) {
    RootItem* active_item = traversable_items.takeFirst();

    children.append(active_item);
    traversable_items.append(active_item->childItems());
  }

  return children;
}

QList<RootItem*> RootItem::getSubTree(RootItem::Kind kind_of_item) const {
  QList<RootItem*> children;
  QList<RootItem*> traversable_items;

  traversable_items.append(const_cast<RootItem* const>(this));

  // Iterate all nested items.
  while (!traversable_items.isEmpty()) {
    RootItem* active_item = traversable_items.takeFirst();

    if (int(active_item->kind() & kind_of_item) > 0) {
      children.append(active_item);
    }

    traversable_items.append(active_item->childItems());
  }

  return children;
}

QList<Category*> RootItem::getSubTreeCategories() const {
  QList<Category*> children;
  QList<RootItem*> traversable_items;

  traversable_items.append(const_cast<RootItem* const>(this));

  // Iterate all nested items.
  while (!traversable_items.isEmpty()) {
    RootItem* active_item = traversable_items.takeFirst();

    if (active_item->kind() == RootItem::Kind::Category) {
      children.append(active_item->toCategory());
    }

    traversable_items.append(active_item->childItems());
  }

  return children;
}

RootItem* RootItem::getItemFromSubTree(std::function<bool(const RootItem*)> tester) const {
  QList<RootItem*> children;
  QList<RootItem*> traversable_items;

  traversable_items.append(const_cast<RootItem* const>(this));

  // Iterate all nested items.
  while (!traversable_items.isEmpty()) {
    RootItem* active_item = traversable_items.takeFirst();

    if (tester(active_item)) {
      return active_item;
    }

    children.append(active_item);
    traversable_items.append(active_item->childItems());
  }

  return nullptr;
}

QHash<int, Category*> RootItem::getHashedSubTreeCategories() const {
  QHash<int, Category*> children;
  QList<RootItem*> traversable_items;

  traversable_items.append(const_cast<RootItem* const>(this));

  // Iterate all nested items.
  while (!traversable_items.isEmpty()) {
    RootItem* active_item = traversable_items.takeFirst();

    if (active_item->kind() == RootItem::Kind::Category && !children.contains(active_item->id())) {
      children.insert(active_item->id(), active_item->toCategory());
    }

    traversable_items.append(active_item->childItems());
  }

  return children;
}

QHash<QString, Feed*> RootItem::getHashedSubTreeFeeds() const {
  QHash<QString, Feed*> children;
  QList<RootItem*> traversable_items;

  traversable_items.append(const_cast<RootItem* const>(this));

  // Iterate all nested items.
  while (!traversable_items.isEmpty()) {
    RootItem* active_item = traversable_items.takeFirst();

    if (active_item->kind() == RootItem::Kind::Feed && !children.contains(active_item->customId())) {
      children.insert(active_item->customId(), active_item->toFeed());
    }

    traversable_items.append(active_item->childItems());
  }

  return children;
}

QList<Feed*> RootItem::getSubTreeFeeds() const {
  QList<Feed*> children;
  QList<RootItem*> traversable_items;

  traversable_items.append(const_cast<RootItem* const>(this));

  // Iterate all nested items.
  while (!traversable_items.isEmpty()) {
    RootItem* active_item = traversable_items.takeFirst();

    if (active_item->kind() == RootItem::Kind::Feed) {
      children.append(active_item->toFeed());
    }

    traversable_items.append(active_item->childItems());
  }

  return children;
}

QList<Feed*> RootItem::getSubTreeAutoFetchingWithManualIntervalsFeeds() const {
  QList<Feed*> children;
  QList<RootItem*> traversable_items;

  traversable_items.append(const_cast<RootItem* const>(this));

  // Iterate all nested items.
  while (!traversable_items.isEmpty()) {
    RootItem* active_item = traversable_items.takeFirst();

    if (active_item->kind() == RootItem::Kind::Feed &&
        active_item->toFeed()->autoUpdateType() == Feed::AutoUpdateType::SpecificAutoUpdate) {
      children.append(active_item->toFeed());
    }

    traversable_items.append(active_item->childItems());
  }

  return children;
}

QList<Feed*> RootItem::getSubAutoFetchingEnabledFeeds() const {
  QList<Feed*> children;
  QList<RootItem*> traversable_items;

  traversable_items.append(const_cast<RootItem* const>(this));

  // Iterate all nested items.
  while (!traversable_items.isEmpty()) {
    RootItem* active_item = traversable_items.takeFirst();

    if (active_item->kind() == RootItem::Kind::Feed &&
        active_item->toFeed()->autoUpdateType() != Feed::AutoUpdateType::DontAutoUpdate) {
      children.append(active_item->toFeed());
    }

    traversable_items.append(active_item->childItems());
  }

  return children;
}

ServiceRoot* RootItem::getParentServiceRoot() const {
  const RootItem* working_parent = this;

  while (working_parent->kind() != RootItem::Kind::Root) {
    if (working_parent->kind() == RootItem::Kind::ServiceRoot) {
      return working_parent->toServiceRoot();
    }
    else {
      working_parent = working_parent->parent();
    }
  }

  return nullptr;
}

RootItem::Kind RootItem::kind() const {
  return m_kind;
}

void RootItem::setKind(RootItem::Kind kind) {
  m_kind = kind;
}

QIcon RootItem::icon() const {
  return m_icon;
}

void RootItem::setIcon(const QIcon& icon) {
  m_icon = icon;
}

QIcon RootItem::fullIcon() const {
  QIcon ico = icon();

  if (ico.isNull()) {
    if (kind() == RootItem::Kind::Feed) {
      return qApp->icons()->fromTheme(QSL("application-rss+xml"));
    }
    else if (kind() == RootItem::Kind::Category) {
      return qApp->icons()->fromTheme(QSL("folder"));
    }
  }

  return ico;
}

int RootItem::id() const {
  return m_id;
}

void RootItem::setId(int id) {
  m_id = id;
}

QString RootItem::title() const {
  return m_title;
}

void RootItem::setTitle(const QString& title) {
  m_title = title;
}

QDateTime RootItem::creationDate() const {
  return m_creationDate;
}

void RootItem::setCreationDate(const QDateTime& creation_date) {
  m_creationDate = creation_date;
}

QString RootItem::description() const {
  return m_description;
}

void RootItem::setDescription(const QString& description) {
  m_description = description;
}

bool RootItem::removeChild(RootItem* child) {
  return m_childItems.removeOne(child);
}

QString RootItem::customId() const {
  return m_customId;
}

int RootItem::customNumericId() const {
  return customId().toInt();
}

void RootItem::setCustomId(const QString& custom_id) {
  m_customId = custom_id;
}

Category* RootItem::toCategory() const {
  return qobject_cast<Category*>(const_cast<RootItem*>(this));
}

Feed* RootItem::toFeed() const {
  return qobject_cast<Feed*>(const_cast<RootItem*>(this));
}

Label* RootItem::toLabel() const {
  return qobject_cast<Label*>(const_cast<RootItem*>(this));
}

ServiceRoot* RootItem::toServiceRoot() const {
  return qobject_cast<ServiceRoot*>(const_cast<RootItem*>(this));
}

bool RootItem::keepOnTop() const {
  return m_keepOnTop;
}

void RootItem::setKeepOnTop(bool keep_on_top) {
  m_keepOnTop = keep_on_top;
}

bool RootItem::removeChild(int index) {
  if (index >= 0 && index < m_childItems.size()) {
    m_childItems.removeAt(index);
    return true;
  }
  else {
    return false;
  }
}

QDataStream& operator>>(QDataStream& in, RootItem::ReadStatus& myObj) {
  int obj;

  in >> obj;

  myObj = (RootItem::ReadStatus)obj;

  return in;
}

QDataStream& operator<<(QDataStream& out, const RootItem::ReadStatus& myObj) {
  out << (int)myObj;

  return out;
}

QDataStream& operator>>(QDataStream& in, RootItem::Importance& myObj) {
  int obj;

  in >> obj;

  myObj = (RootItem::Importance)obj;

  return in;
}

QDataStream& operator<<(QDataStream& out, const RootItem::Importance& myObj) {
  out << (int)myObj;

  return out;
}

RootItem::Kind operator|(RootItem::Kind a, RootItem::Kind b) {
  return static_cast<RootItem::Kind>(static_cast<int>(a) | static_cast<int>(b));
}

RootItem::Kind operator&(RootItem::Kind a, RootItem::Kind b) {
  return static_cast<RootItem::Kind>(static_cast<int>(a) & static_cast<int>(b));
}
