#include "pivariant_edit_widgets.h"

#include "evalspinbox.h"
#include "pivaluetree.h"
#include "pivariant_edit_enum.h"
#include "pivarianttypes.h"
#include "scroll_spin_box.h"
#include "spinslider.h"

#include <QEvent>
#include <QFileDialog>
#include <QInputDialog>
#include <QMessageBox>
#include <QToolButton>

REGISTER_PIVARIANTEDITOR(bool, PIVariantEditors::Bool);
REGISTER_PIVARIANTEDITOR(short, PIVariantEditors::Int);
REGISTER_PIVARIANTEDITOR(ushort, PIVariantEditors::Int);
REGISTER_PIVARIANTEDITOR(int, PIVariantEditors::Int);
REGISTER_PIVARIANTEDITOR(uint, PIVariantEditors::Int);
REGISTER_PIVARIANTEDITOR(float, PIVariantEditors::Double);
REGISTER_PIVARIANTEDITOR(double, PIVariantEditors::Double);
REGISTER_PIVARIANTEDITOR(PIString, PIVariantEditors::String);
REGISTER_PIVARIANTEDITOR(PIStringList, PIVariantEditors::StringList);
REGISTER_PIVARIANTEDITOR(PITime, PIVariantEditors::Time);
REGISTER_PIVARIANTEDITOR(PIDate, PIVariantEditors::Date);
REGISTER_PIVARIANTEDITOR(PIDateTime, PIVariantEditors::DateTime);
REGISTER_PIVARIANTEDITOR(PIVariantTypes::Color, PIVariantEditors::Color);
REGISTER_PIVARIANTEDITOR(PIVariantTypes::Enum, PIVariantEditors::Enum);
REGISTER_PIVARIANTEDITOR(PINetworkAddress, PIVariantEditors::NetworkAddress);
REGISTER_PIVARIANTEDITOR(PIVariantTypes::File, PIVariantEditors::File);
REGISTER_PIVARIANTEDITOR(PIVariantTypes::Dir, PIVariantEditors::Dir);

using Attribute = PIValueTree::Attribute;


// PIVariantEditors::NumberBase

PIVariantEditors::NumberBase::NumberBase() {}


void PIVariantEditors::NumberBase::setValueNumeric(double v) {
	switch (type) {
	case tSpinBox: {
		auto * w = qobject_cast<QDoubleSpinBox *>(widget);
		if (w) w->setValue(v);
	} break;
	case tSlider: {
		auto * w = qobject_cast<QSlider *>(widget);
		if (w) w->setValue(v);
	} break;
	case tSpinSlider: {
		auto * w = qobject_cast<SpinSlider *>(widget);
		if (w) w->setValue(v);
	} break;
	case tEvalSpinBox: {
		auto * w = qobject_cast<EvalSpinBox *>(widget);
		if (w) {
			PIString vs = PIString::fromNumber(v), es = PIString::fromNumber(w->value());
			if (vs != es) w->setValue(v);
		}
	} break;
	case tScrollSpinBox: {
		auto * w = qobject_cast<ScrollSpinBox *>(widget);
		if (w) w->setValue(v);
	} break;
	default: break;
	}
}


double PIVariantEditors::NumberBase::valueNumeric() const {
	switch (type) {
	case tSpinBox: {
		auto * w = qobject_cast<QDoubleSpinBox *>(widget);
		if (w) return w->value();
	} break;
	case tSlider: {
		auto * w = qobject_cast<QSlider *>(widget);
		if (w) return w->value();
	} break;
	case tSpinSlider: {
		auto * w = qobject_cast<SpinSlider *>(widget);
		if (w) return w->value();
	} break;
	case tEvalSpinBox: {
		auto * w = qobject_cast<EvalSpinBox *>(widget);
		if (w) return w->value();
	} break;
	case tScrollSpinBox: {
		auto * w = qobject_cast<ScrollSpinBox *>(widget);
		if (w) return w->value();
	} break;
	default: break;
	}
	return 0.;
}


