#include <QMainWindow>
#include <QWidget>
#include <QToolButton>
#include <algorithm>
#include <atomic>
#include <cstdint>
#include <format>
#include <cstdlib>
#include <exception>
#include <format>
#include <qabstractitemview.h>
#include <qboxlayout.h>
#include <qcoreevent.h>
#include <qlabel.h>
#include <qlayoutitem.h>
#include <qlistview.h>
#include <qnamespace.h>
#include <qscrollarea.h>
#include <qstandarditemmodel.h>
#include <qstringlistmodel.h>
#include <qtoolbutton.h>
#include <Qt>
#include <random>
#include <qwidget.h>
#include <QStandardItemModel>
#include <QDropEvent>
#include <QDrag>
#include <iostream>
#include <QStyledItemDelegate>
#include <QPainter>

#include "trainWindow.h"
#include "parser.h"

enum ElementState {
	NEUTRAL = 0,
	CORRECT,
	INCORRECT
};

const auto listStyle = 
	"QListView {"
	"   background-color: white;"
	"   border: 0px solid black;"
	"}"
	"QListView::item::first {"
	"   padding: 5px 0 0 0;"
	"}"
	"QListView::item {"
	"   color: black;"
	"   padding: 5px;"
	"   background-color: none;"
	"}"
	"QListView::item:selected {"
	"   background-color: transparent;"
	"	padding-left: 0px;"
	"	margin-left: 0px;"
	"}"
	"QListView::item:hover {"
	"   background-color: lightyellow;"
	"	padding-left: 0px;"
	"	margin-left: 0px;"
	"}";

class CustomItemDelegate : public QStyledItemDelegate {
public:
    CustomItemDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent) {}

    // Override the paint method to apply custom styling
    void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override {
	    painter->save(); 
		Qt::GlobalColor color;
		auto colorIdx = index.data(Qt::UserRole + 1).toInt() ;
		switch (colorIdx) {
			case NEUTRAL:
				color = Qt::lightGray;
				break;
			case INCORRECT:
				color = Qt::red;
				break;
			case CORRECT:
				color = Qt::green;
				break;
		}
		painter->setOpacity(0.5);
		if (color) {
			painter->fillRect(option.rect, color);
		}
	    painter->restore();  
		QStyledItemDelegate::paint(painter, option, index);
    }
};

class MultiChoiceListView : public QListView
{
    Q_OBJECT

public:
    explicit MultiChoiceListView(QWidget *parent = nullptr) : QListView(parent) {
		setStyleSheet(listStyle);
		setAcceptDrops(true);
		setEditTriggers(QAbstractItemView::NoEditTriggers);
		setFocusPolicy(Qt::NoFocus);
		setSelectionMode(QAbstractItemView::NoSelection);
	}
};

class OrderListView : public QListView
{
    Q_OBJECT

public:
    explicit OrderListView(QWidget *parent = nullptr) : QListView(parent) {
		setStyleSheet(listStyle);
		setAcceptDrops(true);
		setFocusPolicy(Qt::NoFocus);
		setEditTriggers(QAbstractItemView::NoEditTriggers);
		setDragDropMode(QAbstractItemView::InternalMove);
	}

protected:
    void dropEvent(QDropEvent *event) override
    {
        QModelIndex sourceIndex = currentIndex();
        QModelIndex targetIndex = indexAt(event->pos());

        if (sourceIndex.isValid() && targetIndex.isValid()) {
            QStandardItemModel *model = qobject_cast<QStandardItemModel *>(this->model());
            if (model) {
                QVariant sourceData = model->data(sourceIndex);
                QVariant targetData = model->data(targetIndex);
                model->setData(sourceIndex, targetData);
                model->setData(targetIndex, sourceData);
                event->ignore();
            }
        } else {
			event->ignore();
        }
    }
};

class MoveListView : public QListView
{
    Q_OBJECT

public:
    explicit MoveListView(QWidget *parent = nullptr) : QListView(parent) {
		setStyleSheet(listStyle);
		setAcceptDrops(true);
		setDragDropMode(QAbstractItemView::DragDrop);
		setDefaultDropAction(Qt::MoveAction);
		setEditTriggers(QAbstractItemView::NoEditTriggers);
		setFocusPolicy(Qt::NoFocus);
	}

protected:
    void dragEnterEvent(QDragEnterEvent *event) override {
        event->acceptProposedAction();
    }

