#include "blockview.h"
#include <QApplication>


#define BLOCKITEM_DEFAULT_PIN_MARGIN 20

BlockItem::BlockItem(QGraphicsItem * parent): QGraphicsObject(parent), PropertyStorage(),
g_main(this), g_selection(this) {
	setData(1006, "item");
	setZValue(2.);
	setAcceptHoverEvents(true);
	setFlags(QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsMovable);
	g_main.setData(1002, true);
	g_selection.setData(1007, true);
	g_selection.setAcceptedMouseButtons(0);
	g_selection.setZValue(10.);
	g_selection.hide();
	g_selection.setData(1003, true);
	col = Qt::lightGray;
	_resize(QSizeF(100., 60.));
	QPen p(QColor(128, 128, 255), lineThickness(), Qt::DotLine);
	p.setCosmetic(true);
	g_selection.setPen(p);
	g_selection.setBrush(QColor(128, 128, 255, 32));
	pins_margin = BLOCKITEM_DEFAULT_PIN_MARGIN;
	anim_thick.setTargetObject(this);
	anim_thick.setPropertyName("_thickness");
	anim_thick.setEasingCurve(QEasingCurve::OutQuad);
	anim_thick.setDuration(300);
	anim_sel.setTargetObject(this);
	anim_sel.setPropertyName("_selRect");
	anim_sel.setEasingCurve(QEasingCurve::OutCubic);
	anim_sel.setDuration(400);
	t_sel.start();
	//g_main.setBrush(QColor(128, 128, 128, 64));
	/*
	BlockItemPin * pin = new BlockItemPin(Qt::AlignRight, 0, this);
	pin->setText("text");
	pin->setBusType(0);
	addPin(pin);
	pin = new BlockItemPin(Qt::AlignTop, this);
	pin->setBusType(0); addPin(pin);
	pin = new BlockItemPin(Qt::AlignLeft, this);
	pin->setBusType(1); addPin(pin);
	pin = new BlockItemPin(Qt::AlignRight, this);
	pin->setBusType(1); addPin(pin);*/
}


BlockItem::~BlockItem() {
	clearDecors();
}


void BlockItem::_resize(QSizeF s) {
	g_main.setRect(QRectF(QPointF(), s));
	g_main.setPos(QPointF(-g_main.rect().center().x(), -10.));
	g_selection.setRect(enlargedRect(g_main.rect(), 0, 0, 8));
	g_selection.setPos(g_main.pos());
	QRadialGradient g(g_main.rect().width() / 2., 0, qMax<qreal>(g_main.rect().width() / 2., g_main.rect().height()));
	g.setSpread(QGradient::PadSpread);
	g.setCoordinateMode(QGradient::LogicalMode);
	g.setColorAt(0., col.darker(120.));
	g.setColorAt(1., col.lighter(120.));
	g_main.setBrush(QBrush(g));
	prepareGeometryChange();
	arrangePins();
}


void BlockItem::_moveToTop(bool only_decors) {
	qreal dy = -g_main.rect().center().y() + 10;
	if (!only_decors)
		moveBy(0., dy);
	foreach (QGraphicsItem * d, decors_)
		d->moveBy(0., -dy);

}


BlockItemPin * BlockItem::addPin(BlockItemPin * pin, bool update_) {
	pin->setParentItem(this);
	if (!pins_[pin->alignment()].contains(pin)) pins_[pin->alignment()] << pin;
	pin->parent_ = this;
	if (update_)
		arrangePins();
	return pin;
}


BlockItemPin * BlockItem::addPin(Qt::Alignment align, int bus_type, const QString & text, bool update_) {
	BlockItemPin * pin = new BlockItemPin(align, bus_type, text, this);
	pin->parent_ = this;
	pins_[pin->alignment()] << pin;
	if (update_)
		arrangePins();
	return pin;
}