PIVariantMap PIVariantEditors::NumberBase::attributes() const {
	auto etype = createTypes();
	etype.selectValue(type);
	PIVariantMap ret = {
		{Attribute::widgetType, etype },
		{Attribute::prefix,     prefix},
		{Attribute::suffix,     suffix},
	};
	switch (type) {
	case tSpinBox: {
		auto * w = qobject_cast<QDoubleSpinBox *>(widget);
		if (w) {
			ret[Attribute::minimum]    = w->minimum();
			ret[Attribute::maximum]    = w->maximum();
			ret[Attribute::singleStep] = w->singleStep();
			if (!is_int) ret[Attribute::decimals] = w->decimals();
		}
	} break;
	case tSlider: {
		auto * w = qobject_cast<QSlider *>(widget);
		if (w) {
			ret[Attribute::minimum]    = w->minimum();
			ret[Attribute::maximum]    = w->maximum();
			ret[Attribute::singleStep] = w->singleStep();
		}
	} break;
	case tSpinSlider: {
		auto * w = qobject_cast<SpinSlider *>(widget);
		if (w) {
			ret[Attribute::minimum]    = w->minimum();
			ret[Attribute::maximum]    = w->maximum();
			ret[Attribute::singleStep] = w->singleStep();
			if (!is_int) ret[Attribute::decimals] = w->decimals();
		}
	} break;
	case tEvalSpinBox: {
		auto * w = qobject_cast<EvalSpinBox *>(widget);
		if (w) {
			ret[Attribute::expression] = Q2PIString(w->expression());
			ret[Attribute::singleStep] = w->singleStep();
			ret[Attribute::decimals]   = w->precision();
		}
	} break;
	case tScrollSpinBox: {
		auto * w = qobject_cast<ScrollSpinBox *>(widget);
		if (w) {
			ret[Attribute::minimum] = w->minimum();
			ret[Attribute::maximum] = w->maximum();
		}
	} break;
	default: break;
	}
	return ret;
}


PIVariantMap PIVariantEditors::NumberBase::defaultAttributes() {
	return {
		{Attribute::widgetType, createTypes()                     },
		{Attribute::minimum,    -std::numeric_limits<short>::max()},
		{Attribute::maximum,    std::numeric_limits<short>::max() },
		{Attribute::singleStep, 1.                                },
		{Attribute::decimals,   3								 },
		{Attribute::prefix,     ""								},
		{Attribute::suffix,     ""								},
	};
}


PIVariantTypes::Enum PIVariantEditors::NumberBase::createTypes() {
	PIVariantTypes::Enum ret;
	ret << PIVariantTypes::Enumerator(tSpinBox, "spin box") << PIVariantTypes::Enumerator(tSlider, "slider")
		<< PIVariantTypes::Enumerator(tSpinSlider, "spin-slider") << PIVariantTypes::Enumerator(tEvalSpinBox, "eval spin box")
		<< PIVariantTypes::Enumerator(tScrollSpinBox, "scroll spin box");
	ret.selectValue(tSpinBox);
	return ret;
}


void PIVariantEditors::NumberBase::retranslate() {
	switch (type) {
	case tSpinBox: {
		auto * w = qobject_cast<QDoubleSpinBox *>(widget);
		if (!w) return;
		w->setPrefix(PIVariantEditorBase::vtTr(prefix));
		w->setSuffix(PIVariantEditorBase::vtTr(suffix));
	} break;
	case tSpinSlider: {
		auto * w = qobject_cast<SpinSlider *>(widget);
		if (!w) return;
		w->setPrefix(PIVariantEditorBase::vtTr(prefix));
		w->setSuffix(PIVariantEditorBase::vtTr(suffix));
	} break;
	default: break;
	}
}


