#include "blockbusitem.h"


BlockBusItem::BlockBusItem(bool temp): QGraphicsObject(), PropertyStorage() {
	temp_ = temp;
	_init();
	if (!temp) setData(1005, "connection");
	else hide();
}


BlockBusItem::BlockBusItem(const BlockBusItem & other): QGraphicsObject(), PropertyStorage() {
	temp_ = false;
	_init();
	setData(1005, "connection");
	setBusType(other.busType());
	max_ep = other.max_ep;
	pol = other.pol;
	segments = other.segments;
	updateGeometry();
}


void BlockBusItem::_init() {
	setBusType(-1);
	setAcceptHoverEvents(true);
	ph.setColor(Qt::blue); ph.setJoinStyle(Qt::MiterJoin);
	bh.setColor(Qt::blue); bh.setStyle(Qt::SolidPattern);
	pu = pa = pr = ph; bu = ba = br = bh;
	grid_step = 10.;
	pu.setWidth(1);
	pu.setColor(Qt::black); bu.setColor(Qt::black);
	pa.setColor(Qt::darkGreen); ba.setColor(Qt::darkGreen);
	pr.setColor(Qt::darkRed); br.setColor(Qt::darkRed);
	pn.setColor(Qt::gray); pn.setStyle(Qt::DashLine);
	if (temp_) {
		pu.setStyle(Qt::DashLine);
		pu.setColor(Qt::darkGray);
		bu.setColor(Qt::darkGray);
	}
	setPen(pu); setBrush(bu);
	max_ep = 0;
	selPoint = selSegment = state_ = -1;
	pen_width = 2.;
	moved = deleted = mark_in = mark_out = new_segment = mm_cancel = lm_point = false;
}


void BlockBusItem::reconnect() {
	if (temp_) return;
	if (!scene()) return;
	if (scene()->views().isEmpty()) return;
	QMetaObject::invokeMethod(scene()->views().back(), "reconnectAll");
}


bool BlockBusItem::sceneEvent(QEvent * e) {
	if (temp_) return QGraphicsObject::sceneEvent(e);
	switch (e->type()) {
	case QEvent::GraphicsSceneHoverEnter: hoverEnterEvent((QGraphicsSceneHoverEvent * )e); break;
	case QEvent::GraphicsSceneHoverMove: hoverMoveEvent((QGraphicsSceneHoverEvent * )e); break;
	case QEvent::GraphicsSceneHoverLeave: hoverLeaveEvent((QGraphicsSceneHoverEvent * )e); break;
	case QEvent::GraphicsSceneMousePress: mousePressEvent((QGraphicsSceneMouseEvent * )e); break;
	case QEvent::GraphicsSceneMouseMove: mouseMoveEvent((QGraphicsSceneMouseEvent * )e); break;
	case QEvent::GraphicsSceneMouseRelease: mouseReleaseEvent((QGraphicsSceneMouseEvent * )e); break;
	default: break;
	}
	return QGraphicsObject::sceneEvent(e);
}


int BlockBusItem::addPoint(const QPointF & point, bool update) {
	int ei = pol.indexOf(point);
	if (ei >= 0) return ei;
	if (selSegment < 0 || selSegment >= pol.size() - 1) return -1;
	pol << quantize(nearestPointOnLine(pol[segments[selSegment].first], pol[segments[selSegment].second], point), grid_step);
	selPoint = pol.size() - 1;
	segments << QPair<int, int>(selPoint, segments[selSegment].second);
	segments[selSegment].second = selPoint;
	selSegment = -1;
	updateGeometry();
	if (scene() != 0 && update) scene()->update();
	return pol.size() - 1;
}


int BlockBusItem::segmentPointPair(int point) const {
	for (int i = 0; i < segments.size(); ++i) {
		if (segments[i].first == point) return segments[i].second;
		if (segments[i].second == point) return segments[i].first;
	}
	return -1;
}


void BlockBusItem::removePoint(int index) {
	if (index < 0 || index > pol.size() - 1) return;
	int sc = 0, fs = -1, ss = -1;
	for (int i = 0; i < segments.size(); ++i)
		if (segments[i].first == index ||
			segments[i].second == index) {
				sc++;
				if (fs < 0) fs = i;
				else ss = i;
		}
	int ei(0);
	switch (sc) {
	case 1:
		segments.removeAt(fs);
		break;
	case 2:
		if (segments[ss].first == index) ei = segments[ss].second;
		else ei = segments[ss].first;
		if (segments[fs].first == index) segments[fs].first = ei;
		else segments[fs].second = ei;
		segments.removeAt(ss);
		break;
	default: return;
	}
	pol.remove(index);
	for (int i = 0; i < segments.size(); ++i) {
		if (segments[i].first >= index)
			segments[i].first--;
		if (segments[i].second >= index)
			segments[i].second--;
	}
	selPoint = -1;
	checkDelete();
	updateGeometry();
	if (scene() != 0) scene()->update();
}


