/**
 * anime_list.cpp: defines the anime list page
 * and widgets.
 *
 * much of this file is based around
 * Qt's original QTabWidget implementation, because
 * I needed a somewhat native way to create a tabbed
 * widget with only one subwidget that worked exactly
 * like a native tabbed widget.
 **/
#include "gui/pages/anime_list.h"
#include "core/anime.h"
#include "core/anime_db.h"
#include "core/array.h"
#include "core/session.h"
#include "core/time.h"
#include "gui/dialog/information.h"
#include "gui/translate/anime.h"
#include "services/services.h"
#include <QDebug>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QMenu>
#include <QProgressBar>
#include <QShortcut>
#include <QStylePainter>
#include <QStyledItemDelegate>
#include <cmath>

AnimeListWidgetDelegate::AnimeListWidgetDelegate(QObject* parent) : QStyledItemDelegate(parent) {
}

QWidget* AnimeListWidgetDelegate::createEditor(QWidget*, const QStyleOptionViewItem&, const QModelIndex&) const {
	// no edit 4 u
	return nullptr;
}

void AnimeListWidgetDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option,
                                    const QModelIndex& index) const {
	switch (index.column()) {
#if 0
		case AnimeListWidgetModel::AL_PROGRESS: {
			const int progress = static_cast<int>(index.data(Qt::UserRole).toReal());
			const int episodes =
			    static_cast<int>(index.siblingAtColumn(AnimeListWidgetModel::AL_EPISODES).data(Qt::UserRole).toReal());

			int text_width = 59;
			QRectF text_rect(option.rect.x() + text_width, option.rect.y(), text_width, option.decorationSize.height());
			painter->save();
			painter->drawText(text_rect, tr("/"), QTextOption(Qt::AlignCenter | Qt::AlignVCenter));
			drawText(const QRectF &rectangle, const QString &text, const QTextOption &option =
			   QTextOption()) painter->drawText(QRectF(text_rect.x(), text_rect.y(), text_width / 2 - 2,
			   text_rect.height()), QString::number(progress), QTextOption(Qt::AlignRight | Qt::AlignVCenter));
			   painter->drawText(
			       QRectF(text_rect.x() + text_width / 2 + 2, text_rect.y(), text_width / 2 - 2, text_rect.height()),
			       QString::number(episodes), QTextOption(Qt::AlignLeft | Qt::AlignVCenter));
			   painter->restore();
			   QStyledItemDelegate::paint(painter, option, index);
			   break;
		}
#endif
		default: QStyledItemDelegate::paint(painter, option, index); break;
	}
}

AnimeListWidgetSortFilter::AnimeListWidgetSortFilter(QObject* parent) : QSortFilterProxyModel(parent) {
}

bool AnimeListWidgetSortFilter::lessThan(const QModelIndex& l, const QModelIndex& r) const {
	QVariant left = sourceModel()->data(l, sortRole());
	QVariant right = sourceModel()->data(r, sortRole());

	switch (left.userType()) {
		case QMetaType::Int:
		case QMetaType::UInt:
		case QMetaType::LongLong:
		case QMetaType::ULongLong: return left.toInt() < right.toInt();
		case QMetaType::QDate: return left.toDate() < right.toDate();
		case QMetaType::QString:
		default: return QString::compare(left.toString(), right.toString(), Qt::CaseInsensitive) < 0;
	}
}

AnimeListWidgetModel::AnimeListWidgetModel(QWidget* parent, Anime::ListStatus _status) : QAbstractListModel(parent) {
	status = _status;
	return;
}

int AnimeListWidgetModel::rowCount(const QModelIndex& parent) const {
	return list.size();
	(void)(parent);
}

int AnimeListWidgetModel::columnCount(const QModelIndex& parent) const {
	return NB_COLUMNS;
	(void)(parent);
}

