#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/bounding_box.h>
#include <CGAL/Three/Polyhedron_demo_plugin_helper.h>
#include <CGAL/boost/graph/copy_face_graph.h>
#include <CGAL/algorithm.h>
#include <CGAL/Three/Scene_item_rendering_helper.h>
#include <CGAL/Three/Three.h>
#include <CGAL/Three/Point_container.h>

#include <CGAL/Polygon_mesh_processing/transform.h>
#include "Scene_surface_mesh_item.h"

#include "Scene_facegraph_transform_item.h"
#include "Scene_points_with_normal_item.h"
#include "Scene_polylines_item.h"

#include <QString>
#include <QAction>
#include <QMenu>
#include <QMainWindow>
#include <QDockWidget>
#include <QDialog>
#include <QApplication>
#include <QElapsedTimer>
#include <QMessageBox>

#include "ui_Transformation_widget.h"

using namespace CGAL::Three;
typedef Viewer_interface Vi;
typedef Point_container Pc;

#include "ui_MeshOnGrid_dialog.h"
typedef Scene_surface_mesh_item Facegraph_item;


class Scene_transform_point_set_item : public Scene_item_rendering_helper
{
  Q_OBJECT
  typedef Point_set_3<Kernel> Point_set;
public:
  Scene_transform_point_set_item(Scene_points_with_normal_item *item, const CGAL::qglviewer::Vec& pos)
    : base(item),
      center_(pos),
      frame(new CGAL::Three::Scene_item::ManipulatedFrame())
  {
    const CGAL::qglviewer::Vec offset = Three::mainViewer()->offset();
    frame->setPosition(pos+offset);
    Point_set ps= *item->point_set();
    const Kernel::Point_3& p = ps.point(*(ps.begin()));
    CGAL::Bbox_3 bbox(p.x(), p.y(), p.z(), p.x(), p.y(), p.z());
    CGAL::cpp98::random_shuffle (ps.begin(), ps.end());
    points.reserve(3*ps.size());
    for (Point_set::const_iterator it = ps.begin(); it != ps.first_selected(); it++)
    {
      const Kernel::Point_3& p = ps.point(*it);
      bbox = bbox + p.bbox();
      points.push_back(p.x()-center_.x);
      points.push_back(p.y()-center_.y);
      points.push_back(p.z()-center_.z);
    }
    setPointContainer(0, new Pc(Vi::PROGRAM_NO_SELECTION, false));
    setBbox(Bbox(bbox.xmin(),bbox.ymin(),bbox.zmin(),
                 bbox.xmax(),bbox.ymax(),bbox.zmax()));

    nb_points = points.size();
    for(auto v : CGAL::QGLViewer::QGLViewerPool())
    {
      CGAL::Three::Viewer_interface* viewer = static_cast<CGAL::Three::Viewer_interface*>(v);
      initGL(viewer);
    }
    Pc* pc = getPointContainer(0);
    pc->allocate(Pc::Vertices,
                 points.data(),
                 static_cast<int>(points.size()*sizeof(float)));
    Q_FOREACH(CGAL::QGLViewer* v, CGAL::QGLViewer::QGLViewerPool())
      initializeBuffers(qobject_cast<Vi*>(v));
  }

  void initializeBuffers(Viewer_interface *v) const
  {
    Pc* pc = getPointContainer(0);
    pc->initializeBuffers(v);
    pc->setFlatDataSize(nb_points);
    points.clear();
    points.shrink_to_fit();
  }
  bool manipulatable() const { return true; }

  Scene_item* clone()const{ return nullptr; }

  bool supportsRenderingMode(RenderingMode m) const { return m==Points ; }

  QString toolTip() const {
    return QObject::tr("<p>Affine transformation of <b>%1</b></p>"
                       "<p>Keep <b>Ctrl</b> pressed and use the arcball to define an affine transformation.<br />"
                       "Press <b>S</b> to apply the affine transformation to a copy of <b>%1</b>.</p>")
        .arg(getBase()->name());
  }

  void setFMatrix(double matrix[16])
  {
    for (int i=0; i<16; ++i)
      f_matrix.data()[i] = (float)matrix[i];
  }

