#ifndef CANVAS_H
#define CANVAS_H

//===-- qlogo/canvas.h - Canvas class definition -------*- C++ -*-===//
//
// This file is part of QLogo.
//
// QLogo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// QLogo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with QLogo.  If not, see <http://www.gnu.org/licenses/>.
//
//===----------------------------------------------------------------------===//
///
/// \file
/// This file contains the declaration of the Canvas class, which is the
/// graphics portion of the user interface.
///
//===----------------------------------------------------------------------===//

#include "turtle.h"
#include <QList>
#include <QMatrix4x4>
#include <QOpenGLFunctions>
#include <QOpenGLWidget>
#include <QVector>

class QOpenGLShaderProgram;
class QOpenGLBuffer;
class QOpenGLVertexArrayObject;

/// Contains the information that describes a label's appearance on the Canvas.
struct Label {
  QString text;
  QVector4D position;
  QColor color;
  QFont font;

  Label(const QString &aText, const QVector4D &aPosition, const QColor &aColor,
        const QFont &aFont)
      : text(aText), position(aPosition), color(aColor), font(aFont) {}
};

enum CanvasDrawingElementType {
  canvasDrawArrayType,
  canvasDrawSetPenmodeType,
  canvasDrawSetPensizeType
};

struct CanvasDrawingArrayElement {
  GLenum mode;   // GL_LINES or GL_TRIANGLE_FAN
  GLint first;   // the index of the first vertex in the vertex array
  GLsizei count; // the number of vertices to draw
};

struct CanvasDrawingSetPenmodeElement {
  PenModeEnum penMode;
};

struct CanvasDrawingSetPensizeElement {
  GLfloat width;
};

union CanvasDrawingElementU {
  CanvasDrawingArrayElement drawArrayElement;
  CanvasDrawingSetPenmodeElement penmodeElement;
  CanvasDrawingSetPensizeElement pensizeElement;
};

struct CanvasDrawingElement {
  CanvasDrawingElementType type;
  CanvasDrawingElementU u;
};

/// The widget where turtle graphics are drawn.
class Canvas : public QOpenGLWidget, protected QOpenGLFunctions {
  Q_OBJECT

  // The matrix that converts world coordinates to screen coordinates.
  QMatrix4x4 matrix;
  GLuint matrixUniformID; // matrix ID for GLSL

  // the inverse of matrix. Used for converting mouse coordinates to world
  // coordinates.
  QMatrix4x4 invertedMatrix;
  int widgetWidth;
  int widgetHeight;

  // If the turtle is bounded (FENCE or WRAP), only the bounded portion of
  // the background is drawn. Otherwise, the background is drawn over the
  // entire widget.
  bool isBounded = true;

  // Visible vertices on the X axis range from -boundsX to +boundsX
  qreal boundsX;
  // Visible vertices on the Y axis range from -boundsY to +boundsY
  qreal boundsY;

  // The collection of text labels. Rendered after lines and polygons
  QList<Label> labels;

  QOpenGLShaderProgram *shaderProgram;

  // Border Surface VBO
  QOpenGLVertexArrayObject *surfaceArrayObject;
  QOpenGLBuffer *surfaceVertexBufferObject;
  QOpenGLBuffer *surfaceColorBufferObject;

  // LOGO Drawing VBO
  QOpenGLVertexArrayObject *linesObject = NULL;
  QOpenGLBuffer *linesVertexBufferObject;
  QOpenGLBuffer *linesColorBufferObject;

  // Turtle Drawing VBO
  QOpenGLVertexArrayObject *t_object = NULL;
  QOpenGLBuffer *t_vertex_bo;
  QOpenGLBuffer *t_color_bo;
  QOpenGLBuffer *t_index_bo;

  // Vertices information for user-generated lines and polygons
  QVector<GLfloat> vertices;
  QVector<GLubyte> vertexColors;

  // draw, erase, or reverse
  PenModeEnum currentPenMode;

  GLfloat pensizeRange[2]; // Minimum and maximum valid pen sizes
  GLfloat currentPensize = 0;

  // The main data structure for all of the drawn elements on the canvas
  // (sans labels).
  QList<CanvasDrawingElement> drawingElementList;

  GLclampf backgroundColor[4];

  // Some initializers
  void initTurtleVBO(void);
  void initSurfaceVBO(void);
  void setSurfaceVertices(void);

  // These are called by paintGL()
  void paintSurface();
  void paintTurtle();
  void paintElements();
  void paintLabels(QPainter *painter);

  void updateMatrix(void);
  QPointF worldToScreen(const QVector4D &world);
  QVector2D screenToWorld(const QPointF &p);

  void initializeGL() override;
  void resizeGL(int width, int height) override;
  void paintGL() override;

  void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
  void mouseMoveEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
  void mouseReleaseEvent(QMouseEvent *) Q_DECL_OVERRIDE;

public:
  /// Construct a Canvas
  Canvas(QWidget *parent = 0);

  /// Sets the background color to c.
  ///
  /// The background is drawn either as a filled rectangle when isBounded=true
  /// or the background color fills the entire widget when isBounded=false.
  void setBackgroundColor(const QColor &c);

  /// Adds a line to the list of elements to be drawn.
  void addLine(const QVector4D &vertexA, const QVector4D &vertexB,
               const QColor &color);

  /// Adds a polygon to the list of elements to be drawn.
  void addPolygon(const QList<QVector4D> &points, const QList<QColor> &colors);

  /// Adds a text label to the list of elements to be drawn.
  void addLabel(const QString &aText, const QVector4D &aLocation,
                const QColor &aColor, const QFont &aFont);

  /// Clears the screen and removes all drawing elements from their respective
  /// lists.
  void clearScreen();

  /// Sets the bounds of the drawing portion of the Canvas.
  ///
  /// The bounds describe both the minimum and maximum values for each axis.
  /// The center is always at position (0,0). The X values range from -xMax to
  /// +xMax. Likewise, the Y values range from -yMax to +yMax.
  ///
  /// Note, the bounds to not translate to pixels. Rather, the bounds describe a
  /// rectangle that is maximized to fit inside of the widget. When the widget
  /// is resized the bounds rectangle is zoomed in/out to fit accordingly.
  ///
  /// Note that the bounding rectangle is shown only if isBounded=true.
  /// Otherwise, the background color fills the entire widget.
  void setBounds(qreal xMax, qreal yMax);

  /// Sets xMax and yMax to the bounds of the rectangle.
  void getBounds(qreal &xMax, qreal &yMax);

  /// Returns a QImage of the Canvas.
  ///
  /// This returns a framebuffer grab. So if the bounds rectangle does
  /// not have the same proportions as the widget (which it almost never will)
  /// the returned image will include some widget background.
  QImage getImage();

  /// Sets whether or not the bounds may be shown as a subportion of the widget.
  ///
  /// If aIsBounded is true then the bounding rectangle may be shown on top of
  /// the background of the widget. Otherwise, the entire widget background will
  /// be set to the rectangle's background color.
  void setIsBounded(bool aIsBounded);

  /// Returns true if the bounding rectangle is drawn on top of the widget.
  bool getIsBounded();

  /// Sets future lines and polygons to be drawn using newMode.
  void setPenmode(PenModeEnum newMode);

  /// Sets the width of future lines drawn to aSize.
  void setPensize(GLfloat aSize);

  /// Returns true if aSize is a valid pen size.
  bool isPenSizeValid(GLfloat aSize);
};

#endif // CANVAS_H
