#define _USE_MATH_DEFINES

#include <math.h>
#include <cmath>
#include <array>
#include <chrono> 
#include <string>
#include <vector>
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>

#include "DataStructures.h"
#include <ComputeEngine.h>

using std::cout;
using std::endl;

float sensitivity = 10.0f;

void UpdateKeys(ComputeEngine& renderer, CameraData& camera, std::vector<SceneObject>& objects, shader_data_t& shader_data, Texture& tex_output) {
    if (renderer.IsKeyClicked(GLFW_KEY_P)) {
        renderer.SwitchInput();
    }
    if (renderer.GetInput()) {
        if (renderer.IsKeyClicked(GLFW_KEY_F11)) {
            renderer.SwitchFullScreen();
        }
        if (renderer.IsKeyClicked(GLFW_KEY_ESCAPE))
            renderer.CloseWindow();
        if (renderer.IsKeyClicked(GLFW_KEY_L))
            renderer.SaveScreen("screenshots/screenshot");
    }
    int width, height;
    shader_data.GetWindowSize(width, height);
    renderer.GetWindowSize(shader_data.screenSize[0], shader_data.screenSize[1]);
    if (width != shader_data.GetWidth() || height != shader_data.GetHeight())
        tex_output.Resize(shader_data.GetWidth(), shader_data.GetHeight());
}

void UpdateInput(ComputeEngine& renderer, CameraData& camera, std::vector<SceneObject>& objects, shader_data_t& shader_data, Texture& tex_output) {
    renderer.PollEvents();
    UpdateKeys(renderer, camera, objects, shader_data, tex_output);

    double dx, dy;
    renderer.GetMouseDelta(dx, dy);
    dx *= (renderer.GetFrametime() / 1000.0);
    dy *= (renderer.GetFrametime() / 1000.0);

    camera.rotateCamera(dx, dy);
    camera.updateCameraData(renderer.GetFrametime() / 1000.0);
}

int main(void)
{
    srand((unsigned)time(0));

    int width = 1024;
    int height = 1024;

    // init renderer
    ComputeEngine renderer(width, height, "Hydrogen wave function", true);
    Shader shader("../shaders/hydrogen_wave/pixelCompute.compute");
    Program compute_program(shader);

    // init data for shader
    shader_data_t shader_data = {};
    shader_data.screenSize = { width, height };
    shader_data.iterations = 2;
    shader_data.samples = 1;
    shader_data.dt = .0000001;

    // create scene
    const int spheres = 0;

    int points = 0;
    float mul = 5;

    std::vector<SceneObject> objects;
    //objects.reserve(points);
    //for (int i = 0; i < points; i++) {
    //    objects.push_back(SceneObject());
    //    objects[i].position[0] = (getRand() - 0.5f) * mul;
    //    objects[i].position[1] = (getRand() - 0.5f) * mul;
    //    objects[i].position[2] = (getRand() - 0.5f) * mul;
    //}

    // init textures
    Texture tex_output("img_output", width, height);

    // init buffers
    UBO data_buffer(2);
    //SSBO objects_buffer(3);

    // fill buffers with data
    data_buffer.Set(&shader_data);
    //objects_buffer.Set(&objects);

    // init camera
    CameraData camera;
    camera.position = { 0, 15, -47 };
    camera.rotation = { -23, 0, 0 };
    camera.sensitivity = sensitivity;
    camera.m_width = width;
    camera.m_height = height;
    camera.CopyData(shader_data);

    // bind textures
    compute_program.BindTextureImage(tex_output, 0);

    compute_program.SetUniform1i("width", width);
    compute_program.SetUniform1i("height", height);
    int n, l, m;
    n = 4;
    l = 2;
    m = 1;
    compute_program.SetUniform1i("quantum_n", n);
    compute_program.SetUniform1i("quantum_l", l);
    compute_program.SetUniform1i("quantum_m", m);
    // dispatch compute shader
    //renderer.DispatchPixelCompute(shader_data.GetWidth(), shader_data.GetHeight());
    

    printf("width: %d\ height: %d\n", shader_data.GetWidth(), shader_data.GetHeight());
    printf("points: %d\n", objects.size());

    float last_dt = renderer.GetFrametime();

    float phi = 0;

    while (!renderer.ShouldClose()) {
        // update input
        UpdateInput(renderer, camera, objects, shader_data, tex_output);

        phi += (renderer.GetFrametime() / 1000.0) * M_PI * 0.5;

        phi = std::fmod(phi, M_PI);
        printf("phi: %.2f\n", phi);

        // update variables
        compute_program.SetUniform1f("u_phi", phi);

        // dispatch compute shader
        compute_program.DispatchCompute(shader_data.GetWidth(), shader_data.GetHeight());

        renderer.UpdateFrametime();

        // draw rendered image
        renderer.DrawTexture(tex_output);


        /*
        if (0) {
            // update input
            camera.SetChanged(false);
            UpdateInput(renderer, camera, objects, shader_data, tex_output);

            shader_data.UpdateSeed();
            //shader_data.dt = (renderer.m_framecount - 1 % 40 != 0) ? renderer.GetFrametime() : last_dt;
            shader_data.dt = .5f;
            shader_data.objectcount = objects.size();

            camera.UpdateWindowSize(shader_data.GetWidth(), shader_data.GetHeight());
            camera.CopyData(shader_data);

            // update buffers
            data_buffer.Update(&shader_data);
            //objects_buffer.Update(&objects);

            renderer.m_PixelComputeProgram.SetUniform("_pass", renderer.m_framecount);

            // dispatch compute shader
            renderer.DispatchObjectCompute(objects.size());

            // dispatch compute shader
            renderer.DispatchPixelCompute(shader_data.GetWidth(), shader_data.GetHeight());

            //print frametime
            if (renderer.m_framecount % 5 == 0) {
                //printf("%.2f ms\n", renderer.GetFrametime());
                //printf("%.2f ms\n", shader_data.dt);

                // draw rendered image
                renderer.DrawTexture(tex_output);

                //printf("x %.2f y %.2f\n", objects[0].position[0], objects[0].position[1]);
            }
            //print frametime
            if (renderer.m_framecount % 1000 == 0) {
                //printf("%.2f ms\n", renderer.GetFrametime());
                printf("%.2f ms\n", shader_data.dt);

                // draw rendered image
                //renderer.DrawTexture(tex_output);

                //printf("x %.2f y %.2f\n", objects[0].position[0], objects[0].position[1]);
            }

            // glFinish call
            renderer.Finish();
        }
            */
    }

    //print average frametime
    printf("Avg frametime: %.3f", renderer.GetAverageFrametime());

    return 0;
}