//#define CGAL_USE_BOOST_BIMAP

#include <fstream>
#include <vector>

// CGAL headers
#include <CGAL/Bbox_2.h>
#include <CGAL/assertions_behaviour.h>
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Timer.h> 

//#define CGAL_TESTING_POLYLINE_SIMPLIFICATION
//#define CGAL_POLYLINE_SIMPLIFICATION_TRACE_LEVEL 15

bool lAppToLog = false ;
void Polyline_simplification_2_external_trace( std::string m )
{
  std::ofstream out("polysim_log.txt", ( lAppToLog ? std::ios::app | std::ios::ate : std::ios::trunc | std::ios::ate ) );
  out << std::setprecision(19) << m << std::endl << std::flush ; 
  lAppToLog = true ;
}

void error_handler ( char const* what, char const* expr, char const* file, int line, char const* msg )
{
  std::cerr << "CGAL error: " << what << " violation!" << std::endl
       << "Expr: " << expr << std::endl
       << "File: " << file << std::endl
       << "Line: " << line << std::endl;
  if ( msg != 0)
      std::cerr << "Explanation:" << msg << std::endl;
    
  throw std::runtime_error("CGAL Error");  
}


#include <CGAL/Constrained_Delaunay_triangulation_2.h>
#include <CGAL/Constrained_triangulation_plus_2.h>
#include <CGAL/Polyline_simplification_2/simplify.h>
#include <CGAL/Polyline_simplification_2/Squared_distance_cost.h>
#include <CGAL/Polyline_simplification_2/Scaled_squared_distance_cost.h>

// Qt headers
#include <QtGui>
#include <QString>
#include <QActionGroup>
#include <QFileDialog>
#include <QInputDialog>
#include <QDragEnterEvent>
#include <QDropEvent>
#include <QSlider>
#include <QProgressBar>

// GraphicsView items and event filters (input classes)
#include <CGAL/Qt/GraphicsViewPolylineInput.h>
#include <CGAL/Qt/Polyline_simplification_2_graphics_item.h>
#include <CGAL/Qt/Converter.h>

// the two base classes
#include "ui_Polyline_simplification_2.h"
#include <CGAL/Qt/DemosMainWindow.h>

// for viewportsBbox(QGraphicsScene*)
#include <CGAL/Qt/utility.h>

typedef CGAL::Exact_predicates_inexact_constructions_kernel K;

typedef K::Point_2         Point_2;
typedef K::Segment_2       Segment_2;
typedef K::Iso_rectangle_2 Iso_rectangle_2;

typedef CGAL::Polyline_simplification_2::Vertex_base_2<K> Vb;
typedef CGAL::Constrained_triangulation_face_base_2<K>    Fb;
typedef CGAL::Triangulation_data_structure_2<Vb,Fb>       TDS;
typedef CGAL::Exact_predicates_tag                        Itag;
typedef CGAL::Constrained_Delaunay_triangulation_2<K,TDS, Itag> CDT;

typedef CGAL::Constrained_triangulation_plus_2<CDT>       PCT;

namespace PS2 = CGAL::Polyline_simplification_2 ;

enum Mode { ABS_P, ABS_E, REL_P, REL_E, MIXED_P, MIXED_E } ;

class MainWindow :
  public CGAL::Qt::DemosMainWindow,
  public Ui::Polyline_simplification_2
{
  Q_OBJECT
  
private:  

  PCT                                                m_pct; 
  QGraphicsScene                                    mScene;
  CGAL::Qt::PolylineSimplificationGraphicsItem<PCT> * mGI;
  CGAL::Qt::GraphicsViewPolylineInput<K> *          mPI;
  
private:  

public:

  MainWindow();

protected:
  void dragEnterEvent(QDragEnterEvent *event);
  void dropEvent(QDropEvent *event);
private:

  void loadPoly(QString);
  void loadOSM (QString);

protected Q_SLOTS:
void open(QString);

public Q_SLOTS:

  void processInput(CGAL::Object o);

  void on_actionShowTriangulation_toggled(bool checked);
  
  void on_actionInsertPolyline_toggled(bool checked);
  
  void on_actionClear_triggered();

  void on_actionRecenter_triggered();

  void on_actionLoadConstraints_triggered();

  void on_actionSimplify_triggered();

  Mode getSimplificationMode() ;

  double getThreshold() ;
  
Q_SIGNALS:
  void changed();
};


