/*
 * Copyright (C) 2014
 * Computer Graphics Group, University of Siegen
 * Written by Martin Lambers <martin.lambers@uni-siegen.de>
 * All rights reserved.
 */

#ifndef GLWIDGET_H
#define GLWIDGET_H

#include <cstdio>
#include <cstdlib>
#include <memory>

#include <iostream>

#include <QElapsedTimer>
#include <QOpenGLWidget>
#include <QOpenGLContext>
#include <QMessageBox>
#include <QTimer>

#include <glm/vec3.hpp>

#define GLM_FORCE_RADIANS

/*
 * Forward decleration
 */
class Earth;
class Sun;
class DeathStar;
class Skybox;
class CoordinateSystem;
class DysonSwarm;

/**
 * @brief The GLWidget class handling the opengl widget
 *
 * This class handles everything that has to do with the GL Widget.
 * It also serves as a manager for the drawable objects
 */
class GLWidget : public QOpenGLWidget
{
    Q_OBJECT

private:

    /**
     * @brief The Camera struct
     *
     * Describes the camera (always pointed at the origin)
     */
    struct Camera{
        glm::vec3 right;    /**< pointing to the right side of the camera */
        glm::vec3 up;       /**< pointing to the top side of the camera */
        glm::vec3 front;    /** poinitng to the front of the camera */
        float distance;     /**< the distance from the origin */
    };

    Camera _camera;     /**< The current camera */
    Camera _oldCamera;  /**< Stores the cameras status before updates */
    bool _moving;       /**< Tells if the camera needs to be updated */
    QPoint _startPos;   /**< The point where the user interaction began */

    QTimer _updateTimer;        /**< Used for regular frame updates */
    QElapsedTimer _stopWatch;   /**< Measures time between updates */

    std::shared_ptr<Sun> _sun;                  /**< The root element of the planet hierarchy */
    std::shared_ptr<Skybox> _skybox;                /**< Skybox (not part of the hierarchy */
    std::shared_ptr<CoordinateSystem> _coordSystem; /**< Coordinate system (not part of the hierarchy) */
    std::shared_ptr<DysonSwarm> _swarm1;
    std::shared_ptr<DysonSwarm> _swarm2;

private slots:
    /**
     * @brief animateGL updates the scene
     *
     * The new positions of all elements are generated.
     * This is not used for drawing.
     */
    void animateGL();

public:
    /**
     * @brief setGLFormat sets the GL format to 4.0 core
     */
    static void setGLFormat ()
    {
        QSurfaceFormat format;
        format.setSwapBehavior(QSurfaceFormat::DoubleBuffer);
        format.setSwapInterval(::getenv("COREGL_FPS") ? 0 : 1);
        format.setVersion(4, 0);
        format.setProfile(QSurfaceFormat::CoreProfile);
        QSurfaceFormat::setDefaultFormat(format);
    }

    /**
     * @brief GLWidget constructor
     * @param parent the parent widget
     */
    GLWidget(QWidget*& parent);

    /**
     * @brief show opens the widget
     *
     * If the widget can not start, the program exits
     */
    virtual void show();

    /**
     * @brief initializeGL initializes the context
     *
     * The function initializes GLEW and all drawable objects
     */
    virtual void initializeGL() override;

    /**
     * @brief resizeGL called automatically on resize
     * @param width the new width of the widget
     * @param height the new height of the widget
     */
    virtual void resizeGL(int width, int height) override;

    /**
     * @brief paintGL draws the scene
     */
    virtual void paintGL() override;

    /**
     * @brief mousePressEvent automatically called whenever the mouse is pressed
     * @param event the QMouseEvenent containing all relevant data
     */
    virtual void mousePressEvent(QMouseEvent * event) override;

    /**
     * @brief mouseReleaseEvent automatically called whenever the mouse is released
     * @param event the QMouseEvenent containing all relevant data
     */
    virtual void mouseReleaseEvent(QMouseEvent* event) override;

    /**
     * @brief mousePressEvent automatically called whenever the mouse is moved
     * @param event the QMouseEvenent containing all relevant data
     */
    virtual void mouseMoveEvent(QMouseEvent* event) override;

    /**
     * @brief mousePressEvent automatically called whenever the mouse wheel is used
     * @param event the QWheelEvent containing all relevant data
     */
    virtual void wheelEvent(QWheelEvent *event) override;

    /**
     * @brief getNames Returns the name of all planets
     * @return the names of all planets as vector
     */
    virtual std::vector<std::string> getNames() const;

public slots:
    /**
     * @brief resetCamera Resets the camera to its initial position
     */
    virtual void resetCamera();

};

#endif
