#include "BackupDialog.h"

#include "BackupFormatModel.h"
#include "PgDumpOutputHighlighter.h"
#include <QFileDialog>
#include <QMessageBox>
#include <QProcess>
#include <QScrollBar>
#include <QStandardPaths>

#include <QApplication>
#include <QCheckBox>
#include <QComboBox>
#include <QDialog>
#include <QFormLayout>
#include <QHBoxLayout>
#include <QInputDialog>
#include <QLabel>
#include <QLineEdit>
#include <QPlainTextEdit>
#include <QPushButton>
#include <QSettings>
#include <QSpinBox>
#include <QStackedLayout>
#include <QTabWidget>
#include <QVBoxLayout>
#include <QWidget>


#ifdef WIN32
# include <windows.h> // for CreateProcess flags
#endif

QPlainTextEdit* createStdOutput(QWidget *parent)
{
	auto stdOutput = new QPlainTextEdit(parent);
	stdOutput->setObjectName(QString::fromUtf8("stdOutput"));
	QPalette palette;
	QBrush brush(QColor(255, 255, 255, 255));
	brush.setStyle(Qt::SolidPattern);
	palette.setBrush(QPalette::Active, QPalette::Text, brush);
	QBrush brush1(QColor(0, 0, 0, 255));
	brush1.setStyle(Qt::SolidPattern);
	palette.setBrush(QPalette::Active, QPalette::Base, brush1);
	palette.setBrush(QPalette::Inactive, QPalette::Text, brush);
	palette.setBrush(QPalette::Inactive, QPalette::Base, brush1);
	QBrush brush2(QColor(120, 120, 120, 255));
	brush2.setStyle(Qt::SolidPattern);
	palette.setBrush(QPalette::Disabled, QPalette::Text, brush2);
	QBrush brush3(QColor(240, 240, 240, 255));
	brush3.setStyle(Qt::SolidPattern);
	palette.setBrush(QPalette::Disabled, QPalette::Base, brush3);
	stdOutput->setPalette(palette);
	QFont font;
	font.setFamily(QString::fromUtf8("Source Code Pro"));
	font.setPointSize(10);
	stdOutput->setFont(font);
	stdOutput->setLineWrapMode(QPlainTextEdit::NoWrap);
	stdOutput->setReadOnly(true);

	new PgDumpOutputHighlighter(stdOutput->document());


	return stdOutput;
}