    void dragMoveEvent(QDragMoveEvent *event) override {
        event->acceptProposedAction();
    }

    void dropEvent(QDropEvent *event) override {
		QListView *source = qobject_cast<QListView *>(event->source());
        QStandardItemModel *sourceModel = qobject_cast<QStandardItemModel *>(source->model());
        QStandardItemModel *targetModel = qobject_cast<QStandardItemModel *>(this->model());

        if (source != this) {
            QList<QStandardItem *> items = sourceModel->takeRow(source->currentIndex().row());
            targetModel->appendRow(items);
        }

        event->ignore();
    }
};

#include "trainWindow.moc"

struct GroupView {
	QWidget            widget;
	QVBoxLayout        vWidget;
	QLabel             label;
	QScrollArea        itemScroll;
	QStandardItemModel itemModel;
	MoveListView       itemList;
};


// Main components
QMainWindow *trainWindow;
QWidget     *trainWidget;
QVBoxLayout *vTrainWidget;
CustomItemDelegate *itemDelegate;

// Question
QWidget     *questionBox;	
QVBoxLayout *vQuestionBox;
QLabel      *lQuestionText;

// Bottom buttons
QVBoxLayout *vButtonBox;
QWidget     *actionButtons;
QHBoxLayout *hButtons;
QToolButton *btnPrev;
QSpacerItem *leftSpacer;
QToolButton *btnTriggerAnswer;
QSpacerItem *rightSpacer;
QToolButton *btnNext;

// Answer question
QLabel      *answerText;

// Multi-choice question
QStandardItemModel *multiChoiceModel;
QListView          *multiChoiceList;

// Order question
OrderListView      *orderList;
QStandardItemModel *orderModel;

// Group question
QStandardItemModel               *groupItemModel;
QListView                        *groupItemList;
QWidget                          *wGroupQuestion;
QVBoxLayout                      *vGroups;
std::vector<QStandardItemModel*>  groupModels;

// Questions & State
std::vector<Question*>  trainQuestions = std::vector<Question*>();
int32_t                 currentQuestionIndex          = -1;
std::vector<GroupView*> groupViews;

std::default_random_engine  rng;
std::vector<QStandardItem*> itemPool;

/*QStandardItem *makeItem(std::string content, bool isCheckable) {*/
/*	auto *item = new QStandardItem(); */
/*	item->setText(QString::fromStdString(content));*/
/*	item->setCheckable(isCheckable);*/
/*	return item;*/
/*};*/

#define ITEM_POOL_CHUNK 200

QStandardItem* acquireItem() {
	if (itemPool.size() <= 0) {
		for (int i = 0; i < ITEM_POOL_CHUNK; ++i) {
			itemPool.push_back(new QStandardItem);
		}
	}
	auto item = itemPool.back();
	itemPool.pop_back();
	return item;
}

void releaseItem(QStandardItem** item) {
	itemPool.push_back(*item);
	(*item) = nullptr;
}

void releaseAllItems() {
	std::cout <<
		std::format("Starting release, currently in the pool: {}", itemPool.size()) <<
		std::endl;
	auto releaseAllInModel = [](QStandardItemModel *model) {
		auto itemCount = model->rowCount();
		for (int i = 0; i < itemCount; ++i) {
			auto item = model->item(0);
			model->takeRow(item->row());
			releaseItem(&item);
		}
	};
	releaseAllInModel(multiChoiceModel);
	releaseAllInModel(groupItemModel);
	releaseAllInModel(orderModel);
	for (auto groupView: groupViews) {
		releaseAllInModel(&groupView->itemModel);
	}
	std::cout << std::format("All items released, currently in the pool: {}", itemPool.size()) << std::endl;
}

void hideQuestionElements() {
	lQuestionText->hide();
	answerText->hide();
	orderList->hide();
	multiChoiceList->hide();
	wGroupQuestion->hide();
}

void setupAnswerQuestion(MultiElementQuestion *question) {
	lQuestionText->setText(
		QString::fromStdString(question->QuestionText)
	);
	lQuestionText->show();
	auto ss = std::stringstream();
	for (auto answerEl: question->Choices) {
		ss << std::format("- {}", answerEl.Answer) << std::endl;
	}
	answerText->setText(
		QString::fromStdString(ss.str())
	);
	if (answerText->isVisible()) {
		answerText->hide();
	}	
	QObject::connect(
		btnTriggerAnswer,
		&QToolButton::clicked,
		[](bool checked) {
			answerText->show();
			btnTriggerAnswer->hide();
		}
	);		
	btnTriggerAnswer->show();
}

