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

#include <GL/glew.h>

#include <QtPlugin>
#if defined(QT_STATICPLUGIN) && defined(_WIN32)
Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin)
#endif
#include <QApplication>
#include <QGLFormat>
#include <QMouseEvent>
#include <QTimer>
#include <iostream>
#include <memory>

#define GLM_FORCE_RADIANS
#define GLM_SWIZZLE
#include <glm/glm.hpp>
#include <glm/gtc/constants.hpp>
#include <glm/gtx/transform.hpp>
#include <glm/mat4x4.hpp>
#include <glm/gtx/rotate_vector.hpp>

#include <glm/gtc/quaternion.hpp>

#include "glwidget.hpp"

#include "glbase/gltool.hpp"

#include "gui/config.h"

#include "planets/cone.h"
#include "planets/coordinatesystem.h"
#include "planets/deathstar.h"
#include "planets/planet.h"
#include "planets/earth.h"
#include "planets/sun.h"
#include "planets/saturn.h"
#include "planets/skybox.h"
#include "planets/ring.h"
#include "planets/dysonswarm.h"

//#include "GL/glew.h"

GLWidget::GLWidget(QWidget *&parent) : QOpenGLWidget(parent),//static_cast<QWidget*>(0)),
    _moving(false), _updateTimer(this), _stopWatch()
{

    // update the scene periodically
    QObject::connect(&_updateTimer, SIGNAL(timeout()), this, SLOT(animateGL()));
    _updateTimer.start(18);
    _stopWatch.start();

    // create all drawable elements
    _skybox = std::make_shared<Skybox>();
    _coordSystem = std::make_shared<CoordinateSystem>();

    /***************************
     * DO NOT CHANGE days/year *
     * *************************/
                                                                        // radius, distance, h/day, days/year
    std::shared_ptr<Earth> earth     = std::make_shared<Earth> ("Erde",     0.4,    6.0,    24.0,     365, ":/res/images/earth.bmp");
    std::shared_ptr<Planet> moon     = std::make_shared<Planet>("Mond",   0.215,    1.0,    27.3,    27, ":/res/images/moon.bmp");
    _sun                             = std::make_shared<Sun>   ("Sonne",    1.2,    0.0,    60,   300, ":/res/images/sun.bmp");
    std::shared_ptr<Planet> mercury  = std::make_shared<Planet>("Merkur",   0.34,  3.68,  1416,   100, ":/res/images/mercury.bmp");
    std::shared_ptr<Planet> venus    = std::make_shared<Planet>("Venus",    0.34,   3.0,  8232.0,   250, ":/res/images/venus.bmp");

    std::shared_ptr<Planet> mars     = std::make_shared<Planet>("Mars",     0.453,  16.6,    24.7,   700, ":/res/images/mars.bmp");//TODO UMlaufzeiten bessern
    std::shared_ptr<Planet> jupiter  = std::make_shared<Planet>("Jupiter",  0.453,  19.32,    9.92,  3500, ":/res/images/jupiter.bmp");
    std::shared_ptr<Saturn> saturn   = std::make_shared<Saturn>("Saturn",   0.453,  21.92,   10.55, 5000, ":/res/images/saturn.bmp");

    // jupiter moons
    std::shared_ptr<Planet> io       = std::make_shared<Planet>("Io",       0.036,  0.8,   10.6,      30, ":/res/images/moon.bmp");
    std::shared_ptr<Planet> europa   = std::make_shared<Planet>("Europa",   0.031,  1.0,   10.6,      60, ":/res/images/moon.bmp");
    std::shared_ptr<Planet> ganymede = std::make_shared<Planet>("Ganymed",  0.052,  1.2,   10.6,     120, ":/res/images/moon.bmp");
    std::shared_ptr<Planet> callisto = std::make_shared<Planet>("Callisto", 0.048,  1.8,   10.6,     350, ":/res/images/moon.bmp");

    std::shared_ptr<DeathStar>deathStar = std::make_shared<DeathStar>("Todesstern", 0.315,  4.0,    27.3,    50, ":/res/images/moon.bmp");

    _swarm1                          = std::make_shared<DysonSwarm>(45.f, 0, 0.2, 8.0, 500,500,_sun);
    _swarm2                          = std::make_shared<DysonSwarm>(135.f, 1, 0.2, 8.0, 500,500,_sun);

    // create hierarchy

//    _earth->addChild(sun);

    _sun->addChild(mercury);
    _sun->addChild(venus);
    _sun->addChild(earth);
    earth->addChild(moon);
    _sun->addChild(mars);
    _sun->addChild(jupiter);
    _sun->addChild(saturn);
    mars->addChild(deathStar);

    jupiter->addChild(io);
    jupiter->addChild(europa);
    jupiter->addChild(ganymede);
    jupiter->addChild(callisto);

    _sun->setLights(_sun, deathStar->cone());
}