BackupDialog::BackupDialog(QWidget *parent)
	: QDialog(parent)
{
	// Options tab contents
	// Filename
	labelFileName = new QLabel(this);
	labelFileName->setObjectName(QString::fromUtf8("labelFileName"));

	// Filename edit+button
	editFilename = new QLineEdit(this);
	editFilename->setObjectName(QString::fromUtf8("editFilename"));

	selectDestination = new QPushButton;
	selectDestination->setObjectName(QString::fromUtf8("selectDestination"));

	layoutDestination = new QHBoxLayout;
	layoutDestination->setSpacing(0);
	layoutDestination->setObjectName(QString::fromUtf8("layoutDestination"));
	layoutDestination->setContentsMargins(0, 0, 0, 0);
	layoutDestination->addWidget(editFilename);
	layoutDestination->addWidget(selectDestination);

	widgetDestination = new QWidget;
	widgetDestination->setObjectName(QString::fromUtf8("widgetDestination"));
	widgetDestination->setLayout(layoutDestination);

	// Backup format
	labelFormat = new QLabel;
	labelFormat->setObjectName(QString::fromUtf8("labelFormat"));
	backupFormat = new QComboBox;
	backupFormat->setObjectName(QString::fromUtf8("backupFormat"));
	backupFormat->setModelColumn(0);

	// Jobs
	labelJobs = new QLabel;
	labelJobs->setObjectName(QString::fromUtf8("labelJobs"));
	jobs = new QSpinBox;
	jobs->setObjectName(QString::fromUtf8("jobs"));

	// Verbose
	chkbxVerbose = new QCheckBox;
	chkbxVerbose->setObjectName(QString::fromUtf8("chkbxVerbose"));

	// Compression
	labelCompression = new QLabel;
	labelCompression->setObjectName(QString::fromUtf8("labelCompression"));
	compression = new QSpinBox;
	compression->setObjectName(QString::fromUtf8("compression"));
	compression->setMinimum(-1);
	compression->setMaximum(9);
	compression->setValue(-1);

	// Include BLOBs
	chkbxIncludeBlobs = new QCheckBox(this);
	chkbxIncludeBlobs->setObjectName(QString::fromUtf8("chkbxIncludeBlobs"));

	// Clean
	chkbxClean = new QCheckBox(this);
	chkbxClean->setObjectName(QString::fromUtf8("chkbxClean"));

	// Data and/or Schema
	labelDataOrSchema = new QLabel(this);
	labelDataOrSchema->setObjectName(QString::fromUtf8("labelDataOrSchema"));
	what = new QComboBox(this);
	what->addItem(QString());
	what->addItem(QString());
	what->addItem(QString());
	what->setObjectName(QString::fromUtf8("what"));
	// create
	chkbxCreate = new QCheckBox(this);
	chkbxCreate->setObjectName(QString::fromUtf8("chkbxCreate"));
	// owner
	noOwner = new QCheckBox(this);
	noOwner->setObjectName(QString::fromUtf8("noOwner"));
	// with oids
	oids = new QCheckBox(this);
	oids->setObjectName(QString::fromUtf8("oids"));
	// without acl (permissions)
	noAcl = new QCheckBox(this);
	noAcl->setObjectName(QString::fromUtf8("noAcl"));


	// Options layout
	auto formLayout = new QFormLayout;
	formLayout->setObjectName(QString::fromUtf8("formLayout"));
	int form_row = 0;
	formLayout->setWidget(form_row, QFormLayout::LabelRole, labelFileName);
	formLayout->setWidget(form_row, QFormLayout::FieldRole, widgetDestination);
	++form_row;
	formLayout->setWidget(form_row, QFormLayout::LabelRole, labelFormat);
	formLayout->setWidget(form_row, QFormLayout::FieldRole, backupFormat);
	++form_row;
	formLayout->setWidget(form_row, QFormLayout::LabelRole, labelJobs);
	formLayout->setWidget(form_row, QFormLayout::FieldRole, jobs);
	++form_row;
	formLayout->setWidget(form_row, QFormLayout::FieldRole, chkbxVerbose);
	++form_row;
	formLayout->setWidget(form_row, QFormLayout::LabelRole, labelCompression);
	formLayout->setWidget(form_row, QFormLayout::FieldRole, compression);
	++form_row;
	formLayout->setWidget(form_row, QFormLayout::FieldRole, what);
	formLayout->setWidget(form_row, QFormLayout::LabelRole, labelDataOrSchema);
	++form_row;
	formLayout->setWidget(form_row, QFormLayout::FieldRole, chkbxIncludeBlobs);
	++form_row;
	formLayout->setWidget(form_row, QFormLayout::FieldRole, chkbxClean);
	++form_row;
	formLayout->setWidget(form_row, QFormLayout::FieldRole, chkbxCreate);
	++form_row;
	formLayout->setWidget(form_row, QFormLayout::FieldRole, noOwner);
	++form_row;
	formLayout->setWidget(form_row, QFormLayout::FieldRole, oids);
	++form_row;
	formLayout->setWidget(form_row, QFormLayout::FieldRole, noAcl);

	btnStart = new QPushButton;
	btnStart->setObjectName(QString::fromUtf8("btnStart"));

	auto verticalLayout = new QVBoxLayout;
	verticalLayout->setObjectName(QString::fromUtf8("verticalLayout"));
	verticalLayout->addLayout(formLayout);
	verticalLayout->addWidget(btnStart);

	optionsView = new QWidget();
	optionsView->setObjectName(QString::fromUtf8("tabOptions"));
	optionsView->setLayout(verticalLayout);

	// Progress tab
	stdOutput = createStdOutput(this);

	m_OutputOkFormat.setForeground(Qt::white);
	m_OutputOkFormat.setBackground(QBrush(Qt::darkGreen));
	m_OutputOkFormat.setFontWeight(QFont::Bold);
	m_OutputErrorFormat.setForeground(Qt::white);
	m_OutputErrorFormat.setBackground(QBrush(Qt::darkRed));
	m_OutputErrorFormat.setFontWeight(QFont::Bold);

	btnBack = new QPushButton;
	btnBack->setObjectName("btnBack");
	btnClose = new QPushButton;
	btnClose->setObjectName("btnClose");

	auto layoutButtons = new QHBoxLayout;
	layoutButtons->addWidget(btnBack);
	layoutButtons->addWidget(btnClose);

	auto progressMainLayout = new QVBoxLayout;
	progressMainLayout->setObjectName(QString::fromUtf8("progressMainLayout"));
	progressMainLayout->addWidget(stdOutput);
	progressMainLayout->addLayout(layoutButtons);

	progressView = new QWidget;
	progressView->setObjectName(QString::fromUtf8("progressTab"));
	progressView->setLayout(progressMainLayout);


	viewStack = new QStackedLayout;
	viewStack->setObjectName(QString::fromUtf8("viewStack"));
	viewStack->addWidget(optionsView);
	viewStack->addWidget(progressView);
	viewStack->setCurrentWidget(optionsView);

	setLayout(viewStack);

	QMetaObject::connectSlotsByName(this);
	connect(btnClose, &QPushButton::clicked, this, &QDialog::close);

	auto format_model = new BackupFormatModel(this);
	backupFormat->setModel(format_model);

	retranslateUi();
}

