/*
 * elch6D implementation
 *
 * Copyright (C) Jochen Sprickerhof
 *
 * Released under the GPL version 3.
 *
 */

/** @file graph balancer implementation and utility functions
 *  @author Jochen Sprickerhof. Institute of Computer Science, University of Osnabrueck, Germany.
 *
 * This file implements the paper (ecmr2009.pdf):
 * Jochen Sprickerhof, Andreas Nuechter, Kai Lingemann, Joachim Hertzberg. An Explicit
 * Loop Closing Technique for 6D SLAM, In Proceedings of the 4th European Conference on
 * Mobile Robots (ECMR '09), Mlini/Dubrovnic, Croatia, September 2009.
 */

#include <fstream>
using std::ofstream;

#include <string>
using std::string;

#include <list>
using std::list;

#include <algorithm>
using std::swap;

#include <limits> //for old boost and new gcc
using std::numeric_limits;
#include <boost/graph/graphviz.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include <boost/graph/graph_traits.hpp>
using boost::graph_traits;

#include "slam6d/globals.icc"
#include "slam6d/elch6D.h"

#ifdef _MSC_VER
#define  tie tr1::tie
#endif


/*void printout(graph_t &g, vector<Vertex> &p, vector<int> &d, double *weights)
{
  cout << "distances and parents:" << endl;
  graph_traits <graph_t>::vertex_iterator vi, vend;
  for(tie(vi, vend) = vertices(g); vi != vend; vi++) {
    cout << "distance(" << *vi << ") = " << d[*vi] <<
      ", parent(" << *vi << ") = " << p[*vi] <<
      *vi << " " << weights[*vi] << endl;
  }
}*/

/**
 * sets a filename for graph_weight_out and calls it
 * @param g the graph to save
 * @param first the first node
 * @param last the last node
 * @param weights the computed weights
 */
void elch6D::graph_weight_out(graph_t &g, int first, int last, double *weights)
{
  string name("graph_weight_" + to_string(num_vertices(g), 3) + ".dot");
  graph_weight_out(g, first, last, weights, name);
}

/**
 * writes a graphviz file with the graph labled with the computed weights
 * @param g the graph to save
 * @param first the first node
 * @param last the last node
 * @param weights the computed weights
 * @param out_file the file to write to
 */
void elch6D::graph_weight_out(graph_t &g, int first, int last, double *weights, string &out_file)
{
  ofstream dot_file(out_file.c_str());
  dot_file << "graph D {" << endl;
  graph_traits <graph_t>::vertex_iterator vi, vend;
  for(tie(vi, vend) = vertices(g); vi != vend; vi++) {
    dot_file << *vi << "[label=\"" << *vi << " (" <<
      weights[*vi] << ")\"];" << endl;
  }
  boost::property_map<graph_t, edge_weight_t>::type weightmap = get(boost::edge_weight, g);
  graph_traits <graph_t>::edge_iterator ei, ei_end;
  for(tie(ei, ei_end) = edges(g); ei != ei_end; ei++) {
    dot_file << source(*ei, g) << " -- " << target(*ei, g) <<
      "[label=\"" << get(weightmap, *ei) << "\"];" << endl;
  }
  dot_file << first << " -- " << last << "[color=\"green\"] }";
  dot_file.close();
}

/**
 * sets a filename and calls graph_pos_out
 * @param g the graph to save
 * @param allScans all laser scans
 */
void elch6D::graph_pos_out(graph_t &g, const vector <Scan *> &allScans)
{
  string name("graph_pos_" + to_string(num_vertices(g), 3) + ".dot");
  graph_pos_out(g, allScans, name);
}

/**
 * writes the graph using the computed scan poses
 * @param g the graph to save
 * @param allScans all laser scans
 * @param out_file the file to write to
 */
void elch6D::graph_pos_out(graph_t &g, const vector <Scan *> &allScans, string &out_file)
{
  ofstream graph_file(out_file.c_str());
  ofstream graph2_file(("2" + out_file).c_str());
  graph_traits <graph_t>::edge_iterator ei, ei_end;
  for(tie(ei, ei_end) = edges(g); ei != ei_end; ei++) {
    if(source(*ei, g) + 1 != target(*ei, g)) {
      graph2_file << allScans[source(*ei, g)]->get_rPos()[0] << " " << allScans[source(*ei, g)]->get_rPos()[1] << " " << allScans[source(*ei, g)]->get_rPos()[2] << endl;
      graph2_file << allScans[target(*ei, g)]->get_rPos()[0] << " " << allScans[target(*ei, g)]->get_rPos()[1] << " " << allScans[target(*ei, g)]->get_rPos()[2] << endl << endl;
    } else {
      graph_file << allScans[source(*ei, g)]->get_rPos()[0] << " " << allScans[source(*ei, g)]->get_rPos()[1] << " " << allScans[source(*ei, g)]->get_rPos()[2] << endl;
      graph_file << allScans[target(*ei, g)]->get_rPos()[0] << " " << allScans[target(*ei, g)]->get_rPos()[1] << " " << allScans[target(*ei, g)]->get_rPos()[2] << endl << endl;
    }
  }
  graph_file.close();
  graph2_file.close();
}

/**
 * write graphviz file with real poses
 * @param g the graph
 * @param allScans all laser scans
 * @param out_file the file to write to
 */
