/*********************************************************************************
**
**      @copyright (c) 2025, Cortx Technologies, Montreal, QC.
**      All Rights Reserved.
**
**      PROPRIETARY SOFTWARE.
**      This software is proprietary and confidential to Cortx Technologies.
**      Unauthorized use, reproduction, or distribution in whole or in part
**      is scrictly prohibited.
**
**********************************************************************************
**
**  @file       shaderUtil.cpp
**  @brief      Source file for the shader functions.
**  @author     arturodlrios
**  @date       Created on 2024/03/29
**
**  @details    Handles shader compilation, linking, and uniform variable
**              management for OpenGL applications with comprehensive
**              error reporting.
**
**********************************************************************************/

/**********************************  Includes  ***********************************/
#include "shaderUtil.h"
#include <vector>

/**********************************  Defines  ************************************/
#define MAX_SHADER_SIZE (1024 * 1024)    /* 1MB limit for shader files */
#define FILE_READ_BUFFER_SIZE (4096)     /* 4KB buffer for file reading */

/*********************************  Namespaces  **********************************/
namespace nshaders
{
    CortxCode_t Shader::load(const std::string &vertexShaderFile,
                             const std::string &fragmentShaderFile)
    {
        /* Load vertex shader source code from file */
        std::cout << "Loading vertex shader: " << vertexShaderFile << std::endl;
        std::ifstream vertexShaderFileStream(vertexShaderFile);
        if (!vertexShaderFileStream.is_open())
        {
            std::cerr << "Failed to open vertex shader file: " << vertexShaderFile << std::endl;
            return Error;
        }

        /* Read vertex shader file content with size limit for safety
         * Prevent memory exhaustion from unexpectedly large files
         * Instead of reading entire file at once, read in chunks with size validation */
        std::string vertexShaderSource;
        vertexShaderSource.reserve(MAX_SHADER_SIZE);

        char buffer[FILE_READ_BUFFER_SIZE];
        size_t totalSize = 0;

        /* Read file in chunks to prevent memory exhaustion */
        while (vertexShaderFileStream.read(buffer, FILE_READ_BUFFER_SIZE) ||
               vertexShaderFileStream.gcount() > 0)
        {
            size_t bytesRead = static_cast<size_t>(vertexShaderFileStream.gcount());

            /* Check if adding this chunk would exceed maximum allowed size */
            if (totalSize + bytesRead > MAX_SHADER_SIZE)
            {
                std::cerr << "Vertex shader file too large (max 1MB)" << std::endl;
                return Error;
            }

            /* Append chunk to source string and update total size */
            vertexShaderSource.append(buffer, bytesRead);
            totalSize += bytesRead;
        }
        std::cout << "Vertex shader content length: " << vertexShaderSource.length() << std::endl;

        /* Load fragment shader source code from file */
        std::cout << "Loading fragment shader: " << fragmentShaderFile << std::endl;
        std::ifstream fragmentShaderFileStream(fragmentShaderFile);
        if (!fragmentShaderFileStream.is_open())
        {
            std::cerr << "Failed to open fragment shader file: " << fragmentShaderFile << std::endl;
            return Error;
        }

        /* Read fragment shader file content with size limit for safety
         * Same protection as vertex shader - prevent memory exhaustion from large files
         * Read in chunks with size validation to prevent memory exhaustion */
        std::string fragmentShaderSource;
        fragmentShaderSource.reserve(MAX_SHADER_SIZE);

        totalSize = 0;

        /* Read file in chunks to prevent memory exhaustion */
        while (fragmentShaderFileStream.read(buffer, FILE_READ_BUFFER_SIZE) || fragmentShaderFileStream.gcount() > 0)
        {
            size_t bytesRead = static_cast<size_t>(fragmentShaderFileStream.gcount());

            /* Check if adding this chunk would exceed maximum allowed size */
            if (totalSize + bytesRead > MAX_SHADER_SIZE)
            {
                std::cerr << "Fragment shader file too large (max 1MB)" << std::endl;
                return Error;
            }

            /* Append chunk to source string and update total size */
            fragmentShaderSource.append(buffer, bytesRead);
            totalSize += bytesRead;
        }
        std::cout << "Fragment shader content length: " << fragmentShaderSource.length() << std::endl;

        /* Create OpenGL shader program object */
        glProgramId_m = glCreateProgram();

        if (glProgramId_m == 0)
        {
            std::cerr << "Failed to create OpenGL shader program" << std::endl;
            return Error;
        }

        /* Compile individual shaders */
        uint32_t vertexShader = compileShader(GL_VERTEX_SHADER, vertexShaderSource);
        uint32_t fragmentShader = compileShader(GL_FRAGMENT_SHADER, fragmentShaderSource);

        if (vertexShader == 0 || fragmentShader == 0)
        {
            std::cerr << "Failed to compile shaders" << std::endl;
            /* Clean up any successfully created shaders */
            if (vertexShader != 0) glDeleteShader(vertexShader);
            if (fragmentShader != 0) glDeleteShader(fragmentShader);
            glDeleteProgram(glProgramId_m);
            glProgramId_m = 0;
            return Error;
        }

        /* Attach compiled shaders to program */
        glAttachShader(glProgramId_m, vertexShader);
        glAttachShader(glProgramId_m, fragmentShader);

        /* Link shaders into complete program */
        glLinkProgram(glProgramId_m);

        /* Check if linking was successful */
        GLint linkStatus;
        glGetProgramiv(glProgramId_m, GL_LINK_STATUS, &linkStatus);

        if (linkStatus == GL_FALSE)
        {
            /* Get link error log length */
            GLint logLength;
            glGetProgramiv(glProgramId_m, GL_INFO_LOG_LENGTH, &logLength);

            if (logLength > 0)
            {
                /* Use RAII for automatic memory management */
                std::vector<GLchar> infoLog(static_cast<size_t>(logLength) + 1);
                glGetProgramInfoLog(glProgramId_m, logLength, &logLength, infoLog.data());

                std::cerr << "Shader linking failed: " << infoLog.data() << std::endl;
            }

            /* Clean up failed program */
            glDeleteProgram(glProgramId_m);
            glProgramId_m = 0;
            return Error;
        }

        /* Validate program for current OpenGL state */
        glValidateProgram(glProgramId_m);

        /* Check if program validation was successful */
        GLint validateStatus;
        glGetProgramiv(glProgramId_m, GL_VALIDATE_STATUS, &validateStatus);

        if (validateStatus == GL_FALSE)
        {
            /* Get validation error log length */
            GLint logLength;
            glGetProgramiv(glProgramId_m, GL_INFO_LOG_LENGTH, &logLength);

            if (logLength > 0)
            {
                /* Use RAII for automatic memory management */
                std::vector<GLchar> infoLog(static_cast<size_t>(logLength) + 1);
                glGetProgramInfoLog(glProgramId_m, logLength, &logLength, infoLog.data());

                std::cerr << "Program validation failed: " << infoLog.data() << std::endl;
            }

            /* Clean up failed program and shaders */
            glDeleteProgram(glProgramId_m);
            glProgramId_m = 0;
            glDeleteShader(vertexShader);
            glDeleteShader(fragmentShader);
            return Error;
        }

        /* Clean up individual shader objects (no longer needed after linking) */
        glDeleteShader(vertexShader);
        glDeleteShader(fragmentShader);

        return Success;
    }
    /*****************************************************************************/