QVariant AnimeListWidgetModel::headerData(const int section, const Qt::Orientation orientation, const int role) const {
	if (role == Qt::DisplayRole) {
		switch (section) {
			case AL_TITLE: return tr("Anime title");
			case AL_PROGRESS: return tr("Progress");
			case AL_EPISODES: return tr("Episodes");
			case AL_TYPE: return tr("Type");
			case AL_SCORE: return tr("Score");
			case AL_SEASON: return tr("Season");
			case AL_STARTED: return tr("Date started");
			case AL_COMPLETED: return tr("Date completed");
			case AL_NOTES: return tr("Notes");
			case AL_AVG_SCORE: return tr("Average score");
			case AL_UPDATED: return tr("Last updated");
			default: return {};
		}
	} else if (role == Qt::TextAlignmentRole) {
		switch (section) {
			case AL_TITLE:
			case AL_NOTES: return QVariant(Qt::AlignLeft | Qt::AlignVCenter);
			case AL_PROGRESS:
			case AL_EPISODES:
			case AL_TYPE:
			case AL_SCORE:
			case AL_AVG_SCORE: return QVariant(Qt::AlignCenter | Qt::AlignVCenter);
			case AL_SEASON:
			case AL_STARTED:
			case AL_COMPLETED:
			case AL_UPDATED: return QVariant(Qt::AlignRight | Qt::AlignVCenter);
			default: return QAbstractListModel::headerData(section, orientation, role);
		}
	}
	return QAbstractListModel::headerData(section, orientation, role);
}

QVariant AnimeListWidgetModel::data(const QModelIndex& index, int role) const {
	if (!index.isValid())
		return QVariant();
	switch (role) {
		case Qt::DisplayRole:
			switch (index.column()) {
				case AL_TITLE: return QString::fromUtf8(list[index.row()].GetUserPreferredTitle().c_str());
				case AL_PROGRESS:
					return QString::number(list[index.row()].GetUserProgress()) + "/" +
					       QString::number(list[index.row()].GetEpisodes());
				case AL_EPISODES: return list[index.row()].GetEpisodes();
				case AL_SCORE: return list[index.row()].GetUserScore();
				case AL_TYPE: return QString::fromStdString(Translate::ToString(list[index.row()].GetFormat()));
				case AL_SEASON:
					return QString::fromStdString(Translate::ToString(list[index.row()].GetSeason())) + " " +
					       QString::number(list[index.row()].GetAirDate().GetYear());
				case AL_AVG_SCORE: return QString::number(list[index.row()].GetAudienceScore()) + "%";
				case AL_STARTED: return list[index.row()].GetUserDateStarted().GetAsQDate();
				case AL_COMPLETED: return list[index.row()].GetUserDateCompleted().GetAsQDate();
				case AL_UPDATED: {
					if (list[index.row()].GetUserTimeUpdated() == 0)
						return QString("-");
					Time::Duration duration(Time::GetSystemTime() - list[index.row()].GetUserTimeUpdated());
					return QString::fromUtf8(duration.AsRelativeString().c_str());
				}
				case AL_NOTES: return QString::fromUtf8(list[index.row()].GetUserNotes().c_str());
				default: return "";
			}
			break;
		case Qt::UserRole:
			switch (index.column()) {
				case AL_PROGRESS: return list[index.row()].GetUserProgress();
				case AL_TYPE: return static_cast<int>(list[index.row()].GetFormat());
				case AL_SEASON: return list[index.row()].GetAirDate().GetAsQDate();
				case AL_AVG_SCORE: return list[index.row()].GetAudienceScore();
				case AL_UPDATED: return QVariant::fromValue(list[index.row()].GetUserTimeUpdated());
				default: return data(index, Qt::DisplayRole);
			}
			break;
		case Qt::TextAlignmentRole:
			switch (index.column()) {
				case AL_TITLE:
				case AL_NOTES: return QVariant(Qt::AlignLeft | Qt::AlignVCenter);
				case AL_PROGRESS:
				case AL_EPISODES:
				case AL_TYPE:
				case AL_SCORE:
				case AL_AVG_SCORE: return QVariant(Qt::AlignCenter | Qt::AlignVCenter);
				case AL_SEASON:
				case AL_STARTED:
				case AL_COMPLETED:
				case AL_UPDATED: return QVariant(Qt::AlignRight | Qt::AlignVCenter);
				default: break;
			}
			break;
	}
	return QVariant();
}