void BlockItem::removePin(BlockItemPin * pin) {
	if (!pin) return;
	QMutableMapIterator<Qt::Alignment, QVector<BlockItemPin * > > it(pins_);
	while (it.hasNext()) {
		it.next();
		QVector<BlockItemPin * > & pv(it.value());
		if (pv.contains(pin))
			pv.remove(it.value().indexOf(pin));
	}
	delete pin;
	arrangePins();
}


void BlockItem::addDecor(QGraphicsItem * item) {
	if (decors_.contains(item)) return;
	if (qgraphicsitem_cast<QGraphicsPixmapItem*>(item))
		qgraphicsitem_cast<QGraphicsPixmapItem*>(item)->setTransformationMode(Qt::SmoothTransformation);
	if (qgraphicsitem_cast<QGraphicsSimpleTextItem*>(item))
		qgraphicsitem_cast<QGraphicsSimpleTextItem*>(item)->setData(1010, qgraphicsitem_cast<QGraphicsSimpleTextItem*>(item)->text());
	if (qgraphicsitem_cast<AlignedTextItem*>(item))
		qgraphicsitem_cast<AlignedTextItem*>(item)->setData(1010, qgraphicsitem_cast<AlignedTextItem*>(item)->text());
	item->setData(1002, true);
	item->setData(1011, true);
	decors_ << item;
	item->setParentItem(this);
}


void BlockItem::addDecor(QGraphicsItem & item) {
	if (decors_.contains(&item)) return;
	if (qgraphicsitem_cast<QGraphicsPixmapItem*>(&item))
		qgraphicsitem_cast<QGraphicsPixmapItem*>(&item)->setTransformationMode(Qt::SmoothTransformation);
	if (qgraphicsitem_cast<QGraphicsSimpleTextItem*>(&item))
		qgraphicsitem_cast<QGraphicsSimpleTextItem*>(&item)->setData(1010, qgraphicsitem_cast<QGraphicsSimpleTextItem*>(&item)->text());
	if (qgraphicsitem_cast<AlignedTextItem*>(&item))
		qgraphicsitem_cast<AlignedTextItem*>(&item)->setData(1010, qgraphicsitem_cast<AlignedTextItem*>(&item)->text());
	item.setData(1002, true);
	item.setData(1011, true);
	//decors_ << &item;
	item.setParentItem(this);
}


void BlockItem::removeDecor(QGraphicsItem * item) {
	if (scene() && item)
		scene()->sendEvent(item, new QGraphicsSceneEvent(QEvent::Close));
	decors_.removeAll(item);
	delete item;
}


QVector<BlockItemPin * > BlockItem::takePins() {
	QVector<BlockItemPin * > ret = pins();
	pins_.clear();
	return ret;
}


void BlockItem::clearPins() {
	QList<QVector<BlockItemPin * > > mp = pins_.values();
	for (int i = 0; i < mp.size(); ++i)
		qDeleteAll(mp[i]);
	pins_.clear();
}


void BlockItem::clearDecors() {
	bool pbs = false;
	if (scene()) pbs = scene()->blockSignals(true);
	if (scene())
		foreach (QGraphicsItem * i, decors_)
			scene()->sendEvent(i, new QGraphicsSceneEvent(QEvent::Close));
	qDeleteAll(decors_);
	decors_.clear();
	if (scene()) {
		scene()->blockSignals(pbs);
		QMetaObject::invokeMethod(scene(), "selectionChanged");
	}
}


QVector<BlockItemPin * > BlockItem::pins() const {
	QList<QVector<BlockItemPin * > > mp = pins_.values();
	QVector<BlockItemPin * > ret;
	for (int i = 0; i < mp.size(); ++i)
		ret << mp[i];
	return ret;
}