    uint32_t Shader::compileShader(uint32_t shaderType,
                                   const std::string &shaderSource)
    {
        /* Create OpenGL shader object */
        uint32_t shaderId = glCreateShader(shaderType);

        if (shaderId == 0)
        {
            std::cerr << "Failed to create OpenGL shader object" << std::endl;
            return 0;
        }

        /* Set shader source code */
        const char *source = shaderSource.c_str();
        glShaderSource(shaderId, 1, &source, nullptr);

        /* Compile the shader */
        glCompileShader(shaderId);

        /* Check compilation status */
        GLint result;
        glGetShaderiv(shaderId, GL_COMPILE_STATUS, &result);

        /* Handle compilation errors */
        if (result == GL_FALSE)
        {
            /* Get error log length */
            int32_t logLength;
            glGetShaderiv(shaderId, GL_INFO_LOG_LENGTH, &logLength);

            /* RAII (Resource Acquisition Is Initialization): A C++ technique where
             * resource management is tied to object lifetime. std::vector acquires
             * memory in constructor and automatically releases it in destructor
             * when object goes out of scope.
             *
             * Why not use new/delete? Manual memory management is error-prone:
             * - Memory leaks if exceptions occur before delete
             * - Double deletion crashes the program
             * - Dangling pointers from using freed memory
             * - Exception-unsafe: cleanup code may never execute
             * RAII eliminates these risks by automatic cleanup */
            if (logLength > 0)
            {
                std::vector<GLchar> infoLog(static_cast<size_t>(logLength) + 1);
                glGetShaderInfoLog(shaderId, logLength, &logLength, infoLog.data());

                /* Print compilation error details */
                std::cerr << "Compile error in shader: " << infoLog.data() << std::endl;
            }

            /* Clean up failed shader and return 0 to indicate failure */
            glDeleteShader(shaderId);
            return 0;
        }

        /* Check for OpenGL errors after compilation */
        GLenum err;
        while ((err = glGetError()) != GL_NO_ERROR)
        {
            std::cerr << "OpenGL error after shader compilation: 0x" << std::hex << err << std::endl;
        }

        return shaderId;
    }
    /*****************************************************************************/

