#include "qvariantedit.h"

#include <QDateTimeEdit>
#include <QFileDialog>
#include <QMessageBox>


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


void StringListEdit::setValue(const QStringList & v) {
	int pi = combo->currentIndex();
	combo->clear();
	if (!v.isEmpty()) {
		combo->addItems(v);
		if (pi < combo->count() && pi >= 0) {
			combo->setCurrentIndex(pi);
		} else {
			combo->setCurrentIndex(0);
		}
	}
}


void StringListEdit::addItem() {
	combo->addItem(combo->currentText());
	emit valueChanged();
}


void StringListEdit::delItem() {
	if (combo->currentIndex() < 0) return;
	combo->removeItem(combo->currentIndex());
	emit valueChanged();
}


void StringListEdit::clear() {
	if (combo->count() == 0) return;
	combo->clear();
	emit valueChanged();
}


StringListEdit::StringListEdit(QWidget * parent): QWidget(parent) {
	lay   = new QBoxLayout(QBoxLayout::LeftToRight);
	combo = new EComboBox(this);
	combo->setEditable(true);
	combo->setLineEdit(new CLineEdit);
	combo->setInsertPolicy(QComboBox::NoInsert);
	butt_apply = new QPushButton(this);
	butt_add   = new QPushButton(this);
	butt_del   = new QPushButton(this);
	butt_clear = new QPushButton(this);
	butt_apply->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
	butt_add->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
	butt_del->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
	butt_clear->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
	butt_apply->setIcon(QIcon(":/icons/list-edit-apply.png"));
	butt_add->setIcon(QIcon(":/icons/list-add.png"));
	butt_del->setIcon(QIcon(":/icons/list-remove.png"));
	butt_clear->setIcon(QIcon(":/icons/edit-clear.png"));
	butt_apply->setToolTip(tr("Apply"));
	butt_add->setToolTip(tr("Add"));
	butt_del->setToolTip(tr("Remove"));
	butt_clear->setToolTip(tr("Clear"));
	lay->setContentsMargins(0, 0, 0, 0);
	lay->setSpacing(2);
	lay->addWidget(combo);
	lay->addWidget(butt_apply);
	lay->addWidget(butt_add);
	lay->addWidget(butt_del);
	lay->addWidget(butt_clear);
	setLayout(lay);
	connect(combo->lineEdit(), SIGNAL(returnPressed()), this, SLOT(editItem()));
	connect(combo, SIGNAL(rowsChanged()), this, SIGNAL(valueChanged()));
	connect(butt_apply, SIGNAL(clicked(bool)), this, SLOT(editItem()));
	connect(butt_add, SIGNAL(clicked(bool)), this, SLOT(addItem()));
	connect(butt_del, SIGNAL(clicked(bool)), this, SLOT(delItem()));
	connect(butt_clear, &QPushButton::clicked, [this]() {
		if (QMessageBox::question(this, tr("Clear All"), tr("Clear All?"), QMessageBox::Ok, QMessageBox::Cancel) == QMessageBox::Ok)
			clear();
	});
}


void StringListEdit::changeEvent(QEvent * e) {
	QWidget::changeEvent(e);
	if (e->type() == QEvent::LanguageChange) {
		butt_add->setToolTip(tr("Add"));
		butt_del->setToolTip(tr("Remove"));
		butt_clear->setToolTip(tr("Clear"));
		return;
	}
}


void StringListEdit::editItem() {
	int ci = combo->currentIndex();
	if (ci < 0) return;
	combo->setItemText(ci, combo->currentText());
	emit valueChanged();
}


PathEdit::PathEdit(QWidget * parent): QWidget(parent), lay(QBoxLayout::LeftToRight, this) {
	is_dir = is_abs = is_save = false;
	filter                    = tr("All files(*)");
	line                      = new CLineEdit(this);
	butt_select               = new QPushButton(this);
	butt_select->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
	butt_select->setIcon(QIcon(":/icons/document-open.png"));
	butt_select->setToolTip(tr("Choose") + " ...");
	lay.setContentsMargins(0, 0, 0, 0);
	lay.addWidget(line);
	lay.addWidget(butt_select);
	connect(line, SIGNAL(textChanged(QString)), this, SIGNAL(valueChanged()));
	connect(butt_select, SIGNAL(clicked(bool)), this, SLOT(select()));
}


