#include "config.h"
#include "Scene.h"

#include <CGAL/Three/Scene_item.h>
#include <CGAL/Three/Scene_print_item_interface.h>
#include <CGAL/Three/Scene_transparent_interface.h>
#include <CGAL/Three/Scene_zoomable_item_interface.h>
#include <CGAL/Three/Three.h>

#include  <CGAL/Three/Scene_item.h>
#include <CGAL/Three/Scene_print_item_interface.h>
#include <CGAL/Three/Viewer_interface.h>


#include <QObject>
#include <QMetaObject>
#include <QString>
#include <QEvent>
#include <QMouseEvent>
#include <QPainter>
#include <QColorDialog>
#include <QApplication>
#include <QPointer>
#include <QList>
#include <QAbstractProxyModel>
#include <QMimeData>
#include <QOpenGLFramebufferObject>

#include <unordered_set>

Scene::Scene(QObject* parent)
    : QStandardItemModel(parent),
      selected_item(-1),
      item_A(-1),
      item_B(-1)
{

    connect(this, SIGNAL(selectionRay(double, double, double,
                                      double, double, double)),
            this, SLOT(setSelectionRay(double, double, double,
                                       double, double, double)));
    connect(this, SIGNAL(indexErased(Scene_interface::Item_id)),
              this, SLOT(adjustIds(Scene_interface::Item_id)));
    picked = false;
    gl_init = false;
    dont_emit_changes = false;

}
Scene::Item_id
Scene::addItem(CGAL::Three::Scene_item* item)
{
    Bbox bbox_before = bbox();
    m_entries.push_back(item);
    Item_id id = m_entries.size() - 1;
    connect(item, SIGNAL(itemChanged()),
            this, SLOT(itemChanged()));
    connect(item, SIGNAL(itemVisibilityChanged()),
            this, SLOT(itemVisibilityChanged()));
    connect(item, SIGNAL(redraw()),
            this, SLOT(callDraw()));
    if(item->isFinite()
            && !item->isEmpty()
            && bbox_before + item->bbox() != bbox_before
            )
    {
        Q_EMIT updated_bbox(true);
    }
    QList<QStandardItem*> list;
    for(int i=0; i<5; i++)
    {
        list<<new QStandardItem();
        list.at(i)->setEditable(false);
    }
    invisibleRootItem()->appendRow(list);
    for(int i=0; i<5; i++){
        index_map[list.at(i)->index()] = m_entries.size() -1;
    }
    Q_EMIT updated();
    children.push_back(id);
    Q_EMIT newItem(id);
    CGAL::Three::Scene_group_item* group =
            qobject_cast<CGAL::Three::Scene_group_item*>(item);
    if(group)
        addGroup(group);
    //init the item for the mainViewer to avoid using unexisting
    //VAOs if the mainViewer is not the first to be drawn.
    QOpenGLFramebufferObject* fbo = CGAL::Three::Three::mainViewer()->depthPeelingFbo();
    CGAL::Three::Three::mainViewer()->setDepthPeelingFbo(nullptr);//to prevent crashing as the fbo is not initialized in this call.
    item->draw(CGAL::Three::Three::mainViewer());
    item->drawEdges(CGAL::Three::Three::mainViewer());
    item->drawPoints(CGAL::Three::Three::mainViewer());
    CGAL::Three::Three::mainViewer()->setDepthPeelingFbo(fbo);
    if(group)
       m_groups.append(id);
    return id;
}

CGAL::Three::Scene_item*
Scene::replaceItem(Scene::Item_id index, CGAL::Three::Scene_item* item, bool emit_item_about_to_be_destroyed)
{
    if(index < 0 || index >= m_entries.size())
        return nullptr;

    connect(item, SIGNAL(itemChanged()),
            this, SLOT(itemChanged()));
    connect(item, SIGNAL(itemVisibilityChanged()),
            this, SLOT(itemVisibilityChanged()));
    connect(item, SIGNAL(redraw()),
            this, SLOT(callDraw()));
    CGAL::Three::Scene_group_item* group =
            qobject_cast<CGAL::Three::Scene_group_item*>(m_entries[index]);
    QList<Scene_item*> group_children;
    if(group)
    {
      m_groups.removeAll(index);
      for(Item_id id : group->getChildren())
      {
        CGAL::Three::Scene_item* child = group->getChild(id);
        group->unlockChild(child);
        group_children << child;
      }
    }
    CGAL::Three::Scene_group_item* parent = m_entries[index]->parentGroup();
    bool is_locked = false;
    if(parent)
    {
      is_locked = parent->isChildLocked(m_entries[index]);
      parent->unlockChild(m_entries[index]);
      parent->removeChild(m_entries[index]);
    }
    std::swap(m_entries[index], item);
    if(parent)
    {
      changeGroup(m_entries[index], parent);
      if(is_locked)
        parent->lockChild(m_entries[index]);
    }

    Q_EMIT newItem(index);
    if ( item->isFinite() && !item->isEmpty() &&
         m_entries[index]->isFinite() && !m_entries[index]->isEmpty() &&
         item->bbox()!=m_entries[index]->bbox() )
    {
      Q_EMIT updated_bbox(true);
    }

    if(emit_item_about_to_be_destroyed) {
      Q_EMIT itemAboutToBeDestroyed(item);
      item->aboutToBeDestroyed();
    }

    Q_EMIT updated();
    group =
            qobject_cast<CGAL::Three::Scene_group_item*>(m_entries[index]);
    if(group)
    {
        addGroup(group);
        m_groups.append(index);
    }
    itemChanged(index);
    Q_EMIT restoreCollapsedState();
    redraw_model();
    Q_EMIT selectionChanged(index);
    for(Scene_item* child : group_children)
    {
      erase(item_id(child));
    }
    return item;
}

Scene::Item_id
Scene::erase(Scene::Item_id index)
{
  if(index < 0 || index >= numberOfEntries())
    return -1;

  CGAL::Three::Scene_item* item = m_entries[index];

  if(qobject_cast<Scene_group_item*>(item))
  {
    setSelectedItemIndices(QList<Scene_interface::Item_id>() << item_id(item));
    return erase(selectionIndices());
  }

  m_groups.removeAll(index);
  if(item->parentGroup() && item->parentGroup()->isChildLocked(item))
    return -1;

  // clears the Scene_view
  clear();
  index_map.clear();
  if(item->parentGroup())
    item->parentGroup()->removeChild(item);

  //removes the item from all groups that contain it
  Item_id removed_item = item_id(item);
  children.removeAll(removed_item);
  indexErased(removed_item);
  m_entries.removeAll(item);
  Q_EMIT itemAboutToBeDestroyed(item);
  item->aboutToBeDestroyed();
  item->deleteLater();
  selected_item = -1;
  //re-creates the Scene_view
  for(Item_id id : children)
  {
    organize_items(this->item(id), invisibleRootItem(), 0);
  }
  QStandardItemModel::beginResetModel();
  Q_EMIT updated();
  QStandardItemModel::endResetModel();
  Q_EMIT restoreCollapsedState();
  if(--index >= 0)
    return index;
  if(!m_entries.isEmpty())
    return 0;
  return -1;
}