void AnimeListWidgetModel::UpdateAnime(int id) {
	/* meh... it might be better to just reinit the entire list */
	int i = 0;
	for (const auto& a : Anime::db.items) {
		if (a.second.IsInUserList() && a.first == id && a.second.GetUserStatus() == status) {
			emit dataChanged(index(i), index(i));
		}
		i++;
	}
}

Anime::Anime* AnimeListWidgetModel::GetAnimeFromIndex(QModelIndex index) {
	return &list.at(index.row());
}

void AnimeListWidgetModel::RefreshList() {
	bool has_children = !!rowCount(index(0));
	if (has_children)
		beginResetModel();
	else {
		int count = 0;
		for (const auto& a : Anime::db.items)
			if (a.second.IsInUserList() && a.second.GetUserStatus() == status)
				count++;
		beginInsertRows(index(0), 0, count - 1);
	}

	list.clear();

	for (const auto& a : Anime::db.items) {
		if (a.second.IsInUserList() && a.second.GetUserStatus() == status) {
			list.push_back(a.second);
		}
	}

	if (has_children)
		endResetModel();
	else
		endInsertRows();
}

int AnimeListWidget::VisibleColumnsCount() const {
	int count = 0;

	for (int i = 0, end = tree_view->header()->count(); i < end; i++) {
		if (!tree_view->isColumnHidden(i))
			count++;
	}

	return count;
}

void AnimeListWidget::SetColumnDefaults() {
	tree_view->setColumnHidden(AnimeListWidgetModel::AL_SEASON, false);
	tree_view->setColumnHidden(AnimeListWidgetModel::AL_TYPE, false);
	tree_view->setColumnHidden(AnimeListWidgetModel::AL_UPDATED, false);
	tree_view->setColumnHidden(AnimeListWidgetModel::AL_PROGRESS, false);
	tree_view->setColumnHidden(AnimeListWidgetModel::AL_SCORE, false);
	tree_view->setColumnHidden(AnimeListWidgetModel::AL_TITLE, false);
	tree_view->setColumnHidden(AnimeListWidgetModel::AL_EPISODES, true);
	tree_view->setColumnHidden(AnimeListWidgetModel::AL_AVG_SCORE, true);
	tree_view->setColumnHidden(AnimeListWidgetModel::AL_STARTED, true);
	tree_view->setColumnHidden(AnimeListWidgetModel::AL_COMPLETED, true);
	tree_view->setColumnHidden(AnimeListWidgetModel::AL_UPDATED, true);
	tree_view->setColumnHidden(AnimeListWidgetModel::AL_NOTES, true);
}

void AnimeListWidget::DisplayColumnHeaderMenu() {
	QMenu* menu = new QMenu(this);
	menu->setAttribute(Qt::WA_DeleteOnClose);
	menu->setTitle(tr("Column visibility"));
	menu->setToolTipsVisible(true);

	for (int i = 0; i < AnimeListWidgetModel::NB_COLUMNS; i++) {
		if (i == AnimeListWidgetModel::AL_TITLE)
			continue;
		const auto column_name =
		    sort_models[tab_bar->currentIndex()]->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString();
		QAction* action = menu->addAction(column_name, this, [this, i](const bool checked) {
			if (!checked && (VisibleColumnsCount() <= 1))
				return;

			tree_view->setColumnHidden(i, !checked);

			if (checked && (tree_view->columnWidth(i) <= 5))
				tree_view->resizeColumnToContents(i);

			// SaveSettings();
		});
		action->setCheckable(true);
		action->setChecked(!tree_view->isColumnHidden(i));
	}

	menu->addSeparator();
	QAction* resetAction = menu->addAction(tr("Reset to defaults"), this, [this]() {
		for (int i = 0, count = tree_view->header()->count(); i < count; ++i) {
			SetColumnDefaults();
		}
		// SaveSettings();
	});
	menu->popup(QCursor::pos());
	(void)(resetAction);
}