  ~Scene_transform_point_set_item() {delete frame; Q_EMIT killed(); }
  void drawPoints(Viewer_interface *viewer) const
  {
    GLfloat point_size;
    viewer->glGetFloatv(GL_POINT_SIZE, &point_size);
    viewer->setGlPointSize(6.f);
    double ratio_displayed = 1.0;
    if ((viewer->inFastDrawing () || frame->isManipulated()) &&
        (nb_points /3 > 300000)) // arbitrary large value
      ratio_displayed = 3 * 300000. / (double)nb_points;

    Pc* pc = getPointContainer(0);
    std::size_t real_size = pc->getFlatDataSize();
    pc->setFlatDataSize(ratio_displayed * real_size);
    pc->setColor(QColor(Qt::green));
    pc->setFrameMatrix(f_matrix);
    pc->draw(viewer, true);
    pc->setFlatDataSize(real_size);
  }
  bool keyPressEvent(QKeyEvent* e){
    if (e->key()==Qt::Key_S){
      Q_EMIT stop();
      return true;
    }
    return false;
  }
  const Scene_points_with_normal_item* getBase()const{return base;}
  const CGAL::qglviewer::Vec& center() const { return center_; }
  CGAL::Three::Scene_item::ManipulatedFrame* manipulatedFrame() { return frame; }
  void compute_bbox() const
  {
    Point_set ps= *base->point_set();
    const Kernel::Point_3& p = ps.point(*(ps.begin()));
    CGAL::Bbox_3 bbox(p.x(), p.y(), p.z(), p.x(), p.y(), p.z());

    for (Point_set::const_iterator it = ps.begin(); it != ps.first_selected(); it++)
    {
      bbox = bbox + ps.point(*it).bbox();
    }
    CGAL::qglviewer::Vec v_min(bbox.xmin(),bbox.ymin(),bbox.zmin());
    CGAL::qglviewer::Vec v_max(bbox.xmax(),bbox.ymax(),bbox.zmax());

    setBbox(Bbox(v_min.x,v_min.y,v_min.z,
                 v_max.x,v_max.y,v_max.z));
  }
  bool isEmpty() const{return false;}
Q_SIGNALS:
  void stop();
  void killed();
private:
  const Scene_points_with_normal_item* base;
  CGAL::qglviewer::Vec center_;
  CGAL::Three::Scene_item::ManipulatedFrame* frame;
  mutable std::vector<float> points;
  std::size_t nb_points;
  QMatrix4x4 f_matrix;

};