int
Scene::erase(QList<int> indices)
{
  if(indices.empty())
    return -1;
  std::unordered_set<CGAL::Three::Scene_item*> to_be_removed;
  int max_index = -1;
  for(int index : indices) {
    if(index < 0 || index >= m_entries.size())
      continue;

    max_index = (std::max)(max_index, index);
    CGAL::Three::Scene_item* item = m_entries[index];
    if(item->parentGroup()
       && item->parentGroup()->isChildLocked(item))
      if(!indices.contains(item_id(item->parentGroup())))
        continue;
    Scene_group_item* group = qobject_cast<Scene_group_item*>(item);
    if(group)
    {
      for(Item_id id : group->getChildren())
      {
        CGAL::Three::Scene_item* child = group->getChild(id);
        to_be_removed.insert(child);
      }
    }
    to_be_removed.insert(item);
  }

  for(Scene_item* item : to_be_removed) {
    Item_id removed_item = item_id(item);
    if(removed_item == -1) //case of the selection_item, for example.
      continue;
    if(item->parentGroup())
      item->parentGroup()->removeChild(item);
    children.removeAll(removed_item);
    indexErased(removed_item);
    m_groups.removeAll(removed_item);
    m_entries.removeAll(item);

    Q_EMIT itemAboutToBeDestroyed(item);
    item->aboutToBeDestroyed();
    item->deleteLater();
  }
  clear();
  index_map.clear();
  selected_item = -1;
  for(Item_id id : children)
  {
    organize_items(item(id), invisibleRootItem(), 0);
  }
  QStandardItemModel::beginResetModel();
  Q_EMIT updated();
  QStandardItemModel::endResetModel();
  Q_EMIT restoreCollapsedState();

  int index = max_index + 1 - indices.size();
  if(index >= m_entries.size()) {
    index = m_entries.size() - 1;
  }
  if(index >= 0)
    return index;
  if(!m_entries.isEmpty())
    return 0;
  return -1;

}

void Scene::remove_item_from_groups(Scene_item* item)
{
  CGAL::Three::Scene_group_item* group = item->parentGroup();
  if(group)
  {
    group->removeChild(item);
    children.push_back(item_id(item));
  }
}
Scene::~Scene()
{
  for(CGAL::QGLViewer* viewer : CGAL::QGLViewer::QGLViewerPool())
  {
    removeViewer(static_cast<CGAL::Three::Viewer_interface*>(viewer));
    viewer->setProperty("is_destroyed", true);
  }
  for(QOpenGLVertexArrayObject* vao : vaos.values())
  {
    vao->destroy();
    delete vao;
  }
  for(CGAL::Three::Scene_item* item_ptr : m_entries)
  {
    item_ptr->deleteLater();
  }
  m_entries.clear();
}

CGAL::Three::Scene_item*
Scene::item(Item_id index) const
{
  return m_entries.value(index); // QList::value checks bounds
}

Scene::Item_id
Scene::item_id(CGAL::Three::Scene_item* scene_item) const
{
  return m_entries.indexOf(scene_item);
}

int
Scene::numberOfEntries() const
{
  return m_entries.size();
}

// Duplicate a scene item.
// Return the ID of the new item (-1 on error).
Scene::Item_id
Scene::duplicate(Item_id index)
{
  if(index < 0 || index >= m_entries.size())
    return -1;

  const CGAL::Three::Scene_item* item = m_entries[index];
  CGAL::Three::Scene_item* new_item = item->clone();
  if(new_item)
  {
    new_item->setName(tr("%1 (copy)").arg(item->name()));
    new_item->setColor(item->color());
    new_item->setVisible(item->visible());
    addItem(new_item);
    return m_entries.size() - 1;
  }
  else
  {
    return -1;
  }
}

void Scene::initializeGL(CGAL::Three::Viewer_interface* viewer)
{

  //Vertex source code
  const char vertex_source[] =
  {
    "#version 150                                 \n"
    "in vec4 vertex;                \n"
    "in vec2 v_texCoord;            \n"
    "uniform mat4 projection_matrix;       \n"
    "out vec2 f_texCoord;              \n"
    "void main(void)                             \n"
    "{                                           \n"
    "  f_texCoord = v_texCoord;                  \n"
    "  gl_Position = projection_matrix * vertex; \n"
    "}                                           \n"

  };

  const char vertex_source_comp[] =
  {
    "attribute highp vec4 vertex;                \n"
    "attribute highp vec2 v_texCoord;            \n"
    "uniform highp mat4 projection_matrix;       \n"
    "varying highp vec2 f_texCoord;              \n"
    "void main(void)                             \n"
    "{                                           \n"
    "  f_texCoord = v_texCoord;                  \n"
    "  gl_Position = projection_matrix * vertex; \n"
    "}                                           \n"

  };
  //Fragment source code
  const char fragment_source[] =
  {
    "#version 150                                                            \n"
    "in vec2 f_texCoord;                                         \n"
    "out vec4 out_color ; \n"
    "uniform sampler2D s_texture;                                             \n"
    "void main(void)                                                        \n"
    "{                                                                      \n"
    "  out_color = texture(s_texture, f_texCoord); \n"
    "}                                                                      \n"
  };
  const char fragment_source_comp[] =
  {
    "varying highp vec2 f_texCoord;                                         \n"
    "uniform sampler2D texture;                                             \n"
    "void main(void)                                                        \n"
    "{                                                                      \n"
    "  gl_FragColor = texture2D(texture, f_texCoord); \n"
    "}                                                                      \n"
  };


  QOpenGLShader vertex_shader(QOpenGLShader::Vertex);
  QOpenGLShader fragment_shader(QOpenGLShader::Fragment);
  if(viewer->isOpenGL_4_3())
  {
    if(!vertex_shader.compileSourceCode(vertex_source))
    {
      std::cerr<<"Compiling vertex source FAILED"<<std::endl;
    }

    if(!fragment_shader.compileSourceCode(fragment_source))
    {
      std::cerr<<"Compiling fragmentsource FAILED"<<std::endl;
    }
  }
  else
  {
    if(!vertex_shader.compileSourceCode(vertex_source_comp))
    {
      std::cerr<<"Compiling vertex source FAILED"<<std::endl;
    }

    if(!fragment_shader.compileSourceCode(fragment_source_comp))
    {
      std::cerr<<"Compiling fragmentsource FAILED"<<std::endl;
    }
  }

  if(!program.addShader(&vertex_shader))
  {
    std::cerr<<"adding vertex shader FAILED"<<std::endl;
  }
  if(!program.addShader(&fragment_shader))
  {
    std::cerr<<"adding fragment shader FAILED"<<std::endl;
  }
  if(!program.link())
  {
    //std::cerr<<"linking Program FAILED"<<std::endl;
    qDebug() << program.log();
  }
  points[0] = -1.0f; points[1] = -1.0f; points[2] = 0.0f;
  points[3] = 1.0f; points[4] = 1.0f; points[5] = 0.0f;
  points[6] = 1.0f; points[7] = -1.0f; points[8] = 0.0f;
  points[9] = -1.0f; points[10] = -1.0f; points[11] = 0.0f;
  points[12] = -1.0f; points[13] = 1.0f; points[14] = 0.0f;
  points[15] = 1.0f; points[16] = 1.0f; points[17] = 0.0f;

  uvs[0] = 0.0f; uvs[1] = 0.0f;
  uvs[2] = 1.0f; uvs[3] = 1.0f;
  uvs[4] = 1.0f; uvs[5] = 0.0f;
  uvs[6] = 0.0f; uvs[7] = 0.0f;
  uvs[8] = 0.0f; uvs[9] = 1.0f;
  uvs[10] = 1.0f; uvs[11] = 1.0f;

  vbo[0].create();
  vbo[1].create();

  viewer->makeCurrent();
  vaos[viewer] = new QOpenGLVertexArrayObject();
  vaos[viewer]->create();
  program.bind();
  vaos[viewer]->bind();
  vbo[0].bind();
  vbo[0].allocate(points, 18 * sizeof(float));
  program.enableAttributeArray("vertex");
  program.setAttributeArray("vertex", GL_FLOAT, nullptr, 3);
  vbo[0].release();

  vbo[1].bind();
  vbo[1].allocate(uvs, 12 * sizeof(float));
  program.enableAttributeArray("v_texCoord");
  program.setAttributeArray("v_texCoord", GL_FLOAT, nullptr, 2);
  vbo[1].release();
  vaos[viewer]->release();
  program.release();
  gl_init = true;
}