void AnimeListWidget::DisplayListMenu() {
	QMenu* menu = new QMenu(this);
	menu->setAttribute(Qt::WA_DeleteOnClose);
	menu->setTitle(tr("Column visibility"));
	menu->setToolTipsVisible(true);

	const QItemSelection selection =
	    sort_models[tab_bar->currentIndex()]->mapSelectionToSource(tree_view->selectionModel()->selection());
	if (!selection.indexes().first().isValid()) {
		return;
	}

	QAction* action = menu->addAction(tr("Information"), [this, selection] {
		const QModelIndex index = ((AnimeListWidgetModel*)sort_models[tab_bar->currentIndex()]->sourceModel())
		                              ->index(selection.indexes().first().row());
		Anime::Anime* anime =
		    ((AnimeListWidgetModel*)sort_models[tab_bar->currentIndex()]->sourceModel())->GetAnimeFromIndex(index);
		if (!anime) {
			return;
		}

		InformationDialog* dialog = new InformationDialog(*anime, [this, anime] {
			Services::UpdateAnimeEntry(anime->GetId());
		    Refresh();
	    }, this);

		dialog->show();
		dialog->raise();
		dialog->activateWindow();
	});
	menu->popup(QCursor::pos());
}

void AnimeListWidget::ItemDoubleClicked() {
	/* throw out any other garbage */
	const QItemSelection selection =
	    sort_models[tab_bar->currentIndex()]->mapSelectionToSource(tree_view->selectionModel()->selection());
	if (!selection.indexes().first().isValid()) {
		return;
	}

	const QModelIndex index = ((AnimeListWidgetModel*)sort_models[tab_bar->currentIndex()]->sourceModel())
	                              ->index(selection.indexes().first().row());
	Anime::Anime* anime =
	    ((AnimeListWidgetModel*)sort_models[tab_bar->currentIndex()]->sourceModel())->GetAnimeFromIndex(index);

	InformationDialog* dialog = new InformationDialog(*anime, [this, anime] {
		Services::UpdateAnimeEntry(anime->GetId());
		Refresh();
	}, this);

	dialog->show();
	dialog->raise();
	dialog->activateWindow();
}

void AnimeListWidget::paintEvent(QPaintEvent*) {
	QStylePainter p(this);

	QStyleOptionTabWidgetFrame opt;
	InitStyle(&opt);
	opt.rect = panelRect;
	p.drawPrimitive(QStyle::PE_FrameTabWidget, opt);
}

void AnimeListWidget::resizeEvent(QResizeEvent* e) {
	QWidget::resizeEvent(e);
	SetupLayout();
}

void AnimeListWidget::showEvent(QShowEvent*) {
	SetupLayout();
}

void AnimeListWidget::InitBasicStyle(QStyleOptionTabWidgetFrame* option) const {
	if (!option)
		return;

	option->initFrom(this);
	option->lineWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, nullptr, this);
	option->shape = QTabBar::RoundedNorth;
	option->tabBarRect = tab_bar->geometry();
}

void AnimeListWidget::InitStyle(QStyleOptionTabWidgetFrame* option) const {
	if (!option)
		return;

	InitBasicStyle(option);

	// int exth = style()->pixelMetric(QStyle::PM_TabBarBaseHeight, nullptr, this);
	QSize t(0, tree_view->frameWidth());
	if (tab_bar->isVisibleTo(this)) {
		t = tab_bar->sizeHint();
		t.setWidth(width());
	}

	option->tabBarSize = t;

	QRect selected_tab_rect = tab_bar->tabRect(tab_bar->currentIndex());
	selected_tab_rect.moveTopLeft(selected_tab_rect.topLeft() + option->tabBarRect.topLeft());
	option->selectedTabRect = selected_tab_rect;

	option->lineWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, nullptr, this);
}

void AnimeListWidget::SetupLayout() {
	QStyleOptionTabWidgetFrame option;
	InitStyle(&option);

	QRect tabRect = style()->subElementRect(QStyle::SE_TabWidgetTabBar, &option, this);
	tabRect.setLeft(tabRect.left() + 1);
	panelRect = style()->subElementRect(QStyle::SE_TabWidgetTabPane, &option, this);
	QRect contentsRect = style()->subElementRect(QStyle::SE_TabWidgetTabContents, &option, this);

	tab_bar->setGeometry(tabRect);
	tree_view->parentWidget()->setGeometry(contentsRect);
}

