/*
    This file is part of Rocs.
    Copyright 2011  Andreas Cord-Landwehr <cola@uni-paderborn.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, see <http://www.gnu.org/licenses/>.
*/


#include "DotFilePlugin.h"

#include "Document.h"
#include "DataStructure.h"
#include "Data.h"
#include "Pointer.h"

#include <boost/graph/graphviz.hpp>
#include <boost/graph/topology.hpp>
#include <boost/graph/random_layout.hpp>
#include <boost/graph/fruchterman_reingold.hpp>
#include <boost/random/mersenne_twister.hpp>

#include <KAboutData>
#include <KGenericFactory>

#include <QFile>
#include <QPair>

using namespace boost;

static const KAboutData aboutdata("rocs_dotFilePlugin", 0, ki18n("Open and Save Graphviz files") , "0.1");

K_PLUGIN_FACTORY(FilePLuginFactory, registerPlugin<DotFilePlugin>();)
K_EXPORT_PLUGIN(FilePLuginFactory(aboutdata))


DotFilePlugin::~DotFilePlugin()
{

}


DotFilePlugin::DotFilePlugin(QObject* parent, const QList< QVariant >&) :
    FilePluginInterface(FilePLuginFactory::componentData(), parent)
{
}


const QStringList DotFilePlugin::extensions() const
{
    return QStringList() << i18n("*.dot|Graphviz Files") + '\n';
}


Document* DotFilePlugin::readFile(const QString& fileName)
{
    // read file
    QFile f(fileName);
    if (!f.open(QFile::ReadOnly)) {
        setError(i18n("Cannot open the file: %1. Error %2", fileName, f.errorString()));
        return 0;
    }
    QString content = f.readAll();

    // try to parse the file
    Document* graphDoc = 0;
    try {
        graphDoc = parseGraphvizUndirected(content);
    } catch (boost::directed_graph_error) {
        qDebug() << "try reading as directed";
        try {
            graphDoc = parseGraphvizDirected(content);
        } catch (boost::bad_lexical_cast) {
            qDebug() << "Bad lexical cast when parsing graphviz file";
            delete graphDoc;
            return 0;
        } catch (boost::undirected_graph_error) {
            qDebug() << "this actually should not happen for a proper graph";
            delete graphDoc;
            return 0;
        }
        return graphDoc;
    } catch (boost::bad_graphviz_syntax) {
        setError(i18n("Could not parse Graphviz file due to syntax errors."));
        delete graphDoc;
        return 0;
    }

    return graphDoc;
}