void Scene::s_itemAboutToBeDestroyed(CGAL::Three::Scene_item *rmv_itm)
{
  for(CGAL::Three::Scene_item* item : m_entries)
  {
    if(item == rmv_itm)
      item->itemAboutToBeDestroyed(item);
  }
}
bool
Scene::keyPressEvent(QKeyEvent* e)
{
  bool res = false;
  for(int i : selected_items_list)
  {
    CGAL::Three::Scene_item* item = m_entries[i];
    res |= item->keyPressEvent(e);
  }
  return res;
}

void
Scene::draw(CGAL::Three::Viewer_interface* viewer)
{
  draw_aux(false, viewer);
}
void
Scene::drawWithNames(CGAL::Three::Viewer_interface* viewer)
{
  draw_aux(true, viewer);
}

bool item_should_be_skipped_in_draw(Scene_item* item) {
  if(!item->visible()) return true;
  if(item->has_group == 0) return false;
  Scene_group_item* group = item->parentGroup();
  while(group != nullptr) {
    if(!group->visible()) return false;
    group = group->parentGroup();
  }
  return true;
}


void Scene::renderScene(const QList<Scene_interface::Item_id> &items,
                        Viewer_interface *viewer,
                        QMap<float, int>& picked_item_IDs,
                        bool with_names,
                        int pass,
                        bool writing_depth,
                        QOpenGLFramebufferObject *fbo)
{
  viewer->setCurrentPass(pass);
  viewer->setDepthWriting(writing_depth);
  viewer->setDepthPeelingFbo(fbo);
  for(Scene_interface::Item_id index : items)
  {
    CGAL::Three::Scene_item& item = *m_entries[index];
    CGAL::Three::Scene_group_item* group =
        qobject_cast<CGAL::Three::Scene_group_item*>(&item);
    if(index == selected_item || selected_items_list.contains(index))
    {
      item.selection_changed(true);
    }
    else
    {
      item.selection_changed(false);
    }
    if(group ||item.visible())
    {
      if( group || item.renderingMode() == Flat || item.renderingMode() == FlatPlusEdges || item.renderingMode() == Gouraud || item.renderingMode() == GouraudPlusEdges )
      {
        if(with_names) {
          viewer->glClearDepthf(1.0);
          viewer->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        }
        item.draw(viewer);
        if(with_names) {

          //    read depth buffer at pick location;
          float depth = read_depth_under_pixel(picked_pixel, viewer, viewer->camera());
          if (depth < 2.0)
          {
            //add object to list of picked objects;
            picked_item_IDs[depth] = index;
          }
        }
      }
      if(group)
        group->renderChildren(viewer, picked_item_IDs, picked_pixel, with_names);
    }
  }
}

void Scene::renderWireScene(const QList<Scene_interface::Item_id> &items,
                            Viewer_interface *viewer,
                            QMap<float, int>& picked_item_IDs,
                            bool with_names)
{
  for(Scene_interface::Item_id index : items)
   {
     CGAL::Three::Scene_item& item = *m_entries[index];
     CGAL::Three::Scene_group_item* group =
         qobject_cast<CGAL::Three::Scene_group_item*>(&item);
     if(index == selected_item || selected_items_list.contains(index))
     {
         item.selection_changed(true);
     }
     else
     {
         item.selection_changed(false);
     }

     if(group ||item.visible())
     {
       if( group || (!with_names && item.renderingMode() == FlatPlusEdges )
          || item.renderingMode() == Wireframe
          || item.renderingMode() == PointsPlusNormals
          || item.renderingMode() == GouraudPlusEdges)
       {
         if(with_names) {
           viewer->glClearDepthf(1.0);
           viewer->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
         }
         viewer->setGlPointSize(2.f);
         item.drawEdges(viewer);
       }
       else{
           if( item.renderingMode() == PointsPlusNormals ){
               viewer->setGlPointSize(2.f);
               if(index == selected_item || selected_items_list.contains(index))
               {
                 item.selection_changed(true);
               }
               else
               {
                 item.selection_changed(false);
               }
               item.drawEdges(viewer);
           }
       }

       if((item.renderingMode() == Wireframe || item.renderingMode() == PointsPlusNormals )
          && with_names)
       {

         //    read depth buffer at pick location;
         float depth = 1.0;
         depth = read_depth_under_pixel(picked_pixel, viewer, viewer->camera());
         if (depth != 1.0)
         {
           //add object to list of picked objects;
           picked_item_IDs[depth] = index;
         }
       }
     }
   }
}

void Scene::renderPointScene(const QList<Scene_interface::Item_id> &items,
                             Viewer_interface *viewer,
                             QMap<float, int>& picked_item_IDs,
                             bool with_names)
{
  for(Scene_interface::Item_id index : items)
  {
    CGAL::Three::Scene_item& item = *m_entries[index];
    CGAL::Three::Scene_group_item* group =
        qobject_cast<CGAL::Three::Scene_group_item*>(&item);
    if(group ||item.visible())
    {
      if(item.renderingMode() == Points && with_names) {
          viewer->glClearDepthf(1.0);
          viewer->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      }

      if(group || item.renderingMode() == Points  ||
         (item.renderingMode() == PointsPlusNormals)  ||
         (item.renderingMode() == ShadedPoints))
      {
        if(with_names) {
          viewer->glClearDepthf(1.0);
          viewer->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        }
        viewer->setGlPointSize(3.0f);
        item.drawPoints(viewer);
      }
      if(item.renderingMode() == Points && with_names) {
        //    read depth buffer at pick location;
        float depth = 1.0;
        depth = read_depth_under_pixel(picked_pixel, viewer, viewer->camera());
        if (depth != 1.0)
        {
          //add object to list of picked objects;
          picked_item_IDs[depth] = index;
        }
      }
    }
  }
}


 bool Scene::has_alpha()
 {
   for(Scene_item* item : m_entries)
     if(item->alpha() != 1.0f)
       return true;
   return false;
 }