QByteArray BlockItem::saveModel() {
	/*QDataStream s(&ret, QIODevice::ReadWrite);
	QVector<BlockItemPin * > ps = pins();
	s << pos() << rotation() << size() << color() << ps.size();
	foreach (BlockItemPin * p, ps)
		s << int(p->alignment()) << p->busType() << p->text() << p->devices();
	s << decors_.size();
	foreach (QGraphicsItem * i, decors_)
		s << i;*/
	ChunkStream cs;
	cs << cs.chunk(1, pos()) << cs.chunk(2, rotation()) << cs.chunk(3, size()) << cs.chunk(4, color()) << cs.chunk(5, pins())
	   << cs.chunk(6, decors_) << cs.chunk(7, pins_margin) << cs.chunk(0xFF, _blockitem_current_version_);
	return cs.data();
}


void BlockItem::loadModel(const QByteArray & data) {
	//qDebug() << "load from" << data.size() << "bytes";
	clearPins();
	clearDecors();
	col = Qt::lightGray;
	_resize(QSizeF(100., 60.));
	if (data.isEmpty()) return;
	/*QDataStream s(data);
	{QPointF _v; s >> _v;}
	{qreal _v; s >> _v;}
	{QSizeF _v; s >> _v; setSize(_v);}
	{QColor _v; s >> _v; setColor(_v);}
	int _s; s >> _s;
	for (int i = 0; i < _s; ++i) {
		BlockItemPin * pin = new BlockItemPin();
		{int _v; s >> _v; pin->setAlignment((Qt::Alignment)_v);}
		{int _v; s >> _v; pin->setBusType(_v);}
		{QString _v; s >> _v; pin->setText(_v);}
		{QMap<QString, BlockItemPin::Device> _v; s >> _v; pin->setDevices(_v);}
		addPin(pin);
	}
	s >> _s;
	for (int i = 0; i < _s; ++i) {
		if (s.atEnd()) break;
		QGraphicsItem * ni = 0;
		s >> ni;
		if (ni) {
			addDecor(ni);
		}
	}*/
	ChunkStream cs(data);
	QVector<BlockItemPin * > tp;
	QList<QGraphicsItem * > dl;
	int version = -1;
	while (!cs.atEnd()) {
		switch (cs.read()) {
		case 1: /*setPos(cs.getData<QPointF>());*/ break;
		case 2: /*setRotation(cs.getData<qreal>());*/ break;
		case 3: setSize(cs.getData<QSizeF>()); break;
		case 4: setColor(cs.getData<QColor>()); break;
		case 5:
			cs.get(tp);
			foreach (BlockItemPin * p, tp) addPin(p);
			break;
		case 6:
			cs.get(dl);
			foreach (QGraphicsItem * d, dl) addDecor(d);
			break;
		case 7: setPinsMargin(cs.getData<int>()); break;
		case 0xFF: cs.get(version); break;
		}
	}
	if (version <= 0)
		_moveToTop(true);
}


QByteArray BlockItem::save() const {
	ChunkStream cs;
	QMap<QString, QList<BlockItem::Property> > pp;
	foreach (BlockItemPin * p, pins()) {
		//qDebug() << "save pin" << p->text() << "->" << p->properties().size();
		pp[p->text()] = p->properties();
	}
	cs << cs.chunk(1, pos()) << cs.chunk(2, rotation()) << cs.chunk(3, props) << cs.chunk(5, pp) << cs.chunk(6, size());
	cs << cs.chunk(10, data(2000)) << cs.chunk(11, data(2001)) << cs.chunk(12, prop_bindings) << cs.chunk(0xFF, _blockitem_current_version_);
	return cs.data();
}


void BlockItem::load(const QByteArray & data) {
	if (data.isEmpty()) return;
	ChunkStream cs(data);
	QMap<QString, QList<BlockItem::Property> > _p;
	int version = -1;
	while (!cs.atEnd()) {
		switch (cs.read()) {
		case 1: setPos(cs.getData<QPointF>()); break;
		case 2: setRotation(cs.getData<qreal>()); break;
		case 3: cs.get(props); break;
		case 5:
			cs.get(_p);
			//qDebug() << "load pins" << _p.size();
			foreach (BlockItemPin * p, pins()) {
				//qDebug() << "load pin" << p->text() << "->" << _p.contains(p->text());
				if (_p.contains(p->text()))
					p->properties() = _p[p->text()];
			}
			break;
		case 6: setSize(cs.getData<QSizeF>()); break;
		case 10: setData(2000, cs.getData<QVariant>()); break;
		case 11: setData(2001, cs.getData<QVariant>()); break;
		case 12: prop_bindings = cs.getData<QList<QPair<QString, QString> > >(); break;
		case 0xFF: cs.get(version); break;
		}
	}
	if (version <= 0) {
		_moveToTop();
	}
}


