// #############################################################################
// # main.cpp
// #############################################################################
// # Written by Colton Staiduhar
// # Date Created:       02/03/2025
// # Last Modification:  02/05/2025
// #############################################################################
// # Main Entry point for Cmake project, declare by the int main() function
// #############################################################################
// #
// # This is a simple demonstration of GLUT functioning using STB_Image.H for
// # loading images and MiniAudio.h for playing audio. The repositories for
// # these libraries can be found below.
// #
// # Miniaudio
// # https://github.com/mackron/miniaudio
// #
// # STB_Image.h
// # https://github.com/arizotaz/cmake_stb_image
// #
// #############################################################################
// #
// # For GLUT/OpenGL, the operating system should have GLUT in some form already
// # installed.  However, freeglut exists as the modern implementation of GLUT.
// #
// # GLUT
// # You're on your own for this, but here are some helpful implementation docs
// #     MacOS: https://developer.apple.com/library/archive/documentation/GraphicsImaging/Conceptual/OpenGL-MacProgGuide/opengl_drawing/opengl_drawing.html
// #     Windows & Linux: https://www.opengl.org/resources/libraries/glut/glut_downloads.php
// #
// # FreeGLUT:
// #	https://freeglut.sourceforge.net/docs/api.php
// #
// #############################################################################
// #
// # This file will setup much of GLUT.  Getting some output to the screen
// # The following is executed:
// #     Create a windows with GLUT's API
// #     Create a display update timer
// #     Create Keyboard Interrupts
// #     Load textures the included Texture.H file
// #     Setup the audio engine from MiniAudio
// #     Declare the Main Loop funcitons
// #     Draw a sprite to the screen
// #     Texture said sprite with an image
// #     Rotate, move, and scale the sprite
// #     Apply translations via user keyboard input
// #
// #############################################################################

// Libraries
#if defined(use_freeglut)
#include <glad/glad.h>
#define FREEGLUT_STATIC
#include <GL/freeglut.h>
#else
#include <GLUT/glut.h>
#endif
#include <miniaudio.h>

// STD
#include <iostream>
#include <math.h>
#include <stdio.h>

// Custom includes
#include <texture.h>

/**
 * Function Prototypes
 */

// Basic Glut
void ProcessKeys(unsigned char, int, int);
void ProcessSpecialKeys(int, int, int);
void MainLoop();

// Extended Functionality
void UpdateViewPort();
void DrawSprite();
void LoadUserTextures();

// Miniaudio Engine Declaration
void CreateAudioEngine();
ma_engine engine; // Miniaudio engine

// Demo Program Variables
float rotX, rotY, rotZ;
float moveX, moveY;
float scale = 1;
bool rotPressed, movePressed, scalePressed;
unsigned int texID;
ma_sound musicSound; // Miniaudio sound object
int lastWinW = 0, lastWinH = 0;

bool running = true;
void CloseCallBack()
{
    running = false;
}

/**
 * Main Loop Timer
 * Wait 16.7ms then generate next frame (~60FPS)
 */
void GlutLoopTimer(int v)
{
    // Runs the function specified in glutDisplayFunc
    glutPostRedisplay();

    // Call the timer again in 16.7 seconds ~ 60 times per second
    glutTimerFunc(16.7, GlutLoopTimer, v); // Creates a frame delay that is counted in miliseconds
}

/**
 * Main entry point of the application
 */
int main(int argc, char** argv)
{
    std::cout << "Starting";
    fflush(stdout);

    int winW = 1280;
    int winH = 720;

    // Initialize GLUT
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGB); // RGB mode

    // Setup then create the window
    glutInitWindowSize(winW, winH); // window size
    glutInitWindowPosition(0, 0);
    glutCreateWindow("Transform Example - Using Native Glut");

// This line only needs to run when glad is in use
#if defined(use_freeglut)
	glutSetWindowTitle("Transform Example - using FreeGLUT");
    if (!gladLoadGL()) { }
#endif

    // Clear screen
    glClearColor(0.0, 0.0, 0.0, 1.0); // clear the window screen

    // Sets up the viewport
    UpdateViewPort();

    // Calls the texture load method
    LoadUserTextures();

    // Setup Audio Engine
    CreateAudioEngine();

    // Play the music
    ma_sound_start(&musicSound);

    // Deplare the Main Loop Function
    // (Will be executed when glutPostRedisplay() is called in the timer)
    glutDisplayFunc(MainLoop);

    // Declare Keyboard Interupts
    glutKeyboardFunc(ProcessKeys);

    // Declare Special Keys Keyboard Interrupt
    glutSpecialFunc(ProcessSpecialKeys);

#ifndef use_freeglut
    // Setup a timer to run the main loop at an interval
    // (This call will call the GlutLoopTimer() function immediately)
    glutTimerFunc(0, GlutLoopTimer, 0);
#endif

    std::cout << "Started" << std::endl;
    fflush(stdout);
    // Start the Glut Loop (Calls the funtions above repeatedly)

#if defined(use_freeglut)
	// FreeGlut allows for manual updating of the main loop
	// So we can call the main loop in a while loop and tell the loop when to stop

	// Tell FreeGlut to do nothing on window X press
    glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_CONTINUE_EXECUTION);

	// Run callback when window X is pressed
    glutCloseFunc(CloseCallBack);

	// Our loop, calls the funtion in glutDisplayFunc repeatedly until running = false 
    while (running)
        glutMainLoopEvent();