using namespace CGAL::Three;
class Polyhedron_demo_affine_transform_plugin :
    public QObject,
    public Polyhedron_demo_plugin_helper
{
  Q_OBJECT
  Q_INTERFACES(CGAL::Three::Polyhedron_demo_plugin_interface)
  Q_PLUGIN_METADATA(IID "com.geometryfactory.PolyhedronDemo.PluginInterface/1.0")

public:

  Polyhedron_demo_affine_transform_plugin():started(false){}

  QList<QAction*> actions() const {
    return QList<QAction*>() << actionTransformPolyhedron
                             << actionMeshOnGrid;
  }

  bool applicable(QAction* a) const {
    if(a == actionMeshOnGrid)
    {
      return qobject_cast<Facegraph_item*>(scene->item(scene->mainSelectionIndex()));
    }
    else
      return qobject_cast<Facegraph_item*>(scene->item(scene->mainSelectionIndex()))
          || qobject_cast<Scene_facegraph_transform_item*>(scene->item(scene->mainSelectionIndex()))
          || qobject_cast<Scene_points_with_normal_item*>(scene->item(scene->mainSelectionIndex()))
          || qobject_cast<Scene_transform_point_set_item*>(scene->item(scene->mainSelectionIndex()));
  }

  void init(QMainWindow* _mw, CGAL::Three::Scene_interface* scene_interface, Messages_interface*) {
    for(int i=0; i<3; ++i)
    {
      scaling[i] = 1;
      lastScaling[i] = 1 ;
    }
    lastMatrix.setToIdentity();
    mw = _mw;
    this->scene = scene_interface;

    actionMeshOnGrid = new QAction(
                tr("Create a Grid of Surface Meshes")
          , mw);
    if(actionMeshOnGrid) {
      connect(actionMeshOnGrid, SIGNAL(triggered()),this, SLOT(grid()));
    }

    actionTransformPolyhedron = new QAction(
                tr("Affine Transformation")
          , mw);
    if(actionTransformPolyhedron) {
      connect(actionTransformPolyhedron, SIGNAL(triggered()),this, SLOT(go()));
    }
    transform_item = nullptr;
    transform_points_item = nullptr;

    dock_widget = new QDockWidget(
          tr("Affine Transformation")
          , mw);
    ui.setupUi(dock_widget);
    dock_widget->setWindowTitle(tr(
                                  "Affine Transformation"
                                  ));
    addDockWidget(dock_widget);
    dock_widget->hide();

    QList<QLineEdit*> lineEdits;
    lineEdits<<ui.lineEditA<<ui.lineEditX<<ui.lineEditY<<ui.lineEditZ;
    Q_FOREACH(QLineEdit* widget, lineEdits)
    {
      QSizePolicy sp_retain = widget->sizePolicy();
      sp_retain.setRetainSizeWhenHidden(true);
      widget->setSizePolicy(sp_retain);
    }
    connect(ui.applyTransfo_Button, &QPushButton::clicked,
            this, &Polyhedron_demo_affine_transform_plugin::applySingleTransformation);
    connect(ui.transfo_ComboBox, SIGNAL(currentIndexChanged(int)),
            this, SLOT(updateSingleTransfoValues(int)));
    connect(ui.resetMatrix_Button, &QPushButton::clicked,
            this, &Polyhedron_demo_affine_transform_plugin::resetTransformMatrix);
    connect(ui.clearButton, &QPushButton::clicked,
            this, &Polyhedron_demo_affine_transform_plugin::clear);
    connect(ui.undoButton, &QPushButton::clicked,
            this, &Polyhedron_demo_affine_transform_plugin::undo);
    connect(ui.validatePushButton, &QPushButton::clicked,
            this, &Polyhedron_demo_affine_transform_plugin::end);
    //initial state is Translation: no need for this one
    ui.lineEditA->hide();
  }

  void start(FaceGraph *facegraph, const QString name, const Scene_item::Bbox&);
  void start(Scene_points_with_normal_item*);
  void end();
  void closure()
  {
    dock_widget->hide();
  }

private:
  QDockWidget* dock_widget;
  Ui::TransformationWidget ui;
  QAction*  actionTransformPolyhedron;
  QAction* actionMeshOnGrid;
  Scene_facegraph_transform_item* transform_item;
  Scene_transform_point_set_item* transform_points_item;
  CGAL::Three::Scene_interface::Item_id tr_item_index;
  CGAL::Three::Scene_interface* scene;
  bool started;
  double scaling[3];
  double lastScaling[3];
  QMatrix4x4 lastMatrix;

  //takes a double[16]
  void transformMatrix( double* res)const
  {
    bool is_point_set = false;
    if(!transform_item)
    {
      if(transform_points_item)
        is_point_set = true;
    }

    QMatrix4x4 tMatrix, manipulatedMatrix, scalingMatrix;
    if(!is_point_set)
      for(int i=0; i<16; ++i)
      {
        manipulatedMatrix.data()[i] = transform_item->manipulatedFrame()->matrix()[i];
        scalingMatrix.data()[i] = 0;
      }
    else
      for(int i=0; i<16; ++i)
      {
        manipulatedMatrix.data()[i] = transform_points_item->manipulatedFrame()->matrix()[i];
        scalingMatrix.data()[i] = 0;
      }
    scalingMatrix.data()[0] = scaling[0];
    scalingMatrix.data()[5] = scaling[1];
    scalingMatrix.data()[10] = scaling[2];
    scalingMatrix.data()[15] = 1;
    tMatrix = manipulatedMatrix*scalingMatrix;
    for(int i=0; i<16; ++i)
      res[i] = (double)tMatrix.data()[i];
  }
  template<class Item>
  void normalize(Item*);
public Q_SLOTS:
  void grid();
  void go();
  void transformed_killed();

  void updateUiMatrix();
  void clear()
  {
   ui.lineEditA->clear();
   ui.lineEditX->clear();
   ui.lineEditY->clear();
   ui.lineEditZ->clear();
  }
  void resetTransformMatrix()
  {
    bool is_point_set = false;
    if(!transform_item)
    {
      if(transform_points_item)
        is_point_set = true;
      else
        return;
    }
    double matrix[16] = {0};
    scaling[0]=scaling[1]=scaling[2]=1.0;
    matrix[0]=1; matrix[5] = 1; matrix[10] = 1; matrix[15] = 1;
    matrix[12] = is_point_set ? transform_points_item->center().x : transform_item->center().x;
    matrix[13] = is_point_set ? transform_points_item->center().y : transform_item->center().y;
    matrix[14] = is_point_set ? transform_points_item->center().z : transform_item->center().z;

    if(!is_point_set)
    {
      const CGAL::qglviewer::Vec offset = Three::mainViewer()->offset();
      transform_item->manipulatedFrame()->setFromMatrix(matrix);
      transform_item->manipulatedFrame()->translate(offset);
      transform_item->itemChanged();
    }
    else
    {
      const CGAL::qglviewer::Vec offset = Three::mainViewer()->offset();
      transform_points_item->manipulatedFrame()->setFromMatrix(matrix);
      transform_points_item->manipulatedFrame()->translate(offset);
      transform_points_item->itemChanged();
    }
  }
  void updateSingleTransfoValues(int);
  void applySingleTransformation();
  void resetItems()
  {
    transform_item = nullptr;
    transform_points_item = nullptr;
  }
  void undo()
  {
    bool is_point_set = false;
    if(!transform_item)
    {
      if(transform_points_item)
        is_point_set = true;
      else
        return;
    }

    double matrix[16];
    for(short i = 0; i<16; ++i)
      matrix[i] = (double)lastMatrix.data()[i];

    for(short i = 0; i< 3; ++i)
      scaling[i] = lastScaling[i];

    if(!is_point_set)
    {
      transform_item->manipulatedFrame()->setFromMatrix(matrix);
      transform_item->itemChanged();
    }
    else
    {
      transform_points_item->manipulatedFrame()->setFromMatrix(matrix);
      transform_points_item->itemChanged();
    }
  }

}; // end class Polyhedron_demo_affine_transform_plugin