BlockItem * BlockItem::copy() const {
	BlockItem * ret = new BlockItem();
	ret->setPos(pos());
	ret->setSize(size());
	ret->setColor(color());
	ret->setSelected(false);
	ret->props = props;
	ret->setPinsMargin(pinsMargin());
	QVector<BlockItemPin * > mp = pins();
	foreach (BlockItemPin * p, mp) {
		BlockItemPin * np = new BlockItemPin();
		np->setBusType(p->busType());
		np->setAlignment(p->alignment());
		np->setText(p->text());
		np->setToolTip(p->toolTip());
		np->properties() = p->properties();
		ret->addPin(np);
	}
	QByteArray ba;
	foreach (QGraphicsItem * i, decors_) {
		ba.clear();
		QGraphicsItem * ni = 0;
		QDataStream s(&ba, QIODevice::ReadWrite); s << i;
		QDataStream s2(ba); s2 >> ni;
		if (ni)
			ret->addDecor(ni);
	}
	return ret;
}


QList<BlockBusItem * > BlockItem::connectedBuses() const {
	QList<BlockBusItem * > ret;
	foreach (BlockItemPin * p, pins())
		ret << p->connectedBuses();
	return ret;
}


BlockItemPin * BlockItem::pinByText(const QString & t) const {
	foreach (BlockItemPin * p, pins())
		if (p->text() == t)
			return p;
	return 0;
}


BlockItemPin * BlockItem::pinAtBus(BlockBusItem * bus) const {
	if (bus == 0) return 0;
	foreach (BlockItemPin * p, pins())
		if (p->connectedBuses().contains(bus))
			return p;
	return 0;
}


QRectF BlockItem::sceneRect() const {
	return g_main.mapRectToScene(g_main.boundingRect());
}


QRectF BlockItem::boundingRect() const {
	return g_main.mapRectToParent(g_main.boundingRect());
}


void BlockItem::mouseMoveEvent(QGraphicsSceneMouseEvent * event) {
	//QGraphicsItem::mouseMoveEvent(event);
}


void BlockItem::hoverEnterEvent(QGraphicsSceneHoverEvent * e) {
	bool anim = ((BlockView *)scene()->views().back())->isBlockAnimationEnabled();
	if (anim) {
		anim_thick.stop();
		anim_thick.setStartValue(thickness());
		anim_thick.setEndValue(2.5);
		anim_thick.start();
	} else setThickness(2.5);
	emit blockHoverEnter(this);
}


void BlockItem::hoverLeaveEvent(QGraphicsSceneHoverEvent * e) {
	bool anim = ((BlockView *)scene()->views().back())->isBlockAnimationEnabled();
	if (anim) {
		anim_thick.stop();
		anim_thick.setStartValue(thickness());
		anim_thick.setEndValue(1);
		anim_thick.start();
	} else setThickness(1);
	emit blockHoverLeave(this);
}


#define _POS(m) (i - ((cp.size() - 1) / 2)) * m

void BlockItem::arrangePins() {
	//double w = g_main.rect().width(), h = g_main.rect().height();
	QVector<BlockItemPin * > pl = pins();
//	pl = pl.fromList(pins().toList());
	pins_.clear();
	foreach (BlockItemPin * p, pl)
		pins_[p->alignment()] << p;
	QVector<BlockItemPin * > cp = pins_.value(Qt::AlignBottom);
	for (int i = 0; i < cp.size(); ++i) cp[i]->setPos(_POS(pins_margin), bottom());
	cp = pins_.value(Qt::AlignTop);
	for (int i = 0; i < cp.size(); ++i) cp[i]->setPos(_POS(pins_margin), top());
	cp = pins_.value(Qt::AlignLeft);
	for (int i = 0; i < cp.size(); ++i) cp[i]->setPos(left(), i * pins_margin);
	cp = pins_.value(Qt::AlignRight);
	for (int i = 0; i < cp.size(); ++i) cp[i]->setPos(right(), i * pins_margin);
}