void
Scene::draw_aux(bool with_names, CGAL::Three::Viewer_interface* viewer)
{
    QMap<float, int> picked_item_IDs;
    if(with_names)
      viewer->glEnable(GL_DEPTH_TEST);
    if(!gl_init)
        initializeGL(viewer);
    //treat opaque items first to ensure that when two items are the same, but only one is opaque,
    //the item stays opaque
    QList<Item_id> opaque_items;
    QList<Item_id> transparent_items;
    for(Item_id id : children)
    {
      Scene_item* item = m_entries[id];
      Scene_group_item* group = qobject_cast<Scene_group_item*>(item);
      bool is_transparent=false;
      if(item->alpha() != 1.0f)
        is_transparent = true;
      else if(group)
      {
        for(const auto& child : group->getChildren())
        {
          if(group->getChild(child)->alpha() < 1.0f)
          {
            is_transparent = true;
            break;
          }
        }
      }
      if(!is_transparent)
        opaque_items.push_back(id);
      else
        transparent_items.push_back(id);
    }
    renderScene(children, viewer, picked_item_IDs, with_names, -1, false, nullptr);
    if(with_names)
    {
      //here we get the selected point, before erasing the depth buffer. We store it
      //in a dynamic property as a QList<double>. If there is some alpha, the
      //depth buffer is altered, and the picking will return true even when it is
      // performed in the background, when it should return false. To avoid that,
      // we distinguish the case were there is no alpha, to let the viewer
      //perform it, and the case where the pixel is not found. In the first case,
      //we erase the property, in the latter we return an empty list.
      //According to that, in the viewer, either we perform the picking, either we do nothing.
      if(has_alpha()) {
        bool found = false;
        CGAL::qglviewer::Vec point = viewer->camera()->pointUnderPixel(picked_pixel, found) - viewer->offset();
        if(found){
          QList<QVariant> picked_point;
          picked_point <<point.x
                      <<point.y
                     <<point.z;
          viewer->setProperty("picked_point", picked_point);
        }
        else{
          viewer->setProperty("picked_point", QList<QVariant>());
        }
      }
      else {
        viewer->setProperty("picked_point", {});
      }
    }
    if(!with_names && has_alpha())
    {
      std::vector<QOpenGLFramebufferObject*> fbos;
      std::vector<QOpenGLFramebufferObject*> depth_test;
      QColor background = viewer->backgroundColor();
      fbos.resize(static_cast<int>(viewer->total_pass()));
      depth_test.resize(static_cast<int>(viewer->total_pass())-1);

      int viewport[4];
      viewer->glGetIntegerv(GL_VIEWPORT, viewport);

      int w = viewport[2];// viewer->width();
      int h = viewport[3];// viewer->height();

      //first pass
      fbos[0] = new QOpenGLFramebufferObject(w, h, QOpenGLFramebufferObject::Depth, GL_TEXTURE_2D, GL_RGBA32F);
      fbos[0]->bind();
      viewer->glDisable(GL_BLEND);
      viewer->glEnable(GL_DEPTH_TEST);
      viewer->glDepthFunc(GL_LESS);
      viewer->glClearColor(0.0f,
                           0.0f,
                           0.0f,
                           0.0f);
      viewer->glClearDepthf(1);
      viewer->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      renderScene(opaque_items, viewer, picked_item_IDs, false, 0,false, nullptr);
      renderScene(transparent_items, viewer, picked_item_IDs, false, 0,false, nullptr);
      fbos[0]->release();

      depth_test[0] = new QOpenGLFramebufferObject(w, h,QOpenGLFramebufferObject::Depth, GL_TEXTURE_2D, GL_RGBA32F);
      depth_test[0]->bind();
      viewer->glDisable(GL_BLEND);
      viewer->glEnable(GL_DEPTH_TEST);
      viewer->glDepthFunc(GL_LESS);
      viewer->glClearColor(0.0f,
                           0.0f,
                           0.0f,
                           0.0f);
      viewer->glClearDepthf(1);
      viewer->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      renderScene(opaque_items, viewer, picked_item_IDs, false, 0,true, nullptr);
      renderScene(transparent_items, viewer, picked_item_IDs, false, 0,true, nullptr);
      depth_test[0]->release();

      //other passes
      for(int i=1; i<viewer->total_pass()-1; ++i)
      {
        fbos[i] = new QOpenGLFramebufferObject(w, h,QOpenGLFramebufferObject::Depth, GL_TEXTURE_2D, GL_RGBA32F);
        fbos[i]->bind();
        viewer->glDisable(GL_BLEND);
        viewer->glEnable(GL_DEPTH_TEST);
        viewer->glDepthFunc(GL_LESS);
        viewer->glClearColor(0.0f,
                             0.0f,
                             0.0f,
                             0.0f);
        viewer->glClearDepthf(1);
        viewer->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        renderWireScene(children, viewer, picked_item_IDs, false);
        renderPointScene(children, viewer, picked_item_IDs, false);
        renderScene(opaque_items     , viewer, picked_item_IDs, false, i, false, depth_test[i-1]);
        renderScene(transparent_items, viewer, picked_item_IDs, false, i, false, depth_test[i-1]);
        fbos[i]->release();

        depth_test[i] = new QOpenGLFramebufferObject(w, h,QOpenGLFramebufferObject::Depth, GL_TEXTURE_2D, GL_RGBA32F);
        depth_test[i]->bind();
        viewer->glDisable(GL_BLEND);
        viewer->glEnable(GL_DEPTH_TEST);
        viewer->glDepthFunc(GL_LESS);
        viewer->glClearColor(0.0f,
                             0.0f,
                             0.0f,
                             0.0f);
        viewer->glClearDepthf(1);
        viewer->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        renderScene(opaque_items     , viewer, picked_item_IDs, false, i, true, depth_test[i-1]);
        renderScene(transparent_items, viewer, picked_item_IDs, false, i, true, depth_test[i-1]);
        depth_test[i]->release();
      }

      //last pass
      fbos[static_cast<int>(viewer->total_pass())-1] = new QOpenGLFramebufferObject(w, h,QOpenGLFramebufferObject::Depth, GL_TEXTURE_2D, GL_RGBA32F);
      fbos[static_cast<int>(viewer->total_pass())-1]->bind();
      viewer->glDisable(GL_BLEND);
      viewer->glEnable(GL_DEPTH_TEST);
      viewer->glDepthFunc(GL_LESS);
      viewer->glClearColor(0.0f,
                           0.0f,
                           0.0f,
                           0.0f);
      viewer->glClearDepthf(1);
      viewer->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
      renderScene(opaque_items     , viewer, picked_item_IDs, false, static_cast<int>(viewer->total_pass())-1, false, depth_test[static_cast<int>(viewer->total_pass())-2]);
      renderScene(transparent_items, viewer, picked_item_IDs, false, static_cast<int>(viewer->total_pass())-1, false, depth_test[static_cast<int>(viewer->total_pass())-2]);
      fbos[static_cast<int>(viewer->total_pass())-1]->release();
      if(viewer->getStoredFrameBuffer() != nullptr)
        viewer->getStoredFrameBuffer()->bind();

      //blending
      program.bind();
      vaos[viewer]->bind();
      viewer->glClearColor(static_cast<GLclampf>(background.redF()),
                           static_cast<GLclampf>(background.greenF()),
                           static_cast<GLclampf>(background.blueF()),
                           0.0f);
      viewer->glDisable(GL_DEPTH_TEST);
      viewer->glClear(GL_COLOR_BUFFER_BIT);
      viewer->glEnable(GL_BLEND);
      viewer->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

      QMatrix4x4 proj_mat;
      proj_mat.setToIdentity();
      proj_mat.ortho(-1,1,-1,1,0,1);
      program.setUniformValue("projection_matrix", proj_mat);
      for(int i=0; i< static_cast<int>(viewer->total_pass())-1; ++i)
        delete depth_test[i];
      for(int i = static_cast<int>(viewer->total_pass())-1; i>=0; --i)
      {
        viewer->glBindTexture(GL_TEXTURE_2D, fbos[i]->texture());
        viewer->glDrawArrays(GL_TRIANGLES,0,static_cast<GLsizei>(6));
        delete fbos[i];
      }
      viewer->glDisable(GL_BLEND);
      viewer->glEnable(GL_DEPTH_TEST);
      vaos[viewer]->release();
      program.release();
    }

    viewer->glDepthFunc(GL_LEQUAL);
    // Wireframe OpenGL drawing
    renderWireScene(children, viewer, picked_item_IDs, with_names);
    // Points OpenGL drawing
    renderPointScene(children, viewer, picked_item_IDs, with_names);

    if(with_names)
    {
        QList<float> depths = picked_item_IDs.keys();
        if(!depths.isEmpty())
        {
            std::sort(depths.begin(), depths.end());
            int id = picked_item_IDs[depths.first()];
            setSelectedItemIndex(id);
            viewer->setSelectedName(id);

        }
    }
    if(with_names)
        picked = true;
    else
        picked = false;
    //scrolls the sceneView to the selected item's line.
    if(picked)
    {
        Q_EMIT(itemPicked(index_map.key(mainSelectionIndex())));
    }
    Q_EMIT drawFinished();
}