void PIVariantEditors::NumberBase::applyAttributes(const PIVariantMap & a) {
	bool ro       = a.value(Attribute::readOnly, false).toBool();
	Type new_type = static_cast<Type>(a.value(Attribute::widgetType).toEnum().selectedValue());
	if (new_type == tInvalid) new_type = tSpinBox;
	if (type != new_type) {
		type = new_type;
		if (widget) delete widget;
		widget = nullptr;
		// clang-format off
		switch (type) {
		case tSpinBox      : widget = new QDoubleSpinBox(); break;
		case tSlider       : widget = new QSlider       (Qt::Horizontal); break;
		case tSpinSlider   : widget = new SpinSlider    (); break;
		case tEvalSpinBox  : widget = new EvalSpinBox   (); break;
		case tScrollSpinBox: widget = new ScrollSpinBox (); break;
		default: break;
		}
		// clang-format on
		if (!widget) return;
		widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
		layout()->addWidget(widget);
	}
	prefix      = a.value(Attribute::prefix).toString();
	suffix      = a.value(Attribute::suffix).toString();
	double min  = a.value(Attribute::minimum, -65535).toDouble();
	double max  = a.value(Attribute::maximum, 65535).toDouble();
	double step = a.value(Attribute::singleStep, 1.).toDouble();
	int dec     = is_int ? 0 : a.value(Attribute::decimals, 2).toInt();
	switch (type) {
	case tSpinBox: {
		auto * w = qobject_cast<QDoubleSpinBox *>(widget);
		if (!w) return;
		w->setReadOnly(ro);
		w->setPrefix(PIVariantEditorBase::vtTr(prefix));
		w->setSuffix(PIVariantEditorBase::vtTr(suffix));
		w->setRange(min, max);
		w->setSingleStep(step);
		w->setDecimals(dec);
	} break;
	case tSlider: {
		auto * w = qobject_cast<QSlider *>(widget);
		if (!w) return;
		w->setEnabled(!ro);
		w->setTickInterval(piRoundd(piMaxd(1., (max - min) / 100.)));
		w->setRange(min, max);
		w->setSingleStep(step);
	} break;
	case tSpinSlider: {
		auto * w = qobject_cast<SpinSlider *>(widget);
		if (!w) return;
		w->setReadOnly(ro);
		w->setPrefix(PIVariantEditorBase::vtTr(prefix));
		w->setSuffix(PIVariantEditorBase::vtTr(suffix));
		w->setMinimum(min);
		w->setMaximum(max);
		w->setSingleStep(step);
		w->setDecimals(dec);
	} break;
	case tEvalSpinBox: {
		auto * w = qobject_cast<EvalSpinBox *>(widget);
		if (!w) return;
		w->setReadOnly(ro);
		w->setSingleStep(step);
		w->setPrecision(dec);
		w->setExpression(PI2QString(a.value(Attribute::expression).toString()));
	} break;
	case tScrollSpinBox: {
		auto * w = qobject_cast<ScrollSpinBox *>(widget);
		if (!w) return;
		w->setReadOnly(ro);
		w->setMinimum(min);
		w->setMaximum(max);
	} break;
	default: break;
	}
}


// PIVariantEditors::Bool

void PIVariantEditors::Bool::applyAttributes(const PIVariantMap & a) {
	widget->setEnabled(!a.value(Attribute::readOnly, !widget->isEnabled()).toBool());
}


// PIVariantEditors::Int


// PIVariantEditors::Double


// PIVariantEditors::String

PIVariantMap PIVariantEditors::String::attributes() const {
	return {};
}


PIVariantMap PIVariantEditors::String::defaultAttributes() {
	return {};
}


void PIVariantEditors::String::applyAttributes(const PIVariantMap & a) {
	widget->setReadOnly(a.value(Attribute::readOnly, widget->isReadOnly()).toBool());
}


// PIVariantEditors::StringList