Document* DotFilePlugin::parseGraphvizUndirected(const QString& graphvizContent)
{
    typedef boost::property < vertex_name_t, std::string,
            boost::property < vertex_color_t, qreal,
            boost::property< vertex_shape_t, std::string > > >
            VertexProperty;
    typedef boost::property < edge_weight_t, qreal,
            boost::property< edge_name_t, std::string > >
            EdgeProperty;
    typedef boost::property < graph_name_t, std::string >
    GraphProperty;
    typedef boost::adjacency_list < boost::listS, boost::vecS, boost::undirectedS,
            VertexProperty, EdgeProperty, GraphProperty >
            Graph;
    typedef boost::adjacency_list < boost::listS, boost::vecS, boost::undirectedS,
            VertexProperty, EdgeProperty, GraphProperty >
            Graph;

    typedef boost::rectangle_topology<> topology_type;
    typedef topology_type::point_type point_type;
    typedef QVector<point_type> PositionVec;

    // Construct an empty graph and prepare the dynamic_property_maps.
    Document* graphDoc = new Document("Untitled");
    Graph importGraph(0);

    // set up graph properties that shall be parsed
    dynamic_properties dp;

    property_map<Graph, vertex_name_t>::type name = get(vertex_name, importGraph);
    dp.property("node_id", name);

    property_map<Graph, vertex_color_t>::type color = get(vertex_color, importGraph);
    dp.property("color", color);

    property_map<Graph, edge_name_t>::type edgeName = get(edge_name, importGraph);
    dp.property("label", edgeName);

    property_map<Graph, edge_weight_t>::type weight = get(edge_weight, importGraph);
    dp.property("weight", weight);

    // Use ref_property_map to turn a graph property into a property map
    boost::ref_property_map<Graph*, std::string> gname(get_property(importGraph, graph_name));
    dp.property("name", gname);

    // Use ref_property_map to turn a graph property into a property map
    boost::property_map<Graph, vertex_shape_t>::type vertexShape = get(vertex_shape_t(), importGraph);
    dp.property("shape", vertexShape);

    // try to parse the file
    try {
        bool parse = boost::read_graphviz(QString(graphvizContent).toStdString(), importGraph, dp, "node_id");
        if (!parse) {
            setError(i18n("Graphviz parser returned unsuccessfully for file."));
            qDebug() << "Graphviz parser returned unsuccessfully for file";
            delete graphDoc;
            return 0;
        }
    } catch (boost::bad_graphviz_syntax) {
        setError(i18n("Could not parse Graphviz file due to syntax errors."));
        delete graphDoc;
        return 0;
    } catch (boost::bad_lexical_cast) {
        qDebug() << "throw bad lexical cast exception in DotFilePlugin";
        delete graphDoc;
        return 0;
    } catch (boost::property_not_found) {
        qDebug() << "stopped processing: not all DOT properties are known";
        delete graphDoc;
        return 0;
    }

    // Apply Layout
    // TODO move all general purpose layout functions to support library
    typedef boost::iterator_property_map < PositionVec::iterator,
            boost::property_map<Graph, boost::vertex_index_t>::type >
            PositionMap;
    boost::mt19937 gen;
    gen.seed(static_cast<unsigned int>(1));
    // generate distribution topology and apply
    boost::rectangle_topology< boost::mt19937 > topology(gen, -200, -200, 200, 200);
    PositionVec position_vec(boost::num_vertices(importGraph));
    PositionMap positionMap(position_vec.begin(), get(boost::vertex_index, importGraph));
    boost::random_graph_layout(importGraph, positionMap, topology);
    // minimize cuts by Fruchtman-Reingold layout algorithm
    boost::fruchterman_reingold_force_directed_layout< boost::rectangle_topology< boost::mt19937 >, Graph, PositionMap >
    (importGraph,
     positionMap,
     topology,
     boost::cooling(boost::linear_cooling<double>(100))
    );

    // convert graphviz format to Document
    DataStructurePtr datastructure = graphDoc->addDataStructure(i18n("dotImport"));

    // put nodes at whiteboard as generated
    QMap<int, DataPtr > mapNodes;
    boost::graph_traits<Graph>::vertex_iterator vi, vi_end;
    for (boost::tie(vi, vi_end) = boost::vertices(importGraph); vi != vi_end; ++vi) {
        // TODO imported data values can be extended
        mapNodes[*vi] = datastructure->addData(
                            QString::fromStdString(get(name, *vi)),
                            QPointF(positionMap[*vi][0], positionMap[*vi][1])
                        );
        mapNodes[*vi]->setColor(get(color, *vi));
    }

    boost::graph_traits<Graph>::edge_iterator ei, ei_end;
    for (boost::tie(ei, ei_end) = boost::edges(importGraph); ei != ei_end; ++ei) {
        PointerPtr newEdge = datastructure->addPointer(mapNodes[boost::source(*ei, importGraph)],
                             mapNodes[boost::target(*ei, importGraph)]);
        newEdge->setValue(QString::number(get(weight, *ei)));
    }
    return graphDoc;
}