void BlockBusItem::removeSegment(int index) {
	if (index < 0 || index > segments.size() - 1) return;
	int pif = segments[index].first, pis = segments[index].second;
	if (pif > pis) qSwap<int>(pif, pis);
	int scf = 0, scs = 0;
	for (int i = 0; i < segments.size(); ++i) {
		if (segments[i].first == pif ||
			segments[i].second == pif)
				scf++;
		if (segments[i].first == pis ||
			segments[i].second == pis)
				scs++;
	}
	if (scs <= 2) removePoint(pis);
	if (scf <= 2) removePoint(pif);
	if (scs <= 2 || scf <= 2) selSegment = -1;
	if (scene() != 0) scene()->update();
	
}


void BlockBusItem::appendPoint(const QPointF & p) {
	pol << p;
	if (pol.size() > 1)
		segments << QPair<int, int>(pol.size() - 2, pol.size() - 1);
	updateGeometry();
}


void BlockBusItem::clear() {
	pol.clear();
	segments.clear();
	updateGeometry();
}


void BlockBusItem::markAsInput() {
	mark_in = true;
	mark_out = false;
}


void BlockBusItem::markAsOutput() {
	mark_in = false;
	mark_out = true;
}


void BlockBusItem::unmark() {
	mark_in = mark_out = false;
}


void BlockBusItem::simplify() {
	int pcnt = pol.size();
	for (int i = 0; i < pol.size() - 1; ++i) {
		if (pol[i] == pol[i + 1])
			pol.remove(i--);
	}
	if (pol.size() < 3) return;
	//PIMathVectorT2d cl, pl;
	for (int i = 0; i < pol.size() - 2; ++i) {
		//pl = Q2PIVector2(pol[i]) - Q2PIVector2(pol[i + 1]);
		//cl = Q2PIVector2(pol[i + 1]) - Q2PIVector2(pol[i + 2]);
		//if (pl.turnTo<3, double>() || cl.turnTo<3, double>())
		//	pol.remove(i-- + 1);
	}
	if (pcnt == pol.size()) return;
	//GBFrom->emitAddToHistory(History::ConnectionSimplify, "");
	updateGeometry();
	if (scene() != 0) scene()->update();
}


void BlockBusItem::adjustLine() {
}


int BlockBusItem::endpointCount() const {
	return endpoints().size();
}



QList<BlockItem * > BlockBusItem::connectedBlocks() const {
	QList<BlockItemPin * > pins = connections_.values();
	QSet<BlockItem * > ret;
	foreach (BlockItemPin * p, pins)
		ret << p->parent();
	return ret.toList();
}


QList<BlockItemPin * > BlockBusItem::connectedPins() const {
	return connections_.values();
}


void BlockBusItem::setBusState(bool state) {
	int s = state ? 1 : 0;
	if (state_ == s) return;
	state_ = s;
	update();
}


void BlockBusItem::clearBusState() {
	if (state_ == -1) return;
	state_ = -1;
	update();
}


QByteArray BlockBusItem::save() const {
	ChunkStream cs;
	cs << cs.chunk(1, busType()) << cs.chunk(2, busName()) << cs.chunk(3, width()) << cs.chunk(4, pen())
		<< cs.chunk(5, brush()) << cs.chunk(6, pol) << cs.chunk(7, segments) << cs.chunk(8, props);
	return cs.data();
}


void BlockBusItem::load(const QByteArray & data) {
	clear();
	if (data.isEmpty()) return;
	ChunkStream cs(data);
	while (!cs.atEnd()) {
		switch (cs.read()) {
			case 1: setBusType(cs.getData<int>()); break;
			case 2: setBusName(cs.getData<QString>()); break;
			case 3: setWidth(cs.getData<double>()); break;
			case 4: setPen(cs.getData<QPen>()); break;
			case 5: setBrush(cs.getData<QBrush>()); break;
			case 6: pol = cs.getData<QPolygonF>(); break;
			case 7: segments = cs.getData<QList<QPair<int, int> > >(); break;
			case 8: props = cs.getData<QList<BlockItem::Property> >(); break;
		}
	}
	updateGeometry();
}