void setupOrderQuestion(MultiElementQuestion *question) {
	lQuestionText->setText(
		QString::fromStdString(question->QuestionText)
	);
	lQuestionText->show();
	orderModel->clear();
	auto shuffledAnswers = question->Choices;
	std::shuffle(shuffledAnswers.begin(), shuffledAnswers.end(), rng);
	for (auto answerEl: shuffledAnswers) {
		auto *item = acquireItem();
		item->setData(NEUTRAL, Qt::UserRole + 1);
		item->setData(QVariant(), Qt::CheckStateRole); 
		item->setCheckable(false);
		item->setText(QString::fromStdString(answerEl.Answer));
		orderModel->appendRow(item);
	}
	orderList->show();
	QObject::connect(
		btnTriggerAnswer,
		&QToolButton::clicked,
		[question](bool checked) {
			for (int i = 0; i < orderModel->rowCount(); ++i) {
				auto item = orderModel->item(i, 0);
				auto text = item->text();
				auto isCorrect = question->Choices[i].Answer.compare(text.toStdString()) == 0;
				if (isCorrect) {
					item->setData(CORRECT, Qt::UserRole + 1);
				} else {
					item->setData(INCORRECT, Qt::UserRole + 1);
				}
				orderList->update();
			}
			btnTriggerAnswer->hide();
		}
	);		
	btnTriggerAnswer->show();
}

void setupMultiChoiceQuestion(MultiElementQuestion *question) {
	lQuestionText->setText(
		QString::fromStdString(question->QuestionText)
	);
	lQuestionText->show();

	multiChoiceModel->clear();
	for (auto answerEl: question->Choices) {
		auto *item = acquireItem();
		item->setText(QString::fromStdString(answerEl.Answer));
		item->setData(NEUTRAL, Qt::UserRole + 1);
		item->setCheckable(true);
		item->setCheckState(Qt::CheckState::Unchecked);
		multiChoiceModel->appendRow(item);
	}
	multiChoiceList->show();

	QObject::connect(
		btnTriggerAnswer,
		&QToolButton::clicked,
		[question](bool checked) {
			for (int i = 0; i < multiChoiceModel->rowCount(); ++i) {
				auto item = multiChoiceModel->item(i, 0);
				auto isCorrect = question->Choices[i].IsCorrect == (item->checkState() == Qt::Checked);
				if (isCorrect) {
					item->setData(CORRECT, Qt::UserRole + 1);
				} else {
					item->setData(INCORRECT, Qt::UserRole + 1);
				}
				multiChoiceList->update();
			}
			btnTriggerAnswer->hide();
		}
	);		
	btnTriggerAnswer->show();
}

void setupGroupQuestion(GroupQuestion *question) {
	auto groupSpacer = new QSpacerItem(
		50, 50,
		QSizePolicy::Minimum,
		QSizePolicy::Expanding
	);

	lQuestionText->setText(
		QString::fromStdString(question->QuestionText)
	);
	lQuestionText->show();
	wGroupQuestion->show();

	for (auto group: question->Groups) {
		for (auto itemText: group.elements) {
			auto *qItem = acquireItem();
			qItem->setData(NEUTRAL, Qt::UserRole + 1);
			qItem->setCheckable(false);
			qItem->setData(QVariant(), Qt::CheckStateRole); 
			qItem->setText(QString::fromStdString(itemText));
			groupItemModel->appendRow(qItem);
		}
	}
	groupItemList->update();

	auto makeGroup = []() {
		auto groupView = new GroupView;
		vGroups->addWidget(&groupView->label);
		vGroups->addWidget(&groupView->itemList);
		groupView->itemList.setModel(&groupView->itemModel);
		groupView->itemList.setMaximumHeight(100);
		groupView->itemList.setItemDelegate(itemDelegate);
		/*groupView->itemScroll.setWidget(&groupView->itemScroll);*/
		groupView->widget.setLayout(&groupView->vWidget);
		groupView->widget.layout()->addWidget(&groupView->label);
		groupView->widget.layout()->addWidget(&groupView->itemList);
		return groupView;
	};

	for (int k = 0; k < groupViews.size(); k++) {
		groupViews[k]->widget.hide();	
	}
	for (int i = 0; i < question->Groups.size(); i++) {
		GroupView *groupView;
		if (i < groupViews.size()) {
			groupView = groupViews[i];
		} else {
			groupView = makeGroup();
			groupViews.push_back(groupView);
		}
		groupView->label.setText(
			QString::fromStdString(question->Groups[i].name)
		);
		vGroups->addWidget(&groupView->widget);
		groupView->widget.show();
	}
	vGroups->addItem(groupSpacer);

	QObject::connect(
		btnTriggerAnswer,
		&QToolButton::clicked,
		[question](bool checked) {
			for (int i = 0; i < groupItemModel->rowCount(); ++i) {
				auto item = groupItemModel->item(i, 0);
				item->setData(INCORRECT, Qt::UserRole + 1);
			}
			for (int i = 0; i < groupViews.size(); i++) {
				auto groupView = groupViews[i];
				auto group = question->Groups[i];
				for (int j = 0; j < groupView->itemModel.rowCount(); ++j) {
					auto item = groupView->itemModel.item(j, 0);
					auto itemText = item->text().toStdString();
					bool found = false;
					for (int k = 0; k < group.elements.size(); ++k) {
						if (group.elements[k].compare(itemText) == 0) {
							found = true;
							break;
						}
					}
					if (found) {
						item->setData(CORRECT, Qt::UserRole + 1);
					} else {
						item->setData(INCORRECT, Qt::UserRole + 1);
					}
				} 
				groupView->itemList.update();
			}
			btnTriggerAnswer->hide();
		}
	);		
	btnTriggerAnswer->show();
}