void PathEdit::changeEvent(QEvent * e) {
	QWidget::changeEvent(e);
	if (e->type() == QEvent::LanguageChange) {
		butt_select->setToolTip(tr("Choose") + " ...");
		return;
	}
}


void PathEdit::resizeEvent(QResizeEvent *) {
	butt_select->setMaximumHeight(line->height());
}


void PathEdit::setValue(const QString & v) {
	line->setText(v);
	if (!is_dir && is_save)
		butt_select->setIcon(QIcon(":/icons/document-save.png"));
	else
		butt_select->setIcon(QIcon(":/icons/document-open.png"));
}


void PathEdit::select() {
	QString ret;
	if (is_dir)
		ret = QFileDialog::getExistingDirectory(this, tr("Select directory"), value());
	else {
		if (is_save)
			ret = QFileDialog::getSaveFileName(this, tr("Select file"), value(), filter);
		else
			ret = QFileDialog::getOpenFileName(this, tr("Select file"), value(), filter);
	}
	if (ret.isEmpty()) return;
	if (!is_abs) ret = QDir::current().relativeFilePath(ret);
	line->setText(ret);
}


QVariantEdit::QVariantEdit(QWidget * parent): QWidget(parent) {
	setLayout(new QBoxLayout(QBoxLayout::TopToBottom));
	layout()->setContentsMargins(0, 0, 0, 0);
	_empty  = 0;
	_line   = 0;
	_check  = 0;
	_color  = 0;
	_list   = 0;
	_date   = 0;
	_spin   = 0;
	_espin  = 0;
	_rect   = 0;
	_point  = 0;
	_path   = 0;
	_enum   = 0;
	_custom = _cur_edit = 0;
	_recreate(QVariant());
}


QVariantEdit::~QVariantEdit() {
	_delete();
}


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


