// Vorosweep - Copyright (C) 2010-2014 T. Mouton
//
// See the LICENSE.txt file for license information. Please report all
// bugs and problems to <thibaud.mouton@gmail.com>.

#include "vorosweep.h"
#include "sweepobject.h"
#include "border.h"
#include "csv.h"
#include "domain2d.h"

using namespace vorosweep;

void Graph::read_input_file ( const char *filename )
{
  tprintf ( "Reading file %s ...\n", filename );

  io::CSVReader<8, io::trim_chars<' '>, io::no_quote_escape<'\t'>, io::throw_on_overflow, io::single_line_comment<'#'> > in ( filename );
  in.read_header ( io::ignore_extra_column, "index", "x", "y", "t0", "angle", "v1", "v2", "nf" );

  using namespace vorosweep;


  struct datapoint
  {
    int index;
    double p[3];
    double t0;
    double angle;
    double v1;
    double v2;
    int nf;
  };

  std::vector<datapoint> data;

  int index;
  double pt[3];
  double t0;
  double angle;
  double v1;
  double v2;
  int nf;

  while ( in.read_row ( index, pt[0], pt[1], t0, angle, v1, v2, nf ) )
  {
    datapoint dpt;
    dpt.index = index;
    dpt.p[0] = pt[0];
    dpt.p[1] = pt[1];
    dpt.t0 = t0*0.5;
    dpt.angle = fmod ( angle, M_PI );
    dpt.v1 = v1;
    while ( dpt.v1 < 0.05 )
      dpt.v1 *= 10.0;
    dpt.v2 = v2;
    while ( dpt.v2 < 0.05 )
      dpt.v2 *= 10.0;
    dpt.nf = nf;
    data.push_back ( dpt );
  }

  for ( int i = 0; i < ( int ) data.size(); i++ )
  {
    Convex_generator *cg;
    cg = new Convex_generator ( data[i].p, data[i].t0, data[i].nf, data[i].angle, data[i].v1, data[i].v2, data[i].index );
    cg->generate ( );
    add_generator ( cg );
  }
}

void Graph::insert_border ( Border *bo )
{
  dbgprintf ( 2, "--> inserting border %p : %lf %lf -- %lf %lf\n", bo, bo->get_point ( 0 )->p[0], bo->get_point ( 0 )->p[1], bo->get_point ( 1 )->p[0], bo->get_point ( 1 )->p[1] );
  Point *start = bo->get_point ( 0 );
  double *dir = bo->get_dir();
  int bstart = bucketgrid->get_index ( start->p );
  dbgprintf ( 2, "insert point 0 to bucket %d\n", bstart );
  bucketgrid->get_bucket ( bstart )->insert_border_point ( bo, 0 );
  int bend = bucketgrid->get_index ( bo->get_point ( 1 )->p );
  dbgprintf ( 2, "insert point 1 to bucket %d\n", bend );
  bucketgrid->get_bucket ( bend )->insert_border_point ( bo, 1 );
  int gid = bstart;
  double pos[2];
  int nbuc = 0; // count crossed bucket

  Bucket *buc = NULL;
  dbgprintf ( 2, "--> bucket" );
  while ( gid != bend )
  {
    dbgprintf ( 2, " %d", gid );
    int ngid;
    buc = bucketgrid->get_bucket ( gid );
    buc->insert_border ( bo );
    bucketgrid->compute_exit ( gid, start->p, dir, ngid, pos );
    gid = ngid;
    nbuc++;
  }
  dbgprintf ( 2, " %d\n", gid );
  buc = bucketgrid->get_bucket ( gid );
  buc->insert_border ( bo );
  nbuc++;
  bo->init ( nbuc );
}

void Graph::insert_generator ( Convex_generator *cg )
{
  Point *apex = cg->get_apex();
  dbgprintf ( 2, "apex --> %lf %lf %lf\n", apex->p[0], apex->p[1], apex->p[2] );
  double start_time = cg->get_start_time();
  dbgprintf ( 2, "start time --> %lf\n", start_time );
  int gid = bucketgrid->get_index ( apex->p );
  Event *ne = econtainer->get();
  ne->init ( cg, apex->p, start_time, Event::NEWGENERATOR, gid ); // init event at time 0
  add_event ( ne );
}

