//! \file Scene.h
#ifndef SCENE_H
#define SCENE_H
#include "config.h"
#include "Scene_config.h"
#include <CGAL/Three/Scene_interface.h>
#include <CGAL/Three/Scene_draw_interface.h>
#include <CGAL/Three/Viewer_config.h>

#include <QtOpenGLWidgets>
#include <QStandardItemModel>
#include <QString>
#include <QColor>
#include <QList>
#include <QVector>
#include <QMap>
#include <QItemDelegate>
#include <QPixmap>
#include <QItemSelection>
#include <CGAL/Qt/qglviewer.h>
#include <QDebug>
#include <iostream>
#include <cmath>
#include <variant>
#include <CGAL/Three/Scene_group_item.h>
class QEvent;
class QMouseEvent;
class QOpenGLFramebufferObject;
namespace CGAL { namespace Three{ class Viewer_interface;}}

//! This class is not supposed to be used by Plugins, but sometimes you may need access to
//! peculiar signals or slots.
class SCENE_EXPORT Scene  :
  public QStandardItemModel, public CGAL::Three::Scene_interface, public CGAL::Three::Scene_draw_interface

{
  Q_OBJECT
  Q_PROPERTY(int numberOfEntries READ numberOfEntries)

  friend class SceneDelegate;

public:
  QList<QModelIndex> getModelIndexFromId(int id) const;
  int getIdFromModelIndex(QModelIndex modelId) const;
  enum Columns { NameColumn = 0,
                 ColorColumn,
                 RenderingModeColumn,
                 VisibleColumn,
                 ABColumn,
                 LastColumn = ABColumn,
                 NumberOfColumns = LastColumn + 1};
  Scene(QObject*  parent);
  ~Scene();
  int addItem(CGAL::Three::Scene_item* item) override;
  void addChild(Scene_item* item) override;
  Q_INVOKABLE void changeGroup(CGAL::Three::Scene_item* item, CGAL::Three::Scene_group_item* target_group) override;
  CGAL::Three::Scene_item* replaceItem(int index, CGAL::Three::Scene_item* item, bool emit_item_about_to_be_destroyed = false) override;
  Q_INVOKABLE int erase(int) override;

  int erase(QList<int>) override;
  int duplicate(int index) override;

  // Accessors (getters)
  int numberOfEntries() const override;
  // returns the list of items.
  const QList<CGAL::Three::Scene_item*>& entries() const { return m_entries; }

  Q_INVOKABLE CGAL::Three::Scene_item* item(int) const override;
  int item_id(CGAL::Three::Scene_item*) const override;

  //! \todo Replace Index based selection functionality with those
  //! functions.
  ///@{
  CGAL::Three::Scene_item* selectedItem() const;
  QList<CGAL::Three::Scene_item*> selectedItems() const;
  QList<CGAL::Three::Scene_item*> selectionA() const;
  QList<CGAL::Three::Scene_item*> selectionB() const;
  ///@}

  int mainSelectionIndex() const override;
  QList<int> selectionIndices() const override;
  int selectionAindex() const override;
  int selectionBindex() const override;
  void initializeGL(CGAL::Three::Viewer_interface*) override;
  void initGL(CGAL::Three::Viewer_interface* viewer);
  void setPickedPixel(const QPoint &p) override {picked_pixel = p;}
  void draw(CGAL::Three::Viewer_interface*) override;
  void drawWithNames(CGAL::Three::Viewer_interface*) override;
  bool keyPressEvent(QKeyEvent* e) override;
  void printPrimitiveId(QPoint point,
                        CGAL::Three::Viewer_interface*) override;
  void printVertexIds() override;
  void printEdgeIds() override;
  void printFaceIds() override;
  void printAllIds() override;
  //!Re-computes the primitiveIds for `item`
  void updatePrimitiveIds(Scene_item *item) override;
  bool testDisplayId(double x, double y, double z, CGAL::Three::Viewer_interface* viewer) override;
  Bbox bbox() const override;
  void computeBbox();
  double len_diagonal() const override
  {
    Bbox box = bbox();
    double dx = box.xmax() - box.xmin();
    double dy = box.ymax() - box.ymin();
    double dz = box.zmax() - box.zmin();
    return std::sqrt(dx*dx + dy*dy + dz*dz);
  }

  //Moves a name up in the Geometric Objects view
  void moveRowUp();
  //Moves a name down in the Geometric Objects view
  void moveRowDown();
  // QStandardItemModel functions
  //Defines the behavior when a name is drag-and-dropped in the Geometric Objects view
  bool dropMimeData(const QMimeData *, Qt::DropAction, int, int,
                    const QModelIndex &parent) override;
  //Contains the text and icons of an item in the Geometric Objects view
  QVariant data ( const QModelIndex & index,
                  int role = ::Qt::DisplayRole ) const override;
  //@returns the type of data corresponding to the role.
  QVariant headerData ( int section, ::Qt::Orientation orientation,
                        int role = ::Qt::DisplayRole ) const override;
  //@returns the flags for the item at the target index.
  ::Qt::ItemFlags flags ( const QModelIndex & index ) const override;
  // Sets the column data for the target index. Returns false if index is not valid and
  // if role is not EditRole.
  bool setData(const QModelIndex &index, const QVariant &value,
               int role) override;

  //Returns a list of all the items.
  QList<CGAL::Three::Scene_item*> item_entries() const ;
  // auxiliary public function for QMainWindow
  //Selects the row at index i in the sceneView.
  QItemSelection createSelection(int i);
  //same for lists
  QItemSelection createSelection(QList<int> is);
  //Selects all the rows in the sceneView.
  QItemSelection createSelectionAll();
  //Connects specific signals to a group when it is added and
  // gives a reference to the scene to it.
  void addGroup(Scene_group_item* group);

  void zoomToPosition(QPoint point,
                        CGAL::Three::Viewer_interface*) override;
  void setUpdatesEnabled(bool b) override
  {
    dont_emit_changes = !b;
    if(!b)
      allItemsChanged();
  }

public Q_SLOTS:
  //!Specifies a group as Expanded for the Geometric Objects view
  void setExpanded(QModelIndex);
  //!Specifies a group as Collapsed for the Geometric Objects view
  void setCollapsed(QModelIndex);
  //!Transmits a CGAL::Three::Scene_item::itemChanged() signal to the scene.
  void itemChanged();
  void itemChanged(int i) override;
  void itemChanged(CGAL::Three::Scene_item*) override;
  void allItemsChanged() override;
  //!Transmits a CGAL::Three::Scene_item::itemVisibilityChanged() signal to the scene.
  void itemVisibilityChanged();
  void itemVisibilityChanged(CGAL::Three::Scene_item*) override;
  //!Removes `item` from all the groups of the scene.
  void remove_item_from_groups(CGAL::Three::Scene_item* item);
  //!Re-organizes the sceneView.
  void redraw_model();
  //! Sets the selected item to the target index. Emits a signal to notify
  //! that a new item is now selected, but does not update the Geometric Objects view.
  //! Used in intern and by the mainwindow
  void setSelectedItemIndex(int i)
  {
    selected_item = i;
    Q_EMIT itemIndexSelected(i);
  }
  void setSelectedItem(int i ) override
  {
    selected_item = i;
    Q_EMIT selectionChanged(i);
  }

  //! Does the same as setSelectedItem(int)
  void setSelectedItem(CGAL::Three::Scene_item* item_to_select)
  {
    int i=0;
    for(CGAL::Three::Scene_item* item : m_entries)
    {
      if (item==item_to_select)
      {
        setSelectedItem(i);
        break;
      }
      ++i;
    }
  }

  //! Sets the target list of indices as the selected indices.
  const QList<int>& setSelectedItemIndices(QList<int> l,
                                           const bool do_emit = true)
  {
    for(int i :l)
    {
       CGAL::Three::Scene_group_item* group =
               qobject_cast<CGAL::Three::Scene_group_item*>(item(i));
       if(group)
       {
         QList<int> list;
         for(Item_id id : group->getChildrenForSelection())
           list << id;
         l << setSelectedItemIndices(list, false /*do not emit*/);
       }

    }
    selected_items_list = l;
    if(do_emit)
      Q_EMIT itemIndicesSelected(selected_items_list);
    return selected_items_list;
  }

    //! Sets the target list of indices as the selected indices.
  const QList<int>& setSelectedItemList(QList<int> l,
                                        const bool do_emit = true)
  {
    for(int i :l)
    {
       CGAL::Three::Scene_group_item* group =
               qobject_cast<CGAL::Three::Scene_group_item*>(item(i));
       if(group)
       {
         QList<int> list;
         for(Item_id id : group->getChildrenForSelection())
           list << id;
         l << setSelectedItemList(list, false /*do not emit*/);
       }
    }

    selected_items_list = l;
    if(do_emit)
      Q_EMIT selectionChanged(selected_items_list);
    return selected_items_list;
  }

  // Accessors (setters)
  //!Sets the item at index i to visible or not visible.
  void setItemVisible(int, bool b);
  //!Sets the item_A as the item at index i .
  void setItemA(int i);
  //!Sets the item_B as the item at index i .
  void setItemB(int i);
  void newViewer(CGAL::Three::Viewer_interface*);
  void removeViewer(CGAL::Three::Viewer_interface*);
  void enableVisibilityRecentering(bool);

Q_SIGNALS:
  //generated automatically by moc
  //!Is emitted when the ids of the items are changed.
  void indexErased(Scene_interface::Item_id id);
  //! Emit this to mark `modelindex` as selected in the Geometric Objects view.
  void itemPicked(const QModelIndex& modelindex);
  //! Is emitted when a new item is added to the scene.
  void newItem(int);
  //! Emit this to re-compute the viewer's Bbox;
  //! If `b` is true, the scene will be recentered
  void updated_bbox(bool b);
  //! Emit this to redraw the scene.
  void updated();
  //! Is emitted when `item` is erased.
  void itemAboutToBeDestroyed(CGAL::Three::Scene_item* item);
  //! Is emitted when the selection ray is changed.
  void selectionRay(double, double, double, double, double, double);
  //! Used to update the selected item in the Geometric Objects view.
  void selectionChanged(int i);
  //! Used to update the selected items in the Geometric Objects view.
  void selectionChanged(QList<int> is);
  //! Used when you don't want to update the selectedItem in the Geometric Objects view.
  void itemIndexSelected(int i);
  //! Used when you don't want to update the selectedItem in the Geometric Objects view.
  void itemIndicesSelected(QList<int> is);
  //! Emit this to reset the collapsed state of all groups after the Geometric Objects view has been redrawn.
  void restoreCollapsedState();
  //! Is emitted when draw() is finished.
  void drawFinished();
private Q_SLOTS:
  // Casts a selection ray and calls the item function select.
  void adjustIds(Scene_interface::Item_id removed_id);
  void setSelectionRay(double, double, double, double, double, double);
  void callDraw();
  void s_itemAboutToBeDestroyed(CGAL::Three::Scene_item *);
private:
  /*! Calls the drawing functions of each visible item according
   * to its current renderingMode. If with_names is true, uses
   * the OpenGL mode GL_WITH_NAMES, essentially used for the picking.*/
  void draw_aux(bool with_names, CGAL::Three::Viewer_interface*);
  bool has_alpha();
  void renderScene(const QList<Scene_interface::Item_id > &items,
                   CGAL::Three::Viewer_interface* viewer, QMap<float, int> &picked_item_IDs, bool with_names,
                   int pass, bool writing_depth,
                   QOpenGLFramebufferObject* fbo);
  void renderWireScene(const QList<Scene_interface::Item_id> &items,
                       Viewer_interface *viewer, QMap<float, int> &picked_item_IDs, bool with_names);
  void renderPointScene(const QList<Scene_interface::Item_id> &items,
                        Viewer_interface *viewer,
                        QMap<float, int>& picked_item_IDs,
                        bool with_names);
  // Re-draw the hierarchy of the view.
  void organize_items(CGAL::Three::Scene_item* item, QStandardItem *root, int loop);
  // List of Scene_items.
  typedef QList<CGAL::Three::Scene_item*> Entries;
  //List containing all the scene_items.
  Entries m_entries;
  QList<Item_id> m_groups; //used to optimize createSelectionAll()
  QList<Item_id> children;
  // Index of the currently selected item.
  int selected_item;
  //List of indices of the currently selected items.
  QList<int> selected_items_list;
  //Index of the item_A.
  int item_A;
  //Index of the item_B.
  int item_B;
  bool picked;
  QPoint picked_pixel;
  bool gl_init;
  QMap<QModelIndex, int> index_map;
  float points[18];
  float uvs[12];
  QOpenGLShaderProgram program;
  QMap<CGAL::Three::Viewer_interface*, QOpenGLVertexArrayObject*> vaos;
  mutable QOpenGLBuffer vbo[2];
  Bbox last_bbox;
  //the scene will ignore the itemChanged() signals while this is true.
  bool dont_emit_changes;
  bool visibility_recentering_enabled;
  bool sort_lists(QVector<QList<int> >&sorted_lists, bool up);
}; // end class Scene

class QAbstractProxyModel;

//\brief The SceneDelegate class
//Handles the columns of the sceneView
class SCENE_EXPORT SceneDelegate : public QItemDelegate
{
public:
  SceneDelegate(QObject * parent = nullptr)
    : QItemDelegate(parent),
      checkOnPixmap(":/cgal/icons/check-on.png"),
      checkOffPixmap(":/cgal/icons/check-off.png")
  {
  }
// Handles the clicks on the sceneView
  bool editorEvent(QEvent *event, QAbstractItemModel *model,
                   const QStyleOptionViewItem &option,
                   const QModelIndex &index);
  // Draws the content of the sceneView
  void paint(QPainter *painter, const QStyleOptionViewItem &option,
             const QModelIndex &index) const;
  void setProxy(QAbstractProxyModel* p_proxy){
      proxy = p_proxy;
  }
  void setScene(Scene* p_scene){
      scene = p_scene;
  }

private:
  QPixmap checkOnPixmap;
  QPixmap checkOffPixmap;
  QAbstractProxyModel *proxy;
  Scene *scene;
  mutable int size;

}; // end class SceneDelegate

#endif // SCENE_H