BackupDialog::~BackupDialog()
{
}


void BackupDialog::retranslateUi()
{
	setWindowTitle(QApplication::translate("BackupDialog", "Backup database", nullptr));
	labelFileName->setText(QApplication::translate("BackupDialog", "Filename", nullptr));
	selectDestination->setText(QApplication::translate("BackupDialog", "...", nullptr));
	labelFormat->setText(QApplication::translate("BackupDialog", "Format", nullptr));
	labelJobs->setText(QApplication::translate("BackupDialog", "Jobs:", nullptr));
	chkbxVerbose->setText(QApplication::translate("BackupDialog", "Verbose (-v)", nullptr));
	labelCompression->setText(QApplication::translate("BackupDialog", "Compression (-Z)", nullptr));
#ifndef QT_NO_TOOLTIP
	compression->setToolTip(QApplication::translate("BackupDialog", "-1 means default, 0-9 is no compression to max compression", nullptr));
#endif // QT_NO_TOOLTIP
	labelDataOrSchema->setText(QApplication::translate("BackupDialog", "What", nullptr));
	what->setItemText(0, QApplication::translate("BackupDialog", "data + schema", nullptr));
	what->setItemText(1, QApplication::translate("BackupDialog", "data only (-a)", nullptr));
	what->setItemText(2, QApplication::translate("BackupDialog", "schema-only (-s)", nullptr));

	chkbxIncludeBlobs->setText(QApplication::translate("BackupDialog", "Include blobs (-b)", nullptr));
	chkbxClean->setText(QApplication::translate("BackupDialog", "Clean (-c)", nullptr));
	chkbxCreate->setText(QApplication::translate("BackupDialog", "Create (-C)", nullptr));
	noOwner->setText(QApplication::translate("BackupDialog", "No owner (-O)", nullptr));
	oids->setText(QApplication::translate("BackupDialog", "Oids (-o)", nullptr));
	noAcl->setText(QApplication::translate("BackupDialog", "No privileges/acl (-x)", nullptr));
	btnStart->setText(QApplication::translate("BackupDialog", "START", nullptr));
	btnBack->setText(QApplication::translate("BackupDialog", "Back", nullptr));
	btnClose->setText(QApplication::translate("BackupDialog", "Close", nullptr));

} // retranslateUi

void BackupDialog::setConfig(const ConnectionConfig &cfg)
{
	m_config = cfg;
}