// workaround for Qt-4.2 (see above)
#undef lighter
QVariant
Scene::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
    {
        return QVariant();
    }

    int id = index_map[index];
    if(id < 0 || id >= m_entries.size())
        return QVariant();
    if(role == ::Qt::ToolTipRole)
    {
        return m_entries[id]->toolTip();
    }
    switch(index.column())
    {
    case ColorColumn:
        if(role == ::Qt::DecorationRole)
            return m_entries.value(id)->color();
        break;
    case NameColumn:
        if(role == ::Qt::DisplayRole || role == ::Qt::EditRole)
            return m_entries.value(id)->name();
        if(role == ::Qt::FontRole)
            return m_entries.value(id)->font();
        break;
    case RenderingModeColumn:
        if(role == ::Qt::DisplayRole) {
            return m_entries.value(id)->renderingModeName();
        }
        else if(role == ::Qt::EditRole) {
            return static_cast<int>(m_entries.value(id)->renderingMode());
        }
        else if(role == ::Qt::TextAlignmentRole) {
            return ::Qt::AlignCenter;
        }
        break;
    case ABColumn:
        if(role == ::Qt::DisplayRole) {
            if(id == item_A)
                return "A";
            if(id == item_B)
                return "B";
        }
        else if(role == ::Qt::TextAlignmentRole) {
            return ::Qt::AlignLeft;
        }
        break;
    case VisibleColumn:
        if(role == ::Qt::DisplayRole || role == ::Qt::EditRole)
            return m_entries.value(id)->visible();
        break;
    default:
        return QVariant();
    }
    return QVariant();
}

QVariant
Scene::headerData ( int section, ::Qt::Orientation orientation, int role ) const
{
    if(orientation == ::Qt::Horizontal)  {
        if (role == ::Qt::DisplayRole)
        {
            switch(section)
            {
            case NameColumn:
                return tr("Name");
                break;
            case ColorColumn:
                return tr("#");
                break;
            case RenderingModeColumn:
                return tr("Mode");
            case ABColumn:
                return tr("A/B");
                break;
            case VisibleColumn:
                return tr("View");
                break;
            default:
                return QVariant();
            }
        }
        else if(role == ::Qt::ToolTipRole) {
            if(section == RenderingModeColumn) {
                return tr("Rendering mode (points/wireframe/flat/flat+edges/Gouraud)");
            }
            else if(section == ABColumn) {
                return tr("Selection A/Selection B");
            }
        }
    }
    return QStandardItemModel::headerData(section, orientation, role);
}

Qt::ItemFlags
Scene::flags ( const QModelIndex & index ) const
{
    if (index.isValid() && index.column() == NameColumn) {
        return QStandardItemModel::flags(index) | ::Qt::ItemIsEditable;
    }
    else {
        return QStandardItemModel::flags(index);
    }
}

bool
Scene::setData(const QModelIndex &index,
               const QVariant &value,
               int role)
{

    if( role != ::Qt::EditRole || !index.isValid() )
        return false;

    int id = index_map[index];
    if(id < 0 || id >= m_entries.size()){
        return false;
    }

    CGAL::Three::Scene_item* item = m_entries[id];

    if(!item) return false;
    switch(index.column())
    {
    case NameColumn:
        item->setName(value.toString());
    Q_EMIT dataChanged(index, index);
        return true;
        break;
    case ColorColumn:
      if(selectionIndices().contains(item_id(item)))
        for(Item_id item_index : selectionIndices())
          this->item(item_index)->setColor(value.value<QColor>());
      else
        item->setColor(value.value<QColor>());
    Q_EMIT dataChanged(index, index);
        return true;
        break;
    case RenderingModeColumn:
    {
        RenderingMode rendering_mode = static_cast<RenderingMode>(value.toInt());
        // Find next supported rendering mode
        int counter = 0;
        while ( ! item->supportsRenderingMode(rendering_mode)
                )
        {
            rendering_mode = static_cast<RenderingMode>( (rendering_mode+1) % NumberOfRenderingMode );
            if(counter++ == NumberOfRenderingMode)
              break;
        }
        item->setRenderingMode(rendering_mode);
        QModelIndex nindex = createIndex(m_entries.size()-1,RenderingModeColumn+1);
    Q_EMIT dataChanged(index, nindex);
        return true;
        break;
    }
    case VisibleColumn:
        item->setVisible(value.toBool());
    Q_EMIT dataChanged(index, createIndex(m_entries.size()-1,VisibleColumn+1));
        return true;
    default:
        return false;
    }
    return false;
}

bool Scene::dropMimeData(const QMimeData * /*data*/,
                         Qt::DropAction /*action*/,
                         int /*row*/,
                         int /*column*/,
                         const QModelIndex &parent)
{
    //gets the moving items
    QList<Scene_item*> items;
    QList<int> groups_children;

    //get IDs of all children of selected groups
    for(int i : selected_items_list)
    {
      CGAL::Three::Scene_group_item* group =
          qobject_cast<CGAL::Three::Scene_group_item*>(item(i));
      if(group)
      {
        for(Item_id id : group->getChildren())
        {
          CGAL::Three::Scene_item* child = item(id);
          groups_children << item_id(child);
        }
      }
    }
    // Insure that children of selected groups will not be added twice
    for(int i : selected_items_list)
    {
      if(!groups_children.contains(i))
      {
        items << item(i);
      }
    }
    //Gets the group at the drop position
    CGAL::Three::Scene_group_item* group = nullptr;
    if(parent.isValid())
        group = qobject_cast<CGAL::Three::Scene_group_item*>(this->item(index_map[parent]));
    bool one_contained = false;
    if(group)
    {
      for(int id : selected_items_list)
      {
        if(group->getChildren().contains(id))
        {
          one_contained = true;
          break;

        }
      }
    }
    //if the drop item is not a group_item or if it already contains the item, then the drop action must be ignored
    if(!group ||one_contained)
    {
      //unless the drop zone is empty, which means the item should be removed from all groups.
      if(!parent.isValid())
      {
        for(Scene_item* item : items)
        {
          if(item->parentGroup())
          {
            item->parentGroup()->removeChild(item);
            addChild(item);
          }
        }
        redraw_model();
        return true;
      }
      return false;
    }
    for(Scene_item* item : items)
      changeGroup(item, group);
    redraw_model();
    return true;
}