AnimeListWidget::AnimeListWidget(QWidget* parent) : QWidget(parent) {
	/* Tab bar */
	tab_bar = new QTabBar(this);
	tab_bar->setExpanding(false);
	tab_bar->setDrawBase(false);

	/* Tree view... */
	QWidget* tree_widget = new QWidget(this);
	tree_view = new QTreeView(tree_widget);
	tree_view->setItemDelegate(new AnimeListWidgetDelegate(tree_view));
	tree_view->setUniformRowHeights(true);
	tree_view->setAllColumnsShowFocus(false);
	tree_view->setAlternatingRowColors(true);
	tree_view->setSortingEnabled(true);
	tree_view->setSelectionMode(QAbstractItemView::ExtendedSelection);
	tree_view->setItemsExpandable(false);
	tree_view->setRootIsDecorated(false);
	tree_view->setContextMenuPolicy(Qt::CustomContextMenu);
	tree_view->setFrameShape(QFrame::NoFrame);

	for (unsigned int i = 0; i < ARRAYSIZE(sort_models); i++) {
		tab_bar->addTab(QString::fromStdString(Translate::ToString(Anime::ListStatuses[i])) + " (" +
		                QString::number(Anime::db.GetListsAnimeAmount(Anime::ListStatuses[i])) + ")");
		sort_models[i] = new AnimeListWidgetSortFilter(tree_view);
		sort_models[i]->setSourceModel(new AnimeListWidgetModel(this, Anime::ListStatuses[i]));
		sort_models[i]->setSortRole(Qt::UserRole);
		sort_models[i]->setSortCaseSensitivity(Qt::CaseInsensitive);
	}
	tree_view->setModel(sort_models[0]);

	QHBoxLayout* layout = new QHBoxLayout;
	layout->addWidget(tree_view);
	layout->setMargin(0);
	tree_widget->setLayout(layout);

	/* Double click stuff */
	connect(tree_view, &QAbstractItemView::doubleClicked, this, &AnimeListWidget::ItemDoubleClicked);
	connect(tree_view, &QWidget::customContextMenuRequested, this, &AnimeListWidget::DisplayListMenu);

	/* Enter & return keys */
	connect(new QShortcut(Qt::Key_Return, tree_view, nullptr, nullptr, Qt::WidgetShortcut), &QShortcut::activated, this,
	        &AnimeListWidget::ItemDoubleClicked);

	connect(new QShortcut(Qt::Key_Enter, tree_view, nullptr, nullptr, Qt::WidgetShortcut), &QShortcut::activated, this,
	        &AnimeListWidget::ItemDoubleClicked);

	tree_view->header()->setStretchLastSection(false);
	tree_view->header()->setContextMenuPolicy(Qt::CustomContextMenu);
	connect(tree_view->header(), &QWidget::customContextMenuRequested, this, &AnimeListWidget::DisplayColumnHeaderMenu);

	connect(tab_bar, &QTabBar::currentChanged, this, [this](int index) {
		if (sort_models[index])
			tree_view->setModel(sort_models[index]);
	});

	setFocusPolicy(Qt::TabFocus);
	setFocusProxy(tab_bar);
}

void AnimeListWidget::RefreshList() {
	for (unsigned int i = 0; i < ARRAYSIZE(sort_models); i++)
		((AnimeListWidgetModel*)sort_models[i]->sourceModel())->RefreshList();
}

void AnimeListWidget::RefreshTabs() {
	for (unsigned int i = 0; i < ARRAYSIZE(sort_models); i++)
		tab_bar->setTabText(i, QString::fromStdString(Translate::ToString(Anime::ListStatuses[i])) + " (" +
		                           QString::number(Anime::db.GetListsAnimeAmount(Anime::ListStatuses[i])) + ")");
}

void AnimeListWidget::Refresh() {
	RefreshList();
	RefreshTabs();
}

/* This function, really, really should not be called.
   Ever. Why would you ever need to clear the anime list?
   Also, this sucks. */
void AnimeListWidget::Reset() {
	while (tab_bar->count())
		tab_bar->removeTab(0);
	for (unsigned int i = 0; i < ARRAYSIZE(sort_models); i++)
		delete sort_models[i];
}

#include "gui/pages/moc_anime_list.cpp"
