// This example was started by Guillaume Laurent.
// It has become a place to dump code that tests parts of the
// gnomemm canvas code. Little thought has been given to the
// actual on-screen output.

#include <libgnomecanvasmm.h>
#include <libgnomecanvasmm/polygon.h>

#include <fstream>
#include <iostream>

#include <lemon/list_graph.h>
#include <lemon/graph_reader.h>
#include <lemon/graph_writer.h>
#include <lemon/graph_utils.h>
#include <lemon/maps.h>
#include <lemon/error.h>
#include <lemon/xy.h>

using namespace lemon;

typedef xy<double> Coordinates;
typedef ListGraph Graph;
typedef Graph::NodeMap<Coordinates> CoordinatesMap;
typedef Graph::Node Node;
typedef Graph::EdgeIt EdgeIt;
typedef Graph::NodeIt NodeIt;

class GraphDisplayerCanvas : public Gnome::Canvas::CanvasAA
{
  typedef Gnome::Canvas::CanvasAA Parent;

public:
  GraphDisplayerCanvas(Graph &, CoordinatesMap &);
  virtual ~GraphDisplayerCanvas();

private:

  ///Event handler function that handles dragging nodes of displayed_graph
  bool event_handler(GdkEvent* e, Node n);

	///The graph, on which we work
	Graph g;
  ///Map of nodes of planefigure
  Graph::NodeMap<Gnome::Canvas::Ellipse *> nodesmap;  
  ///Map of edges of planefigure
  Graph::EdgeMap<Gnome::Canvas::Line *> edgesmap;  
  ///Group of graphical elements of displayed_graph
  Gnome::Canvas::Group displayed_graph;

  ///Indicates whether the button of mouse is pressed or not
  bool isbutton;

  ///At this location was the mousebutton pressed.
  ///It helps to calculate the distance of dragging.
  double clicked_x, clicked_y;

  ///Remembers which Gnome::Canvas::Item was pressed.
  ///this variable is needed, because
  ///1. we cannot query the item at he cursor as fast as it could not cause a Segmentation Fault
  ///2. we would like to handle only ony item per movement, therefore quering it is not a working solution
  Gnome::Canvas::Item * active_item;


};


///This function moves only one node of displayed_graph,
///but recalculate the location of weight point,
///and also redraw the sides of the planefigure.
bool GraphDisplayerCanvas::event_handler(GdkEvent* e, Node n)
{
  switch(e->type)
  {
    case GDK_BUTTON_PRESS:
      clicked_x=e->button.x;
      clicked_y=e->button.y;
      active_item=(get_item_at(e->button.x, e->button.y));
      isbutton=true;
      break;
    case GDK_BUTTON_RELEASE:
      isbutton=false;
      active_item=NULL;
      break;
    case GDK_MOTION_NOTIFY:
      if(isbutton)
      {
        double dx=e->motion.x-clicked_x;
        double dy=e->motion.y-clicked_y;
        active_item->move(dx, dy);
        clicked_x=e->motion.x;
        clicked_y=e->motion.y;

				EdgeIt e;

				g.firstOut(e,n);
				for(;e!=INVALID;g.nextOut(e))
				{
						Gnome::Canvas::Points coos;
						double x1, x2, y1, y2;

						nodesmap[g.source(e)]->get_bounds(x1, y1, x2, y2);
						coos.push_back(Gnome::Art::Point((x1+x2)/2,(y1+y2)/2));

						nodesmap[g.target(e)]->get_bounds(x1, y1, x2, y2);
						coos.push_back(Gnome::Art::Point((x1+x2)/2,(y1+y2)/2));

						edgesmap[e]->property_points().set_value(coos);
				}

				g.firstIn(e,n);
				for(;e!=INVALID;g.nextIn(e))
				{
						Gnome::Canvas::Points coos;
						double x1, x2, y1, y2;

						nodesmap[g.source(e)]->get_bounds(x1, y1, x2, y2);
						coos.push_back(Gnome::Art::Point((x1+x2)/2,(y1+y2)/2));

						nodesmap[g.target(e)]->get_bounds(x1, y1, x2, y2);
						coos.push_back(Gnome::Art::Point((x1+x2)/2,(y1+y2)/2));

						edgesmap[e]->property_points().set_value(coos);
				}
      }
    default: break;
  }
  return true;
}