void BackupDialog::ConnectTo(QProcess *process)
{
	disconnectCurrentProcess();
	m_process = process;
	if (process) {
		process->setProcessChannelMode(QProcess::MergedChannels);
		process->setReadChannel(QProcess::StandardOutput);

		connect(process,  SIGNAL(readyRead()), this, SLOT(process_readyRead()));
		connect(process,  SIGNAL(errorOccurred(QProcess::ProcessError)), this,
				SLOT(process_errorOccurred(QProcess::ProcessError)));
		connect(process,  SIGNAL(finished(int, QProcess::ExitStatus)), this,
				SLOT(process_finished(int, QProcess::ExitStatus)));
	}
}

void BackupDialog::disconnectCurrentProcess()
{
	if (m_process) {
		disconnect(m_process,  SIGNAL(readyRead()), this, SLOT(process_readyRead()));
		disconnect(m_process,  SIGNAL(errorOccurred(QProcess::ProcessError)), this,
				SLOT(process_errorOccurred(QProcess::ProcessError)));
		disconnect(m_process,  SIGNAL(finished(int, QProcess::ExitStatus)), this,
				SLOT(process_finished(int, QProcess::ExitStatus)));
		m_process = nullptr;
	}

}


void BackupDialog::writeOutput(const QString &s, Format f)
{
	switch (f) {
	case Format::Normal:
		stdOutput->appendPlainText(s);
		break;
	case Format::Error:
		stdOutput->appendHtml("<span style='background-color: maroon; color: white; font-weight: bold'>" + s.toHtmlEscaped() + "</span>");
		break;
	case Format::Success:
		stdOutput->appendHtml("<span style='background-color: green; color: white; font-weight: bold'>" + s.toHtmlEscaped() + "</span>");
		break;
	}

	QScrollBar *bar = stdOutput->verticalScrollBar();
	bar->setValue(bar->maximum());
}

void BackupDialog::process_readyRead()
{
	QByteArray data = m_process->readAllStandardOutput();
	writeOutput(QString::fromUtf8(data));
}

void BackupDialog::process_errorOccurred(QProcess::ProcessError error)
{
	QString msg;
	switch (error) {
	case 0:
		msg = tr("Failed to start. Possibly the executable does not exist or you have insufficient privileges to invoke the program.");
		break;
	case 1:
		msg = tr("Crashed");
		break;
	case 2:
		msg = tr("Timedout");
		break;
	case 3:
		msg = tr("Read error");
		break;
	case 4:
		msg = tr("Write error");
		break;
	case 5:
		msg = tr("Unknown error");
		break;
	default:
		msg = tr("Unexpected error");
	}


//	auto res = QMessageBox::critical(this, "pglab", msg, QMessageBox::Close);
//	if (res == QMessageBox::Yes) {
//		QApplication::quit();
//	}
	writeOutput(msg, Format::Error);
	btnBack->setEnabled(true);
	btnClose->setEnabled(true);
}

void BackupDialog::process_finished(int exitCode, QProcess::ExitStatus)
{
	btnBack->setEnabled(true);
	btnClose->setEnabled(true);
	QString msg = tr("Completed, with exitcode %1\n").arg(exitCode);
	writeOutput(msg, exitCode == 0 ? Format::Success : Format::Error);
}

void BackupDialog::setParams(QStringList &args)
{
	QString short_args("-");
	if (chkbxVerbose->checkState() == Qt::Checked)
		short_args += "v";
	if (chkbxClean->checkState() == Qt::Checked)
		short_args += "c";
	if (chkbxCreate->checkState() == Qt::Checked)
		short_args += "C";
	if (chkbxIncludeBlobs->checkState() == Qt::Checked)
		short_args += "b";
	switch (what->currentIndex()) {
	case 1:
		short_args += "a";
		break;
	case 2:
		short_args += "s";
		break;
	}
	if (oids->checkState() == Qt::Checked)
		short_args += "o";
	if (noAcl->checkState() == Qt::Checked)
		short_args += "x";
	if (short_args.length() > 1) // larger then one because always includes '-'
		args << short_args;

	// Todo check path exists and name is valid?

	QFileInfo fi(QDir::fromNativeSeparators(editFilename->text()));
	QDir dir(fi.absolutePath());
	if (!dir.exists())
		dir.mkdir(fi.absolutePath());

	args << "-f" << editFilename->text(); // R"-(c:\temp\backup.sql)-";

	int format_index = backupFormat->currentIndex();
	auto format_model_base = backupFormat->model();
	auto *bfm = dynamic_cast<BackupFormatModel *>(format_model_base);
	if (bfm) {
		QVariant v = bfm->data(bfm->index(format_index, 1));
		QString format("-F");
		format += v.toString();
		args << format;
	}

	int j = jobs->value();
	if (j > 0)
		args << "-j" << QString::number(j);

	int comp = compression->value();
	if (comp >= 0)
		args << "-Z" << QString::number(comp);
}

