// compute_arrangement: a new method to compute arrangement of segments.
// Copyright (C) 2020 CNRS and LIRIS' Establishments (France).
//
// 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 3 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 <https://www.gnu.org/licenses/>.
//
// Author(s)     : Guillaume Damiand <guillaume.damiand@liris.cnrs.fr>
//

#include <cassert>
#include <cstdlib>
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <stack>
#include <tuple>
#include <thread>
#include <boost/filesystem.hpp>

#include <CGAL/Memory_sizer.h>
#include <CGAL/assertions.h>
#include <CGAL/Surface_sweep_2.h>
#include <CGAL/Surface_sweep_2_algorithms.h>
#include <CGAL/Arr_segment_traits_2.h>
#include <CGAL/Arrangement_2.h>
#include <CGAL/Arr_consolidated_curve_data_traits_2.h>
#include <CGAL/Arr_non_caching_segment_basic_traits_2.h>

#include "CGAL_typedefs.h"
#include "Segment_readers.h"
#include "My_visitor.h"
#include "SHds.h"

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
[[ noreturn ]] void usage(int /*argc*/, char** argv)
{
  // Name
  std::cout<<"Name"<<std::endl;
  std::cout<<"        "<<argv[0]<<" - a new method to compute arrangement of segments";
  std::cout<<std::endl<<std::endl;
  // Synopsis
  std::cout<<"SYNOPSIS"<<std::endl;
  std::cout<<"        "<<argv[0]<<" [--help|-h|-?] "
           <<"[-t1 filename] [-nbs S] [-nbt N] [-crop] [-optimized-strips] [-cgal]"
           <<std::endl<<std::endl;
  // Description
  std::cout<<"DESCRIPTION"<<std::endl;
  std::cout<<"        "<<"Compute the arrangement of segments given in the filename."
           <<std::endl<<std::endl;
  // Options
  std::cout<<"        --help, -h, -?"<<std::endl
           <<"                display this help and exit."
           <<std::endl<<std::endl;
  std::cout<<"        -t1 filename"
           <<std::endl
           <<"                load the text segment file, and creates the arrangement: required."
           <<std::endl<<std::endl;
  std::cout<<"        -nbs S"
           <<std::endl
           <<"                number of strips (1 by default)."
           <<std::endl<<std::endl;
  std::cout<<"        -nbt N"
           <<std::endl
           <<"                number of threads (1 by default)."
           <<std::endl<<std::endl;
  std::cout<<"        -crop"
          <<std::endl
          <<"                crop the segments to fit in its strip."
          <<std::endl<<std::endl;
  std::cout<<"        -repeat R"
           <<std::endl
           <<"                repeat each test R times (1 by default)."
           <<std::endl<<std::endl;
  std::cout<<"        -optimized-strips"
           <<std::endl
           <<"                compute the length of each strips dynamically (by default each strip has the same size) (used only if the number of threads is > 1)."
           <<std::endl<<std::endl;
  std::cout<<"        -cgal"
           <<std::endl
           <<"                run cgal arrangement (and not our new method)."
           <<std::endl<<std::endl;
// Author
  std::cout<<"AUTHOR"<<std::endl;
  std::cout<<"        "<<"Written by Guillaume Damiand."
           <<std::endl<<std::endl;
  // Copyright
  std::cout<<"COPYRIGHT"<<std::endl;
  std::cout<<"        "<<"Copyright (C) 2020 CNRS and LIRIS' Establishments (France). "
           <<"License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>"
           <<std::endl
           <<"        This is free software: you are free to change and redistribute it under certain conditions. "
           <<"There is NO WARRANTY, to the extent permitted by law."
           <<std::endl<<std::endl;
  // Reporting bugs
  std::cout<<"REPORTING BUGS"<<std::endl;
  std::cout<<"        "<<"Email bug reports to Guillaume Damiand ⟨guillaume.damiand@liris.cnrs.fr⟩."
           <<std::endl<<std::endl;
  exit(EXIT_FAILURE);
}
////////////////////////////////////////////////////////////////////////////////
[[ noreturn ]] void error_command_line(int argc, char** argv, const char* msg)
{
  std::cout<<"ERROR: "<<msg<<std::endl;
  usage(argc, argv);
}
////////////////////////////////////////////////////////////////////////////////
void process_command_line(int argc, char** argv,
                          bool& t1,
                          std::string& filename,
                          std::size_t& nbstrips,
                          std::size_t& nbthreads,
                          std::size_t& repeat,
                          bool &optimized_strips,
                          bool &cgal,
                          bool &crop)
{
  t1=false;
  filename="";
  nbstrips=1;
  nbthreads=1;
  optimized_strips=false;
  cgal=false;
  repeat=1;
  crop=false;

  bool helprequired=false;
  std::string arg;

  for (int i=1; i<argc; ++i)
  {
    arg=std::string(argv[i]);
    if (arg==std::string("-h") || arg==std::string("--help") || arg==std::string("-?"))
    { helprequired=true; }
    else if (arg==std::string("-t1"))
    {
      t1=true;
      if (argc-1-i<1)
      { error_command_line(argc, argv, "Error: no filename after -t1 option."); }
      filename=std::string(argv[++i]);
    }
    else if (arg==std::string("-nbs"))
    {
      if (argc-1-i<1)
      { error_command_line(argc, argv, "Error: no number after -nbs option."); }
      nbstrips=std::stoi(std::string(argv[++i]));
    }
    else if (arg==std::string("-nbt"))
    {
      if (argc-1-i<1)
      { error_command_line(argc, argv, "Error: no number after -nbt option."); }
      nbthreads=std::stoi(std::string(argv[++i]));
    }
    else if (arg==std::string("-repeat"))
    {
      if (argc-1-i<1)
      { error_command_line(argc, argv, "Error: no number after -repeat option."); }
      repeat=std::stoi(std::string(argv[++i]));
    }
    else if (arg==std::string("-optimized-strips"))
    { optimized_strips=true; }
    else if (arg==std::string("-cgal"))
    { cgal=true; }
    else if (arg==std::string("-crop"))
    { crop=true; }
    else if (arg[0]=='-')
    {
      std::cout<<"Unknown option "<<arg<<std::endl;
      helprequired=true;
    }
  }

  if (nbthreads==0) { nbthreads=1; }
  if (repeat==0) { repeat=1; }
  if (!t1) { std::cout<<"ERROR: missing txt file name."<<std::endl; }
  if (helprequired || !t1) { usage(argc, argv); }
}
///////////////////////////////////////////////////////////////////////////////
int main(int argc, char** argv)
{
  std::string filename;
  bool t1;
  std::size_t nbstrips, nbthreads, repeat;
  bool optimized_strips;
  bool cgal;
  bool crop;

  process_command_line(argc, argv, t1, filename, nbstrips, nbthreads, repeat,
                       optimized_strips, cgal, crop);

  std::ofstream ftimes("times.dat", std::ios_base::app);
  std::ofstream fdetailedtimes("detailed-times.dat", std::ios_base::app);
  std::ofstream fcells("nbcells.dat", std::ios_base::app);
  ftimes.setf( std::ios::fixed, std:: ios::floatfield );
  ftimes.precision(2);
  fdetailedtimes.setf( std::ios::fixed, std:: ios::floatfield );
  fdetailedtimes.precision(2);

  boost::filesystem::path p(filename);

  if (cgal)
  {
    ftimes<<p.stem()<<'\t';
    fdetailedtimes<<p.stem()<<'\t';
    fcells<<"####################"<<p.stem()<<std::endl;
  }

  if (cgal)
  {
    typedef CGAL::Arr_segment_traits_2<EPECK> Traits_2;
    typedef CGAL::Arrangement_2<Traits_2>  Arrangement_2;
    std::vector<ESegment> all_segments;
    all_segments.reserve(number_of_lines(filename));
    Read_from_file<EPECK> reader(filename);
    fill_segment_array<EPECK>(reader, all_segments);

    My_timer timer;
    for (std::size_t i=0; i<repeat; ++i)
    {
      timer.start();
      Arrangement_2 arr;
      CGAL::insert(arr, all_segments.begin(), all_segments.end());
      timer.stop();

      if (i==0)
      {
        fcells<<arr.number_of_halfedges()<<'\t'
             <<arr.number_of_vertices()<<'\t'
            <<arr.number_of_edges()<<'\t'
           <<arr.number_of_faces()-arr.number_of_unbounded_faces()<<'\t'
          <<arr.number_of_faces()<<std::endl;
      }
    }

    ftimes<<timer.time()/repeat<<'\t';
  }
  else
  {
    std::vector<double> times;
    // times[0] : global time
    // times[1] : load and dispatch
    // times[2] : compute all local arrangements
    // times[3] : compute faces

    for (std::size_t i=0; i<repeat; ++i)
    {
      SHds shds(filename, nbstrips, nbthreads, optimized_strips, times, crop);
      if (i==0)
      {
        fcells<<shds.number_of_halfedges()<<'\t'
              <<shds.number_of_vertices()<<'\t'
              <<shds.number_of_edges()<<'\t'
              <<shds.number_of_finite_faces()<<'\t'
              <<shds.number_of_faces()<<'\t'
              <<shds.number_of_external_halfedges()<<std::endl;
      }
    }

    ftimes<<(times[0]-times[1])/repeat<<'\t';
    fdetailedtimes<<times[1]/repeat<<'\t'
                  <<times[2]/repeat<<'\t'
                  <<times[3]/repeat<<'\t';
  }

  return EXIT_SUCCESS;
}
///////////////////////////////////////////////////////////////////////////////