void setupQuestion(Question *question) {
	hideQuestionElements();
	releaseAllItems();
	QObject::disconnect(btnTriggerAnswer, 0, 0, 0);		
	if (auto *question = dynamic_cast<MultiElementQuestion*>(trainQuestions[currentQuestionIndex])) {
		switch (question->type) {
			case MultiElementType::Order:
				setupOrderQuestion(question);
				break;
			case MultiElementType::MultiChoice:
				setupMultiChoiceQuestion(question);
				break;
			case MultiElementType::Regular:
				setupAnswerQuestion(question);
				break;
		} 
	} else if (auto *question = dynamic_cast<GroupQuestion*>(trainQuestions[currentQuestionIndex])) {
		setupGroupQuestion(question);
	}
}

void updatePaginationVisibility() {
	if (currentQuestionIndex == 0) {
		btnPrev->hide();
	} else {
		btnPrev->show();
	}
	if (currentQuestionIndex == trainQuestions.size() - 1) {
		btnNext->hide();
	} else {
		btnNext->show();
	}
}

void setQuestions(std::vector<Question*> questions) {
	trainQuestions = questions;
	if (questions.size() <= 0) {
		return;
	}
	currentQuestionIndex = 0;
	updatePaginationVisibility();
	setupQuestion(trainQuestions[currentQuestionIndex]);
}

