#include <glad/glad.h>
#include <GLFW/glfw3.h>
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
#include <irrKlang.h>

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>


#include <shader_m.h>
#include <camera.h>

#include <iostream>
#include <map>

#include <ft2build.h>
#include FT_FREETYPE_H


void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void processInput(GLFWwindow* window);
unsigned int loadTexture(const char* path);

// settings
const unsigned int SCR_WIDTH = 1600;
const unsigned int SCR_HEIGHT = 900;

float mixValue = 0.2f;

// cube stuff
// camera
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
float lastX = SCR_WIDTH / 2.0f;
float lastY = SCR_HEIGHT / 2.0f;
bool firstMouse = true;

// timing
float deltaTime = 0.0f; // time between current frame and last frame
float lastFrame = 0.0f;

// lighting
glm::vec3 lightPos(1.2f, 1.0f, 2.0f);

// irrKlang stuff
using namespace irrklang;
ISoundEngine* SoundEngine = createIrrKlangDevice();

// Truetype stuff

void RenderText(Shader& shader, std::string text, float x, float y, float scale, glm::vec3 color);

/// Holds all state information relevant to a character as loaded using FreeType
struct Character {
    unsigned int TextureID; // ID handle of the glyph texture
    glm::ivec2   Size;      // Size of glyph
    glm::ivec2   Bearing;   // Offset from baseline to left/top of glyph
    unsigned int Advance;   // Horizontal offset to advance to next glyph
};
std::map<GLchar, Character> Characters;
unsigned int VAO, VBO;
//unsigned int VAO2, VBO2; // for second font type?