class GridDialog :
    public QDialog,
    public Ui::GridDialog
{
  Q_OBJECT
public:
  GridDialog(QWidget* =nullptr)
  {
    setupUi(this);
  }
};

void Polyhedron_demo_affine_transform_plugin::grid()
{
      Facegraph_item* item =
          qobject_cast<Facegraph_item*>(scene->item(scene->mainSelectionIndex()));
      if(!item)
        return;


      FaceGraph m = *item->face_graph();

      Scene_item::Bbox b = item->bbox();


      double x_t(1.001*((b.max)(0)- (b.min)(0))),
          y_t(1.001*((b.max)(1)- (b.min)(1))),
          z_t(1.001*((b.max)(2)- (b.min)(2)));

      GridDialog dialog(mw);
      dialog.x_space_doubleSpinBox->setValue(x_t);
      dialog.y_space_doubleSpinBox->setValue(y_t);
      dialog.z_space_doubleSpinBox->setValue(z_t);
      if(!dialog.exec())
        return;
      QApplication::setOverrideCursor(Qt::WaitCursor);
      int i_max=dialog.x_spinBox->value(),
          j_max=dialog.y_spinBox->value(),
          k_max=dialog.z_spinBox->value();
      x_t = dialog.x_space_doubleSpinBox->value();
      y_t = dialog.y_space_doubleSpinBox->value();
      z_t = dialog.z_space_doubleSpinBox->value();
      bool single = dialog.singleItemCheckBox->isChecked();
      Facegraph_item* t_item = nullptr;
      if(single)
      {
        t_item = new Facegraph_item();
        t_item->setName(tr("%1 %2x%3x%4")
                        .arg(item->name())
                        .arg(i_max)
                        .arg(j_max)
                        .arg(k_max));
        scene->addItem(t_item);
      }
      for(int i = 0; i < i_max; ++i)
      {
        for(int j = 0; j< j_max; ++j)
        {
          for(int k = 0; k< k_max; ++k)
          {
            FaceGraph e;
            CGAL::copy_face_graph(m,e);
            Kernel::Aff_transformation_3 trans(CGAL::TRANSLATION, Kernel::Vector_3(i*x_t,j*y_t,k*z_t));
            CGAL::Polygon_mesh_processing::transform(trans, e);
            if(!single)
            {
              t_item = new Facegraph_item(e);
              t_item->setName(tr("%1 %2,%3,%4")
                              .arg(item->name())
                              .arg(i)
                              .arg(j)
                              .arg(k));
              scene->addItem(t_item);
            }
            else
            {
              CGAL::copy_face_graph(e, *t_item->face_graph());
            }
          }
        }
      }
      if(single && t_item)
        t_item->invalidateOpenGLBuffers();
      QApplication::restoreOverrideCursor();
}
void Polyhedron_demo_affine_transform_plugin::go(){
  if (!started){
    Scene_item* item = scene->item(scene->mainSelectionIndex());
    Scene_points_with_normal_item* points_item = nullptr;
    Facegraph_item* poly_item = qobject_cast<Facegraph_item*>(item);
    if(!poly_item)
    {
      points_item = qobject_cast<Scene_points_with_normal_item*>(item);
      if(!points_item)
        return;
    }
    dock_widget->show();
    dock_widget->raise();
    started=true;
    actionTransformPolyhedron->setText("Apply affine transformation");
    if(poly_item)
    {
      start(poly_item->polyhedron(), poly_item->name(), poly_item->bbox());
    }
    else if(points_item)
      start(points_item);
    ui.validatePushButton->setEnabled(true);
  }
  else
    end();
}