MainWindow::MainWindow()
  : DemosMainWindow()
{
  CGAL::set_error_handler  (error_handler);
  CGAL::set_warning_handler(error_handler);
  
  setupUi(this);

  setAcceptDrops(true);

  // Add a GraphicItem for the PS triangulation
  mGI = new CGAL::Qt::PolylineSimplificationGraphicsItem<PCT>(&m_pct);
    
  QObject::connect(this, SIGNAL(changed()), mGI, SLOT(modelChanged()));

  mGI->setVerticesPen(QPen(Qt::black, 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
  mGI->setUnremovableVerticesPen(QPen(Qt::blue, 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
  mGI->setZValue(-1);
  mGI->setVisibleEdges(false);
  mGI->setVisibleConstraints(true);
  
  mScene.addItem(mGI);

  // Setup input handlers. They get events before the mScene gets them
  // and the input they generate is passed to the triangulation with 
  // the signal/slot mechanism    
  mPI = new CGAL::Qt::GraphicsViewPolylineInput<K>(this, &mScene, 0, true); // inputs polylines which are not closed
  this->on_actionInsertPolyline_toggled(true);
  QObject::connect(mPI, SIGNAL(generate(CGAL::Object)), this, SLOT(processInput(CGAL::Object)));
    

  // 
  // Manual handling of actions
  //
  QObject::connect(this->actionQuit, SIGNAL(triggered()), this, SLOT(close()));

  // We put mutually exclusive actions in an QActionGroup
  //  QActionGroup* ag = new QActionGroup(this);
  //  ag->addAction(this->actionInsertPolyline);

  this->actionShowTriangulation->setChecked(false);
  
  //
  // Setup the mScene and the view
  //
  mScene.setItemIndexMethod(QGraphicsScene::NoIndex);
  mScene.setSceneRect(-100, -100, 100, 100);
  this->graphicsView->setScene(&mScene);
  this->graphicsView->setMouseTracking(true);

  // Turn the vertical axis upside down
  this->graphicsView->scale(1, -1);
                                                      
  // The navigation adds zooming and translation functionality to the
  // QGraphicsView
  this->addNavigation(this->graphicsView);

  this->setupStatusBar();
  this->setupOptionsMenu();
  this->addAboutDemo(":/cgal/help/about_Polyline_simplification_2.html");
  this->addAboutCGAL();
  this->setupExportSVG(action_Export_SVG, graphicsView);

  this->addRecentFiles(this->menuFile, this->actionQuit);
  connect(this, SIGNAL(openRecentFile(QString)), this, SLOT(open(QString)));
	  
//  QObject::connect(thresholdSlider, SIGNAL(valueChanged(int)), this, SLOT(set_Threshold(int)));
	  
}


void 
MainWindow::dragEnterEvent(QDragEnterEvent *event)
{
  if (event->mimeData()->hasFormat("text/uri-list"))
    event->acceptProposedAction();
}

void 
MainWindow::dropEvent(QDropEvent *event)
{
  QString filename = event->mimeData()->urls().at(0).path();
  open(filename);
  event->acceptProposedAction();
}

void
MainWindow::processInput(CGAL::Object o)
{
  std::list<Point_2> points;
  if(CGAL::assign(points, o))
  {
    if(points.size() >= 2) 
    {
      m_pct.insert_constraint(points.begin(), points.end());
#if 0
      std::ofstream out("polygon.txt");
      out.precision(12);
      out << points.size() << std::endl;
      for(std::list<Point_2>::iterator it = points.begin(); it!= points.end(); ++it){
        out << *it << std::endl;
      }
#endif
      Q_EMIT( changed());
    }  
  }
  Q_EMIT( changed());
}


/* 
 *  Qt Automatic Connections
 *  http://doc.qt.io/qt-5/designer-using-a-ui-file.html#automatic-connections
 * 
 *  setupUi(this) generates connections to the slots named
 *  "on_<action_name>_<signal_name>"
 */
void
MainWindow::on_actionInsertPolyline_toggled(bool checked)
{
  if(checked){
    mScene.installEventFilter(mPI);
  } else {
    mScene.removeEventFilter(mPI);
  }
}

void
MainWindow::on_actionShowTriangulation_toggled(bool checked)
{
  mGI->setVisibleEdges(checked);
  update();
}

void
MainWindow::on_actionClear_triggered()
{
  m_pct.clear();
  mGI->modelChanged();
  Q_EMIT( changed());
}

Mode MainWindow::getSimplificationMode()
{
  bool lP     = rbuttonPercentage->isChecked();
  int  lError = rbuttonAbsError  ->isChecked() ? 0 : ( rbuttonRelError  ->isChecked() ? 1 : 2 ) ;

  return lP ? lError == 0 ? ABS_P : ( lError == 1 ? REL_P : MIXED_P )
            : lError == 0 ? ABS_E : ( lError == 1 ? REL_E : MIXED_E ) ;
}

double MainWindow::getThreshold()
{
  double lValue = this->textValue->text().toDouble() ;
  return rbuttonPercentage->isChecked() ? lValue / 100.0
           : rbuttonAbsError->isChecked() ? lValue * lValue
              : lValue  ;
}

void MainWindow::on_actionSimplify_triggered()
{
  // wait cursor
  QApplication::setOverrideCursor(Qt::WaitCursor);

  
  try
  {
    switch( getSimplificationMode() )
    {
    case ABS_P   : simplify(m_pct, PS2::Squared_distance_cost(),  PS2::Stop_below_count_ratio_threshold(getThreshold())   ) ; 
 break ;
    case ABS_E   : simplify(m_pct, PS2::Squared_distance_cost(), PS2::Stop_above_cost_threshold(getThreshold()) ) ; break ;
    case REL_P   : simplify(m_pct, PS2::Scaled_squared_distance_cost(),  PS2::Stop_below_count_ratio_threshold(getThreshold()) ) ; break ;
    case REL_E   : simplify(m_pct, PS2::Scaled_squared_distance_cost(), PS2::Stop_above_cost_threshold(getThreshold()) ) ; break ;
    case MIXED_P : simplify(m_pct, PS2::Hybrid_squared_distance_cost<double>(1.0), PS2::Stop_below_count_ratio_threshold(getThreshold())) ; break ;
 case MIXED_E : simplify(m_pct, PS2::Hybrid_squared_distance_cost<double>(1.0), PS2::Stop_above_cost_threshold(getThreshold())) ; break ;

      break ;
    }
    
    statusBar()->showMessage(QString("Simplification done"));
  }  
  catch(...) 
  {
    statusBar()->showMessage(QString("Exception ocurred"));
  }
  
   // default cursor
  QApplication::restoreOverrideCursor();
  mGI->modelChanged();
  Q_EMIT( changed());
}


void 
MainWindow::open(QString fileName)
{
  if(! fileName.isEmpty()){
    if(fileName.endsWith(".poly")){
      loadPoly(fileName);
      this->addToRecentFiles(fileName);
    }  
    else if(fileName.endsWith(".osm")){
      loadOSM(fileName);
      this->addToRecentFiles(fileName);
    }  
  }
}

void
MainWindow::on_actionLoadConstraints_triggered()
{
  QString fileName = QFileDialog::getOpenFileName(this,
						                                      tr("Open Constraint File"),
						                                      "../data",
						                                      tr("Polylines (*.osm *.poly)")
						                                      );
  open(fileName);
}

std::string trim_right ( std::string str )
{
  if ( str.length() > 0 )
  {
    std::size_t pos = str.find_last_not_of( " " ) ;
    if ( pos != std::string::npos )
      return str.substr(0,pos+1);
  }
  
  return std::string("") ;  
}

enum { POLYGON, POLYLINE, POINT_2 } ;

void MainWindow::loadPoly(QString fileName)
{
  m_pct.clear();
  mGI->modelChanged();
  
  std::ifstream ifs(qPrintable(fileName));
  
  std::string line ;
  
  std::vector<Point_2> poly ;
  
  bool lIsClosed = true ;
  
  while ( std::getline(ifs,line) )
  {
    line = trim_right(line);
    
    if ( line.size() > 0 )
    {
      int lCode = POINT_2 ;
      
      if ( line == "POLYGON" )    
      {
        lCode = POLYGON ; 
        lIsClosed = true ; 
      }
      else if ( line == "POLYLINE" )
      {
        lCode = POLYLINE ;
        lIsClosed = false ; 
      }
      
      if ( lCode == POINT_2 )
      {
        double x,y ;
        
        std::istringstream ss(line);
        
        ss >> x >> y ;
        
        poly.push_back( Point_2(x,y) );
      }
      else
      {
        if ( poly.size() > 0 )  
        {
          if ( lIsClosed )
          {
            if(poly.back() != poly.front()){
              poly.push_back(poly.front());
            }
            m_pct.insert_constraint(poly.begin(), poly.end() ) ;
          }
          else
          {
            m_pct.insert_constraint(poly.begin(), poly.end() ) ;
          }
        }
        poly.clear();
      }
    }
  }
  
  if ( poly.size() > 0 )  
  {
    if ( lIsClosed )
    {
      if(poly.back() != poly.front()){
        poly.push_back(poly.front());
      }
      m_pct.insert_constraint(poly.begin(), poly.end() ) ;
    }
    else
    {
      m_pct.insert_constraint(poly.begin(), poly.end() ) ;
    }
  }
      
  Q_EMIT( changed());
  
  actionRecenter->trigger();
}

void MainWindow::loadOSM(QString fileName)
{
  m_pct.clear();
  mGI->modelChanged();

  try
  {
    std::ifstream ifs(qPrintable(fileName));
    
    std::string line ;
    
    std::vector<Point_2> poly ;
    
    while ( std::getline(ifs,line) )
    {
      line = trim_right(line);
      
      if ( line.size() > 0 )
      {
        if ( line.find(':') != std::string::npos )
        {
          if ( poly.size() > 0 )  
          {
            if ( poly.front() == poly.back() && poly.size() >= 4 )
            {
              if ( is_simple_2(poly.begin(), poly.end() - 1) )
                m_pct.insert_constraint(poly.begin(), poly.end() - 1 ) ;
            }
            else if ( poly.size() >= 2 )
            {
              if ( is_simple_2(poly.begin(), poly.end()) )
                m_pct.insert_constraint(poly.begin(), poly.end() ) ;
            }
          }
          poly.clear();
        }
        else
        {
          double x,y ;
          
          std::string::size_type pos = line.find(',');
          if ( pos != std::string::npos )
            line[pos]= ' ' ;
          
          std::istringstream ss(line);
          
          ss >> x >> y ;
          
          poly.push_back( Point_2(x,y) );
        }
      }
    }
    
    if ( poly.size() > 0 )  
    {
      if ( poly.front() == poly.back() )
      {
        m_pct.insert_constraint(poly.begin(), poly.end() - 1 ) ;
      }
      else
      {
        m_pct.insert_constraint(poly.begin(), poly.end() ) ;
      }
    }
  }  
  catch(...) 
  {
    statusBar()->showMessage(QString("Exception ocurred"));
  }
  
  Q_EMIT( changed());
  
  actionRecenter->trigger();
}

void
MainWindow::on_actionRecenter_triggered()
{
  this->graphicsView->setSceneRect(mGI->boundingRect());
  this->graphicsView->fitInView(mGI->boundingRect(), Qt::KeepAspectRatio);  
}

#include "Polyline_simplification_2.moc"

int main(int argc, char **argv)
{
  QApplication app(argc, argv);

  app.setOrganizationDomain("geometryfactory.com");
  app.setOrganizationName("GeometryFactory");
  app.setApplicationName("Polyline_simplification_2 demo");

  // Import resources from libCGALQt5.
  CGAL_QT_INIT_RESOURCES;
	
  MainWindow mainWindow;
  mainWindow.show();
  return app.exec();
}