int main()
{
    // glfw: initialize and configure
    // ------------------------------
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

#ifdef __APPLE__
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif

    // glfw window creation :: drop in glfwGetPrimaryMonitor() to replace the first NULL for full screen
    // --------------------
    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "", glfwGetPrimaryMonitor(), NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
    glfwSetCursorPosCallback(window, mouse_callback);

    // tell GLFW to capture our mouse
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);


    // glad: load all OpenGL function pointers
    // ---------------------------------------
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }


    // OpenGL state
    // ------------
    glEnable(GL_CULL_FACE);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);


    // texture background - shader thingie & other stuff for background
    Shader neckShader("4.1.texture.vs", "4.1.texture.fs");
    // set up vertex data (and buffer(s)) and configure vertex attributes
    // ------------------------------------------------------------------
    float bvertices[] = {
        // positions          // colors           // texture coords
         1.0f,  1.0f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 1.0f, // top right
         1.0f, -1.0f, 0.0f,   0.0f, 1.0f, 0.0f,   1.0f, 0.0f, // bottom right
        -1.0f, -1.0f, 0.0f,   0.0f, 0.0f, 1.0f,   0.0f, 0.0f, // bottom left
        -1.0f,  1.0f, 0.0f,   1.0f, 1.0f, 0.0f,   0.0f, 1.0f  // top left 
    };
    unsigned int bindices[] = {
        0, 1, 3, // first triangle
        1, 2, 3  // second triangle
    };

    unsigned int VBOB, VAOB, EBOB;
    glGenVertexArrays(1, &VAOB);
    glGenBuffers(1, &VBOB);
    glGenBuffers(1, &EBOB);

    glBindVertexArray(VAOB);

    glBindBuffer(GL_ARRAY_BUFFER, VBOB);
    glBufferData(GL_ARRAY_BUFFER, sizeof(bvertices), bvertices, GL_STATIC_DRAW);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBOB);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(bindices), bindices, GL_STATIC_DRAW);

    // position attribute
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    // color attribute
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);
    // texture coord attribute
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
    glEnableVertexAttribArray(2);


    // load and create a texture 
    // -------------------------
    unsigned int btexture;
    glGenTextures(3, &btexture);
    glBindTexture(GL_TEXTURE_2D, btexture); // all upcoming GL_TEXTURE_2D operations now have effect on this texture object
    // set the texture wrapping parameters
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);	// set texture wrapping to GL_REPEAT (default wrapping method)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    // set texture filtering parameters
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    // load image, create texture and generate mipmaps
    int width, height, nrChannels;
    // The FileSystem::getPath(...) is part of the GitHub repository so we can find files on any IDE/platform; replace it with your own image path.
    unsigned char* bdata = stbi_load("background3.png", &width, &height, &nrChannels, 0);
    if (bdata)
    {
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, bdata);
        glGenerateMipmap(GL_TEXTURE_2D);
    }
    else
    {
        std::cout << "Failed to load texture" << std::endl;
    }
    stbi_image_free(bdata);
    glBindTexture(GL_TEXTURE_2D, 0);

    // build and compile our shader programs for cube(s)
    // ------------------------------------
    Shader lightingShader("2.9.basic_lighting.vs", "2.9.basic_lighting.fs");
    Shader lightCubeShader("2.7.light_cube.vs", "2.7.light_cube.fs");

    // for the hidden amiga effect:
    Shader lightingShader2("2.10.basic_lighting.vs", "2.10.basic_lighting.fs");

    // compile and setup the shader for text
    // ----------------------------
    Shader shader("text.vs", "text.fs");
    glm::mat4 projection = glm::ortho(0.0f, static_cast<float>(SCR_WIDTH), 0.0f, static_cast<float>(SCR_HEIGHT));
    shader.use();
    glUniformMatrix4fv(glGetUniformLocation(shader.ID, "projection"), 1, GL_FALSE, glm::value_ptr(projection));

    // FreeType
    // --------
    FT_Library ft;
    // All functions return a value different than 0 whenever an error occurred
    if (FT_Init_FreeType(&ft))
    {
        std::cout << "ERROR::FREETYPE: Could not init FreeType Library" << std::endl;
        return -1;
    }
    // find path to font
    std::string font_name = ("ShadowsIntoLightTwo-Regular.ttf");
    if (font_name.empty())
    {
        std::cout << "ERROR::FREETYPE: Failed to load font_name" << std::endl;
        return -1;
    }

    // load font as face
    FT_Face face;
    if (FT_New_Face(ft, font_name.c_str(), 0, &face)) {
        std::cout << "ERROR::FREETYPE: Failed to load font" << std::endl;
        return -1;
    }
    else {
        // set size to load glyphs as
        FT_Set_Pixel_Sizes(face, 0, 48);

        // disable byte-alignment restriction
        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

        // load first 128 characters of ASCII set
        for (unsigned char c = 0; c < 128; c++)
        {
            // Load character glyph 
            if (FT_Load_Char(face, c, FT_LOAD_RENDER))
            {
                std::cout << "ERROR::FREETYTPE: Failed to load Glyph" << std::endl;
                continue;
            }
            // generate texture
            unsigned int texture;
            glGenTextures(1, &texture);
            glBindTexture(GL_TEXTURE_2D, texture);
            glTexImage2D(
                GL_TEXTURE_2D,
                0,
                GL_RED,
                face->glyph->bitmap.width,
                face->glyph->bitmap.rows,
                0,
                GL_RED,
                GL_UNSIGNED_BYTE,
                face->glyph->bitmap.buffer
            );
            // set texture options
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
            // now store character for later use
            Character character = {
                texture,
                glm::ivec2(face->glyph->bitmap.width, face->glyph->bitmap.rows),
                glm::ivec2(face->glyph->bitmap_left, face->glyph->bitmap_top),
                static_cast<unsigned int>(face->glyph->advance.x)
            };
            Characters.insert(std::pair<char, Character>(c, character));
        }
        glBindTexture(GL_TEXTURE_2D, 0);
    }
    // destroy FreeType once we're finished
    FT_Done_Face(face);
    FT_Done_FreeType(ft);


    // configure VAO/VBO for texture quads
    // -----------------------------------
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    glBindVertexArray(VAO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6 * 4, NULL, GL_DYNAMIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

    // stuff for cubes
    // 
    //  look up on the text coords again - and things to remix with them
    // set up vertex data (and buffer(s)) and configure vertex attributes
    // ------------------------------------------------------------------
    float vertices[] = {
        // positions          // normals          // texture coordinates
        -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f, 0.0f, 0.0f,
         0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f, 1.0f, 0.0f,
         0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f, 1.0f, 1.0f,
         0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f, 1.0f, 1.0f,
        -0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f, 0.0f, 1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f, 0.0f, 0.0f,

        -0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f, 0.0f, 0.0f,
         0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f, 1.0f, 0.0f,
         0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f, 1.0f, 1.0f,
         0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f, 1.0f, 1.0f,
        -0.5f,  0.5f,  0.5f,  0.0f,  0.0f,  1.0f, 0.0f, 1.0f,
        -0.5f, -0.5f,  0.5f,  0.0f,  0.0f,  1.0f, 0.0f, 0.0f,

        -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f, 1.0f, 0.0f,
        -0.5f,  0.5f, -0.5f, -1.0f,  0.0f,  0.0f, 1.0f, 1.0f,
        -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f, 0.0f, 1.0f,
        -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f, 0.0f, 1.0f,
        -0.5f, -0.5f,  0.5f, -1.0f,  0.0f,  0.0f, 0.0f, 0.0f,
        -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f, 1.0f, 0.0f,

         0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f, 1.0f, 0.0f,
         0.5f,  0.5f, -0.5f,  1.0f,  0.0f,  0.0f, 1.0f, 1.0f,
         0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f, 0.0f, 1.0f,
         0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f, 0.0f, 1.0f,
         0.5f, -0.5f,  0.5f,  1.0f,  0.0f,  0.0f, 0.0f, 0.0f,
         0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f, 1.0f, 0.0f,

        -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f, 0.0f, 1.0f,
         0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f, 1.0f, 1.0f,
         0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f, 1.0f, 0.0f,
         0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f, 1.0f, 0.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f, 0.0f, 0.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f, 0.0f, 1.0f,

        -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f, 0.0f, 1.0f,
         0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f, 1.0f, 1.0f,
         0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f, 1.0f, 0.0f,
         0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f, 1.0f, 0.0f,
        -0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f, 0.0f, 0.0f,
        -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f, 0.0f, 1.0f
    };

    glm::vec3 cubePositions[] = {
        glm::vec3(0.0f,  0.0f, 0.0f),
        glm::vec3(2.0f,  5.0f, -15.0f),
        glm::vec3(-1.5f, -2.2f, -2.5f),
        glm::vec3(-3.8f, -2.2f, -12.3f),
        glm::vec3(2.4f, -0.4f, -3.5f),
        glm::vec3(-1.7f, 3.0f, -7.5f),
        glm::vec3(1.3f, -2.0f, -2.5f),
        glm::vec3(1.5f, 2.0f, -2.5f),
        glm::vec3(1.5f, 0.2f, -1.5f),
        glm::vec3(-1.3f, 1.0f, -1.5f),
        glm::vec3(-6.0f, -6.0f, -6.0f),
        glm::vec3(6.0f, 7.0f, 3.0f),
        glm::vec3(11.0f, 9.0f, 0.0f),
        glm::vec3(14.0f, 16.0f, -19.0f)
    };


    // VBO and VAO for cube(s)
    // first, configure the cube's VAO (and VBO)
    unsigned int VBO2, cubeVAO;
    glGenVertexArrays(1, &cubeVAO);
    glGenBuffers(1, &VBO2);

    glBindBuffer(GL_ARRAY_BUFFER, VBO2);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    glBindVertexArray(cubeVAO);

    // position attribute
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    // normal attribute
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
    glEnableVertexAttribArray(2);

    // second, configure the light's VAO (VBO stays the same; the vertices are the same for the light object which is also a 3D cube)
    unsigned int lightCubeVAO;
    glGenVertexArrays(1, &lightCubeVAO);
    glBindVertexArray(lightCubeVAO);

    glBindBuffer(GL_ARRAY_BUFFER, VBO2);
    // note that we update the lamp's position attribute's stride to reflect the updated buffer data
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);

    // load textures (we now use a utility function to keep the code more organized)
    unsigned int diffuseMap = loadTexture("ilovedis.png");
    unsigned int diffuseMap2 = loadTexture("amiga.png");
    unsigned int specularMap = loadTexture("black.png");

    // shader for hidden amiga
    lightingShader2.use();
    lightingShader2.setInt("material.diffuse", 0);
    lightingShader2.setInt("material.specular", 1);

    // shader configuration
    lightingShader.use();
    lightingShader.setInt("material.diffuse", 0);

    //variables for cube(s)
    //
    float angle = 20.0f;

    float movingX = 0.0f;
    float movingY = 0.0f;
    float movingZ = 5.0f;

    float lightA = 1.2f;
    float lightB = 1.0f;
    float lightC = 2.0f;
    float scalerOne = 1.2f;
    int sc = 0;

    // the clear color
    float backColor = 0.5f;
    int bc = 0;

    // variables for text
    //
    float moveX = 20.0f;
    float moveY = 20.0f;
    float move2X = 175.0f;
    float move2Y = 250.0f;

    // configure global opengl state - text rendering doesn't like it ?
    //glEnable(GL_DEPTH_TEST);

    int newFont = 0;
    int colorChange = 0;
    
    // fix this for after debugging:
    //int sceneCheck = 0;
    // debugging:
    int sceneCheck = 0;
    int loadaudio = 0;
    float blends = 0.0f;
    int texture = 0;
    // music tune 1
    SoundEngine->play2D("Mark-0sTune.mp3", false);
    // render loop
    // -----------
    while (!glfwWindowShouldClose(window))
    {


        if (sceneCheck <= 79)
        {
            // input
            // -----
            processInput(window);
            glClearColor(0.35f, 0.25f, 0.55f, 1.0f);
            glClear(GL_COLOR_BUFFER_BIT);

            // test if audio okay?
            // render
            // ------


            sceneCheck = glfwGetTime();

            if (sceneCheck <= 16) // change to 25 when finished
            {
                shader.use();
                processInput(window);
                glClearColor(0.4f, 0.3f, 0.55f, 1.0f);
                glClear(GL_COLOR_BUFFER_BIT);
                float color = sin(glfwGetTime());
                float color2 = cos(glfwGetTime());
                float size = cos(glfwGetTime()) * 1.2f;
                float size2 = 1.0f - color;
                moveX += 0.1f;
                moveY += 0.15f;
                move2X -= 0.1f + (color);
                move2Y += 0.15f;
                if (blends <= 1.0f)
                {
                    glDisable(GL_BLEND);
                    blends = blends + 0.015f;
                }
                if (blends >= 1.0f)
                {
                    glEnable(GL_BLEND);
                    blends = blends + 0.01f;
                }
                if (blends >= 2.0f) glDisable(GL_BLEND);
                if (blends >= 3.0f) glEnable(GL_BLEND);

                RenderText(shader, " Ahooooooy mateys!", (moveX * 7.2f), 600.0f + (moveY), (size - 0.2f), glm::vec3(color, color2, moveX));
                RenderText(shader, " Musica by: Mark-0 ", sceneCheck + 2.0f, moveY, size2, glm::vec3(0.2f, sceneCheck, (sceneCheck * 1.75f)));
                RenderText(shader, "Horror o'code by: superstande", move2X, move2Y, size2, glm::vec3(move2X, color2, color));
                RenderText(shader, "CUBES JUST WANNA HAVE FUNS ", 20.0f, (move2Y + 160.0f), 1.65f, glm::vec3(move2X, (color2 / 0.3f), sceneCheck));
            }

            if (sceneCheck >= 17 && sceneCheck <= 25) // change to 27 && 45 when finished
            {
                processInput(window);
                glClearColor(0.35f, 0.25f, 0.55f, 1.0f);
                glClear(GL_COLOR_BUFFER_BIT);

                if (colorChange == 0)
                    moveX = 0.2f;
                colorChange = 1;

                if (colorChange == 1)
                    moveX = moveX + 0.003f;

                if (moveX >= 1.0f)
                    colorChange == 2;

                float color = sin(glfwGetTime());
                moveX += 0.1f;
                moveY += 0.15f;
                move2X -= 0.1f + (color);
                move2Y += 0.15f;
                RenderText(shader, "Greetings to everybody in the Demoscene!", 45, (215 + moveX), 1, glm::vec3(1.0f, move2Y + color , (moveX - sceneCheck) ));
                RenderText(shader, "You Rock!", 120, 170, 1, glm::vec3(1.0f, 0.0f, 0.0f));
                RenderText(shader, "This boat is sinkin' ", move2Y + 490, 140 - sceneCheck, 1, glm::vec3(0.5f, 0.4f, 0.75f));
                RenderText(shader, "Need more Rum!", moveX + 325.0f, 100, 1, glm::vec3(move2X, sceneCheck, moveX));
            }


            if (sceneCheck >= 25 && sceneCheck <= 75)
            {
                // per-frame time logic
                // --------------------
                glDisable(GL_CULL_FACE);
                glDisable(GL_BLEND);
                glEnable(GL_DEPTH_TEST);
                float currentFrame = glfwGetTime();
                deltaTime = currentFrame - lastFrame;
                lastFrame = currentFrame;

                // input
                // -----
                processInput(window);

                // render
                // ------


                if (backColor >= 1.0f && bc == 0)
                    bc = 1;

                if (bc == 1)
                    backColor = backColor - 0.03f;

                if (backColor <= 0.0f)
                    bc = 0;

                if (bc == 0)
                    backColor = backColor + 0.03f;

                glm::mat4 view = glm::mat4(1.0f); // make sure to initialize matrix to identity matrix first
                float radius = 10.0f;
                float camX = sin(glfwGetTime()) * radius;
                float camZ = cos(glfwGetTime()) * radius;

                glm::vec3 cameraPosition = glm::vec3(camX, 0.0f, camZ);

                lightingShader.setMat4("view", view);

                //light box moving
                angle = glfwGetTime() * 75.0f; // rotating around itself
                lightA = 1.0f + sin(glfwGetTime()) * 7.0f;
                lightB = sin(glfwGetTime() / 2.0f) * 1.0f;
                glm::vec3 lightPos(-lightA, lightB, 3.6f);

                glClearColor(0.0f, 0.1f, 0.25f, 1.0f);
                glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

                // be sure to activate shader when setting uniforms/drawing objects
                lightingShader.use();
                lightingShader.setVec3("light.position", lightPos);
                lightingShader.setVec3("viewPos", cameraPosition);

                // light properties
                glm::vec3 lightColor;
                lightColor.x = sin(glfwGetTime() * 2.0f);
                lightColor.y = sin(glfwGetTime() * 0.7f);
                lightColor.z = sin(glfwGetTime() * 1.3f);
                //glm::vec3 diffuseColor = lightColor * glm::vec3(0.5f); // decrease the influence
                //glm::vec3 ambientColor = diffuseColor * glm::vec3(0.2f); // low influence
                //lightingShader.setVec3("light.ambient", ambientColor);
                //lightingShader.setVec3("light.diffuse", diffuseColor);
                lightingShader.setVec3("light.ambient", 1.0f, 1.0f, 1.0f);
                lightingShader.setVec3("light.diffuse", 1.0f, 1.0f, 1.0f);
                lightingShader.setVec3("light.specular", 2.0f, 2.0f, 2.0f);

                // material properties
                lightingShader.setVec3("material.ambient", 0.2125f, 0.1275f, 0.054f);
                lightingShader.setVec3("material.diffuse", 0.714f, 0.4284f, 0.18144f);
                lightingShader.setVec3("material.specular", 0.393548f, 0.271906f, 0.166721f);
                lightingShader.setFloat("material.shininess", 25.6f);

                // view/projection transformations
                glm::mat4 projection = glm::perspective(glm::radians(45.0f), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
                view = glm::lookAt(glm::vec3(camX, 0.0f, camZ), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
                lightingShader.setMat4("projection", projection);
                lightingShader.setMat4("view", view);

                // world transformation
                glm::mat4 model = glm::mat4(1.0f);
                if (sceneCheck <= 40)
                {
                    model = glm::scale(model, glm::vec3(3.0f));
                    model = glm::rotate(model, glm::radians(-angle), glm::vec3(3.0f, 0.5f, 1.0f));
                    lightingShader.setMat4("model", model);

                    // bind diffuse map
                    glActiveTexture(GL_TEXTURE0);
                    glBindTexture(GL_TEXTURE_2D, diffuseMap);
                    // render the cube
                    glBindVertexArray(cubeVAO);
                    glDrawArrays(GL_TRIANGLES, 0, 36);

                }

                if (sceneCheck >= 41 && sceneCheck <= 54) // change to <= 31 when finished
                {
                    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
                    if (sc == 0)
                        scalerOne = scalerOne + 0.02f;
                    if (scalerOne >= 4.4f && sc == 0)
                        sc = 1;
                    if (sc == 1)
                        scalerOne = scalerOne - 0.03f;
                    if (scalerOne <= 0.4f)
                        sc = 0;

                    model = glm::scale(model, glm::vec3(scalerOne));
                    model = glm::rotate(model, glm::radians(-angle), glm::vec3(3.0f, 0.3f, 1.0f));
                    lightingShader.setMat4("model", model);

                    // bind diffuse map
                    glActiveTexture(GL_TEXTURE0);
                    glBindTexture(GL_TEXTURE_2D, diffuseMap);
                    // render the cube
                    glBindVertexArray(cubeVAO);
                    glDrawArrays(GL_TRIANGLES, 0, 36);

                }

                if (sceneCheck >= 55 && sceneCheck <= 75 )           // change to >= 32 when finished
                {
                    glBindVertexArray(cubeVAO);
                    for (unsigned int i = 0; i < 14; i++)
                    {
                        // calculate the model matrix for each object and pass it to shader before drawing
                        glm::mat4 model = glm::mat4(1.0f);
                        model = glm::translate(model, cubePositions[i]);
                        float angle = 20.0f * i;
                        model = glm::rotate(model, (float)(glfwGetTime()), glm::vec3(1.0f, backColor, -0.1f * ((float)i)));
                        lightingShader.setMat4("model", model);
                        // bind diffuse map
                        glActiveTexture(GL_TEXTURE0);
                        glBindTexture(GL_TEXTURE_2D, diffuseMap);
                        // render the cube
                        glBindVertexArray(cubeVAO);
                        glDrawArrays(GL_TRIANGLES, 0, 36);
                    }
                }

                // color stuff for lamp object
                //float LampValue = lightColor.x;
                int fragmentColorLocation = glGetUniformLocation(lightCubeShader.ID, "LampColor");
                //int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");
                // also draw the lamp object
                lightCubeShader.use();
                glUniform3f(fragmentColorLocation, lightColor.x, lightColor.y, lightColor.z);

                //lightCubeShader.setVec3("LightColor", ambientColor);
                lightCubeShader.setMat4("projection", projection);
                lightCubeShader.setMat4("view", view);
                model = glm::mat4(1.0f);
                model = glm::translate(model, lightPos);
                model = glm::scale(model, glm::vec3(0.4f)); // a smaller cube
                model = glm::rotate(model, glm::radians(-angle), glm::vec3(3.0f, 0.5f, 1.0f));
                lightCubeShader.setMat4("model", model);

                glBindVertexArray(lightCubeVAO);
                glDrawArrays(GL_TRIANGLES, 0, 36);

            }


        }

        if (sceneCheck >= 76 && sceneCheck <= 91)
        {
            lightingShader2.use();
            
            // per-frame time logic
            // --------------------
            float currentFrame = glfwGetTime();
            deltaTime = currentFrame - lastFrame;
            lastFrame = currentFrame;
            sceneCheck = glfwGetTime();

            // input
            // -----
            processInput(window);

            // render
            // ------


            if (backColor >= 1.0f && bc == 0)
                bc = 1;

            if (bc == 1)
                backColor = backColor - 0.03f;

            if (backColor <= 0.0f)
                bc = 0;

            if (bc == 0)
                backColor = backColor + 0.03f;



            glm::mat4 view = glm::mat4(1.0f); // make sure to initialize matrix to identity matrix first
            float radius = 10.0f;
            float camX = sin(glfwGetTime()) * radius;
            float camZ = cos(glfwGetTime()) * radius;

            glm::vec3 cameraPosition = glm::vec3(camX, 0.0f, camZ);

            lightingShader2.setMat4("view", view);

            //light box moving
            angle = glfwGetTime() * 55.0f; // rotating around itself
            lightA = 2.0f + cos(glfwGetTime()) * 5.0f;
            lightB = sin(glfwGetTime()) * 2.0f;
            lightC = cos(glfwGetTime());
            glm::vec3 lightPos(lightA, lightB, lightC);

            glClearColor(0.0f, 0.1f, 0.25f, 1.0f);
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

            // be sure to activate shader when setting uniforms/drawing objects
            lightingShader2.use();
            lightingShader2.setVec3("light.position", lightPos);
            lightingShader2.setVec3("viewPos", cameraPosition);

            // light properties
            glm::vec3 lightColor;
            lightColor.x = sin(glfwGetTime() * 2.0f);
            lightColor.y = sin(glfwGetTime() * 0.7f);
            lightColor.z = sin(glfwGetTime() * 1.3f);
            // light properties
            lightingShader2.setVec3("light.ambient", 0.2f, 0.2f, 0.2f);
            lightingShader2.setVec3("light.diffuse", 0.5f, 0.5f, 0.5f);
            lightingShader2.setVec3("light.specular", 1.2f, 1.2f, 1.2f);

            // material properties
            lightingShader2.setFloat("material.shininess", 5.0f);

            // view/projection transformations
            //glm::mat4 projection = glm::perspective(glm::radians(45.0f), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
            glm::mat4 projection = glm::perspective(glm::radians(45.0f), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
            lightingShader2.setMat4("projection", projection);
            view = glm::lookAt(glm::vec3(camX, 0.0f, camZ), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f));

            lightingShader.setMat4("view", view);

            // world transformation
            glm::mat4 model = glm::mat4(1.0f);
            model = glm::translate(model, glm::vec3(0.0f, 0.5f, 2.0f));
            model = glm::scale(model, glm::vec3(3.1f));
            model = glm::rotate(model, glm::radians((-angle) / 2 - 4.0f), glm::vec3(3.0f, 0.5f, 1.0f));

            lightingShader2.setMat4("model", model);

            // bind diffuse map
            glActiveTexture(GL_TEXTURE0);
            glBindTexture(GL_TEXTURE_2D, diffuseMap2);
            // bind specular map
            glActiveTexture(GL_TEXTURE1);
            glBindTexture(GL_TEXTURE_2D, specularMap);

            // render the cube
            glBindVertexArray(cubeVAO);
            glDrawArrays(GL_TRIANGLES, 0, 36);

            // color stuff for lamp object
            //float LampValue = lightColor.x;
            int fragmentColorLocation = glGetUniformLocation(lightCubeShader.ID, "LampColor");
            //int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");
            // also draw the lamp object
            lightCubeShader.use();
            glUniform3f(fragmentColorLocation, lightColor.x, lightColor.y, lightColor.z);

            //lightCubeShader.setVec3("LightColor", ambientColor);
            lightCubeShader.setMat4("projection", projection);
            lightCubeShader.setMat4("view", view);
            model = glm::mat4(1.0f);

            model = glm::translate(model, lightPos);
            model = glm::scale(model, glm::vec3(0.4f)); // a smaller cube

            model = glm::rotate(model, glm::radians(45.0f), glm::vec3(1.0f, 1.5f, 1.5f));
            lightCubeShader.setMat4("model", model);

            glBindVertexArray(lightCubeVAO);
            glDrawArrays(GL_TRIANGLES, 0, 36);

        }

        if (sceneCheck >= 91 && sceneCheck <= 110)
        {   
            
                glDeleteVertexArrays(1, &cubeVAO);
                glDeleteVertexArrays(1, &lightCubeVAO);
                glDeleteBuffers(1, &VBO);
                glDeleteBuffers(1, &VAOB);
                glDeleteBuffers(1, &EBOB);
                glDeleteBuffers(1, &VBOB);
                glfwTerminate();
                return 0;
            
        }
        
        
    
        

        glfwSwapBuffers(window);
        glfwPollEvents();
}

    glDeleteVertexArrays(1, &cubeVAO);
    glDeleteVertexArrays(1, &lightCubeVAO);
    glDeleteBuffers(1, &VBO);
    glDeleteBuffers(1, &VBOB);
    glDeleteBuffers(1, &VAOB);
    glDeleteBuffers(1, &EBOB);
    glfwTerminate();
    return 0;
}
// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
// ---------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow* window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);
}