void Polyhedron_demo_affine_transform_plugin::transformed_killed(){
  started=false;
  actionTransformPolyhedron->setText("Affine Transformation");
}

void Polyhedron_demo_affine_transform_plugin::start(FaceGraph *facegraph, const QString name, const Scene_item::Bbox &bbox){
  QApplication::setOverrideCursor(Qt::PointingHandCursor);

  double x=(bbox.xmin()+bbox.xmax())/2;
  double y=(bbox.ymin()+bbox.ymax())/2;
  double z=(bbox.zmin()+bbox.zmax())/2;
  lastMatrix.setToIdentity();
  lastMatrix.data()[12] = x;
  lastMatrix.data()[13] = y;
  lastMatrix.data()[14] = z;
  transform_item = new Scene_facegraph_transform_item(CGAL::qglviewer::Vec(x,y,z),facegraph, name);
  connect(transform_item, &Scene_item::aboutToBeDestroyed,
          [](){ QApplication::restoreOverrideCursor(); });
  transform_item->setManipulatable(true);
  transform_item->setColor(Qt::green);
  transform_item->setRenderingMode(Wireframe);
  transform_item->setName(tr("Affine Transformation"));
  scaling[0] = 1;
  scaling[1] = 1;
  scaling[2] = 1;
  connect(transform_item, SIGNAL(stop()),this, SLOT(go()));
  connect(transform_item, SIGNAL(killed()),this, SLOT(transformed_killed()));
  connect(transform_item->manipulatedFrame(), &CGAL::qglviewer::ManipulatedFrame::modified,
          this, &Polyhedron_demo_affine_transform_plugin::updateUiMatrix);
  connect(transform_item, &Scene_facegraph_transform_item::aboutToBeDestroyed,
          dock_widget, &QDockWidget::hide);
  connect(transform_item, &Scene_facegraph_transform_item::aboutToBeDestroyed,
          this, &Polyhedron_demo_affine_transform_plugin::resetItems);
  tr_item_index=scene->addItem(transform_item);
  scene->setSelectedItem(tr_item_index);
  resetTransformMatrix();
}