BlockBusItem * BlockBusItem::copy() const {
	return new BlockBusItem(*this);
}


void BlockBusItem::updateGeometry() {
	ends = endpoints();
	ends_ind.clear();
	for (int e = 0; e < ends.size(); ++e) {
		int ce = ends[e];
		for (int s = 0; s < segments.size(); ++s) {
			if (segments[s].first == ce) {
				ends_ind << QPair<int, int>(segments[s].first, segments[s].second);
				break;
			}
			if (segments[s].second == ce) {
				ends_ind << QPair<int, int>(segments[s].second, segments[s].first);
				break;
			}
		}
	}
	reconnect();
	prepareGeometryChange();
}


void BlockBusItem::checkDelete() {
	if (pol.size() < 2 || segments.size() < 1) deleteLater();
}


void BlockBusItem::emitAction(BlockItemBase::Action a) {
	QMetaObject::invokeMethod(scene()->views().back(), "actionEvent", Q_ARG(BlockItemBase::Action, a), Q_ARG(QList<QGraphicsItem*>, QList<QGraphicsItem*>() << this));
	QMetaObject::invokeMethod(scene()->views().back(), "connectionsChanged");
}


QVector<int> BlockBusItem::endpoints() const {
	QVector<int> counts(pol.size(), 0), ret;
	for (int i = 0; i < segments.size(); ++i) {
		counts[segments[i].first]++;
		counts[segments[i].second]++;
	}
	for (int i = 0; i < counts.size(); ++i) {
		if (counts[i] == 1)
			ret << i;
	}
	return ret;
}


int BlockBusItem::pointSegments(int point) const {
	int ret = 0;
	for (int i = 0; i < segments.size(); ++i)
		if (segments[i].first == point ||
			segments[i].second == point)
				ret++;
	return ret;
}


void BlockBusItem::testPoint(QPointF pos, int * sel_point, int * sel_segment) {
	for (int i = 0; i < pol.size(); ++i) {
		if ((pol[i] - pos).manhattanLength() <= 10.) { // Point
			*sel_point = i;
			*sel_segment = -1;
			return;
		}
	}
	for (int i = 0; i < segments.size(); ++i) {
		if (distPointToLine(pol[segments[i].first], pol[segments[i].second], pos) <= 7.) { // Segment
			*sel_point = -1;
			*sel_segment = i;
			return;
		}
	}
	*sel_point = -1;
	*sel_segment = -1;
}


void BlockBusItem::hoverEnterEvent(QGraphicsSceneHoverEvent * e) {
	tt = bus_name + (bus_name.isEmpty() ? "" : "\n\n") + tr("Add point: Ctrl + LeftClick\nRemove point\\segment: Ctrl + RightClick\nNew branch: Shift + LeftClick\nRemove connection: Shift + RightClick");
}


void BlockBusItem::hoverMoveEvent(QGraphicsSceneHoverEvent * e) {
	if (temp_) return;
	QPointF sp = e->scenePos();
	testPoint(sp, &selPoint, &selSegment);
	if (selPoint >= 0 || selSegment >= 0) {
		setToolTip(tt);
		update();
		return;
	}
	setToolTip(QString());
	QList<QGraphicsItem * > il = scene()->items(sp, Qt::ContainsItemBoundingRect, Qt::DescendingOrder), bil;
	bil << this;
	for (int i = 0; i < il.size(); ++i) {
		QGraphicsItem * b = il[i];
		if (b->data(1005) == "connection" && b != this) {
			int tp = -1, ts = -1;
			((BlockBusItem*)b)->testPoint(sp, &tp, &ts);
			if (tp >= 0 || ts >= 0) {
				foreach (QGraphicsItem * b2, bil)
					b2->stackBefore(b);
				break;
			}
			bil << b;
		}
	}
	update();
}


void BlockBusItem::hoverLeaveEvent(QGraphicsSceneHoverEvent * e) {
	if (temp_) return;
	selPoint = selSegment = -1;
	setPen(pu); setBrush(bu);
	setToolTip(QString());
	update();
	QGraphicsObject::hoverLeaveEvent(e);
}