PIVariantEditors::StringList::StringList() {
	combo = new EComboBox(this);
	combo->setEditable(true);
	combo->setLineEdit(new CLineEdit);
	combo->setInsertPolicy(QComboBox::NoInsert);
	layout()->setContentsMargins(0, 0, 0, 0);
	layout()->addWidget(combo);
	auto newButton = [this](QString icon, QString tooltip) {
		auto * b = new QToolButton(this);
		b->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
		b->setIcon(QIcon(icon));
		b->setToolTip(tooltip);
		layout()->addWidget(b);
		return b;
	};
	butt_apply = newButton(":/icons/list-edit-apply.png", tr("Apply"));
	butt_add   = newButton(":/icons/list-add.png", tr("Add"));
	butt_del   = newButton(":/icons/list-remove.png", tr("Remove"));
	butt_clear = newButton(":/icons/edit-clear.png", tr("Clear"));
	connect(combo->lineEdit(), SIGNAL(returnPressed()), butt_apply, SLOT(click()));
	connect(butt_apply, &QToolButton::clicked, [this]() {
		int ci = combo->currentIndex();
		if (ci < 0) return;
		combo->setItemText(ci, combo->currentText());
	});
	connect(butt_add, &QToolButton::clicked, [this]() { combo->addItem(combo->currentText()); });
	connect(butt_del, &QToolButton::clicked, [this]() {
		if (combo->currentIndex() < 0) return;
		combo->removeItem(combo->currentIndex());
	});
	connect(butt_clear, &QToolButton::clicked, [this]() {
		if (QMessageBox::question(nullptr, tr("Clear All"), tr("Clear All?"), QMessageBox::Ok, QMessageBox::Cancel) == QMessageBox::Ok)
			setValue(PIStringList());
	});
}


void PIVariantEditors::StringList::setValue(const PIVariant & v) {
	int pi = combo->currentIndex();
	combo->clear();
	combo->addItems(PI2QStringList(v.toStringList()));
	if (combo->count() > 0) {
		if (pi < combo->count() && pi >= 0) {
			combo->setCurrentIndex(pi);
		} else {
			combo->setCurrentIndex(0);
		}
	}
}


PIVariant PIVariantEditors::StringList::value() const {
	QStringList l;
	for (int i = 0; i < combo->count(); ++i)
		l << combo->itemText(i);
	return Q2PIStringList(l);
}


PIVariantMap PIVariantEditors::StringList::attributes() const {
	return {
		{Attribute::readOnly, !combo->isEditable()},
	};
}


void PIVariantEditors::StringList::applyAttributes(const PIVariantMap & a) {
	bool ro = a.value(Attribute::readOnly, !combo->isEditable()).toBool();
	combo->setEditable(!ro);
	butt_apply->setEnabled(!ro);
	butt_add->setEnabled(!ro);
	butt_del->setEnabled(!ro);
	butt_clear->setEnabled(!ro);
}


void PIVariantEditors::StringList::retranslate() {
	butt_apply->setToolTip(tr("Apply"));
	butt_add->setToolTip(tr("Add"));
	butt_del->setToolTip(tr("Remove"));
	butt_clear->setToolTip(tr("Clear"));
}


// PIVariantEditors::Color

PIVariantMap PIVariantEditors::Color::attributes() const {
	return {
		{"useAlpha", widget->useAlphaChannel()},
	};
}


PIVariantMap PIVariantEditors::Color::defaultAttributes() {
	return {
		{"useAlpha", true},
	};
}


void PIVariantEditors::Color::applyAttributes(const PIVariantMap & a) {
	widget->setUseAlphaChannel(a.value("useAlpha", widget->useAlphaChannel()).toBool());
	widget->setEnabled(!a.value(Attribute::readOnly, !widget->isEnabled()).toBool());
}


// PIVariantEditors::Time

void PIVariantEditors::Time::applyAttributes(const PIVariantMap & a) {
	widget->setReadOnly(a.value(Attribute::readOnly, widget->isReadOnly()).toBool());
}


// PIVariantEditors::Date

void PIVariantEditors::Date::applyAttributes(const PIVariantMap & a) {
	widget->setReadOnly(a.value(Attribute::readOnly, widget->isReadOnly()).toBool());
}