void initTrainWindow() {
    unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
    rng = std::default_random_engine(seed);

    trainWindow  = new QMainWindow();
	trainWidget  = new QWidget();
	vTrainWidget = new QVBoxLayout();

	trainWidget->setLayout(vTrainWidget);
    trainWindow->setCentralWidget(trainWidget);
	trainWidget->setLayout(vTrainWidget);
	vTrainWidget->setAlignment(Qt::AlignCenter);
	trainWidget->setObjectName("answer-question-widget");

	itemDelegate = new CustomItemDelegate(multiChoiceList);

	{ // Make the question box.
		questionBox   = new QWidget();
		vQuestionBox  = new QVBoxLayout();
		questionBox->setLayout(vQuestionBox);
		questionBox->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding);
		vQuestionBox->setAlignment(Qt::AlignCenter);
	}

	{ // Make question text. 
		lQuestionText = new QLabel();
		lQuestionText->setText("What is the capital of Latvia?");
		lQuestionText->setWordWrap(true);
		lQuestionText->setStyleSheet(QString(
			"QLabel {"
				"font-size: 20px;"
			"}"
		));
		lQuestionText->setTextInteractionFlags(Qt::TextSelectableByMouse);
		vQuestionBox->addWidget(lQuestionText);
		lQuestionText->hide();
	}

	{ // Make multi-choice list.
		multiChoiceModel = new QStandardItemModel();
		multiChoiceList = new MultiChoiceListView();
		multiChoiceList->setModel(multiChoiceModel);
		multiChoiceList->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
		questionBox->layout()->addWidget(multiChoiceList);
		multiChoiceList->hide();
		multiChoiceList->setItemDelegate(itemDelegate);
	}

	{ // Make answer text.
		answerText = new QLabel();
		answerText->setText("- Riga\n- Second line");
		answerText->setWordWrap(true);
		answerText->setStyleSheet(QString(
			"QLabel {"
				"font-size: 15px;"
			"}"
		));
		answerText->setTextInteractionFlags(Qt::TextSelectableByMouse);
		vQuestionBox->addWidget(answerText);
		answerText->hide();
	}

	{ // Make order list.
		orderModel = new QStandardItemModel();
		orderList  = new OrderListView();
		orderList->setModel(orderModel);
		orderList->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
		orderList->setItemDelegate(itemDelegate);

		// Connect to handle the drop event properly
		QObject::connect(
			 orderModel,
			 &QStandardItemModel::rowsMoved,
			 [](const QModelIndex &, int sourceRow, int, const QModelIndex &, int destinationRow) {
			     if (sourceRow != destinationRow - 1) {
					 auto *movedItem = orderModel->takeItem(sourceRow);
					 orderModel->insertRow(destinationRow > sourceRow ? destinationRow - 1 : destinationRow, movedItem);
				 }
			}
		);

		questionBox->layout()->addWidget(orderList);
		orderList->hide();
	}

	{ // Make buttons menu. There are left middle and right buttons.
		vButtonBox       = new QVBoxLayout();
		actionButtons    = new QWidget();
		hButtons         = new QHBoxLayout();
		btnPrev          = new QToolButton();
		leftSpacer       = new QSpacerItem(50, 50, QSizePolicy::Expanding, QSizePolicy::Minimum);
		btnTriggerAnswer = new QToolButton();
		rightSpacer      = new QSpacerItem(50, 50, QSizePolicy::Expanding, QSizePolicy::Minimum);
		btnNext          = new QToolButton();

		hButtons->addWidget(btnPrev);
		hButtons->addItem(leftSpacer);
		hButtons->addWidget(btnTriggerAnswer);
		hButtons->addItem(rightSpacer);
		hButtons->addWidget(btnNext);
		vButtonBox->addWidget(actionButtons);
		actionButtons->setLayout(hButtons);
		btnPrev->setText("Previous");
		QObject::connect(btnPrev, &QToolButton::clicked, []() {
			currentQuestionIndex--;
			if (currentQuestionIndex > -1) {
				setupQuestion(trainQuestions[currentQuestionIndex]);
			} 
			updatePaginationVisibility();
		});

		btnTriggerAnswer->setText("Show answer");

		btnNext->setText("Next");
		QObject::connect(btnNext, &QToolButton::clicked, []() {
			currentQuestionIndex++;
			if (currentQuestionIndex < trainQuestions.size()) {
				setupQuestion(trainQuestions[currentQuestionIndex]);
			}
			updatePaginationVisibility();
		});
		questionBox->setObjectName("question-box");
		actionButtons->setStyleSheet(QString(
			"QToolButton {"
				"padding: 4px 5px;"
				"font-size: 15px;"
			"}"
		));
	}

	{ // Make group question view. 
		wGroupQuestion = new QWidget();
		wGroupQuestion->setStyleSheet(
			"border:none;"
		);
		auto hGroupQuestion = new QHBoxLayout();
		wGroupQuestion->setLayout(hGroupQuestion);
		

		// Items on the left.
		auto itemScroll = new QScrollArea();
		auto vItems     = new QVBoxLayout();
		itemScroll->setLayout(vItems);
		groupItemModel    = new QStandardItemModel();
		groupItemList = new MoveListView();
		groupItemList->setItemDelegate(itemDelegate);
		groupItemList->setModel(groupItemModel);
		vItems->addWidget(groupItemList);
		hGroupQuestion->addWidget(itemScroll);

		// Items on the right.
		auto groupScroll = new QScrollArea();
		auto wGroupContainer = new QWidget();
		vGroups = new QVBoxLayout();
		groupScroll->setWidget(wGroupContainer);
		groupScroll->setWidgetResizable(true);
		wGroupContainer->setLayout(vGroups);
		hGroupQuestion->addWidget(groupScroll);


		wGroupQuestion->setStyleSheet("padding: 0; margin: 0;");
		wGroupQuestion->hide();
		questionBox->layout()->addWidget(wGroupQuestion);
	}

	vTrainWidget->addWidget(questionBox);
	vTrainWidget->addWidget(actionButtons);
}