void BlockBusItem::mousePressEvent(QGraphicsSceneMouseEvent * e) {
	if (temp_) return;
	lp = quantize(e->scenePos(), grid_step);
	if (new_segment) {
		QMetaObject::invokeMethod(scene()->views().back(), "newBranchCancel");
	}
	new_segment = false;
	if ((selPoint < 0 || selPoint > pol.size() - 1) && (selSegment < 0)) {
		QGraphicsObject::mousePressEvent(e);
		return;
	}
	int btncnt = 0;
	if (e->buttons().testFlag(Qt::LeftButton)) btncnt++;
	if (e->buttons().testFlag(Qt::RightButton)) btncnt++;
	if (e->buttons().testFlag(Qt::MidButton)) btncnt++;
	if (btncnt > 0) mm_mods = e->modifiers();
	//qDebug() << "press" << e;
	if (btncnt >= 2 && e->button() == Qt::RightButton) {
		mm_cancel = true;
		moved = false;
		QPointF lp = qp - press_pos;
		//qDebug() << lp;
		if (selPoint >= 0 && selPoint <= pol.size() - 1)
			pol[selPoint] += lp;
		if (selSegment >= 0 && selSegment <= segments.size() - 1) {
			pol[segments[selSegment].first] += lp;
			pol[segments[selSegment].second] += lp;
		}
		moved = true;
		prepareGeometryChange();
		return;
	}
	if (e->modifiers().testFlag(Qt::ShiftModifier)) {
		if (e->buttons().testFlag(Qt::LeftButton)) {
			if (selSegment >= 0)
				press_pos = quantize(nearestPointOnLine(pol[segments[selSegment].first], pol[segments[selSegment].second], e->scenePos()), grid_step);
			else {
				if (selPoint >= 0)
					press_pos = pol[selPoint];
				else
					return;
			}
			if (max_ep >= 2) {
				if (endpointCount() >= max_ep)
					if (pointSegments(selPoint) >= 2 || selSegment >= 0)
						return;
			}
			QMetaObject::invokeMethod(scene()->views().back(), "newBranch", Q_ARG(BlockBusItem * , this));
			new_segment = true;
			return;
		}
		if (e->buttons().testFlag(Qt::RightButton)) {
			deleteLater();
		}
	}
	if (e->modifiers().testFlag(Qt::ControlModifier)) {
		if (e->buttons().testFlag(Qt::RightButton)) {
			if (selPoint >= 0 && selPoint <= pol.size() - 1) {
				removePoint(selPoint);
				emitAction(BlockItemBase::BusPointRemove);
				return;
			}
			if (selSegment >= 0 && selSegment <= segments.size() - 1) {
				removeSegment(selSegment);
				emitAction(BlockItemBase::BusSegmentRemove);
				return;
			}
		}
		if (e->buttons().testFlag(Qt::LeftButton) && selSegment >= 0) {
			if (addPoint(e->scenePos()) >= 0)
				emitAction(BlockItemBase::BusPointAdd);
			return;
		}
	}
	if (e->modifiers().testFlag(Qt::ShiftModifier)) {
		if (e->buttons().testFlag(Qt::RightButton)) {
			if (deleted) return;
			deleted = true;
		}
	}
}


void BlockBusItem::mouseMoveEvent(QGraphicsSceneMouseEvent * e) {
	if (temp_ || mm_cancel) return;
	if (((selPoint < 0 || selPoint > pol.size() - 1) && (selSegment < 0)) && !new_segment) {
		QGraphicsObject::mouseMoveEvent(e);
		return;
	}
	qp = quantize(e->scenePos(), grid_step);
	lp = qp - lp;
	if (e->buttons().testFlag(Qt::LeftButton) && mm_mods.testFlag(Qt::ShiftModifier) && new_segment) {
		QMetaObject::invokeMethod(scene()->views().back(), "newBranchTrace", Q_ARG(BlockBusItem * , this), Q_ARG(QPointF, e->scenePos()));
		return;
	}
	if (new_segment) {
		new_end = qp;
		prepareGeometryChange();
	} else {
		if (e->buttons().testFlag(Qt::LeftButton)) {
			lm_point = selPoint >= 0;
			if (selPoint >= 0 && selPoint <= pol.size() - 1)
				pol[selPoint] += lp;
			if (selSegment >= 0 && selSegment <= segments.size() - 1) {
				pol[segments[selSegment].first] += lp;
				pol[segments[selSegment].second] += lp;
			}
			moved = true;
			prepareGeometryChange();
		}
	}
	lp = qp;
}


