/*
Copyright (C) 2006 - 2009 Evan Teran
                   eteran@alum.rit.edu

Copyright (C) 2009 Arvin Schnell
                   aschnell@suse.de

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/


#include <cmath>
#include <climits>

#include <QObject>
#include <QDebug>
#include <QKeyEvent>
#include <QWheelEvent>
#include <QGraphicsSceneMouseEvent>

#include "GraphWidget.h"
#include "QY2Node.h"
#include "QY2Edge.h"

#include <graphviz/gvc.h>
#include <boost/bind.hpp>

//------------------------------------------------------------------------------
// Name:
// Desc:
//------------------------------------------------------------------------------
GraphWidget::GraphWidget(const QString& filename, const QString& layout, QWidget* parent) : GraphWidgetBase(parent) {
	renderGraph(filename, layout);
}

//------------------------------------------------------------------------------
// Name:
// Desc:
//------------------------------------------------------------------------------
GraphWidget::GraphWidget(GVC_t *gvc, graph_t *graph, const QString& layout, QWidget* parent) : GraphWidgetBase(parent) {
	renderGraph(gvc, graph, layout);
}

//------------------------------------------------------------------------------
// Name:
// Desc:
//------------------------------------------------------------------------------
GraphWidget::~GraphWidget() {
}

//------------------------------------------------------------------------------
// Name:
// Desc:
//------------------------------------------------------------------------------
void GraphWidget::keyPressEvent(QKeyEvent* event) {
	switch (event->key()) {
	case Qt::Key_Plus:
		scaleView(1.2);
		break;
	case Qt::Key_Minus:
		scaleView(1.0 / 1.2);
		break;
	case Qt::Key_Asterisk:
		rotate(10.0);
		break;
	case Qt::Key_Slash:
		rotate(-10.0);
		break;
	default:
		QGraphicsView::keyPressEvent(event);
	}
}

//------------------------------------------------------------------------------
// Name:
// Desc:
//------------------------------------------------------------------------------
void GraphWidget::wheelEvent(QWheelEvent* event) {
	scaleView(std::pow(2.0, +event->delta() / 240.0));
}

//------------------------------------------------------------------------------
// Name:
// Desc:
//------------------------------------------------------------------------------
void GraphWidget::scaleView(qreal scaleFactor) {
	const qreal f = ::sqrt(matrix().det());
	
	scaleFactor = qBound(0.1 / f, scaleFactor, 8.0 / f);
		
	scale(scaleFactor, scaleFactor);
}

//------------------------------------------------------------------------------
// Name:
// Desc:
//------------------------------------------------------------------------------
void GraphWidget::contextMenuEvent(QContextMenuEvent* event) {
	
	QY2Node *node = qgraphicsitem_cast<QY2Node*>(itemAt(event->pos()));

	if(node) {
		emit nodeContextMenuEvent(event, node->name);
	} else {
		emit backgroundContextMenuEvent(event);
	}
}

//------------------------------------------------------------------------------
// Name:
// Desc:
//------------------------------------------------------------------------------
void GraphWidget::mouseDoubleClickEvent(QMouseEvent* event) {
	QY2Node *node = qgraphicsitem_cast<QY2Node*>(itemAt(event->pos()));

	if(node) {
		emit nodeDoubleClickEvent(event, node->name);
	}
}

//------------------------------------------------------------------------------
// Name:
// Desc:
//------------------------------------------------------------------------------
QPointF GraphWidget::gToQ(const pointf& p, bool upside_down) const {
	return upside_down ? QPointF(p.x, graphRect.height() - p.y) : QPointF(p.x, -p.y);
}

//------------------------------------------------------------------------------
// Name:
// Desc:
//------------------------------------------------------------------------------
QPointF GraphWidget::gToQ(const point& p, bool upside_down) const {
	return upside_down ? QPointF(p.x, graphRect.height() - p.y) : QPointF(p.x, -p.y);
}

//------------------------------------------------------------------------------
// Name:
// Desc:
//------------------------------------------------------------------------------
QColor GraphWidget::aggetToQColor(void *obj, const char *name, const QColor& fallback) const {
	const char *tmp = agget(obj, const_cast<char*>(name));
	if (tmp == 0 || strlen(tmp) == 0)
		return fallback;
	
	return QColor(tmp);
}

//------------------------------------------------------------------------------
// Name:
// Desc:
//------------------------------------------------------------------------------
Qt::PenStyle GraphWidget::aggetToQPenStyle(void *obj, const char *name, const Qt::PenStyle fallback) const {
	const char *tmp = agget(obj, const_cast<char*>(name));
	if (tmp == 0 || strlen(tmp) == 0)
		return fallback;
	
	if (strcmp(tmp, "dashed") == 0)
		return Qt::DashLine;

	if (strcmp(tmp, "dotted") == 0)
		return Qt::DotLine;
		
	if (strcmp(tmp, "solid") == 0)
		return Qt::SolidLine;
		
	return fallback;
}

//------------------------------------------------------------------------------
// Name:
// Desc:
//------------------------------------------------------------------------------
void GraphWidget::renderGraph(const QString& filename, const QString& layout) {
	FILE *const fp = fopen(qPrintable(filename), "r");
	if(fp != 0) {
		GVC_t *gvc = gvContext();
		if (gvc != 0) {
			graph_t *graph = agread(fp);
			if (graph != 0) {
				renderGraph(gvc, graph, layout);
				agclose(graph);			
			} else {
				qCritical("agread() failed");
			}
			gvFreeContext(gvc);
		} else {
			qCritical("gvContext() failed");
		}
		fclose(fp);
	} else {
		qCritical("failed to open %s", qPrintable(filename));
	}
}

//------------------------------------------------------------------------------
// Name:
// Desc:
//------------------------------------------------------------------------------
void GraphWidget::renderSubGraph(GVC_t *gvc, graph_t *graph) {
	
	if(graph) {
		for(int i = 1; i <= graph->u.n_cluster; ++i) {
			graph_t * sub_graph = graph->u.clust[i];
			renderSubGraph(gvc, sub_graph);
		}
		
		drawGraphLabel(graph);
	}

	const QRectF subGraphRect(
		gToQ(GD_bb(graph).LL),
		gToQ(GD_bb(graph).UR)
		);
	
	QPen graphPen(aggetToQColor(graph, "color", Qt::white));
	
	const QString style = QString::fromUtf8(agget(graph, const_cast<char *>("style")));
	const QBrush graphBrush(
		style.contains("filled") ? aggetToQColor(graph, "color", Qt::white) : QBrush()
		);
	
	QGraphicsRectItem *item = scene->addRect(subGraphRect, graphPen, graphBrush);
	item->setZValue(INT_MIN);
}

//------------------------------------------------------------------------------
// Name:
// Desc:
//------------------------------------------------------------------------------
void GraphWidget::renderEdge(edge_t *edge) {
	const splines* spl = ED_spl(edge);

	if (spl == 0)
		return;

	for (int i = 0; i < spl->size; ++i) {
		const bezier& bz = spl->list[i];
		const QColor color(aggetToQColor(edge, "color", Qt::black));
		
		QY2Edge *const item = new QY2Edge(this, bz, color);

		QPen pen(color);
		pen.setStyle(aggetToQPenStyle(edge, "style", Qt::SolidLine));
		pen.setWidthF(1.0);
		item->setPen(pen);

		item->setZValue(-1.0);
		scene->addItem(item);
	}
}

//------------------------------------------------------------------------------
// Name:
// Desc:
//------------------------------------------------------------------------------
void GraphWidget::renderNode(graph_t *graph, node_t *node) {
	const QString node_style = QString::fromUtf8(agget(node, const_cast<char *>("style")));
	const QStringList node_styles = node_style.split(",");
	
	if(!node_styles.contains("invisible")) {

		QY2Node* item = new QY2Node(this, node);
		item->setZValue(1.0);
#ifdef ND_coord_i
		item->setPos(gToQ(ND_coord_i(node)));
#else
		item->setPos(gToQ(ND_coord(node)));
#endif

		QPen pen(aggetToQColor(node, "color", Qt::black));
		pen.setWidthF(1.0);
		item->setPen(pen);

		if(node_styles.contains("filled")) {
			QString fill_color = QString::fromUtf8(agget(node, const_cast<char *>("fillcolor")));
			if(fill_color.isEmpty()) {
				fill_color = QString::fromUtf8(agget(node, const_cast<char *>("color")));
			}
			
			QColor color(fill_color);
			if(color.isValid()) {
				item->setBrush(QBrush(color));
			} else {
				item->setBrush(QBrush(Qt::gray));
			}
		}

		QString tooltip = QString::fromUtf8(agget(node, const_cast<char *>("tooltip")));
		if (!tooltip.isEmpty()) {
			tooltip.replace("\\n", "\n");
			item->setToolTip(tooltip);
		}

		scene->addItem(item);
				
		for(edge_t *e = agfstout(graph, node); e != 0; e = agnxtout(graph, e)) {
			renderEdge(e);
		}
	}
}

//------------------------------------------------------------------------------
// Name:
// Desc:
//------------------------------------------------------------------------------
void GraphWidget::renderGraph(GVC_t *gvc, graph_t *graph, const QString& layout) {

	if (gvLayout(gvc, graph, const_cast<char *>(qPrintable(layout))) == 0) {
		clearGraph();
		if (GD_charset(graph) != 0) {
			qWarning("unsupported charset");
		}
		
		// don't use gToQ here since it adjusts the values
		graphRect = QRectF(
			GD_bb(graph).LL.x,
			GD_bb(graph).LL.y,
			GD_bb(graph).UR.x,
			GD_bb(graph).UR.y);
			
		scene->setSceneRect(graphRect.adjusted(-5, -5, +5, +5));
		scene->setBackgroundBrush(aggetToQColor(graph, "bgcolor", Qt::white));
		
		renderSubGraph(gvc, graph);
		
		for(node_t *n = agfstnode(graph); n != 0; n = agnxtnode(graph, n)) {
			renderNode(graph, n);
		}
	} else {
		qCritical("gvLayout() failed");
	}
}

//------------------------------------------------------------------------------
// Name:
// Desc:
//------------------------------------------------------------------------------
void GraphWidget::drawGraphLabel(graph_t *graph) {
	const textlabel_t *textlabel = GD_label(graph);
	if(textlabel) {
#if 0
		const QString text(QString::fromUtf8(textlabel->text));

		// Since I always just take the points from graph and pass them to Qt
		// as pixel I also have to set the pixel size of the font.
		QFont font(textlabel->fontname, textlabel->fontsize);
		font.setPixelSize(textlabel->fontsize);

		if (!font.exactMatch()) {
			QFontInfo fontinfo(font);
			qWarning("replacing font \"%s\" by font \"%s\"", qPrintable(font.family()), qPrintable(fontinfo.family()));
		}

		QGraphicsTextItem *item = scene->addText(text, font);

		const QRectF subGraphRect(
			gToQ(GD_bb(graph).LL),
			gToQ(GD_bb(graph).UR)
			);

		const pointf p = textlabel->pos;
		QPointF pos = true ? QPointF(p.x, subGraphRect.height() - p.y) : QPointF(p.x, -p.y);
		item->setPos(pos);
#endif
	}
}

//------------------------------------------------------------------------------
// Name:
// Desc:
//------------------------------------------------------------------------------
void GraphWidget::clearGraph() {
	qDeleteAll(scene->items());
}