//todo : if a group is selected, don't treat its children.
bool Scene::sort_lists(QVector<QList<int> >&sorted_lists, bool up)
{
  QVector<int> group_found;
  for(int i : selectionIndices())
  {
    Scene_item* item = this->item(i);
    if(item->has_group == 0)
    {
      sorted_lists.first().push_back(i);
    }
    else
    {
      int group_id = item_id(item->parentGroup());
      if(group_found.contains(group_id))
        sorted_lists[group_id].push_back(i);
      else
      {
        group_found.push_back(group_id);
        if(sorted_lists.size() < group_id+1)
          sorted_lists.resize(group_id+1);
        sorted_lists[group_id].push_back(i);
      }
    }
  }
  //iterate the first list to find the groups that are selected and remove the corresponding
  //sub lists.
  //todo: do that for each group. (treat subgroups)
  for(int i = 0; i< sorted_lists.first().size(); ++i)
  {
    Scene_group_item* group = qobject_cast<Scene_group_item*>(this->item(sorted_lists.first()[i]));
    if(group && ! group->getChildren().isEmpty() && sorted_lists.first()[i] < sorted_lists.size())
    {
      sorted_lists[sorted_lists.first()[i]].clear();
    }
  }
  std::sort(sorted_lists.first().begin(), sorted_lists.first().end(),
            [this](int a, int b) {
    return children.indexOf(a) < children.indexOf(b);
});
  if(!sorted_lists.first().isEmpty())
  {
    if(up &&  children.indexOf(sorted_lists.first().first()) == 0)
      return false;
    else if(!up &&  children.indexOf(sorted_lists.first().last()) == children.size() -1)
      return false;
  }
  for(int i=1; i<sorted_lists.size(); ++i)
  {
    QList<int>& list = sorted_lists[i];
    if(list.isEmpty())
      continue;
    Scene_group_item* group = qobject_cast<Scene_group_item*>(this->item(i));
    if(!group)
      continue;
    std::sort(list.begin(), list.end(),
              [group](int a, int b) {
      return group->getChildren().indexOf(a) < group->getChildren().indexOf(b);
  });
    if(up && group->getChildren().indexOf(list.first()) == 0)
      return false;
    else if(!up && group->getChildren().indexOf(list.last()) == group->getChildren().size()-1)
      return false;
  }
  return true;
}
void Scene::moveRowUp()
{
  if(selectionIndices().isEmpty())
    return;
  QVector<QList<int> >sorted_lists(1);
  QList<int> to_select;
  //sort lists according to the indices of each item in its container (scene or group)
  //if moving one up would put it out of range, then we stop and do nothing.
  if(!sort_lists(sorted_lists, true))
    return;

  for(int i=0; i<sorted_lists.first().size(); ++i)
  {
    Item_id selected_id = sorted_lists.first()[i];
    Scene_item* selected_item = item(selected_id);
    if(!selected_item)
      return;
    if(index_map.key(selected_id).row() > 0)
    {
      //if not in group
      QModelIndex baseId = index_map.key(selected_id);
      int newId = children.indexOf(
            index_map.value(index(baseId.row()-1, baseId.column(),baseId.parent()))) ;
      children.move(children.indexOf(selected_id), newId);
      redraw_model();
      to_select.append(m_entries.indexOf(selected_item));
    }
  }
  for(int i=1; i<sorted_lists.size(); ++i)
  {
    for(int j = 0; j< sorted_lists[i].size(); ++j)
    {
      Item_id selected_id = sorted_lists[i][j];
      Scene_item* selected_item = item(selected_id);
      if(!selected_item)
        return;
      if(index_map.key(selected_id).row() > 0)
      {
        Scene_group_item* group = selected_item->parentGroup();
        if(group)
        {
          int id = group->getChildren().indexOf(item_id(selected_item));
          group->moveUp(id);
          redraw_model();
          to_select.append(m_entries.indexOf(selected_item));
        }
      }
    }
  }
  if(!to_select.isEmpty()){
    selectionChanged(to_select);
  }
}
void Scene::moveRowDown()
{
  if(selectionIndices().isEmpty())
    return;
  QVector<QList<int> >sorted_lists(1);
  QList<int> to_select;
  //sort lists according to the indices of each item in its container (scene or group)
  //if moving one up would put it out of range, then we stop and do nothing.
  if(!sort_lists(sorted_lists, false))
    return;
  for(int i=sorted_lists.first().size()-1; i>=0; --i)
  {
    Item_id selected_id = sorted_lists.first()[i];
    Scene_item* selected_item = item(selected_id);
    if(!selected_item)
      return;
    if(index_map.key(selected_id).row() < rowCount(index_map.key(selected_id).parent())-1)
    {
        //if not in group
        QModelIndex baseId = index_map.key(selected_id);
        int newId = children.indexOf(
              index_map.value(index(baseId.row()+1, baseId.column(),baseId.parent()))) ;
        children.move(children.indexOf(selected_id), newId);

      redraw_model();
      to_select.prepend(m_entries.indexOf(selected_item));
    }
  }
  for(int i=1; i<sorted_lists.size(); ++i){
    if(sorted_lists[i].isEmpty())
      continue;
    for(int j = sorted_lists[i].size()-1; j >=0; --j)
    {
      Item_id selected_id = sorted_lists[i][j];
      Scene_item* selected_item = item(selected_id);
      if(!selected_item)
        return;
      if(index_map.key(selected_id).row() < rowCount(index_map.key(selected_id).parent())-1)
      {
        if(item(selected_id)->has_group >0)
        {
          Scene_group_item* group = selected_item->parentGroup();
          if(group)
          {
            int id = group->getChildren().indexOf(item_id(selected_item));
            group->moveDown(id);
          }
        }
        redraw_model();
        to_select.prepend(m_entries.indexOf(selected_item));
      }
    }
  }
  if(!to_select.isEmpty()){
    selectionChanged(to_select);
  }
}
Scene::Item_id Scene::mainSelectionIndex() const {
    return (selectionIndices().size() == 1) ? selected_item : -1;
}

QList<int> Scene::selectionIndices() const {
    return selected_items_list;
}

int Scene::selectionAindex() const {
    return item_A;
}

int Scene::selectionBindex() const {
    return item_B;
}

QItemSelection Scene::createSelection(int i)
{
    return QItemSelection(index_map.keys(i).at(0),
                          index_map.keys(i).at(4));
}

QItemSelection Scene::createSelection(QList<int> is)
{
    QItemSelection sel;
    for(int i : is)
      sel.select(index_map.keys(i).at(0),
                 index_map.keys(i).at(4));
    return sel;
}

QItemSelection Scene::createSelectionAll()
{
  //it is not possible to directly create a selection with items that have different parents, so
  //we do it iteratively.
  QItemSelection sel;
  sel.select(this->createIndex(0, 0),
             this->createIndex(m_entries.size(), LastColumn));
  for(const auto& gid : m_groups)
  {
    CGAL::Three::Scene_group_item* group =
        qobject_cast<CGAL::Three::Scene_group_item*>(item(gid));
    sel.select(index_map.keys(group->getChildren().first()).at(0),
               index_map.keys(group->getChildren().last()).at(4));
  }
  return sel;
}

void Scene::itemChanged()
{
    CGAL::Three::Scene_item* item = qobject_cast<CGAL::Three::Scene_item*>(sender());
    if(item)
        itemChanged(item);
}

void Scene::itemChanged(Item_id i)
{
  if(dont_emit_changes)
    return;
  if(i < 0 || i >= m_entries.size())
    return;

  Q_EMIT dataChanged(this->createIndex(i, 0),
                     this->createIndex(i, LastColumn));
}

void Scene::itemChanged(CGAL::Three::Scene_item*item )
{
  if(dont_emit_changes)
    return;
  itemChanged(item_id(item));
}

void Scene::allItemsChanged()
{
  Q_EMIT dataChanged(this->createIndex(0, 0),
                     this->createIndex(m_entries.size() - 1, LastColumn));
}

void Scene::itemVisibilityChanged()
{
    CGAL::Three::Scene_item* item = qobject_cast<CGAL::Three::Scene_item*>(sender());
    if(item)
        itemVisibilityChanged(item);
}

void Scene::itemVisibilityChanged(CGAL::Three::Scene_item* item)
{
  if(item->isFinite()
     && !item->isEmpty())
  {
    //does not recenter
    if(visibility_recentering_enabled){
      Q_EMIT updated_bbox(true);

    }
  }
}