void elch6D::dot_pos_out(graph_t &g, const vector <Scan *> &allScans, string &out_file)
{
  ofstream dot_file(out_file.c_str());

  dot_file << "graph D {" << endl << "size=\"20,20\"" << endl;

  int n = num_vertices(g);
  for(int i = 0; i < n; i++) {
    dot_file << i << "[pos=\"" <<
      allScans[i]->get_rPos()[0] << "," <<
      allScans[i]->get_rPos()[2] << "\", label=\"\", height=0.1, width=0.1, fixedsize=true];" << endl;
  }
  graph_traits <graph_t>::edge_iterator ei, ei_end;
  for(tie(ei, ei_end) = edges(g); ei != ei_end; ei++) {
    if(source(*ei, g) + 1 != target(*ei, g)) {
      dot_file << source(*ei, g) << " -- " << target(*ei, g) << "[color=\"green\"];" << endl;
    } else {
      dot_file << source(*ei, g) << " -- " << target(*ei, g) << ";" << endl;
    }
  }
  dot_file << "}";
  dot_file.close();
}

/**
 * sets filename and calls graph_out
 * @param g the graph
 */
void elch6D::graph_out(graph_t &g)
{
  string name("graph_" + to_string(num_vertices(g), 3) + ".dot");
  graph_out(g, name);
}

/**
 * uses boost write_graphviz to write the graph
 * @param g the graph
 * @param out_file the file to write to
 */
void elch6D::graph_out(graph_t &g, string &out_file)
{
  ofstream dot_file(out_file.c_str());
  write_graphviz(dot_file, g);
  dot_file.close();
}

/**
 * sets filename and calls slim_graph_out
 */
void elch6D::slim_graph_out(graph_t g)
{
  string name("slim_graph_" + to_string(num_vertices(g), 3) + ".dot");
  slim_graph_out(g, name);
}

/**
 * writes slim graph (supressing unimportant nodes)
 * @param g the graph
 * @param out_file the file to write to
 */
void elch6D::slim_graph_out(graph_t g, string &out_file)
{
  bool todo;
  graph_traits < graph_t >::vertex_iterator vi, vend;
  graph_traits < graph_t >::adjacency_iterator ai;
  do {
    todo = false;
    for(tie(vi, vend) = vertices(g); vi != vend; vi++) {
      int me = *vi;
      if(degree(me, g) == 2) {
        ai = adjacent_vertices(me, g).first;
        int one = *ai;
        ai++;
        int two = *ai;
        if(degree(one, g) == 2 && degree(two, g) == 2 && one != me && two != me && one != two) {
          clear_vertex(me, g);
          add_edge(one, two, g);
          remove_vertex(me, g);
          todo = true;
          break;
        }
      }
    }
  } while(todo);

  ofstream dot_file(out_file.c_str());
  write_graphviz(dot_file, g);
  dot_file.close();
}

/**
 * graph balancer algorithm computes the weights
 * @param g the graph
 * @param f index of the first node
 * @param l index of the last node
 * @param weights array for the weights
 */
void elch6D::graph_balancer(graph_t &g, int f, int l, double *weights)
{
  list<int> crossings, branches;
  crossings.push_back(f);
  crossings.push_back(l);
  weights[f] = 0;
  weights[l] = 1;

  int *p = new int[num_vertices(g)];
  int *p_min = new int[num_vertices(g)];
  double *d = new double[num_vertices(g)];
  double *d_min = new double[num_vertices(g)];
  double dist;
  bool do_swap = false;
  list<int>::iterator si, ei, s_min, e_min;

  // process all junctions
  while(!crossings.empty()) {
    dist = -1;
    // find shortest crossing for all vertices on the loop
    for(si = crossings.begin(); si != crossings.end(); ) {
      dijkstra_shortest_paths(g, *si, boost::predecessor_map(p).distance_map(d));
      ei = si;
      ei++;
      // find shortest crossing for one vertex
      for(; ei != crossings.end(); ei++) {
        if(*ei != p[*ei] && (dist < 0 || d[*ei] < dist)) {
          dist = d[*ei];
          s_min = si;
          e_min = ei;
          do_swap = true;
        }
      }
      if(do_swap) {
        swap(p, p_min);
        swap(d, d_min);
        do_swap = false;
      }
      // vertex starts a branch
      if(dist < 0) {
        branches.push_back(*si);
        si = crossings.erase(si);
      } else {
        si++;
      }
    }

    if(dist > -1) {
      remove_edge(*e_min, p_min[*e_min], g);
      for(int i = p_min[*e_min]; i != *s_min; i = p_min[i]) {
        //even right with weights[*s_min] > weights[*e_min]! (math works)
        weights[i] = weights[*s_min] + (weights[*e_min] - weights[*s_min]) * d_min[i] / d_min[*e_min];
        remove_edge(i, p_min[i], g);
        if(degree(i, g) > 0) {
          crossings.push_back(i);
        }
      }

      if(degree(*s_min, g) == 0) {
        crossings.erase(s_min);
      }

      if(degree(*e_min, g) == 0) {
        crossings.erase(e_min);
      }
    }
  }

  delete[] p;
  delete[] p_min;
  delete[] d;
  delete[] d_min;

  graph_traits <graph_t>::adjacency_iterator ai, ai_end;
  int s;

  // error propagation
  while(!branches.empty()) {
    s = branches.front();
    branches.pop_front();

    for(tie(ai, ai_end) = adjacent_vertices(s, g); ai != ai_end; ++ai) {
      weights[*ai] = weights[s];
      if(degree(*ai, g) > 1) {
        branches.push_back(*ai);
      }
    }
    clear_vertex(s, g);
  }
}
