#include "qcd_model.h"
#include "cdutils_interface.h"
#include "cdutils_core.h"
#include "cdutils_x.h"
#include "piqt.h"
#include <QDebug>
#include <QBrush>
#include <QColor>
#include <QMimeData>
#include "qvariantedit.h"
#include "qad_types.h"


using namespace CDUtils;

// CDKItem

CDItem::CDItem(CDUtils::Interface * i, int _index, CDItem::CDItemType type, CDItem *parent) {
	interface = i;
	index_ = _index;
	parent_ = parent;
	type_ = type;
	item_count = 0;
	expanded = true;
}


CDItem::~CDItem() {
	qDeleteAll(childs);
}


QVariant CDItem::data(int column, int role) const {
	if (role == Qt::BackgroundRole) {
		switch (type_) {
			case ItemCDType: {
				CDType & t(interface->section(buildPath())[index_]);
				if (t.errorString().isEmpty()) return QBrush(QColor(255, 250, 230));
				else return QBrush(QColor(255, 128, 128));
				}
			case ItemCDSection: return QBrush(QColor(230, 250, 230));
		}
	}
	if (role == Qt::CheckStateRole && type_ == ItemCDType) {
		CDType & t(interface->section(buildPath())[index_]);
		if (column == cValue && t.cd_type() == CDType::cdK) {
			if (t.type() == "b") return t.toBool() ? Qt::Checked : Qt::Unchecked;
			else QVariant();
		}
		if (column == cName_Cmd && t.cd_type() == CDType::cdX) {
			return t.isSelectedX() ? Qt::Checked : Qt::Unchecked;
		}
	}
	if (role == Qt::ToolTipRole && type_ == ItemCDType) {
		CDType & t(interface->section(buildPath())[index_]);
		return PI2QString(t.errorString());
	}
	if (role != Qt::DisplayRole && role != Qt::EditRole) return QVariant();
	PIDeque<int> path = buildPath();
	CDSection & rs = interface->section(path);
	CDSection s;
	switch (type_) {
		case ItemCDType:
			switch (column) {
			case cID:         return QString::number(index_);
			case cName_Cmd:   return PI2QString(rs[index_].name());
			case cType:       return stringType(rs[index_].type());
			case cXMode:      return QVariant::fromValue(xModeEnum(rs[index_].xmode()));
			case cXAvg:       return rs[index_].avg();
			case cExpression: return PI2QString(rs[index_].formula());
			case cValue:      return value(rs[index_], role);
			case cComment:    return PI2QString(rs[index_].comment());
			default: break;
			}
		break;
		case ItemCDSection:
			s = rs.section(index_);
//			piCout << rs.name << rs.alias << s.name << s.alias;
			switch (column) {
			case cID:       return QString("[") + QString::number(index_) + QString("]");
			case cName_Cmd: return PI2QString(s.alias);
			case cType:     return PI2QString(s.name);
			default: break;
			}
		break;
	}
	return QVariant();
}


QVariant CDItem::value(CDType & t, int role) const {
	if (t.type() == "f") return t.toDouble();
	if (t.type() == "n") return t.toInt();
	if (t.type() == "b") return t.toBool();
	if (t.type() == "e") {
		QAD::Enum et = PI2QADEnum(t.enumValues());
		et.selectValue(t.toInt());
		if (role == Qt::EditRole) return QVariant::fromValue<QAD::Enum>(et);
		else return et.selectedName();
	}
	return PI2QString(t.value());
}


bool CDItem::setData(int column, const QVariant & value) {
	if (type_ == ItemCDType) {
		CDType & t(interface->section(buildPath())[index_]);
		if ((column == cExpression || column == cValue) && (t.cd_type() == CDType::cdK)) {
			interface->section(buildPath())[index_].setValue(Q2PIString(value.toString()));
			interface->calculate();
			return true;
		}
		if (t.cd_type() == CDType::cdX) {
			switch (column) {
			case cName_Cmd:
				X.setEnabled(t, value.toBool());
				return true;
			case cXMode:
				t.setXMode((CDType::XMode)value.toInt());
				return true;
			case cXAvg:
				t.setAvg(piMax(value.toInt(), 1));
				return true;
			default: break;
			}
		}
	}
	return false;
}


PIDeque<int> CDItem::buildPath() const {
	PIDeque<int> path;
	CDItem * p = parent_;
	while (p) {
		path.push_front(p->index_);
		p = p->parent_;
	}
	path.take_front();
	return path;
}


QString CDItem::stringType(const PIString & t) const {
	QString n = PI2QString(t);
	if (n.isEmpty()) return QString("");
	switch (n[0].toLatin1()) {
	case 'l': return QString("list"); break;
	case 'b': return QString("bool"); break;
	case 'n': return QString("int"); break;
	case 'f': return QString("double"); break;
	case 'c': return QString("color"); break;
	case 'r': return QString("rect"); break;
	case 'a': return QString("rect"); break;
	case 'p': return QString("point"); break;
	case 'v': return QString("vector"); break;
	case 'i': return QString("IP"); break;
	case 'e': return QString("enum"); break;
	case 'F': return QString("file"); break;
	case 'D': return QString("dir"); break;
	}
	return QString("string");
}