Document* DotFilePlugin::parseGraphvizDirected(const QString& graphvizContent)
{
    typedef boost::property < vertex_name_t, std::string,
            boost::property < vertex_color_t, qreal,
            boost::property< vertex_shape_t, std::string > > >
            VertexProperty;
    typedef boost::property < edge_weight_t, qreal,
            boost::property< edge_name_t, std::string > >
            EdgeProperty;
    typedef boost::property < graph_name_t, std::string >
    GraphProperty;
    typedef boost::adjacency_list < boost::listS, boost::vecS, boost::directedS,
            VertexProperty, EdgeProperty, GraphProperty >
            Graph;
    typedef boost::adjacency_list < boost::listS, boost::vecS, boost::directedS,
            VertexProperty, EdgeProperty, GraphProperty >
            Graph;

    typedef boost::rectangle_topology<> topology_type;
    typedef topology_type::point_type point_type;
    typedef QVector<point_type> PositionVec;

    // Construct an empty graph and prepare the dynamic_property_maps.
    Document* graphDoc = new Document("Untitled");
    Graph importGraph(0);

    // set up graph properties that shall be parsed
    dynamic_properties dp;

    property_map<Graph, vertex_name_t>::type name = get(vertex_name, importGraph);
    dp.property("node_id", name);

    property_map<Graph, vertex_color_t>::type color = get(vertex_color, importGraph);
    dp.property("color", color);

    property_map<Graph, edge_name_t>::type edgeName = get(edge_name, importGraph);
    dp.property("label", edgeName);

    property_map<Graph, edge_weight_t>::type weight = get(edge_weight, importGraph);
    dp.property("weight", weight);

    // Use ref_property_map to turn a graph property into a property map
    boost::ref_property_map<Graph*, std::string> gname(get_property(importGraph, graph_name));
    dp.property("name", gname);

    // Use ref_property_map to turn a graph property into a property map
    boost::property_map<Graph, vertex_shape_t>::type vertexShape = get(vertex_shape_t(), importGraph);
    dp.property("shape", vertexShape);

    // try to parse the file
    try {
        bool parse = boost::read_graphviz(QString(graphvizContent).toStdString(), importGraph, dp, "node_id");
        if (!parse) {
            setError(i18n("Graphviz parser returned unsuccessfully for file."));
            qDebug() << "Graphviz parser returned unsuccessfully for file";
            delete graphDoc;
            return 0;
        }
    } catch (boost::bad_graphviz_syntax) {
        setError(i18n("Could not parse Graphviz file due to syntax errors."));
        qDebug() << "bad graphviz file syntax";
        delete graphDoc;
        return 0;
    } catch (boost::bad_lexical_cast) {
        qDebug() << "throw bad lexical cast exception in DotFilePlugin";
        delete graphDoc;
        return 0;
    } catch (boost::property_not_found) {
        qDebug() << "stopped processing: not all DOT properties are known";
        delete graphDoc;
        return 0;
    }

    // Apply Layout
    // TODO move all general purpose layout functions to support library
    typedef boost::iterator_property_map < PositionVec::iterator,
            boost::property_map<Graph, boost::vertex_index_t>::type >
            PositionMap;
    boost::mt19937 gen;
    gen.seed(static_cast<unsigned int>(1));
    // generate distribution topology and apply
    boost::rectangle_topology< boost::mt19937 > topology(gen, -200, -200, 200, 200);
    PositionVec position_vec(boost::num_vertices(importGraph));
    PositionMap positionMap(position_vec.begin(), get(boost::vertex_index, importGraph));
    boost::random_graph_layout(importGraph, positionMap, topology);
    // minimize cuts by Fruchtman-Reingold layout algorithm
    boost::fruchterman_reingold_force_directed_layout< boost::rectangle_topology< boost::mt19937 >, Graph, PositionMap >
    (importGraph,
     positionMap,
     topology,
     boost::cooling(boost::linear_cooling<double>(100))
    );

    // convert graphviz format to Document
    DataStructurePtr datastructure = graphDoc->addDataStructure(i18n("dotImport"));

    // put nodes at whiteboard as generated
    QMap<int, DataPtr > mapNodes;
    boost::graph_traits<Graph>::vertex_iterator vi, vi_end;
    for (boost::tie(vi, vi_end) = boost::vertices(importGraph); vi != vi_end; ++vi) {
        // TODO imported data values can be extended
        mapNodes[*vi] = datastructure->addData(
                            QString::fromStdString(get(name, *vi)),
                            QPointF(positionMap[*vi][0], positionMap[*vi][1])
                        );
        mapNodes[*vi]->setColor(get(color, *vi));
    }

    boost::graph_traits<Graph>::edge_iterator ei, ei_end;
    for (boost::tie(ei, ei_end) = boost::edges(importGraph); ei != ei_end; ++ei) {
        PointerPtr newEdge = datastructure->addPointer(mapNodes[boost::source(*ei, importGraph)],
                             mapNodes[boost::target(*ei, importGraph)]);
        newEdge->setValue(QString::number(get(weight, *ei)));
    }
    return graphDoc;
}


bool DotFilePlugin::writeFile(Document& graph, const QString& filename)
{
    typedef boost::property < vertex_name_t, std::string, boost::property < vertex_color_t, qreal > >
    VertexProperty;
    typedef boost::property < edge_weight_t, qreal >
    EdgeProperty;
    typedef boost::property < graph_name_t, std::string >
    GraphProperty;
    typedef boost::adjacency_list < boost::listS, boost::vecS, boost::directedS,
            VertexProperty, EdgeProperty, GraphProperty >
            Graph;
    typedef QPair<int, int> Edge;

    DataList dataList = graph.activeDataStructure()->dataList();

    // create IDs for all nodes
    QMap<Data*, int> node_mapping;
    QMap<QPair<int, int>, PointerPtr > edge_mapping; // to map all edges back afterwards
    int counter = 0;
    foreach(DataPtr data, dataList) {
        node_mapping[data.get()] = counter++;
    }

    DataStructurePtr ds = dataList.first()->dataStructure();
    QVector<Edge> edges(ds->pointers().count());

    counter = 0;
    foreach(PointerPtr p, ds->pointers()) {
        edges[counter++] = Edge(node_mapping[p->from().get()], node_mapping[p->to().get()]);
    }

    // setup the graph
    Graph serializeGraph(edges.begin(), edges.end(), dataList.count());

    // write to file
    std::ofstream fileStream;
    QFile file(filename);
    fileStream.open(file.fileName().toLocal8Bit());
    boost::write_graphviz(fileStream, serializeGraph);


    return true;
}


const QString DotFilePlugin::lastError()
{
    return _lastError;
}


void DotFilePlugin::setError(QString arg)
{
    _lastError = arg;
}


#include "DotFilePlugin.moc"