GraphDisplayerCanvas::GraphDisplayerCanvas(Graph & gr, CoordinatesMap & cm):g(gr),nodesmap(g),edgesmap(g),displayed_graph(*(root()), 0, 0),isbutton(false),active_item(NULL)
{
		for (EdgeIt i(g); i!=INVALID; ++i)
    {
				Gnome::Canvas::Points coos;
				coos.push_back(Gnome::Art::Point(cm[g.source(i)].x,cm[g.source(i)].y));
				coos.push_back(Gnome::Art::Point(cm[g.target(i)].x,cm[g.target(i)].y));
				edgesmap[i]=new Gnome::Canvas::Line(displayed_graph, coos);
				*(edgesmap[i]) << Gnome::Canvas::Properties::fill_color("green");
				edgesmap[i]->property_width_pixels().set_value(10);
    }
    for (NodeIt i(g); i!=INVALID; ++i)
    {
				nodesmap[i]=new Gnome::Canvas::Ellipse(displayed_graph, cm[i].x-20, cm[i].y-20, cm[i].x+20, cm[i].y+20);
				*(nodesmap[i]) << Gnome::Canvas::Properties::fill_color("blue");
				*(nodesmap[i]) << Gnome::Canvas::Properties::outline_color("black");
				(nodesmap[i])->signal_event().connect(sigc::bind(sigc::mem_fun(*this, &GraphDisplayerCanvas::event_handler),i));
    }

}

GraphDisplayerCanvas::~GraphDisplayerCanvas()
{
		Graph::NodeMap <int> id(g);
		Graph::NodeMap <double> xc(g);
		Graph::NodeMap <double> yc(g);

		int j=1;

    for (NodeIt i(g); i!=INVALID; ++i)
    {
				double x1,y1,x2,y2;
				nodesmap[i]->get_bounds(x1, y1, x2, y2);

				id[i]=j++;
				xc[i]=(x1+x2)/2;
				yc[i]=(y1+y2)/2;
		}

		GraphWriter<Graph> writer(std::cout,g);

		writer.addNodeMap("id", id);
		writer.addNodeMap("coordinates_x", xc);
		writer.addNodeMap("coordinates_y", yc);
		writer.run();
}


//MainWin:
class MainWin : public Gtk::Window
{
public:
  MainWin(const std::string& title, Graph &, CoordinatesMap &);

protected:
  //Member widgets:
  GraphDisplayerCanvas gd_canvas;
};

MainWin::MainWin(const std::string& title, Graph & graph, CoordinatesMap & cm):gd_canvas(graph, cm)
{
  set_title (title);
  add(gd_canvas);
  set_default_size(900,600);

  show_all();
}


///This class is responsible for being able
///to read xy datastructure from file. It is
///based on BaseMap. The set method sets the
///appropriate value in the final xy NodeMap
///that was given to the constructor.
class CoordReaderMap: public MapBase <Node, double>
{
    CoordinatesMap & cm;
    char xoy;

public:
    CoordReaderMap(char xory, CoordinatesMap & coordmap);
    void set(Node node, double coord);
};

///The constructor expects for an xy NodeMap,
///and we have to tell it, for which  value
///of the xy vector is responsible the actual
///copy.
CoordReaderMap::CoordReaderMap(char xory, CoordinatesMap & coordmap): cm(coordmap)
{
    switch(xory)
    {
	case 'x':
	case 'y':
	    xoy=xory;
	    break;
	default:
	    throw UninitializedParameter() ;
    }
}

///set method sets the appropriate value in the
///xy type NodeMap that is under construction
void CoordReaderMap::set(Node node, double coord)
{
    switch(xoy)
    {
	case 'x':
	    cm[node].x=coord;
	    break;
	case 'y':
	    cm[node].y=coord;
	    break;
	default:
	    throw UninitializedParameter() ;
    }
}

//main():

int main(int argc, char *argv[])
{
  if(argc<2)
  {
      std::cerr << "USAGE: gd <input filename.lgf>" << endl;
      return 0;
  }

  Coordinates coosvector;

  Graph g;

  CoordinatesMap cm(g);
  Graph::EdgeMap<double> cap(g);

  //we create one object to read x coordinates
  //and one to read y coordinate of nodes and write them to cm NodeMap.
  CoordReaderMap xreader('x',cm);
  CoordReaderMap yreader('y',cm);

  std::ifstream is(argv[1]);

  GraphReader<Graph> reader(is, g);
  reader.addNodeMap("coordinates_x", xreader);
  reader.addNodeMap("coordinates_y", yreader);
  reader.run();

  Gnome::Canvas::init();
  Gtk::Main app(argc, argv);

  MainWin mainwin("Displayed Graph", g, cm);
  app.run(mainwin);

  return 0;
}