// glfw: whenever the window size changed (by OS or user resize) this callback function executes
// ---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    // make sure the viewport matches the new window dimensions; note that width and 
    // height will be significantly larger than specified on retina displays.
    glViewport(0, 0, width, height);
}


// render line of text
// -------------------
void RenderText(Shader& shader, std::string text, float x, float y, float scale, glm::vec3 color)
{
    // activate corresponding render state	
    shader.use();
    glUniform3f(glGetUniformLocation(shader.ID, "textColor"), color.x, color.y, color.z);
    glActiveTexture(GL_TEXTURE0);
    glBindVertexArray(VAO);

    // iterate through all characters
    std::string::const_iterator c;
    for (c = text.begin(); c != text.end(); c++)
    {
        Character ch = Characters[*c];

        float xpos = x + ch.Bearing.x * scale;
        float ypos = y - (ch.Size.y - ch.Bearing.y) * scale;

        float w = ch.Size.x * scale;
        float h = ch.Size.y * scale;
        // update VBO for each character
        float vertices[6][4] = {
            { xpos,     ypos + h,   0.0f, 0.0f },
            { xpos,     ypos,       0.0f, 1.0f },
            { xpos + w, ypos,       1.0f, 1.0f },

            { xpos,     ypos + h,   0.0f, 0.0f },
            { xpos + w, ypos,       1.0f, 1.0f },
            { xpos + w, ypos + h,   1.0f, 0.0f }
        };
        // render glyph texture over quad
        glBindTexture(GL_TEXTURE_2D, ch.TextureID);
        // update content of VBO memory
        glBindBuffer(GL_ARRAY_BUFFER, VBO);
        glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices); // be sure to use glBufferSubData and not glBufferData

        glBindBuffer(GL_ARRAY_BUFFER, 0);
        // render quad
        glDrawArrays(GL_TRIANGLES, 0, 6);
        // now advance cursors for next glyph (note that advance is number of 1/64 pixels)
        x += (ch.Advance >> 6) * scale; // bitshift by 6 to get value in pixels (2^6 = 64 (divide amount of 1/64th pixels by 64 to get amount of pixels))
    }
    glBindVertexArray(0);
    glBindTexture(GL_TEXTURE_2D, 0);
}

// glfw: whenever the mouse moves, this callback is called
// -------------------------------------------------------
void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
    if (firstMouse)
    {
        lastX = xpos;
        lastY = ypos;
        firstMouse = false;
    }

    float xoffset = xpos - lastX;
    float yoffset = lastY - ypos; // reversed since y-coordinates go from bottom to top

    lastX = xpos;
    lastY = ypos;

    camera.ProcessMouseMovement(xoffset, yoffset);
}


// utility function for loading a 2D texture from file

unsigned int loadTexture(char const* path)
{
    unsigned int textureID;
    glGenTextures(1, &textureID);

    int width, height, nrComponents;
    unsigned char* data = stbi_load(path, &width, &height, &nrComponents, 0);
    if (data)
    {
        GLenum format;
        if (nrComponents == 1)
            format = GL_RED;
        else if (nrComponents == 3)
            format = GL_RGB;
        else if (nrComponents == 4)
            format = GL_RGBA;

        glBindTexture(GL_TEXTURE_2D, textureID);
        glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
        glGenerateMipmap(GL_TEXTURE_2D);

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

        stbi_image_free(data);
    }
    else
    {
        std::cout << "Texture failed to load at path: " << path << std::endl;
        stbi_image_free(data);
    }

    return textureID;
}