bool SceneDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
                                const QStyleOptionViewItem &option,
                                const QModelIndex &index)
{
    QAbstractProxyModel* proxyModel = dynamic_cast<QAbstractProxyModel*>(model);
    Q_ASSERT(proxyModel);
    Scene *scene = dynamic_cast<Scene*>(proxyModel->sourceModel());
    Q_ASSERT(scene);
    int id = scene->index_map[proxyModel->mapToSource(index)];
    switch(index.column()) {
    case Scene::VisibleColumn:
        if (event->type() == QEvent::MouseButtonPress) {
            QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
            if(mouseEvent->button() == ::Qt::LeftButton) {
                int x = mouseEvent->pos().x() - option.rect.x();
                if(x >= (option.rect.width() - size)/2 &&
                        x <= (option.rect.width() + size)/2) {
                    model->setData(index, !model->data(index).toBool());
                }
            }
            return false; //so that the selection can change
        }
        return true;
        break;
    case Scene::ColorColumn:
        if (event->type() == QEvent::MouseButtonPress) {
            QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
            if(mouseEvent->button() == ::Qt::LeftButton) {
                QColor color =
                        QColorDialog::getColor(model->data(index).value<QColor>(),
                                               nullptr/*,
                                               tr("Select color"),
                                               QColorDialog::ShowAlphaChannel*/);
                if (color.isValid()) {
                    model->setData(index, color );
                }
            }
        }
        else if(event->type() == QEvent::MouseButtonDblClick) {
            return true; // block double-click
        }
        return false;
        break;
    case Scene::RenderingModeColumn:
        if (event->type() == QEvent::MouseButtonPress) {
            QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
            if(mouseEvent->button() == ::Qt::LeftButton) {
                // Switch rendering mode
                /*RenderingMode*/int rendering_mode = model->data(index, ::Qt::EditRole).toInt();
                rendering_mode = (rendering_mode+1) % NumberOfRenderingMode;
                model->setData(index, rendering_mode);
            }
        }
        else if(event->type() == QEvent::MouseButtonDblClick) {
            return true; // block double-click
        }
        return false;
        break;
    case Scene::ABColumn:
        if (event->type() == QEvent::MouseButtonPress) {
            if(id == scene->item_B) {
                scene->item_A = id;
                scene->item_B = -1;
            }
            else if(id == scene->item_A) {
                scene->item_B = id;
                scene->item_A = -1;
            }
            else if(scene->item_A == -1) {
                scene->item_A = id;
            }
            else {
                scene->item_B = id;
            }
            scene->dataChanged(scene->createIndex(0, Scene::ABColumn),
                               scene->createIndex(scene->rowCount() - 1, Scene::ABColumn));
        }
        return false;
        break;
    default:
        return QItemDelegate::editorEvent(event, model, option, index);
    }
}

void SceneDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
                          const QModelIndex &index) const
{
    QModelIndex test = proxy->mapToSource(index);
    if (index.column() != Scene::VisibleColumn) {
        QItemDelegate::paint(painter, option, index);
    } else {
        const QAbstractItemModel *model = index.model();

        QPalette::ColorGroup cg = (option.state & QStyle::State_Enabled) ?
                    (option.state & QStyle::State_Active) ? QPalette::Normal : QPalette::Inactive : QPalette::Disabled;

        if (option.state & QStyle::State_Selected)
            painter->fillRect(option.rect, option.palette.color(cg, QPalette::Highlight));
        bool checked = model->data(index, ::Qt::DisplayRole).toBool();
        int width = option.rect.width();
        int height = option.rect.height();
        size = (std::min)(width, height);
        int x = option.rect.x() + (option.rect.width() / 2) - (size / 2);;
        int y = option.rect.y() + (option.rect.height() / 2) - (size / 2);
        if(test.row()>=0 && test.row()<scene->m_entries.size()){

            if(checked) {
                painter->drawPixmap(x, y, checkOnPixmap.scaled(QSize(size, size),
                                                               ::Qt::KeepAspectRatio,
                                                               ::Qt::SmoothTransformation));
            }
            else {
                painter->drawPixmap(x, y, checkOffPixmap.scaled(QSize(size, size),
                                                                ::Qt::KeepAspectRatio,
                                                                ::Qt::SmoothTransformation));
            }
        }
        drawFocus(painter, option, option.rect); // since we draw the grid ourselves
    }
}

void Scene::setItemVisible(int index, bool b)
{
    if( index < 0 || index >= m_entries.size() )
        return;
    m_entries[index]->setVisible(b);
  Q_EMIT dataChanged(this->createIndex(index, VisibleColumn),
                     this->createIndex(index, VisibleColumn));
}

void Scene::setSelectionRay(double orig_x,
                            double orig_y,
                            double orig_z,
                            double dir_x,
                            double dir_y,
                            double dir_z)
{
    CGAL::Three::Scene_item* item = this->item(selected_item);
    if(item) item->select(orig_x,
                          orig_y,
                          orig_z,
                          dir_x,
                          dir_y,
                          dir_z);
}

void Scene::setItemA(int i)
{
    item_A = i;
    if(item_A == item_B)
    {
        item_B = -1;
    }
  Q_EMIT dataChanged(this->createIndex(0, ABColumn),
                     this->createIndex(m_entries.size()-1, ABColumn));
}

void Scene::setItemB(int i)
{
    item_B = i;
    if(item_A == item_B)
    {
        item_A = -1;
    }
  Q_EMIT updated();
  Q_EMIT dataChanged(this->createIndex(0, ABColumn),
                     this->createIndex(m_entries.size()-1, ABColumn));
}

Scene::Bbox Scene::bbox() const
{
    if(m_entries.empty())
        return Bbox(0,0,0,0,0,0);

    bool bbox_initialized = false;
    Bbox bbox = Bbox(0,0,0,0,0,0);
    for(CGAL::Three::Scene_item* item : m_entries)
    {
        if(item->isFinite() && !item->isEmpty() && item->visible()) {
            if(bbox_initialized) {

                bbox = bbox + item->bbox();
            }
            else {
                bbox = item->bbox();
                bbox_initialized = true;

            }
        }

    }
    return bbox;
}

QList<Scene_item*> Scene::item_entries() const
{
    return m_entries;
}
void Scene::redraw_model()
{
    //makes the hierarchy in the tree
    //clears the model
    clear();
    index_map.clear();
    //fills the model
    for(Item_id id : children)
    {
        organize_items(m_entries[id], invisibleRootItem(), 0);
    }
    Q_EMIT restoreCollapsedState();
}
void Scene::changeGroup(Scene_item *item, CGAL::Three::Scene_group_item *target_group)
{
    //remove item from the containing group if any
    if(item->parentGroup())
    {
      if(item->parentGroup()->isChildLocked(item))
        return;
      item->parentGroup()->removeChild(item);
      children.push_back(item_id(item));
    }
      else
      {
        children.removeAll(item_id(item));
      }
    //add the item to the target group
    target_group->addChild(item);
    item->moveToGroup(target_group);
    redraw_model();
    Q_EMIT updated();
}

void Scene::printPrimitiveId(QPoint point, CGAL::Three::Viewer_interface* viewer)
{
  Scene_item *it = item(mainSelectionIndex());
  if(it)
  {
    //Only call printPrimitiveId if the item is a Scene_print_item_interface
    Scene_print_item_interface* item= qobject_cast<Scene_print_item_interface*>(it);
    if(item)
      item->printPrimitiveId(point, viewer);
  }
}
void Scene::printVertexIds()
{
  Scene_item *it = item(mainSelectionIndex());
  if(it)
  {
    Scene_print_item_interface* item= qobject_cast<Scene_print_item_interface*>(it);
    if(item)
      item->printVertexIds();
  }
}

void Scene::printEdgeIds()
{
  Scene_item *it = item(mainSelectionIndex());
  if(it)
  {
    //Only call printEdgeIds if the item is a Scene_print_item_interface
    Scene_print_item_interface* item= qobject_cast<Scene_print_item_interface*>(it);
    if(item)
      item->printEdgeIds();
  }
}

void Scene::printFaceIds()
{
  Scene_item *it = item(mainSelectionIndex());
  if(it)
  {
    //Only call printFaceIds if the item is a Scene_print_item_interface
    Scene_print_item_interface* item= qobject_cast<Scene_print_item_interface*>(it);
    if(item)
      item->printFaceIds();
  }
}