// PIVariantEditors::DateTime

void PIVariantEditors::DateTime::applyAttributes(const PIVariantMap & a) {
	widget->setReadOnly(a.value(Attribute::readOnly, widget->isReadOnly()).toBool());
}


// PIVariantEditors::Enum

PIVariantEditors::Enum::Enum() {
	widget = new QComboBox();
	layout()->addWidget(widget);
	edit_widget = new QWidget();
	edit_widget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
	edit_widget->setLayout(new QBoxLayout(QBoxLayout::LeftToRight));
	QMargins m = edit_widget->layout()->contentsMargins();
	edit_widget->layout()->setContentsMargins(m.left(), 0, 0, 0);
	auto * b = new QToolButton();
	b->setIcon(QIcon(":/icons/document-edit.png"));
	connect(b, &QToolButton::clicked, this, [this]() {
		PIValueTreeEditEnum dlg;
		if (!dlg.showFor(src)) return;
		setValue(PIVariant(dlg.ret));
	});
	edit_widget->layout()->addWidget(b);
	layout()->setSpacing(0);
	layout()->addWidget(edit_widget);
	setFullEditMode(false);
}


void PIVariantEditors::Enum::setValue(const PIVariant & v) {
	src    = v.toEnum();
	int sv = src.selectedValue();
	widget->clear();
	for (const auto & e: src.enum_list) {
		widget->addItem(PI2QString(e.name), e.value);
		if (e.value == sv) widget->setCurrentIndex(widget->count() - 1);
	}
}


PIVariant PIVariantEditors::Enum::value() const {
	src.selectValue(widget->currentData().toInt());
	return src;
}


void PIVariantEditors::Enum::applyAttributes(const PIVariantMap & a) {
	widget->setEnabled(!a.value(Attribute::readOnly, !widget->isEnabled()).toBool());
}


void PIVariantEditors::Enum::setFullEditMode(bool on) {
	edit_widget->setVisible(on);
}


// PIVariantEditors::NetworkAddress

PIVariantEditors::NetworkAddress::NetworkAddress() {
	widget = new QLineEdit();
	widget->setInputMask("000.000.000.000:00000;_");
	layout()->addWidget(widget);
}


void PIVariantEditors::NetworkAddress::setValue(const PIVariant & v) {
	if (has_port)
		widget->setText(PI2QString(v.toNetworkAddress().toString()));
	else
		widget->setText(PI2QString(v.toNetworkAddress().ipString()));
}


PIVariant PIVariantEditors::NetworkAddress::value() const {
	return PINetworkAddress(Q2PIString(widget->text()));
}


PIVariantMap PIVariantEditors::NetworkAddress::attributes() const {
	return {
		{"port", has_port}
	};
}


PIVariantMap PIVariantEditors::NetworkAddress::defaultAttributes() {
	return {
		{"port", true}
	};
}


void PIVariantEditors::NetworkAddress::applyAttributes(const PIVariantMap & a) {
	has_port = a.value("port", true).toBool();
	if (has_port)
		widget->setInputMask("000.000.000.000:00000;_");
	else
		widget->setInputMask("000.000.000.000;_");
	widget->setReadOnly(a.value(Attribute::readOnly, widget->isReadOnly()).toBool());
}


// PIVariantEditors::FileBase