void Polyhedron_demo_affine_transform_plugin::start(Scene_points_with_normal_item* points_item){
  QApplication::setOverrideCursor(Qt::PointingHandCursor);

  Scene_points_with_normal_item::Bbox bbox = points_item->bbox();
  double x=(bbox.xmin()+bbox.xmax())/2;
  double y=(bbox.ymin()+bbox.ymax())/2;
  double z=(bbox.zmin()+bbox.zmax())/2;
  lastMatrix.setToIdentity();
  lastMatrix.data()[12] = x;
  lastMatrix.data()[13] = y;
  lastMatrix.data()[14] = z;

  transform_points_item = new Scene_transform_point_set_item(points_item,CGAL::qglviewer::Vec(x,y,z));
  connect(transform_points_item, &Scene_item::aboutToBeDestroyed,
          [](){ QApplication::restoreOverrideCursor(); });
  transform_points_item->setRenderingMode(Points);
  transform_points_item->setName(tr("Affine Transformation"));
  connect(transform_points_item, SIGNAL(stop()),this, SLOT(go()));
  connect(transform_points_item, SIGNAL(killed()),this, SLOT(transformed_killed()));
  connect(transform_points_item->manipulatedFrame(), &CGAL::qglviewer::ManipulatedFrame::modified,
          this, &Polyhedron_demo_affine_transform_plugin::updateUiMatrix);
  connect(transform_points_item, &Scene_transform_point_set_item::aboutToBeDestroyed,
          dock_widget, &QDockWidget::hide);
  connect(transform_points_item, &Scene_transform_point_set_item::aboutToBeDestroyed,
          this, &Polyhedron_demo_affine_transform_plugin::resetItems);
  tr_item_index=scene->addItem(transform_points_item);
  scene->setSelectedItem(tr_item_index);
  resetTransformMatrix();
}


void Polyhedron_demo_affine_transform_plugin::end(){
  ui.validatePushButton->setEnabled(false);
  QApplication::restoreOverrideCursor();
  double matrix[16];
  transformMatrix(&matrix[0]);
  const CGAL::qglviewer::Vec offset = Three::mainViewer()->offset();
  matrix[12]-=offset.x;
  matrix[13]-=offset.y;
  matrix[14]-=offset.z;
  resetTransformMatrix();
  QMatrix4x4 transform_matrix;
  for(int i=0; i<16; ++i)
    transform_matrix.data()[i] = (float)matrix[i];

  if(transform_item)
  {
    CGAL::qglviewer::Vec c = transform_item->center();
    FaceGraph* new_sm = new FaceGraph();
    CGAL::copy_face_graph(*transform_item->getFaceGraph(), *new_sm);
    typedef boost::property_map<FaceGraph,CGAL::vertex_point_t>::type VPmap;
    VPmap vpmap = get(CGAL::vertex_point, *new_sm);

    for (boost::graph_traits<FaceGraph>::vertex_iterator it=vertices(*new_sm).begin(),
         endit=vertices(*new_sm).end();endit!=it;++it)
    {
      QVector3D vec = transform_matrix * QVector3D(get(vpmap, *it).x() - c.x,
                                                   get(vpmap, *it).y() - c.y,
                                                   get(vpmap, *it).z() - c.z);

      put(vpmap, *it, Kernel::Point_3 (vec.x(), vec.y(), vec.z()));
    }
    Facegraph_item* new_item=new Facegraph_item(new_sm);
    new_item->setName(tr("%1_transformed").arg(transform_item->name()));
    scene->replaceItem(tr_item_index,new_item);
    delete transform_item;
    transform_item = nullptr;
  }
  else if(transform_points_item)
  {
    Scene_points_with_normal_item* new_item
      = new Scene_points_with_normal_item (*(transform_points_item->getBase()));
    Point_set* new_ps = new_item->point_set();
    CGAL::qglviewer::Vec c = transform_points_item->center();

    for(Point_set::Index idx : *new_ps)
    {
      QVector3D vec = transform_matrix * QVector3D(new_ps->point(idx).x() - c.x,
                                                   new_ps->point(idx).y() - c.y,
                                                   new_ps->point(idx).z() - c.z);
      new_ps->point(idx) = Kernel::Point_3 (vec.x(), vec.y(), vec.z());
    }

    new_item->setName(tr("%1_transformed").arg(transform_points_item->getBase()->name()));
    scene->replaceItem(tr_item_index,new_item);
    delete transform_points_item;
    transform_points_item = nullptr;
  }
  dock_widget->hide();
  QApplication::restoreOverrideCursor();
}