void BackupDialog::on_btnStart_clicked()
{
	stdOutput->clear();
	btnBack->setEnabled(false);
	btnClose->setEnabled(false);
	viewStack->setCurrentWidget(progressView);

	QSettings user_settings; // Default constructor is for UserScope
	QVariant var = user_settings.value("pg_tools_path", {});
	QDir dir;
	if (var.type() == QMetaType::QString) {
		dir.setPath(var.toString());
	}
	if (!dir.exists() || !dir.exists("pg_dump.exe")) {
		QString result = QInputDialog::getText(this, tr("Postgres tools location"), tr("Please specify the folder that contains the postgresql tools executables"));
		if (!result.isEmpty()) {
			QDir d(result);
			if (d.exists() && d.exists("pg_dump.exe")) {
				user_settings.setValue("pg_tools_path", result);
				dir = d;
			}
			else {
				return;
			}
		}
	}
	//QString program = R"-(C:\Prog\bigsql\pg96\bin\pg_dump.exe)-";
	QString program = dir.path() + "/pg_dump.exe";
	QStringList arguments;
	setParams(arguments);

	// Database connection paramters are passed through the environment as this is far less visible to others. Commandline
	// parameters can often be viewed even if the user is not the owner of the process.
	// We use the systemEnvironment as a sane default. Then we let the connection overwrite all PG variables in it.
	QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
	m_config.writeToEnvironment(env);
	//env.insert("SESSIONNAME", "Console");
	auto p = new QProcess(this);
	ConnectTo(p);
	p->setProcessEnvironment(env);
#ifdef WIN32
//	p->setCreateProcessArgumentsModifier([] (QProcess::CreateProcessArguments *args)
//		{
//			args->flags |= CREATE_NEW_CONSOLE;
//			args->flags &= ~DETACHED_PROCESS;
//			args->startupInfo->dwFlags &= ~STARTF_USESTDHANDLES;
//		});
#endif
	p->start(program, arguments);
}

void BackupDialog::on_backupFormat_currentIndexChanged(int /*index*/)
{
	int format_index = backupFormat->currentIndex();
	auto format_model_base = backupFormat->model();
	auto *bfm = dynamic_cast<BackupFormatModel *>(format_model_base);
	if (bfm) {
		QVariant v = bfm->data(bfm->index(format_index, 1));
		QString format = v.toString();

		bool comp_enable = (format == "c" || format == "d");
		compression->setEnabled(comp_enable);
		if (!comp_enable)
			compression->setValue(-1);

		bool jobs_enable = (format == "d");
		jobs->setEnabled(jobs_enable);
		if (!jobs_enable)
			jobs->setValue(0);
	}
}

void BackupDialog::on_selectDestination_clicked()
{
	QString home_dir = QStandardPaths::locate(QStandardPaths::HomeLocation, "", QStandardPaths::LocateDirectory);
	QString fn = QFileDialog::getSaveFileName(this, tr("Save backup"), home_dir,
										tr("Backup file (*.backup)"));
	if (!fn.isEmpty()) {
		editFilename->setText(QDir::toNativeSeparators(fn));
	}
}

void BackupDialog::on_btnBack_clicked()
{
	viewStack->setCurrentWidget(optionsView);
}