void Scene::printAllIds()
{
  Scene_item *it = item(mainSelectionIndex());
  if(it)
  {
    //Only call printFaceIds if the item is a Scene_print_item_interface
    Scene_print_item_interface* item= qobject_cast<Scene_print_item_interface*>(it);
    if(item)
      item->printAllIds();
  }
}
void Scene::updatePrimitiveIds(CGAL::Three::Scene_item* it)
{
  if(it)
  {
    Scene_print_item_interface* item= qobject_cast<Scene_print_item_interface*>(it);
    if(item)
    {
      //As this function works as a toggle, the first call hides the ids and the second one shows them again,
      //thereby triggering their re-computation.
      item->printVertexIds();
      item->printVertexIds();

      item->printEdgeIds();
      item->printEdgeIds();

      item->printFaceIds();
      item->printFaceIds();
    }
  }
}
bool Scene::testDisplayId(double x, double y, double z, CGAL::Three::Viewer_interface* viewer)
{
    CGAL::Three::Scene_item *i = item(mainSelectionIndex());
    if(!i)
      return false;
    Scene_print_item_interface* spit= qobject_cast<Scene_print_item_interface*>(i);
    if(spit && i->visible())
    {
        bool res = spit->testDisplayId(x,y,z, viewer);
        return res;
    }
    else
      return false;
}
#include "Scene_find_items.h"

void Scene::organize_items(Scene_item* item, QStandardItem* root, int loop)
{
    if(item->has_group <= loop)
    {
        QList<QStandardItem*> list;
        for(int i=0; i<5; i++)
        {
            list<<new QStandardItem();
            list.at(i)->setEditable(false);

        }
        root->appendRow(list);
        for(int i=0; i<5; i++){
            index_map[list.at(i)->index()] = m_entries.indexOf(item);
        }
        CGAL::Three::Scene_group_item* group =
                qobject_cast<CGAL::Three::Scene_group_item*>(item);
        if(group)
        {
          for(Item_id id : group->getChildren())
          {
            CGAL::Three::Scene_item* child = group->getChild(id);
                organize_items(child, list.first(), loop+1);
            }
        }
    }
}

void Scene::setExpanded(QModelIndex id)
{
    CGAL::Three::Scene_group_item* group =
            qobject_cast<CGAL::Three::Scene_group_item*>(item(getIdFromModelIndex(id)));
    if(group)
    {
        group->setExpanded(true);
    }
}
void Scene::setCollapsed(QModelIndex id)
{
    CGAL::Three::Scene_group_item* group =
            qobject_cast<CGAL::Three::Scene_group_item*>(item(getIdFromModelIndex(id)));
    if(group)
    {
        group->setExpanded(false);
    }
}

int Scene::getIdFromModelIndex(QModelIndex modelId)const
{
    return index_map.value(modelId);
}

QList<QModelIndex> Scene::getModelIndexFromId(int id) const
{
    return index_map.keys(id);
}

void Scene::addGroup(Scene_group_item* group)
{
    connect(this, SIGNAL(drawFinished()), group, SLOT(resetDraw()));
    connect(this, SIGNAL(indexErased(Scene_interface::Item_id)),
                group, SLOT(adjustIds(Scene_interface::Item_id)));
}

namespace scene { namespace details {

Q_DECL_EXPORT
CGAL::Three::Scene_item*
findItem(const CGAL::Three::Scene_interface* scene_interface,
         const QMetaObject& metaobj,
         QString name, Scene_item_name_fn_ptr fn) {
    const Scene* scene = dynamic_cast<const Scene*>(scene_interface);
    if(!scene) return nullptr;
    for(CGAL::Three::Scene_item* item : scene->entries()) {
       CGAL::Three::Scene_item* ptr = qobject_cast<CGAL::Three::Scene_item*>(metaobj.cast(item));
        if(ptr && ((ptr->*fn)() == name)) return ptr;
    }
    return nullptr;
}

Q_DECL_EXPORT
QList<CGAL::Three::Scene_item*>
findItems(const CGAL::Three::Scene_interface* scene_interface,

          const QMetaObject&,
          QString name, Scene_item_name_fn_ptr fn)
{
    const Scene* scene = dynamic_cast<const Scene*>(scene_interface);
    QList<CGAL::Three::Scene_item*> list;
    if(!scene) return list;

    for(CGAL::Three::Scene_item* item : scene->entries()) {
        CGAL::Three::Scene_item* ptr = qobject_cast<CGAL::Three::Scene_item*>(item);
        if(ptr && ((ptr->*fn)() == name)) {
            list << ptr;
        }
    }
    return list;
}

} // end namespace details
                } // end namespace scene

void Scene::zoomToPosition(QPoint point, Viewer_interface *viewer)
{
  for(int i=0; i<numberOfEntries(); ++i)
  {
    if(!item(i)->visible())
      continue;
    Scene_zoomable_item_interface* zoom_item = qobject_cast<Scene_zoomable_item_interface*>(item(i));
    if(zoom_item)
    {
      zoom_item->zoomToPosition(point, viewer);
    }
  }
}

void Scene::adjustIds(Item_id removed_id)
{
  for(int i = 0; i < children.size(); ++i)
  {
    if(children[i] >= removed_id)
      --children[i];
  }
  for(int i = removed_id; i < numberOfEntries(); ++i)
  {
    m_entries[i]->setId(i-1);//the signal is emitted before m_entries is amputed from the item, so new id is current id -1.
  }
}

void Scene::computeBbox()
{
  if(m_entries.empty())
  {
    last_bbox = Bbox(0,0,0,0,0,0);
    return;
  }

  bool bbox_initialized = false;
  Bbox bbox = Bbox(0,0,0,0,0,0);
  for(CGAL::Three::Scene_item* item : m_entries)
  {
    if(item->isFinite() && !item->isEmpty() ) {
      if(bbox_initialized) {

        bbox = bbox + item->bbox();
      }
      else {
        bbox = item->bbox();
        bbox_initialized = true;

      }
    }

  }
  last_bbox = bbox;
}

void Scene::newViewer(Viewer_interface *viewer)
{
  initGL(viewer);
  for(Scene_item* item : m_entries)
  {
    item->newViewer(viewer);
  }
}

void Scene::removeViewer(Viewer_interface *viewer)
{
 //already destroyed;
  if(viewer->property("is_destroyed").toBool())
    return;

  viewer->makeCurrent();
  vaos[viewer]->destroy();
  vaos[viewer]->deleteLater();
  vaos.remove(viewer);
  for(Scene_item* item : m_entries)
  {
    item->removeViewer(viewer);
  }
}

void Scene::initGL(Viewer_interface *viewer)
{
  viewer->makeCurrent();
  vaos[viewer] = new QOpenGLVertexArrayObject();
  vaos[viewer]->create();
  program.bind();
  vaos[viewer]->bind();
  vbo[0].bind();
  vbo[0].allocate(points, 18 * sizeof(float));
  program.enableAttributeArray("vertex");
  program.setAttributeArray("vertex", GL_FLOAT, nullptr, 3);
  vbo[0].release();

  vbo[1].bind();
  vbo[1].allocate(uvs, 12 * sizeof(float));
  program.enableAttributeArray("v_texCoord");
  program.setAttributeArray("v_texCoord", GL_FLOAT, nullptr, 2);
  vbo[1].release();
  vaos[viewer]->release();
  program.release();
}

void Scene::callDraw(){
  for(CGAL::QGLViewer* v : CGAL::QGLViewer::QGLViewerPool())
  {
    qobject_cast<Viewer_interface*>(v)->update();
  }
}

void Scene::enableVisibilityRecentering(bool b)
{
  visibility_recentering_enabled = b;
}

void Scene::addChild(Scene_item *item)
{
  children.push_back(item_id(item));
}