#undef _POS


void BlockItem::removeBindings(const QString & bind_name) {
	for(int i=0; i<prop_bindings.size(); ++i) {
		if (prop_bindings[i].second == bind_name) {
			prop_bindings.removeAt(i);
			i--;
		}
	}
}


void BlockItem::removeBindingByProperty(const QString & prop_name) {
	for(int i=0; i<prop_bindings.size(); ++i) {
		if (prop_bindings[i].first == prop_name) {
			prop_bindings.removeAt(i);
			i--;
		}
	}
}


void BlockItem::addBinding(const QString & prop_name, const QString & bind_name) {
	QPair<QString, QString> pb(prop_name, bind_name);
	prop_bindings.removeAll(pb);
	prop_bindings << pb;
}


void BlockItem::applyBinding(const QString & bind_name, const QVariant & bind_value) {
	for(int i=0; i<prop_bindings.size(); ++i) {
		if (prop_bindings[i].second == bind_name) {
			setPropertyValue(prop_bindings[i].first, bind_value);
		}
	}
}


void BlockItem::applyBindings(const PropertyStorage & bindings) {
	for(int i=0; i<prop_bindings.size(); ++i) {
		if (bindings.isPropertyExists(prop_bindings[i].second)) {
			setPropertyValue(prop_bindings[i].first, bindings.propertyValueByName(prop_bindings[i].second));
		}
	}
}


void BlockItem::setBindings(const QList<QPair<QString, QString> > & bindings) {
	prop_bindings = bindings;
}


QList<QPair<QString, QString> > BlockItem::getBindings() {
	return prop_bindings;
}


QString BlockItem::getBindName(const QString & prop_name) const {
	for(int i=0; i<prop_bindings.size(); ++i) {
		if (prop_bindings[i].first == prop_name) {
			return prop_bindings[i].second;
		}
	}
	return QString();
}


QStringList BlockItem::getBindNames() const {
	QStringList ret;
	for(int i=0; i<prop_bindings.size(); ++i)
		if (!ret.contains(prop_bindings[i].second)) ret << prop_bindings[i].second;
	return ret;
}

QStringList BlockItem::getBindProps() const {
	QStringList ret;
	for(int i=0; i<prop_bindings.size(); ++i)
		ret << prop_bindings[i].first;
	return ret;
}


void BlockItem::paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget) {
	//if ()
}


QVariant BlockItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant & value) {
	if (change == QGraphicsItem::ItemSelectedChange) {
//		qDebug() << "select" << value.toBool();
		if (value.toBool() && !isSelected() && ((BlockView *)scene()->views().back())->isBlockAnimationEnabled() && t_sel.elapsed() > 50) {
			g_selection.setRect(enlargedRect(g_main.rect(), 0, 0, 16));
			anim_sel.setStartValue(selectionRect());
			anim_sel.setEndValue(enlargedRect(g_main.rect(), 0, 0, 8));
			anim_sel.start();
		}
		t_sel.restart();
		g_selection.setVisible(value.toBool());
	}
	return QGraphicsItem::itemChange(change, value);
}


double BlockItem::thickness() const {
	return g_main.pen().widthF();
}


void BlockItem::setThickness(double w) {
	QPen p = g_main.pen();
	p.setWidthF(w);
	g_main.setPen(p);
}


QRectF BlockItem::selectionRect() const {
	return g_selection.rect();
}


void BlockItem::setSelectionRect(const QRectF & r) {
	g_selection.setRect(r);
}

