/*
rkdebugconsole - This file is part of RKWard (https://rkward.kde.org). Created: Wed Oct 19 2011
SPDX-FileCopyrightText: 2011-2022 by Thomas Friedrichsmeier <thomas.friedrichsmeier@kdemail.net>
SPDX-FileContributor: The RKWard Team <rkward@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/

#include "rkdebugconsole.h"

#include <QHBoxLayout>
#include <QLabel>
#include <QPushButton>
#include <QTextEdit>
#include <QVBoxLayout>

#include <KLocalizedString>
#include <khistorycombobox.h>
#include <kmessagebox.h>

#include "../agents/rkdebughandler.h"
#include "../misc/rkcommonfunctions.h"
#include "../misc/rkdummypart.h"
#include "../rkconsole.h"

#include "../debug.h"

RKDebugConsole *RKDebugConsole::_instance = nullptr;

RKDebugConsole::RKDebugConsole(QWidget *parent, bool tool_window, const char *name) : RKMDIWindow(parent, DebugConsoleWindow, tool_window, name) {
	RK_TRACE(APP);

	QVBoxLayout *main_layout = new QVBoxLayout(this);
	main_layout->setContentsMargins(0, 0, 0, 0);
	QHBoxLayout *upper_layout = new QHBoxLayout();
	main_layout->addLayout(upper_layout);

	context_view = new QTextEdit(this);
	context_view->setReadOnly(true);
	context_view->setAcceptRichText(false);
	upper_layout->addWidget(context_view);

	QVBoxLayout *button_layout = new QVBoxLayout();
	upper_layout->addLayout(button_layout);
	button_layout->addWidget(addButton(QStringLiteral("n\n"), i18n("Next (step over)"), i18n("Evaluate the next statement, stepping over function calls.")));
	button_layout->addWidget(addButton(QStringLiteral("s\n"), i18n("Next (step into)"), i18n("Evaluate the next statement, stepping into function calls.")));
	button_layout->addWidget(addButton(QStringLiteral("browserSetDebug(1)\ncont\n"), i18n("Step out"), i18n("<p>Continue until the caller of this function is reached (unless another debug statement is hit, earlier)</p>"
	                                                                                                        "<p><b>Note:</b> In some cases, the calling function will never be reached, because the call was the last step in the caller. "
	                                                                                                        "In these cases, the behavior is identical to 'Continue'.</p>")));
	button_layout->addWidget(addButton(QStringLiteral("f\n"), i18n("Finish current"), i18n("Finish current loop or function.")));
	button_layout->addWidget(addButton(QStringLiteral("cont\n"), i18n("Continue"), i18n("Continue evaluation.")));
	button_layout->addStretch();
	button_layout->addWidget(addButton(QStringLiteral("Q\n"), i18n("Cancel"), i18n("Exit debugger and return to top-level statement.")));
	button_layout->addStretch();

	QHBoxLayout *lower_layout = new QHBoxLayout();
	main_layout->addLayout(lower_layout);

	prompt_label = new QLabel(this);
	lower_layout->addWidget(prompt_label);
	reply_edit = new KHistoryComboBox(this);
	reply_edit->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum);
	connect(reply_edit, static_cast<void (KHistoryComboBox::*)(const QString &)>(&KHistoryComboBox::returnPressed), this, &RKDebugConsole::sendReplySlot);
	lower_layout->addWidget(reply_edit);
	setFocusProxy(reply_edit);

	setFocusPolicy(Qt::StrongFocus);

	setPart(new RKDummyPart(this, this));
	initializeActivationSignals();

	connect(RKDebugHandler::instance(), &RKDebugHandler::newDebugState, this, &RKDebugConsole::newDebugState);
	newDebugState();
}

RKDebugConsole::~RKDebugConsole() {
	RK_TRACE(APP);
}

QPushButton *RKDebugConsole::addButton(const QString &command, const QString &text, const QString &tip) {
	QPushButton *button = new QPushButton(text);
	connect(button, &QPushButton::clicked, this, [this, command]() { this->sendReply(command); });
	buttons.append(button);
	RKCommonFunctions::setTips(tip, button);
	return button;
}

void RKDebugConsole::newDebugState() {
	RK_TRACE(APP);

	bool enable = true;
	if (RKDebugHandler::instance()->state() == RKDebugHandler::NotInDebugger) {
		context_view->setPlainText(i18n("Not in a debugger context"));
		setEnabled(false);
		return;
	} else if (RKDebugHandler::instance()->state() == RKDebugHandler::InDebugRun) {
		enable = false;
		reply_edit->setEnabled(false);
	} else {
		context_view->setPlainText(RKDebugHandler::instance()->outputContext());
		prompt_label->setText(RKDebugHandler::instance()->debugPrompt());
		reply_edit->setEnabled(true); // must come before focus
		QStringList ch = RKConsole::mainConsole()->commandHistory();
		QStringList ch_rev; // limit to 100 items (dropdown list!), and reverse to have most recent item first.
		for (int i = ch.size() - 1; i >= qMax(0, ch.size() - 101); --i) {
			ch_rev.append(ch[i]);
		}
		reply_edit->setMaxCount(ch_rev.size());
		reply_edit->setHistoryItems(ch_rev);
		activate(true);
	}

	setEnabled(true);
	for (int i = 0; i < buttons.size(); ++i)
		buttons[i]->setEnabled(enable);
}

void RKDebugConsole::sendReplySlot() {
	RK_TRACE(APP);

	QString reply = reply_edit->currentText();
	sendReply(reply);
	RKConsole::mainConsole()->addCommandToHistory(reply);
	reply_edit->clear();
}

void RKDebugConsole::sendReply(const QString &reply) {
	RK_TRACE(APP);

	RKDebugHandler::instance()->submitDebugString(reply);
}

bool RKDebugConsole::close(CloseWindowMode ask_save) {
	RK_TRACE(APP);

	if (RKDebugHandler::instance()->state() != RKDebugHandler::NotInDebugger) {
		KMessageBox::error(this, i18n("This window cannot be closed, while a debugger is active. If you have no idea what this means, and you want to get out, press the 'Cancel' button on the right hand side of this window."));
		return false;
	}
	return RKMDIWindow::close(ask_save);
}
