/***************************************************************************
 *   Copyright (C) 2001 by Bernd Gehrmann                                  *
 *   bernd@kdevelop.org                                                    *
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#include "digraphview.h"

#include <math.h>
#include <stdlib.h>
#include <tqapplication.h>
#include <tqpainter.h>
#include <tqpaintdevicemetrics.h>
#include <tqtextstream.h>
#include <tdeglobal.h>
#include <tdelocale.h>
#include <tdemessagebox.h>
#include <tdeprocess.h>
#include <tdestandarddirs.h>
#include <tdeglobalsettings.h>
#include <tdetempfile.h>
#include <kdebug.h>

struct DigraphNode
{
    int x;
    int y;
    int w;
    int h;
    TQString name;
};


struct DigraphEdge
{
    TQPointArray points;
};


DigraphView::DigraphView(TQWidget *parent, const char *name)
    : TQScrollView(parent, name, WRepaintNoErase|WStaticContents|WResizeNoErase)
{
    viewport()->setBackgroundMode(PaletteBase);

    TQPaintDeviceMetrics m(this);
    xscale = m.logicalDpiX();
    yscale = m.logicalDpiY();

    width = -1;
    height = -1;

    nodes.setAutoDelete(true);
    edges.setAutoDelete(true);
    selNode = 0;
}


DigraphView::~DigraphView()
{
}


int DigraphView::toXPixel(double x)
{
    return (int) (x*xscale);
}


int DigraphView::toYPixel(double y)
{
    return height - (int) (y*yscale);
}


void DigraphView::setRenderedExtent(double w, double h)
{
    width = (int) (w*xscale);
    height = (int) (h*yscale);
    resizeContents(width+1, height+1);
}


void DigraphView::addRenderedNode(const TQString &name,
                                  double x, double y, double w, double h)
{
    DigraphNode *node = new DigraphNode;
    node->x = toXPixel(x);
    node->y = toYPixel(y);
    node->w = (int) (w*xscale);
    node->h = (int) (h*yscale);
    node->name = name;
    nodes.append(node);
}


void DigraphView::addRenderedEdge(const TQString &/*name1*/, const TQString &/*name2*/,
                                  TQMemArray<double> coords)
{
    if (coords.count() < 4)
        return;

    DigraphEdge *edge = new DigraphEdge;
    edge->points.resize(coords.count()/2);

    for (uint i = 0; i < edge->points.count(); ++i)
        edge->points[i] = TQPoint(toXPixel(coords[2*i]), toYPixel(coords[2*i+1]));

    edges.append(edge);
}


void DigraphView::addEdge(const TQString &name1, const TQString &name2)
{
    TQString line;
    line = "\"";
    line += name1;
    line += "\" -> \"";
    line += name2;
    line += "\";";
    inputs.append(line);
}


void DigraphView::clear()
{
    nodes.clear();
    edges.clear();
    selNode = 0;
    width = -1;
    height = -1;
    inputs.clear();
    viewport()->update();
}


void DigraphView::setSelected(const TQString &name)
{
    TQPtrListIterator<DigraphNode> it(nodes);
    for (; it.current(); ++it) {
        if (it.current()->name == name) {
            updateContents(selNode->x-selNode->w/2, selNode->y-selNode->h/2,
                           selNode->w, selNode->h);
            selNode = it.current();
            updateContents(selNode->x-selNode->w/2, selNode->y-selNode->h/2,
                           selNode->w, selNode->h);
            return;
        }
    }
}


void DigraphView::ensureVisible(const TQString &name)
{
    TQPtrListIterator<DigraphNode> it(nodes);
    for (; it.current(); ++it) {
        if (it.current()->name == name) {
            TQScrollView::ensureVisible((*it)->x, (*it)->y, (*it)->w, (*it)->h);
            return;
        }
    }
}


TQStringList DigraphView::splitLine(TQString str)
{
    TQStringList result;

    while (!str.isEmpty()) {
        if (str[0] == '"') {
            int pos = str.find('"', 1);
            if (pos == -1)
                pos = str.length();
            result << str.mid(1, pos-1);
            str.remove(0, pos+1);
        } else {
            int pos = str.find(' ');
            if (pos == -1)
                pos = str.length();
            result << str.left(pos);
            str.remove(0, pos+1);
        }
        uint i = 0; while (i<str.length() && str[i] == ' ') ++i;
        str.remove(0, i);
    }

    return result;
}