QAD::Enum CDItem::xModeEnum(int v) const {
	QAD::Enum ret;
	ret << QAD::Enumerator(CDType::X_Current, "Current")
		<< QAD::Enumerator(CDType::X_All_Avg, "All, Averaging");
	ret.selectValue(v);
	return ret;
}


// CDKDelegate

CDDelegate::CDDelegate(QObject *parent) : QStyledItemDelegate(parent) {
}


void CDDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const {
	CDItem * item = ((CDItemModel*)index.model())->getItem(index);
	if (item) {
		if (item->itemType() == CDItem::ItemCDType && item->interface->cdType() == CDType::cdC) {
			QStyleOptionButton bo;
			bo.direction   = option.direction;
			bo.fontMetrics = option.fontMetrics;
			bo.palette     = option.palette;
			bo.rect        = option.rect;
			bo.state       = option.state;// & ~(QStyle::State_HasFocus | QStyle::State_MouseOver);
			bo.text        = item->data(1, Qt::DisplayRole).toString();
			QWidget * v = (QWidget*)(painter->device());
			if (v) {
				QPoint cp = v->mapFromGlobal(QCursor::pos());
				if (bo.rect.contains(cp, true)) {
					//bo.state |= QStyle::State_MouseOver;
					if (qApp->mouseButtons().testFlag(Qt::LeftButton))
						bo.state |= QStyle::State_On;
				}
			}
			qApp->style()->drawControl(QStyle::CE_PushButton, &bo, painter);
			return;
		}
	}
	QStyledItemDelegate::paint(painter, option, index);

}


QWidget * CDDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const {
	return new QVariantEdit(parent);
}


void CDDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const {
	QVariantEdit *edit = static_cast<QVariantEdit*>(editor);
	edit->setValue(index.model()->data(index, Qt::EditRole));
}


void CDDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const {
	QVariantEdit *edit = static_cast<QVariantEdit*>(editor);
	QVariant v = edit->value();
	if (v.canConvert<QAD::Enum>()) {
		QAD::Enum et = v.value<QAD::Enum>();
		model->setData(index, et.selectedValue(), Qt::EditRole);
	} else model->setData(index, v, Qt::EditRole);
}


void CDDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const {
	editor->setGeometry(option.rect);
}


QSize CDDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const {
	QSize s = QStyledItemDelegate::sizeHint(option, index);
	s.setWidth(s.width() + 20);
	return s;
}


// CDItemModel

CDItemModel::CDItemModel(int type_, QObject *parent) : QAbstractItemModel(parent) {
	interface = new Interface((CDType::cdT)type_);
	root = 0;
	internalRebuild();
}


CDItemModel::~CDItemModel() {
	delete root;
	delete interface;
}


QVariant CDItemModel::data(const QModelIndex &index, int role) const {
	if (!index.isValid()) return QVariant();
	CDItem * item = getItem(index);
	return item->data(index.column(), role);
}


QVariant CDItemModel::headerData(int section, Qt::Orientation orientation, int role) const {
	if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
		switch (section) {
		case cID:         return tr("Index");
		case cName_Cmd:   return interface->cdType() == CDType::cdC ? tr("Command") : tr("Name");
		case cType:       return tr("Type");
		case cXMode:      return tr("Mode");
		case cXAvg:       return tr("Averaging");
		case cExpression: return tr("Expression");
		case cValue:      return tr("Value");
		case cComment:    return tr("Comment");
		}
	}
	return QVariant();
}


QModelIndex CDItemModel::index(int row, int column, const QModelIndex &parent) const {
	if (parent.isValid() && parent.column() != cID) return QModelIndex();
	CDItem * p = getItem(parent);
	CDItem * c = p->childs.value(row, 0);
	if (c) return createIndex(row, column, c);
	else return QModelIndex();
}


QModelIndex CDItemModel::parent(const QModelIndex &index) const {
	if (!index.isValid()) return QModelIndex();
	CDItem * c = getItem(index);
	CDItem * p = c->parent_;
	if (p == root) return QModelIndex();
	return createIndex(p->parent_->childs.indexOf(p), cID, p);
}


int CDItemModel::rowCount(const QModelIndex &parent) const {
	CDItem * p = getItem(parent);
	return p->childs.count();
}


int CDItemModel::columnCount(const QModelIndex &parent) const {
	return cLastColumn;
}