void Graph::handle ( Event *ev )
{
  dbgprintf ( 1, "handling " );
  ev->print();

  int old_time = get_time();
  update_time ( ev->get_time() );

  for ( int i = 0; i < 2; i++ )
  {
    SweepObject *so = ev->get_sweepobject ( i );
    if ( so )
    {
      dbgprintf ( 2, "sweepobject %d --> %s %p\n", i, SweepObject::objectname[so->get_type() ], so );
      so->remove_earliest_event ( ev );
      so->print_event_queue();
    }
  }

  if ( ev->disabled() )
    return;

  stats->add_event ( ev->get_type() );

  if ( ev->get_type() == Event::EDGE_EDGECRASH )
    handle_edge_edgecrash ( ev );
  else if ( ev->get_type() == Event::EDGE_FACETCRASH )
    handle_edge_facetcrash ( ev );
  else if ( ev->get_type() == Event::NEWEDGESWITCH )
    handle_newedgeswitch ( ev );
  else if ( ev->get_type() == Event::EDGESWITCH )
    handle_edgeswitch ( ev );
  else if ( ev->get_type() == Event::FACETSWITCH )
    handle_facetswitch ( ev );
  else if ( ev->get_type() == Event::EDGE_BORDERCRASH )
    handle_edge_bordercrash ( ev );
  else if ( ev->get_type() == Event::FACET_BORDERCRASH )
    handle_facet_bordercrash ( ev );
  else if ( ev->get_type() == Event::BORDEREDGESWITCH )
    handle_borderedgeswitch ( ev );
  else if ( ev->get_type() == Event::NEWBORDEREDGESWITCH )
    handle_newborderedgeswitch ( ev );
  else if ( ev->get_type() == Event::NEWGENERATOR )
    handle_newgenerator ( ev );
  else if ( ev->get_type() == Event::NEWFACETSWITCH )
    handle_newfacetswitch ( ev );

#if DEBUG_LEVEL == 0
  return;
#endif

  // debugging purpose
  static int count = 0;
  if ( get_time() > old_time && count%dumpmod == 0 )
  {
    update_all();
    generate_internals();
    export_vtk ( "vorosweep" );
//     export_vector_graphic ( "vorosweep" );
    pointlist.clear();
    indexmap.clear();
  }
  count++;
}

void Graph::update_all ( )
{
  dbgprintf ( 5, "--> updating all\n" );
  for ( int i = 0; i < ( int ) generatorlist.size(); i++ )
  {
    Convex_generator *cg = generatorlist[i];
    cg->update_current ( get_time() );
  }
}
/////////////////////////////////////////////////////////
// DJB2 hash function by Professor Daniel J. Bernstein //
/////////////////////////////////////////////////////////
unsigned long Graph::hash ( const char *str )
{
  unsigned long hash = 5381;
  int c;
  while ( ( c = *str++ ) )
    hash = ( ( hash << 5 ) + hash ) + c; /* hash * 33 + c */
  return hash;
}

void Graph::generate_internals()
{
  tprintf ( "Generate internals\n" );
  std::string chkstr;
  std::map<int, int> polyedge;
  std::map<int, int>::iterator it;

  nbg = 0;
  nbf = 0;
  nbvf = 0;
  pointlist.clear();
  indexmap.clear();

  stats->init_entities();

  for ( int i = 0; i < ( int ) generatorlist.size(); i++ )
  {
    Convex_generator *cg = generatorlist[i];
    if ( cg->is_active() )
    {
      stats->add_entity ( Statistic::ACTIVE_GENERATOR, 1 );
      stats->add_entity ( Statistic::FACET, cg->face_size() );

      for ( int j = 0; j < cg->face_size(); j++ )
      {
        SweepFacet *sf = cg->get_sweepfacet ( j );
        FrontLine *fl = sf->get_frontline ( );
        std::list<Polygon *> plist = fl->get_polygons();
//         printf("fl --> %p\n", fl);

        stats->add_entity ( Statistic::POLYGON, plist.size() );

        for ( std::list<Polygon *>::iterator itp = plist.begin(); itp != plist.end(); itp++ )
        {
          Polygon *pol = *itp;
          std::list<SweepEdge *> elist = pol->get_edges();

          stats->add_entity ( Statistic::EDGE, elist.size() );

          it = polyedge.find ( elist.size() );
          if ( it == polyedge.end() )
            polyedge.insert ( std::pair<int, int> ( elist.size(), 1 ) );
          else
            it->second++;

//           printf("pol --> %p\n", pol);
          pol->generate_point_list ( );
          std::list<Point *> vlist = pol->get_points();
//           chkstr.append ( std::to_string ( ( int ) vlist.size() ) );
          for ( std::list<Point *>::iterator itv = vlist.begin(); itv != vlist.end(); itv++ )
          {
            Point *pt = *itv;
            itindex = indexmap.find ( pt );
            if ( itindex == indexmap.end() )
            {
              indexmap[pt] = pointlist.size();
              pointlist.push_back ( pt );
            }
          }
          nbf++;
          nbvf += vlist.size() + 1;
        }
      }
      nbg++;
    }
    else
    {
      stats->add_entity ( Statistic::INACTIVE_GENERATOR, 1 );
    }
  }

  // generate the topological checksum
  for ( it = polyedge.begin(); it != polyedge.end(); it++ )
  {
    chkstr.append ( std::to_string ( it->first ) );
    chkstr.append ( std::to_string ( it->second ) );
  }
  unsigned long sum = hash ( chkstr.c_str() );
  sprintf ( checksum,"%lx",sum );
  tprintf ( "Checksum --> %s\n", checksum );
}