PIVariantEditors::FileBase::FileBase() {
	filter = "All files(*)";
	widget = new CLineEdit();
	layout()->addWidget(widget);
	sel_widget = new QWidget();
	sel_widget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
	sel_widget->setLayout(new QBoxLayout(QBoxLayout::LeftToRight));
	QMargins m = sel_widget->layout()->contentsMargins();
	sel_widget->layout()->setContentsMargins(m.left(), 0, 0, 0);
	auto * b = new QToolButton();
	b->setIcon(QIcon(":/icons/document-open.png"));
	b->setToolTip(tr("Choose") + " ...");
	connect(b, &QToolButton::clicked, this, [this]() {
		QString ret;
		if (is_dir)
			ret = QFileDialog::getExistingDirectory(this, tr("Select directory"), widget->text());
		else {
			if (is_save)
				ret = QFileDialog::getSaveFileName(this, tr("Select file"), widget->text(), PIVariantEditorBase::vtTr(filter));
			else
				ret = QFileDialog::getOpenFileName(this, tr("Select file"), widget->text(), PIVariantEditorBase::vtTr(filter));
		}
		if (ret.isEmpty()) return;
		if (!is_abs) ret = QDir::current().relativeFilePath(ret);
		widget->setText(ret);
	});
	sel_widget->layout()->addWidget(b);
	layout()->addWidget(sel_widget);
	edit_widget = new QWidget();
	edit_widget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
	edit_widget->setLayout(new QBoxLayout(QBoxLayout::LeftToRight));
	m = edit_widget->layout()->contentsMargins();
	edit_widget->layout()->setContentsMargins(m.left(), 0, 0, 0);
	b = new QToolButton();
	b->setIcon(QIcon(":/icons/document-edit.png"));
	b->setPopupMode(QToolButton::InstantPopup);
	b->setMenu(&edit_menu);
	edit_widget->layout()->addWidget(b);
	layout()->setSpacing(0);
	layout()->addWidget(edit_widget);
}


PIVariantMap PIVariantEditors::FileBase::attributes() const {
	return {
		{Attribute::filter,       filter  },
		{Attribute::absolutePath, is_abs  },
		{Attribute::onlyExisting, !is_save}
	};
}


PIVariantMap PIVariantEditors::FileBase::defaultAttributes() {
	return {
		{Attribute::filter,       ""   },
		{Attribute::absolutePath, false},
		{Attribute::onlyExisting, true }
	};
}


void PIVariantEditors::FileBase::applyAttributes(const PIVariantMap & a) {
	filter  = a.value(Attribute::filter).toString();
	is_abs  = a.value(Attribute::absolutePath).toBool();
	is_save = !a.value(Attribute::onlyExisting, true).toBool();
	bool ro = a.value(Attribute::readOnly).toBool();
	widget->setReadOnly(ro);
	sel_widget->setHidden(ro);
	if (act_abs) act_abs->setChecked(is_abs);
	if (act_save) act_save->setChecked(!is_save);
}


void PIVariantEditors::FileBase::setFullEditMode(bool on) {
	edit_widget->setVisible(on);
	if (act_save) act_save->setVisible(on);
	if (act_filter) act_filter->setVisible(on);
}


void PIVariantEditors::FileBase::createMenu() {
	act_abs = edit_menu.addAction(tr("Absolute path"), this, [this](bool on) { is_abs = on; });
	act_abs->setCheckable(true);
	act_abs->setChecked(is_abs);
	if (is_dir) return;
	act_save = edit_menu.addAction(tr("Existing only"), this, [this](bool on) { is_save = !on; });
	act_save->setCheckable(true);
	act_save->setChecked(!is_save);
	act_filter = edit_menu.addAction(tr("Set filter ..."), this, [this]() {
		bool ok    = false;
		QString nf = QInputDialog::getText(nullptr, tr("Select filter"), tr("Input filter:"), QLineEdit::Normal, PI2QString(filter), &ok);
		if (!ok) return;
		filter = Q2PIString(nf);
	});
}


// PIVariantEditors::File

void PIVariantEditors::File::setValue(const PIVariant & v) {
	widget->setText(PI2QString(v.toFile().file));
}


PIVariant PIVariantEditors::File::value() const {
	PIVariantTypes::File v;
	v.file = Q2PIString(widget->text());
	return v;
}


// PIVariantEditors::Dir

void PIVariantEditors::Dir::setValue(const PIVariant & v) {
	widget->setText(PI2QString(v.toDir().dir));
}


PIVariant PIVariantEditors::Dir::value() const {
	PIVariantTypes::Dir v;
	v.dir = Q2PIString(widget->text());
	return v;
}