void GLWidget::show()
{
    QOpenGLWidget::show();

    // check for a valid context
    if (!isValid() || !context()->isValid() || context()->format().majorVersion() != 4) {
        QMessageBox::critical(this, "Error", "Cannot get a valid OpenGL 4 context.");
        exit(1);
    }
}

void GLWidget::initializeGL()
{
    /* Initialize OpenGL extensions */
    glewExperimental = GL_TRUE; // otherwise some function pointers are NULL...
    GLenum err = glewInit();
    if (GLEW_OK != err)
    {
      /* Problem: glewInit failed, something is seriously wrong. */
      fprintf(stderr, "Error: %s\n", glewGetErrorString(err));
    }
    glGetError(); // clear a gl error produced by glewInit

    // make sure the context is current
    makeCurrent();

    // init all drawables

    /// TODO: Init all drawables here
    _sun->init();

}

void GLWidget::resizeGL(int width, int height)
{
    /// TODO: store the resolution in the config in case someone needs it

    // update the viewport
    glViewport(0, 0, width, height);
}

void GLWidget::paintGL()
{

    /// TODO: recreate the scene if needed

    // Render: set up view
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnable(GL_DEPTH_TEST);

    /// TODO: calculate projection matrix from resolution
    glm::mat4 projection_matrix = glm::perspective(glm::radians(50.0f),
                                                   783.0f / 691,
                                                   0.1f, 100.0f);

    // always draw the skybox
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    /// TODO: call draw for all drawables
    _sun->draw(projection_matrix);
}

void GLWidget::mousePressEvent(QMouseEvent *event)
{
    // ignore if it was not the left mouse button
    if(!event->button() & Qt::LeftButton)
        return;


    /// TODO: handle left press here
}

void GLWidget::mouseReleaseEvent(QMouseEvent *event)
{
    // ignore if it was not the left mouzse button
    if(!event->button() & Qt::LeftButton)
        return;

    /// TODO: handle left release here
}

void GLWidget::mouseMoveEvent(QMouseEvent *event)
{

    /// TODO: handle camera movement here
}

void GLWidget::wheelEvent(QWheelEvent *event)
{
    /// TODO: handle zoom here

    // Hint: you can use:
    // event->angleDelta().ry()
}

std::vector<std::string> GLWidget::getNames() const
{
    // recursively get all names of planets
    return _sun->getNames();
}

void GLWidget::resetCamera()
{
    // default values. change them if needed
    _camera.right = glm::vec3(1.0, 0.0, 0.0);
    _camera.up = glm::vec3(0.0, 1.0, 0.0);
    _camera.front = glm::vec3(0.0, 0.0, -1.0);
    _camera.distance = 5;

    _oldCamera = _camera;
}

void GLWidget::animateGL()
{
    // make the context current in case there are glFunctions called
    makeCurrent();

    // get the time delta
    float timeElapsedMs = _stopWatch.nsecsElapsed() / 1000000.0f;
    // restart stopwatch for next update
    _stopWatch.restart();

    // calculate current modelViewMatrix for the camera position
    /// TODO: use your camera logic here
    glm::mat4 modelViewMatrix = glm::lookAt(glm::vec3(0.0, 0.0, -5.0), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0, 1.0, 0.0));

    // update drawables
    /// TODO update all drawables
    _sun->update(timeElapsedMs, modelViewMatrix);

    // update the widget
    update();
}