// borders have to be oriented counterclockwise !!
void Graph::default_borders()
{
  Point *pt[4];
  for ( int i = 0; i < 4; i++ )
    pt[i] = new Point;
  BBox *bb = bucketgrid->get_bbox();  
  bb->lock();
  double dx = bb->get_lenght ( 0 ) * 1e-5;
  double dy = bb->get_lenght ( 1 ) * 1e-5;
  pt[0]->set ( bb->get_min() [0]+dx, bb->get_min() [1]+dy, 0.0 );
  pt[1]->set ( bb->get_max() [0]-dx, bb->get_min() [1]+dy, 0.0 );
  pt[2]->set ( bb->get_max() [0]-dx, bb->get_max() [1]-dy, 0.0 );
  pt[3]->set ( bb->get_min() [0]+dx, bb->get_max() [1]-dy, 0.0 );

  Border *bo[4];
  for ( int i = 0; i < 4; i++ )
    bo[i] = new Border ( pt[i], pt[ ( i+1 ) %4] );

  for ( int i = 0; i < 4; i++ )
    bo[i]->set_adjs ( bo[ ( i+1 ) %4], bo[ ( i+3 ) %4] );

  for ( int i = 0; i < 4; i++ )
    borderlist.push_back ( bo[i]  );
}

// void Graph::add_linear_obstacle ( double *p0, double *p1 )
// {
//   Point *pt0 = new Point;
//   Point *pt1 = new Point;
//   pt0->set ( p0[0], p0[1], 0.0 );
//   pt1->set ( p1[0], p1[1], 0.0 );
//   Border *bo0 = new Border ( pt0, pt1 );
//   Border *bo1 = new Border ( pt1, pt0 );
//   bo0->set_adjs ( bo1, bo1 );
//   bo1->set_adjs ( bo0, bo0 );
//   add_border ( bo0 );
//   add_border ( bo1 );
// }

// void Graph::default_obstacles()
// {
//   tprintf ( "Warning --> obstacles are still exprimental !!\n" );
//   double p[16][2] =
//   {
//     {0.2, 0.2},
//     {0.33, 0.33},
//     {0.4, 0.4},
//     {0.5, 0.5},
//     {0.6, 0.6},
//     {0.7, 0.7},
//     {0.2, 0.82},
//     {0.3, 0.7},
//     {0.4, 0.6},
//     {0.48, 0.52},
//     {0.6, 0.4},
//     {0.7, 0.3},
//     {0.5, 0.8},
//     {0.5, 0.6},
//     {0.5, 0.4},
//     {0.5, 0.1},
//   };
// 
//   for ( int i = 0; i < 8; i++ )
//   {
//     add_linear_obstacle ( p[2*i], p[2*i+1] );
//   }
// }

// output the rough diagram without intersections
// --> good to see a result
void Graph::fakerun()
{
  update_time ( 1.0 );
  for ( int i = 0; i < ( int ) generatorlist.size(); i++ )
  {
    Convex_generator *cg = generatorlist[i];
    cg->set_active();
  }
  update_all();
  export_vtk ( "fake_vorosweep" );
  exit ( 0 );
}

void Graph::read_borders (const char *filename)
{  
  dom = new Domain ( filename );
  for ( int i = 0; i < dom->segment_size(); i++ )
    add_border ( dom->get_border ( i ) );
}

void Graph::init()
{  
//   bucketgrid->get_bbox()->set(0.0,1.0,0.0,1.0);
  int sqrtnb = ( int ) floor ( sqrt ( ( double ) ( gnf ) ) ) + 1;
  bucketgrid->init ( sqrtnb, sqrtnb );
  bucketgrid->export_vtk ( "vorobucket" );  
  
  tprintf ( "Init diagram --> %d generators in list\n", ( int ) generatorlist.size() );
  // insert the borders
  ASSERT_ERROR(borderlist.size(), "borders are needed\n");
  for ( int i = 0; i < ( int ) borderlist.size(); i++ )
  {
    Border *bo = borderlist[i];
    insert_border ( bo );
  }
  // insert the generators
  for ( int i = 0; i < ( int ) generatorlist.size(); i++ )
  {
    Convex_generator *cg = generatorlist[i];
    if ( dom )
    {
      if ( dom->point_in_domain ( cg->get_apex()->p ) )
        insert_generator ( cg );
    }
    else
    {
      insert_generator ( cg );
    }
  }
}

void Graph::run()
{
  stats->init_events();
  tprintf ( "Start building diagram --> %d events in queue\n", ( int ) eventqueue.size() );
  while ( !is_queue_empty() )
  {
    dbgprintf ( 1, "--> getting event %d from main queue\n", stats->get_processed_events ( ) );
    Event *ev = get_next_event();
    handle ( ev );
    econtainer->del ( ev );
    stats->add_processed_event ( );
  }
  tprintf ( "End building diagram --> processed %d events\n", stats->get_processed_events ( ) );
}

void Graph::finalize()
{
  generate_internals();
  stats->report();
}