void Polyhedron_demo_affine_transform_plugin::updateUiMatrix()
{
  bool is_point_set = false;
  if(!transform_item)
  {
    if(transform_points_item)
      is_point_set = true;
    else
      return;
  }
  double tmatrix[16];
  transformMatrix(&tmatrix[0]);
  if(is_point_set)
    transform_points_item->setFMatrix(tmatrix);
  else
    transform_item->setFMatrix(tmatrix);
  //this matrix is not mandatory but it clarifies the code to use one.
  QMatrix4x4 matrix;
  for (int i=0; i<16; ++i)
    matrix.data()[i] = tmatrix[i];
  if(!is_point_set)
  {
    matrix(0,3) -= transform_item->center().x;
    matrix(1,3) -= transform_item->center().y;
    matrix(2,3) -= transform_item->center().z;
  }
  else
  {
    matrix(0,3) -= transform_points_item->center().x;
    matrix(1,3) -= transform_points_item->center().y;
    matrix(2,3) -= transform_points_item->center().z;
  }
  const CGAL::qglviewer::Vec offset = Three::mainViewer()->offset();
  matrix.data()[12]-=offset.x;
  matrix.data()[13]-=offset.y;
  matrix.data()[14]-=offset.z;
  ui.matrix_00->setText(QString("%1").arg(matrix(0,0))); ui.matrix_01->setText(QString("%1").arg(matrix(0,1))); ui.matrix_02->setText(QString("%1").arg(matrix(0,2))); ui.matrix_03->setText(QString("%1").arg(matrix(0,3)));
  ui.matrix_10->setText(QString("%1").arg(matrix(1,0))); ui.matrix_11->setText(QString("%1").arg(matrix(1,1))); ui.matrix_12->setText(QString("%1").arg(matrix(1,2))); ui.matrix_13->setText(QString("%1").arg(matrix(1,3)));
  ui.matrix_20->setText(QString("%1").arg(matrix(2,0))); ui.matrix_21->setText(QString("%1").arg(matrix(2,1))); ui.matrix_22->setText(QString("%1").arg(matrix(2,2))); ui.matrix_23->setText(QString("%1").arg(matrix(2,3)));
  ui.matrix_30->setText(QString("%1").arg(matrix(3,0))); ui.matrix_31->setText(QString("%1").arg(matrix(3,1))); ui.matrix_32->setText(QString("%1").arg(matrix(3,2))); ui.matrix_33->setText(QString("%1").arg(matrix(3,3)));

}

void Polyhedron_demo_affine_transform_plugin::updateSingleTransfoValues(int index)
{
  ui.lineEditZ->setToolTip("Value along the z-axis.");
  switch(index)
  {
  case 0:
    ui.lineEditA->show();
    ui.lineEditX->show();
    ui.lineEditY->show();
    ui.lineEditZ->show();
    ui.transfo_ComboBox->setToolTip("Angle, axis coordinates");
    break;
  case 1:
    ui.lineEditA->hide();
    ui.lineEditX->show();
    ui.lineEditY->show();
    ui.lineEditZ->show();
    ui.transfo_ComboBox->setToolTip("Axis coordinates");
    break;
  case 2:
    ui.lineEditA->hide();
    ui.lineEditX->show();
    ui.lineEditY->show();
    ui.lineEditZ->show();
    ui.transfo_ComboBox->setToolTip("Scaling along each axis");
    break;
  default:
    ui.lineEditA->hide();
    ui.lineEditX->hide();
    ui.lineEditY->hide();
    ui.lineEditZ->hide();
    ui.transfo_ComboBox->setToolTip("Scales coordinates between [0..1] ");
    break;
  }
}