void DigraphView::parseDotResults(const TQStringList &list)
{
    TQStringList::ConstIterator it;
    for (it = list.begin(); it != list.end(); ++it) {
        TQStringList tokens = splitLine(*it);
        if (tokens.count() == 0)
            continue;
        if (tokens[0] == "graph") {
            if (tokens.count() < 4)
                continue;
            setRenderedExtent(tokens[2].toDouble(), tokens[3].toDouble());
        } else if (tokens[0] == "node") {
            if (tokens.count() < 6)
                continue;
            addRenderedNode(tokens[1], tokens[2].toDouble(), tokens[3].toDouble(),
                            tokens[4].toDouble(), tokens[5].toDouble());
        } else if (tokens[0] == "edge") {
            if (tokens.count() < 8)
                continue;
            TQMemArray<double> coords(tokens.count()-6);
            for (uint i=0; i != tokens.count()-6; ++i)
                coords[i] = tokens[i+4].toDouble();
            addRenderedEdge(tokens[1], tokens[2], coords);
        }
    }
}


void DigraphView::process( const TQString& file, const TQString& ext )
{
    TQString cmd = TDEGlobal::dirs()->findExe("dot");
    if (cmd.isEmpty()) {
        KMessageBox::sorry(0, i18n("You do not have 'dot' installed.\nIt can be downloaded from www.graphviz.org."));
        return;
    }

    TQStringList results;

    KTempFile ifile, ofile;
    TQTextStream &is = *ifile.textStream();
    is << "digraph G {" << endl;
    is << "rankdir=LR;" << endl;
    is << "node [shape=box,fontname=Helvetica,fontsize=12];" << endl;
    TQStringList::Iterator it;
    for (it = inputs.begin(); it != inputs.end(); ++it)
        is << (*it) << endl;
    is << "}" << endl;
    ifile.close();

    TDEProcess proc;
    if( !file.isEmpty() && !ext.isEmpty() )
    {
        proc << cmd << TQString("-T")+ext << ifile.name() << "-o" << file;
	kdDebug() << "Executing: " << cmd <<" "<<TQString("-T")+ext <<" "<< ifile.name() << "-o"<<file << endl;
    }else
    {
        proc << cmd << "-Tplain" << ifile.name() << "-o" << ofile.name();
    }
    proc.start(TDEProcess::Block);

    if( !file.isEmpty() && !ext.isEmpty() ) 
    {
        return;
    }

    TQTextStream &os = *ofile.textStream();
    while (!os.atEnd())
        results << os.readLine();
    ofile.close();

    parseDotResults(results);
    inputs.clear();

    if (nodes.first())
        selNode = nodes.first();
    viewport()->update();
}

void DigraphView::drawContents(TQPainter* p, int clipx, int clipy, int clipw, int cliph)
{
    TQRect clipRect(clipx, clipy, clipw, cliph);
    p->eraseRect(clipRect);

    p->setFont(TDEGlobalSettings::generalFont());
    TQPtrListIterator<DigraphNode> it1(nodes);
    for (; it1.current(); ++it1) {
        TQRect r((*it1)->x-(*it1)->w/2, (*it1)->y-(*it1)->h/2, (*it1)->w, (*it1)->h);
        if (r.intersects(clipRect)) {
            if (it1.current() == selNode)
                p->fillRect(r, TQBrush(lightGray, SolidPattern));
            else
                p->drawRect(r);
            p->drawText(r, AlignCenter, (*it1)->name);
        }
    }
    p->setBrush(TQBrush(black, SolidPattern));
    TQPtrListIterator<DigraphEdge> it2(edges);
    for (; it2.current(); ++it2) {
        int n = (*it2)->points.count();
        for (int i=0; i+3 < n; i+=3)
            {
                TQPointArray a(4);
                TQPointArray &b = (*it2)->points;
                for (int j=0; j<4; ++j)
                    a.setPoint(j, b.point(i+j));
                if (a.boundingRect().intersects(clipRect))
                    p->drawCubicBezier((*it2)->points, i);
            }
        TQPoint p1 = (*it2)->points[n-2];
        TQPoint p2 = (*it2)->points[n-1];
        TQPoint d = p1-p2;
        double l = sqrt(d.x()*d.x()+d.y()*d.y());
        double d11 = (10.0)/l*d.x();
        double d12 = (10.0)/l*d.y();
        double d21 = -(3.0/l)*d.y();
        double d22 = (3.0/l)*d.x();
        TQPointArray triangle(3);
        triangle[0] = p2 + TQPoint((int)(d11+d21),(int)(d12+d22));
        triangle[1] = p2 + TQPoint((int)(d11-d21),(int)(d12-d22));
        triangle[2] = p2;
        p->drawPolygon(triangle, true);
    }
}