    bool Shader::validateProgramLoaded(const std::string &functionName)
    {
        if (glProgramId_m == 0)
        {
            std::cerr << "Error: " << functionName << " - shader program not loaded" << std::endl;
            return false;
        }
        return true;
    }
    /*****************************************************************************/

    bool Shader::validateUniformLocation(GLint location, const std::string &uniformName)
    {
        if (location == -1)
        {
            std::cerr << "Warning: Uniform '" << uniformName << "' not found in shader program" << std::endl;
            return false;
        }
        return true;
    }
    /*****************************************************************************/

    void Shader::use()
    {
        /* Validate program exists before using it */
        if (!validateProgramLoaded("use"))
        {
            return;
        }

        /* Activate this shader program for subsequent rendering operations */
        glUseProgram(glProgramId_m);
    }
    /*****************************************************************************/

    void Shader::unload()
    {
        /* Validate program exists before deleting it */
        if (glProgramId_m != 0)
        {
            /* Delete the OpenGL shader program and free associated GPU resources */
            glDeleteProgram(glProgramId_m);

            /* Reset program ID to prevent double deletion */
            glProgramId_m = 0;
        }
    }
    /*****************************************************************************/

    void Shader::setFloatArray(std::string const &name, uint32_t const &count,
                                          float const *value)
    {
        /* Validate program is loaded before setting uniforms */
        if (!validateProgramLoaded("setFloatArray"))
        {
            return;
        }

        /* Validate input parameters */
        if (value == nullptr)
        {
            std::cerr << "Error: Cannot set uniform '" << name << "' - null pointer provided" << std::endl;
            return;
        }

        if (count == 0)
        {
            std::cerr << "Warning: Setting uniform '" << name << "' with zero count" << std::endl;
            return;
        }

        /* Get the location of the uniform variable in the shader program */
        GLint location = glGetUniformLocation(getProgramId(), name.c_str());

        /* Validate uniform location exists */
        if (!validateUniformLocation(location, name))
        {
            return;
        }

        /* Set the float array uniform with the provided values */
        glUniform1fv(location, count, value);
    }
    /*****************************************************************************/

    void Shader::setMat4(const glm::mat4 &mat4, const std::string &name)
    {
        /* Validate program is loaded before setting uniforms */
        if (!validateProgramLoaded("setMat4"))
        {
            return;
        }

        /* Get the location of the uniform variable in the shader program */
        GLint location = glGetUniformLocation(getProgramId(), name.c_str());

        /* Validate uniform location exists */
        if (!validateUniformLocation(location, name))
        {
            return;
        }

        /* Set the 4x4 matrix uniform (GL_FALSE = no transpose, column-major order) */
        glUniformMatrix4fv(location, 1, GL_FALSE, glm::value_ptr(mat4));
    }
    /*****************************************************************************/

    void Shader::setVec3(const glm::vec3 &vec3, const std::string &name)
    {
        /* Validate program is loaded before setting uniforms */
        if (!validateProgramLoaded("setVec3"))
        {
            return;
        }

        /* Get the location of the uniform variable in the shader program */
        GLint location = glGetUniformLocation(getProgramId(), name.c_str());

        /* Validate uniform location exists */
        if (!validateUniformLocation(location, name))
        {
            return;
        }

        /* Set the 3D vector uniform using glProgramUniform (program-specific, no need to bind) */
        glUniform3fv(location, 1, glm::value_ptr(vec3));
    }
    /*****************************************************************************/

    void Shader::setBool(bool value, const std::string &name)
    {
        /* Validate program is loaded before setting uniforms */
        if (!validateProgramLoaded("setBool"))
        {
            return;
        }

        /* Get the location of the uniform variable in the shader program */
        GLint location = glGetUniformLocation(getProgramId(), name.c_str());

        /* Validate uniform location exists */
        if (!validateUniformLocation(location, name))
        {
            return;
        }

        /* Set the boolean uniform (converted to integer: 0 = false, 1 = true) */
        glUniform1i(location, (int)value);
    }
    /*****************************************************************************/

    void Shader::setInt(int value, const std::string &name)
    {
        /* Validate program is loaded before setting uniforms */
        if (!validateProgramLoaded("setInt"))
        {
            return;
        }

        /* Get the location of the uniform variable in the shader program */
        GLint location = glGetUniformLocation(getProgramId(), name.c_str());

        /* Validate uniform location exists */
        if (!validateUniformLocation(location, name))
        {
            return;
        }

        /* Set the integer uniform with the provided value */
        glUniform1i(location, value);
    }
    /*****************************************************************************/
} /* namespace nshaders */
/******************************  End of File  ************************************/