template<class Item>
void Polyhedron_demo_affine_transform_plugin::normalize(Item* item)
{
  Kernel::Point_3 bil(item->bbox().xmin(),
                          item->bbox().ymin(),
                          item->bbox().zmin());

  Kernel::Point_3 tsr(item->bbox().xmax(),
                          item->bbox().ymax(),
                          item->bbox().zmax());
  //Get the scale factor for the item's coordinates to be in [0..1]
  double max = (std::max)((double)tsr.x()-bil.x(), (double)tsr.y()-bil.y());
  max = (std::max)(max, (double)tsr.z()-bil.z());
  QVector3D v_bil= QVector3D(bil.x(),bil.y(),bil.z());
  QMatrix4x4 frameMat = QMatrix();
  QVector3D center(item->center().x,
                   item->center().y,
                   item->center().z);
  frameMat.translate(-(v_bil-center)/max);
  double d_mat[16];
  for(int i=0; i<16; ++i)
    d_mat[i] = (double)frameMat.data()[i];
  scaling[0]=scaling[1]=scaling[2]=1.0/max;
  item->manipulatedFrame()->setFromMatrix(d_mat);
}
void Polyhedron_demo_affine_transform_plugin::applySingleTransformation()
{
  bool is_point_set = false;
  if(!transform_item)
  {
    if(transform_points_item)
      is_point_set = true;
    else
      return;
  }
  //save the matrix before the change
  for(short i = 0; i<3; ++i)
    lastScaling[i]=scaling[i];
  double currentMatrix[16];
  transformMatrix(&currentMatrix[0]);
  for(short i = 0; i < 16; ++i)
    lastMatrix.data()[i] = (float)currentMatrix[i];

  switch(ui.transfo_ComboBox->currentIndex())
  {
  //rotation
  case 0:
  {
    CGAL::qglviewer::Vec axis(ui.lineEditX->text().toDouble(),
                        ui.lineEditY->text().toDouble(),
                        ui.lineEditZ->text().toDouble());

    if(!is_point_set)
      transform_item->manipulatedFrame()->rotate(CGAL::qglviewer::Quaternion(axis, ui.lineEditA->text().toDouble()*CGAL_PI/180.0));
    else
      transform_points_item->manipulatedFrame()->rotate(CGAL::qglviewer::Quaternion(axis, ui.lineEditA->text().toDouble()*CGAL_PI/180.0));
    break;
  }
    //translation
  case 1:
  {
    if(!is_point_set)
      transform_item->manipulatedFrame()->translate(CGAL::qglviewer::Vec(ui.lineEditX->text().toDouble() ,
                                                                   ui.lineEditY->text().toDouble() ,
                                                                   ui.lineEditZ->text().toDouble() ));
    else
      transform_points_item->manipulatedFrame()->translate(CGAL::qglviewer::Vec(ui.lineEditX->text().toDouble() ,
                                                                          ui.lineEditY->text().toDouble() ,
                                                                          ui.lineEditZ->text().toDouble() ));
    break;
  }
    //scaling
  case 2:
  {
    scaling[0] = ui.lineEditX->text().isEmpty() ? 1 : scaling[0]*ui.lineEditX->text().toDouble();
    scaling[1] = ui.lineEditY->text().isEmpty() ? 1 : scaling[1]*ui.lineEditY->text().toDouble();
    scaling[2] = ui.lineEditZ->text().isEmpty() ? 1 : scaling[2]*ui.lineEditZ->text().toDouble();
    break;
  }
    //normalizing
  case 3:
  {
    resetTransformMatrix();
    if(!is_point_set)
      normalize(transform_item);
    else
      normalize(transform_points_item);
    break;
  }
  default:
  {
    break;
  }
  }
  updateUiMatrix();
  if(is_point_set)
  {
    transform_points_item->compute_bbox();
    transform_points_item->itemChanged();
  }
  else
  {
    transform_item->compute_bbox();
    transform_item->itemChanged();
  }
}

#include "Affine_transform_plugin.moc"