void DigraphView::contentsMousePressEvent(TQMouseEvent *e)
{
    TQPtrListIterator<DigraphNode> it1(nodes);
    for (; it1.current(); ++it1) {
        TQRect r((*it1)->x-(*it1)->w/2, (*it1)->y-(*it1)->h/2, (*it1)->w, (*it1)->h);
        if (r.contains(e->pos())) {
            if (selNode) {
                TQRect oldr(selNode->x-selNode->w/2, selNode->y-selNode->h/2,
                           selNode->w, selNode->h);
                updateContents(oldr);
            }
            selNode = it1.current();
            emit selected(selNode->name);
            updateContents(r);
        }

    }
}


TQSize DigraphView::sizeHint() const
{
    if (width == -1)
        return TQSize(100, 100); // arbitrary

    TQSize dsize = TDEGlobalSettings::desktopGeometry(viewport()).size();
    kdDebug(9003) << "sizehint for inheritance diagram" << dsize << " " << width << " " << height << endl;
    return TQSize(width, height).boundedTo(TQSize(dsize.width()*2/3, dsize.height()*2/3));
}


#if 0
int main(int argc, char **argv)
{
    TQApplication app(argc, argv);

    DigraphView *dw = new DigraphView(0, "dot widget");
dw->addEdge(	"5th Edition", "6th Edition");
dw->addEdge(	"5th Edition", "PWB 1.0");
dw->addEdge(	"6th Edition", "LSX");
dw->addEdge(	"6th Edition", "1 BSD");
dw->addEdge(	"6th Edition", "Mini Unix");
dw->addEdge(	"6th Edition", "Wollongong");
dw->addEdge(	"6th Edition", "Interdata");
dw->addEdge(	"Interdata", "Unix/TS 3.0");
dw->addEdge(	"Interdata", "PWB 2.0");
dw->addEdge(	"Interdata", "7th Edition");
dw->addEdge(	"7th Edition", "8th Edition");
dw->addEdge(	"7th Edition", "32V");
dw->addEdge(	"7th Edition", "V7M");
dw->addEdge(	"7th Edition", "Ultrix-11");
dw->addEdge(	"7th Edition", "Xenix");
dw->addEdge(	"7th Edition", "UniPlus+");
dw->addEdge(	"V7M", "Ultrix-11");
dw->addEdge(	"8th Edition", "9th Edition");
dw->addEdge(	"1 BSD", "2 BSD");
dw->addEdge(	"2 BSD", "2.8 BSD");
dw->addEdge(	"2.8 BSD", "Ultrix-11");
dw->addEdge(	"2.8 BSD", "2.9 BSD");
dw->addEdge(	"32V", "3 BSD");
dw->addEdge(	"3 BSD", "4 BSD");
dw->addEdge(	"4 BSD", "4.1 BSD");
dw->addEdge(	"4.1 BSD", "4.2 BSD");
dw->addEdge(	"4.1 BSD", "2.8 BSD");
dw->addEdge(	"4.1 BSD", "8th Edition");
dw->addEdge(	"4.2 BSD", "4.3 BSD");
dw->addEdge(	"4.2 BSD", "Ultrix-32");
dw->addEdge(	"PWB 1.0", "PWB 1.2");
dw->addEdge(	"PWB 1.0", "USG 1.0");
dw->addEdge(	"PWB 1.2", "PWB 2.0");
dw->addEdge(	"USG 1.0", "CB Unix 1");
dw->addEdge(	"USG 1.0", "USG 2.0");
dw->addEdge(	"CB Unix 1", "CB Unix 2");
dw->addEdge(	"CB Unix 2", "CB Unix 3");
dw->addEdge(	"CB Unix 3", "Unix/TS++");
dw->addEdge(	"CB Unix 3", "PDP-11 Sys V");
dw->addEdge(	"USG 2.0", "USG 3.0");
dw->addEdge(	"USG 3.0", "Unix/TS 3.0");
dw->addEdge(	"PWB 2.0", "Unix/TS 3.0");
dw->addEdge(	"Unix/TS 1.0", "Unix/TS 3.0");
dw->addEdge(	"Unix/TS 3.0", "TS 4.0");
dw->addEdge(	"Unix/TS++", "TS 4.0");
dw->addEdge(	"CB Unix 3", "TS 4.0");
dw->addEdge(	"TS 4.0", "System V.0");
dw->addEdge(	"System V.0", "System V.2");
dw->addEdge(	"System V.2", "System V.3");
    dw->process();
    dw->show();

    return app.exec();
}
#endif

#include "digraphview.moc"