void QVariantEdit::_recreate(const QVariant & new_value) {
	if (!new_value.isValid()) {
		if (_cur_edit != _empty) _delete();
		if (_empty == 0) {
			_empty = new QLabel(tr("Invalid value"), this);
			_empty->setAlignment(Qt::AlignCenter);
			_cur_edit = _empty;
			_resize();
		}
		_value = new_value;
		return;
	}
	if (_value.userType() == new_value.userType()) {
		_value = new_value;
		return;
	}
	_delete();
#if QT_VERSION_MAJOR <= 5
	switch (new_value.type()) {
#else
	switch (new_value.metaType().id()) {
#endif
#if QT_VERSION_MAJOR <= 5
	case QVariant::Bool:
#else
	case QMetaType::Bool:
#endif
		_check = new QCheckBox(this);
		_check->setAutoFillBackground(true);
		_cur_edit = _check;
		connect(_check, SIGNAL(toggled(bool)), this, SLOT(_changed()));
		break;
#if QT_VERSION_MAJOR <= 5
	case QVariant::Int:
#else
	case QMetaType::Int:
#endif
		_spin = new QDoubleSpinBox(this);
		_spin->setDecimals(0);
		_spin->setRange(-0x7FFFFFFF, 0x7FFFFFFF);
		_cur_edit = _spin;
		connect(_spin, SIGNAL(valueChanged(double)), this, SLOT(_changed()));
		break;
#if QT_VERSION_MAJOR <= 5
	case QVariant::UInt:
#else
	case QMetaType::UInt:
#endif
		_spin = new QDoubleSpinBox(this);
		_spin->setDecimals(0);
		_spin->setRange(0, 0xFFFFFFFF);
		_cur_edit = _spin;
		connect(_spin, SIGNAL(valueChanged(double)), this, SLOT(_changed()));
		break;
#if QT_VERSION_MAJOR <= 5
	case QVariant::LongLong:
#else
	case QMetaType::LongLong:
#endif
		_spin = new QDoubleSpinBox(this);
		_spin->setDecimals(0);
		_spin->setRange(-0x7FFFFFFFFFFFFFFFL, 0x7FFFFFFFFFFFFFFFL);
		_cur_edit = _spin;
		connect(_spin, SIGNAL(valueChanged(double)), this, SLOT(_changed()));
		break;
#if QT_VERSION_MAJOR <= 5
	case QVariant::ULongLong:
#else
	case QMetaType::ULongLong:
#endif
		_spin = new QDoubleSpinBox(this);
		_spin->setDecimals(0);
		_spin->setRange(0L, 0x7FFFFFFFFFFFFFFFL);
		_cur_edit = _spin;
		connect(_spin, SIGNAL(valueChanged(double)), this, SLOT(_changed()));
		break;
#if QT_VERSION_MAJOR <= 5
	case QVariant::Double:
#else
	case QMetaType::Double:
#endif
		_espin    = new EvalSpinBox(this);
		_cur_edit = _espin;
		connect(_espin, SIGNAL(valueChanged(double)), this, SLOT(_changed()));
		break;
#if QT_VERSION_MAJOR <= 5
	case QVariant::Color:
#else
	case QMetaType::QColor:
#endif
		_color = new ColorButton(this);
		_color->setUseAlphaChannel(true);
		_cur_edit = _color;
		connect(_color, SIGNAL(colorChanged(QColor)), this, SLOT(_changed()));
		break;
#if QT_VERSION_MAJOR <= 5
	case QVariant::String:
#else
	case QMetaType::QString:
#endif
		_line     = new CLineEdit(this);
		_cur_edit = _line;
		connect(_line, SIGNAL(textChanged(QString)), this, SLOT(_changed()));
		break;
#if QT_VERSION_MAJOR <= 5
	case QVariant::StringList:
#else
	case QMetaType::QStringList:
#endif
		_list     = new StringListEdit(this);
		_cur_edit = _list;
		connect(_list, SIGNAL(valueChanged()), this, SLOT(_changed()));
		break;
#if QT_VERSION_MAJOR <= 5
	case QVariant::Rect:
#else
	case QMetaType::QRect:
#endif
		_rect = new QRectEdit(this);
		_rect->setDecimals(0);
		_cur_edit = _rect;
		connect(_rect, SIGNAL(valueChanged(QRectF)), this, SLOT(_changed()));
		break;
#if QT_VERSION_MAJOR <= 5
	case QVariant::RectF:
#else
	case QMetaType::QRectF:
#endif
		_rect = new QRectEdit(this);
		_rect->setDecimals(3);
		_cur_edit = _rect;
		connect(_rect, SIGNAL(valueChanged(QRectF)), this, SLOT(_changed()));
		break;
#if QT_VERSION_MAJOR <= 5
	case QVariant::Point:
#else
	case QMetaType::QPoint:
#endif
		_point = new QPointEdit(this);
		_point->setDecimals(0);
		_cur_edit = _point;
		connect(_point, SIGNAL(valueChanged(QPointF)), this, SLOT(_changed()));
		break;
#if QT_VERSION_MAJOR <= 5
	case QVariant::PointF:
#else
	case QMetaType::QPointF:
#endif
		_point = new QPointEdit(this);
		_point->setDecimals(3);
		_cur_edit = _point;
		connect(_point, SIGNAL(valueChanged(QPointF)), this, SLOT(_changed()));
		break;
#if QT_VERSION_MAJOR <= 5
	case QVariant::Date:
#else
	case QMetaType::QDate:
#endif
		_date     = new QDateEdit(this);
		_cur_edit = _date;
		connect(_date, SIGNAL(dateTimeChanged(QDateTime)), this, SLOT(_changed()));
		break;
#if QT_VERSION_MAJOR <= 5
	case QVariant::Time:
#else
	case QMetaType::QTime:
#endif
		_date     = new QTimeEdit(this);
		_cur_edit = _date;
		connect(_date, SIGNAL(dateTimeChanged(QDateTime)), this, SLOT(_changed()));
		break;
#if QT_VERSION_MAJOR <= 5
	case QVariant::DateTime:
#else
	case QMetaType::QDateTime:
#endif
		_date     = new QDateTimeEdit(this);
		_cur_edit = _date;
		connect(_date, SIGNAL(dateTimeChanged(QDateTime)), this, SLOT(_changed()));
		break;
	default: break;
	}
	if (!_cur_edit) {
		if (new_value.canConvert<QAD::Enum>()) {
			_enum = new EComboBox(this);
			_setEnum(new_value.value<QAD::Enum>());
			_cur_edit = _enum;
			connect(_enum, SIGNAL(currentIndexChanged(int)), this, SLOT(_changed()));
		}
		if (new_value.canConvert<QAD::File>()) {
			_path = new PathEdit(this);
			_setFile(new_value.value<QAD::File>());
			_cur_edit = _path;
			connect(_path, SIGNAL(valueChanged()), this, SLOT(_changed()));
		}
		if (new_value.canConvert<QAD::Dir>()) {
			_path = new PathEdit(this);
			_setDir(new_value.value<QAD::Dir>());
			_cur_edit = _path;
			connect(_path, SIGNAL(valueChanged()), this, SLOT(_changed()));
		}
		if (!_cur_edit) { // try custom
			QVariantEditorFactoryBase * f = QVariantEditorFactories::editorFactory(new_value.userType());
			if (f) {
				QWidget * fw = f->createEditor();
				if (fw) {
					fw->setParent(this);
					_custom   = fw;
					_cur_edit = _custom;
					connect(_custom, SIGNAL(valueChanged()), this, SLOT(_changed()));
				}
			}
		}
	}
	if (_cur_edit) {
		_resize();
	}
	_value = new_value;
}


QVariant QVariantEdit::value() const {
#if QT_VERSION_MAJOR <= 5
	switch (_value.type()) {
#else
	switch (_value.metaType().id()) {
#endif
#if QT_VERSION_MAJOR <= 5
	case QVariant::Bool:
#else
	case QMetaType::Bool:
#endif
		return _check->isChecked();
#if QT_VERSION_MAJOR <= 5
	case QVariant::Int:
#else
	case QMetaType::Int:
#endif
		return int(_spin->value());
#if QT_VERSION_MAJOR <= 5
	case QVariant::UInt:
#else
	case QMetaType::UInt:
#endif
		return (unsigned int)(_spin->value());
#if QT_VERSION_MAJOR <= 5
	case QVariant::LongLong:
#else
	case QMetaType::LongLong:
#endif
		return qlonglong(_spin->value());
#if QT_VERSION_MAJOR <= 5
	case QVariant::ULongLong:
#else
	case QMetaType::ULongLong:
#endif
		return qulonglong(_spin->value());
#if QT_VERSION_MAJOR <= 5
	case QVariant::Double:
#else
	case QMetaType::Double:
#endif
		return _espin->value();
#if QT_VERSION_MAJOR <= 5
	case QVariant::Color:
#else
	case QMetaType::QColor:
#endif
		return _color->color();
#if QT_VERSION_MAJOR <= 5
	case QVariant::String:
#else
	case QMetaType::QString:
#endif
		return _line->text();
#if QT_VERSION_MAJOR <= 5
	case QVariant::StringList:
#else
	case QMetaType::QStringList:
#endif
		return _list->value();
#if QT_VERSION_MAJOR <= 5
	case QVariant::Rect:
#else
	case QMetaType::QRect:
#endif
		return _rect->value().toRect();
#if QT_VERSION_MAJOR <= 5
	case QVariant::RectF:
#else
	case QMetaType::QRectF:
#endif
		return _rect->value();
#if QT_VERSION_MAJOR <= 5
	case QVariant::Point:
#else
	case QMetaType::QPoint:
#endif
		return _point->value().toPoint();
#if QT_VERSION_MAJOR <= 5
	case QVariant::PointF:
#else
	case QMetaType::QPointF:
#endif
		return _point->value();
#if QT_VERSION_MAJOR <= 5
	case QVariant::Date:
#else
	case QMetaType::QDate:
#endif
		return _date->date();
#if QT_VERSION_MAJOR <= 5
	case QVariant::Time:
#else
	case QMetaType::QTime:
#endif
		return _date->time();
#if QT_VERSION_MAJOR <= 5
	case QVariant::DateTime:
#else
	case QMetaType::QDateTime:
#endif
		return _date->dateTime();
	default:
		if (_value.canConvert<QAD::Enum>() && _enum) {
			QAD::Enum ret;
			for (int i = 0; i < _enum->count(); ++i)
				ret.enum_list << QAD::Enumerator(_enum->itemData(i).toInt(), _enum->itemText(i));
			ret.enum_name = _enum->property("enum_name").toString();
			ret.selected  = _enum->currentText();
			return QVariant::fromValue<QAD::Enum>(ret);
		}
		if (_value.canConvert<QAD::File>() && _path) {
			if (!_path->is_dir) {
				QAD::File ret;
				ret.file    = _path->value();
				ret.filter  = _path->filter;
				ret.is_abs  = _path->is_abs;
				ret.is_save = _path->is_save;
				return QVariant::fromValue<QAD::File>(ret);
			}
		}
		if (_value.canConvert<QAD::Dir>() && _path) {
			if (_path->is_dir) {
				QAD::Dir ret;
				ret.dir    = _path->value();
				ret.is_abs = _path->is_abs;
				return QVariant::fromValue<QAD::Dir>(ret);
			}
		}
		if (_custom) {
			return _custom->property("value");
		}
	}
	return QVariant();
}


void QVariantEdit::setValue(const QVariant & v) {
	_recreate(v);
	if (_cur_edit) _cur_edit->blockSignals(true);
	if (_line) {
		_line->setText(v.toString());
	}
	if (_check) {
		_check->setChecked(v.toBool());
		_check->setText(v.toBool() ? "true" : "false");
	}
	if (_color) {
		_color->setColor(v.value<QColor>());
	}
	if (_list) {
		_list->setValue(v.toStringList());
	}
	if (_date) {
		_date->setDateTime(v.toDateTime());
	}
	if (_spin) {
		_spin->setValue(v.toDouble());
	}
	if (_espin) {
		_espin->setValue(v.toDouble());
	}
	if (_rect) {
		_rect->setValue(v.toRectF());
	}
	if (_point) {
		_point->setValue(v.toPointF());
	}
	if (_path) {
		if (_path->is_dir)
			_setDir(v.value<QAD::Dir>());
		else
			_setFile(v.value<QAD::File>());
	}
	if (_enum) {
		_setEnum(v.value<QAD::Enum>());
	}
	if (_custom) {
		_setCustom(v);
	}
	if (_cur_edit) _cur_edit->blockSignals(false);
}


void QVariantEdit::_delete() {
	if (_cur_edit) delete _cur_edit;
	_cur_edit = 0;
	_custom   = 0;
	_empty    = 0;
	_line     = 0;
	_check    = 0;
	_color    = 0;
	_list     = 0;
	_date     = 0;
	_spin     = 0;
	_espin    = 0;
	_rect     = 0;
	_point    = 0;
	_path     = 0;
	_enum     = 0;
}


void QVariantEdit::_resize() {
	if (!_cur_edit) return;
	layout()->addWidget(_cur_edit);
}


void QVariantEdit::_newPath() {
	_delete();
	_path     = new PathEdit(this);
	_cur_edit = _path;
	_value    = _value.toString();
	connect(_path, SIGNAL(valueChanged()), this, SLOT(_changed()));
	_resize();
}


void QVariantEdit::_setEnum(const QAD::Enum & v) {
	_enum->clear();
	_enum->setProperty("enum_name", v.enum_name);
	foreach(const QAD::Enumerator & e, v.enum_list)
		_enum->addItem(e.name, QVariant(e.value));
	int i(0);
	for (i = 0; i < _enum->count(); ++i)
		if (_enum->itemText(i) == v.selected) {
			_enum->setCurrentIndex(i);
			break;
		}
	if (i == _enum->count()) _enum->setCurrentIndex(-1);
}


void QVariantEdit::_setFile(const QAD::File & v) {
	_path->is_dir  = false;
	_path->filter  = v.filter;
	_path->is_abs  = v.is_abs;
	_path->is_save = v.is_save;
	_path->setValue(v.file);
}


void QVariantEdit::_setDir(const QAD::Dir & v) {
	_path->is_dir = true;
	_path->is_abs = v.is_abs;
	_path->setValue(v.dir);
}


void QVariantEdit::_setCustom(const QVariant & v) {
	_custom->setProperty("value", v);
}


void QVariantEdit::_changed() {
	if (_check) _check->setText(_check->isChecked() ? "true" : "false");
	emit valueChanged(value());
}