Qt::ItemFlags CDItemModel::flags(const QModelIndex & index) const {
	if (!index.isValid()) return 0;
	Qt::ItemFlags f = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
	CDItem * item = getItem(index);
	if (!item) return 0;
	if (item->type_ == CDItem::ItemCDType) {
		CDType & t(interface->section(item->buildPath())[item->index_]);
		if (t.cd_type() == CDType::cdK) {
			if (index.column() == cExpression || index.column() == cValue)
				f |= Qt::ItemIsEditable;
			if (index.column() == cValue && t.type() == "b")
				f |= Qt::ItemIsUserCheckable;
			if (index.column() == cName_Cmd)
				f |= Qt::ItemIsDragEnabled;
		}
		if (t.cd_type() == CDType::cdX) {
			if (index.column() == cXMode || index.column() == cXAvg)
				f |= Qt::ItemIsEditable;
			if (index.column() == cName_Cmd)
				f |= Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled;
		}
	}
	return f;
}


bool CDItemModel::setData(const QModelIndex & index, const QVariant & value, int role) {
	if (role == Qt::CheckStateRole && (index.column() == cName_Cmd || index.column() == cValue)) {
		CDItem * item = getItem(index);
		if (item->type_ == CDItem::ItemCDType) {
			CDType & t(interface->section(item->buildPath())[item->index_]);
			if (index.column() == cValue && (t.cd_type() == CDType::cdK)) {
				if (t.type() == "b") {
					bool result = item->setData(index.column(), PI2QString(PIString::fromBool(value.toBool())));
					QModelIndex rin(CDItemModel::index(index.row(), cExpression, index.parent()));
					emit dataChanged(rin, rin);
					return result;
				}
			}
			if (index.column() == cName_Cmd && (t.cd_type() == CDType::cdX)) {
				bool result = item->setData(index.column(), value);
				//QModelIndex rin(CDItemModel::index(index.row(), 1, index.parent()));
				//emit dataChanged(rin, rin);
				return result;
			}
		}
	}
	if (role != Qt::EditRole) return false;
	CDItem * item = getItem(index);
	bool result = item->setData(index.column(), value);
	if (result) {
		QModelIndex rin(CDItemModel::index(index.row(), cExpression, index.parent()));
		emit dataChanged(rin, rin);
		emit dataChanged(index, index);
	}
	return result;
}


QMimeData * CDItemModel::mimeData(const QModelIndexList & indexes) const {
	if (indexes.size() == 1) {
		QModelIndex index = indexes[0];
		if (index.isValid()/* && interface->cdType() == CDType::cdX*/) {
			CDItem * item = getItem(index);
			if (item) {
				CDType & t(interface->section(item->buildPath())[item->index_]);
				QMimeData * mime = new QMimeData();
				mime->setText(PI2QString(CDCore::instance()->typeLetter(interface->cdType()) +
										 CDCore::pathToString(t.path())));
				return mime;
			}
		}
	}
	return QAbstractItemModel::mimeData(indexes);
}


void CDItemModel::rebuildModel() {
	beginResetModel();
	internalRebuild();
	endResetModel();
}


void CDItemModel::buildItem(CDItem * it, CDSection & r) {
	//piCout << "build item" << r.name << r.alias;
	PIMap<int, CDType>::iterator i;
	for (i = r.cd.begin(); i != r.cd.end(); ++i) {
		it->childs << new CDItem(interface, i.key(), CDItem::ItemCDType, it);
	}
	it->item_count = it->childs.size();
	PIMap<int, CDSection>::iterator j;
	for (j = r.s.begin(); j != r.s.end(); ++j) {
		it->childs << new CDItem(interface, j.key(), CDItem::ItemCDSection, it);
		buildItem(it->childs.back(), j.value());
	}
}


void CDItemModel::updateModel() {
	beginResetModel();
	endResetModel();
}


void CDItemModel::internalRebuild() {
	//qDebug() << "[CDKItemModel]" << "internalRebuild()";
	if (root) delete root;
	root = new CDItem(interface, 0, CDItem::ItemCDSection, 0);
	CDSection & r = interface->root();
	buildItem(root, r);
}


CDItem * CDItemModel::getItem(const QModelIndex &index) const {
	if (index.isValid()) {
		CDItem * item = static_cast<CDItem*>(index.internalPointer());
		if (item) return item;
	}
	return root;
}


QModelIndex CDItemModel::indexByPath(const PIDeque<int> & path, int column) const {
	if (path.isEmpty()) return QModelIndex();
	CDItem * item = root;
	//piCout << path << "...";
	bool ok = false;
	for (int i = 0; i < path.size_s() - 1; ++i) {
		ok = false;
		foreach (CDItem * j, item->childs)
			if (j->type_ == CDItem::ItemCDSection && j->index_ == path[i]) {
				item = j;
				ok = true;
				break;
			}
		if (!ok) return QModelIndex();
	}
	ok = false;
	foreach (CDItem * j, item->childs)
		if (j->type_ == CDItem::ItemCDType && j->index_ == path.back()) {
			item = j;
			ok = true;
			break;
		}
	if (!ok || !item->parent_) return QModelIndex();
	QModelIndex ret = createIndex(item->parent_->childs.indexOf(item), column, item);
	//piCout << path << Q2PIString(item->data(cName_Cmd, Qt::DisplayRole).toString()) << getItem(ret)->buildPath();
	return ret;
}