void BlockBusItem::mouseReleaseEvent(QGraphicsSceneMouseEvent * e) {
	mm_mods = 0;
	int btncnt = 0;
	if (e->buttons().testFlag(Qt::LeftButton)) btncnt++;
	if (e->buttons().testFlag(Qt::RightButton)) btncnt++;
	if (e->buttons().testFlag(Qt::MidButton)) btncnt++;
	if (btncnt == 0) mm_cancel = false;
	if (new_segment) {
		QMetaObject::invokeMethod(scene()->views().back(), "newBranchAccept", Q_ARG(BlockBusItem * , this));
		updateGeometry();
		selPoint = selSegment = -1;
	}
	if (moved) {
		reconnect();
		emitAction(lm_point ? BlockItemBase::BusPointMove : BlockItemBase::BusSegmentMove);
	}
	moved = new_segment = false;
    QGraphicsObject::mouseReleaseEvent(e);
}


void BlockBusItem::paint(QPainter * p, const QStyleOptionGraphicsItem * o, QWidget * w) {
	ph.setWidthF(pen_width + 1.);
	pu.setWidthF(pen_width);
	pa.setWidthF(pen_width);
	pr.setWidthF(pen_width);
	pn.setWidthF(pen_width);
	if (pol.size() < 2) return;
	if (selPoint >= 0 || selSegment >= 0) {
		p->setPen(pa);
		p->setBrush(ba);
	} else {
		p->setPen(pu);
		p->setBrush(bu);
	}
	//if (mark_in) {p->setPen(pa); p->setBrush(ba);}
	//if (mark_out) {p->setPen(pr); p->setBrush(br);}
	if (im_bus.isNull()) {
		for (int i = 0; i < segments.size(); ++i) {
			p->drawLine(pol[segments[i].first], pol[segments[i].second]);
		}
	} else {
		QBrush br;
		br.setTextureImage(im_bus);
		for (int i = 0; i < segments.size(); ++i) {
			QPointF sp(pol[segments[i].first]), ep(pol[segments[i].second]);
			QTransform tf;
			tf.translate(sp.x(), sp.y());
			tf.rotate(-QLineF(sp, ep).angle());
			tf.translate(0., -im_bus.height() / 2.);
			/*
			br.setTransform(tf);
			p->setPen(QPen(br, im_bus.height(), Qt::SolidLine, Qt::FlatCap, Qt::BevelJoin));
			p->drawLine(sp, ep);
			*/
			p->save();
			p->setTransform(tf, true);
			p->setPen(Qt::NoPen);
			p->setBrush(br);
			//p->drawLine(QPointF(0., 0.), QPointF(QLineF(sp, ep).length(), 0.));
			p->drawRect(QRectF(0., 0., QLineF(sp, ep).length(), im_bus.height()));
			p->restore();
		}
	}
	if (!im_end.isNull()) {
		for (int i = 0; i < ends_ind.size(); ++i) {
			QPointF sp = pol[ends_ind[i].first], ep = pol[ends_ind[i].second];
			QTransform tf;
			tf.translate(sp.x(), sp.y());
			tf.rotate(-QLineF(sp, ep).angle());
			tf.translate(-pen_width, -im_end.height() / 2.);
			p->save();
			p->setTransform(tf, true);
			p->drawImage(0, 0, im_end);
			p->restore();
		}
	}
	if (state_ >= 0) {
		if (state_ == 0) {
			pr.setWidthF(pen_width + 1.);
			p->setPen(pr);
		} else if (state_ == 1) {
			pa.setWidthF(pen_width + 1.);
			p->setPen(pa);
		}
		p->setOpacity(0.5);
		for (int i = 0; i < segments.size(); ++i) {
			p->drawLine(pol[segments[i].first], pol[segments[i].second]);
		}
		p->setOpacity(1.);
	}
	if (!im_bus.isNull() && (selPoint >= 0 || selSegment >= 0)) {
		p->setPen(pa);
		p->setBrush(ba);
		for (int i = 0; i < segments.size(); ++i) {
			p->drawLine(pol[segments[i].first], pol[segments[i].second]);
		}
	}
	//if (mark_in) {p->setPen(pa); p->setBrush(ba);}
	//if (mark_out) {p->setPen(pr); p->setBrush(br);}
	if (selPoint >= 0) {
		p->save();
		p->setPen(ph);
		p->setBrush(bh);
		p->translate(pol[selPoint]);
		p->drawEllipse(QPointF(0, 0), 3, 3);
		p->restore();
	}
	if (selSegment >= 0) {
		p->save();
		p->setPen(ph);
		p->drawLine(pol[segments[selSegment].first], pol[segments[selSegment].second]);
		p->restore();
	}
}


QRectF BlockBusItem::boundingRect() const {
	QPolygonF p(pol);
	p << new_end;
	return enlargedRect(p.boundingRect(), 0, 0, 10.f);
}