#else
    // Start the Glut Loop (Calls the funtions above repeatedly)
    glutMainLoop();
#endif

    std::cout << "exited" << std::endl;
    fflush(stdout);
    return 0;
}

/**
 * Function to update the viewport to the size of the window
 */
void UpdateViewPort()
{
    // Gets the window size
    int winW = glutGet(GLUT_WINDOW_WIDTH);
    int winH = glutGet(GLUT_WINDOW_HEIGHT);

    // Sets up OpenGL Matrix Mode
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    // Create projection type (Orthographic with size of window)
    glOrtho(-winW / 2, winW / 2, winH / 2, -winH / 2, -1000.0f, 1000.0f); // Clipping plane is set to 1000 behind camera and 1000 infront, this works because ortho is cool

    // Set last size to the new size
    lastWinW = winW;
    lastWinH = winH;
}

/**
 * The function set as the Keyboard Interrupt of GLUT
 * This function will be called whenever a keystrke is pressed by the user
 * This function suffers from key repeat
 */
void ProcessSpecialKeys(int key, int x, int y)
{
    if (key == GLUT_KEY_LEFT) {
        moveX = -10;
        movePressed = true;
    }

    if (key == GLUT_KEY_RIGHT) {
        moveX = 10;
        movePressed = true;
    }

    if (key == GLUT_KEY_UP) {
        moveY = 10;
        movePressed = true;
    }

    if (key == GLUT_KEY_DOWN) {
        moveY = -10;
        movePressed = true;
    }
}

/**
 * Processes Keys as characters instead of keys
 * IE when a is pressed, "key" is set to "a" instead of 65
 */
void ProcessKeys(unsigned char key, int x, int y)
{
    switch (key) {
    case 'q':
        rotX += 1;
        break;
    case 'w':
        rotY += 1;
        break;
    case 'e':
        rotZ += 1;
        break;
    case 'a':
        rotX -= 1;
        break;
    case 's':
        rotY -= 1;
        break;
    case 'd':
        rotZ -= 1;
        break;
    case 'p':
        scale += .1;
        break;
    case 'l':
        scale -= .1;
        break;
    case 27: // escape
        exit(0);
    }

    rotPressed = true;
    scalePressed = true;
}

/**
 * Creates the miniaudio engine
 * (Bare minimum to play a sound)
 */
void CreateAudioEngine()
{
    ma_result result = ma_engine_init(NULL, &engine);
    if (result != MA_SUCCESS) {
        std::cout << "Failed to setup the miniaudio engine\n";
        fflush(stdout);
    }

    // Play Test Sound
    ma_sound_init_from_file(
        &engine,
        RESOURCES_PATH "audio/CrazyLaPaint.mp3",
        0,
        NULL,
        NULL,
        &musicSound);
}

/**
 * This is the main loop functions that was declared during window creation
 */
void MainLoop()
{

    // Get the window size
    int winW = glutGet(GLUT_WINDOW_WIDTH);
    int winH = glutGet(GLUT_WINDOW_HEIGHT);

    // If Changed, update the view port
    if (winW != lastWinW || winH != lastWinH) {
        UpdateViewPort();
    }

    // Clear the screen
    glClear(GL_COLOR_BUFFER_BIT);

    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

    // If scale oneshot is set, scale the image
    if (scalePressed) {
        glScalef(scale, scale, scale);
        scalePressed = false;
        scale = 1;
    }

    // If rotation oneshot is set, rotate the image
    if (rotPressed) {
        glRotatef((GLfloat)rotX, 1.0, 0.0, 0.0);
        glRotatef((GLfloat)rotY, 0.0, 1.0, 0.0);
        glRotatef((GLfloat)rotZ, 0.0, 0.0, 1.0);
        rotPressed = false;
        rotX = rotY = rotZ = 0;
    }
    // If move oneshot is set, move the image and set move back to 0
    if (movePressed) {
        glTranslatef(moveX, moveY, 0);
        movePressed = false;
        moveX = moveY = 0;
    }

    // Draws the image
    DrawSprite();

    glFlush();

    // Swap buffers to display the new frame
    glutSwapBuffers();

	// With freeglut, we can trigger screen update right after we are done
#if defined(use_freeglut)
    glutPostRedisplay();
#endif
}

/**
 * Our function to draw the image
 */
void DrawSprite()
{
    // Set texture enviorment
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

    // Enable textures so OpenGL will apply the textures to the created object
    glEnable(GL_TEXTURE_2D);

    // Bind our texture
    glBindTexture(GL_TEXTURE_2D, texID); // Which texture

    // Create a polygon and apply textures to it
    glBegin(GL_POLYGON);
    {

        glTexCoord2f(0.0, 0.0);
        glVertex3f(-50, -50, 0);
        glTexCoord2f(1.0, 0.0);
        glVertex3f(50, -50, 0);
        glTexCoord2f(1.0, 1.0);
        glVertex3f(50, 50, 0);
        glTexCoord2f(0.0, 1.0);
        glVertex3f(-50, 50, 0);
    }
    glEnd();

    // Disable textures so you dont continue adding textures to other objects
    glDisable(GL_TEXTURE_2D); // Turn texturing off
}

/**
 * Just loads a bunch of textures
 */
void LoadUserTextures()
{
    /*
    We add RESOURCES_PATH to the front of the filename
    RESOURCES_PATH is the absolute path of the resources folder on the computer
    when the application is build for production it turns into a relative path
    */
    texID = LoadTexture(RESOURCES_PATH "container.jpg");
}