#include "ShaderManager.h"
#include <iostream>
#include <SDL.h>

#ifdef __ANDROID__
#include <android/log.h>
#endif

namespace ZL {

    ShaderResource::ShaderResource(const std::string &vertexCode, const std::string &fragmentCode) {

        const int CONST_INFOLOG_LENGTH = 256;

        char infoLog[CONST_INFOLOG_LENGTH];
        int infoLogLength;
        char infoLog2[CONST_INFOLOG_LENGTH];
        int infoLogLength2;
        int vertexShaderCompiled;
        int fragmentShaderCompiled;
        int programLinked;

        GLuint vertexShader;
        GLuint fragmentShader;

        int vertexCodeLength = static_cast<int>(strlen(vertexCode.c_str()));
        int fragmentCodeLength = static_cast<int>(strlen(fragmentCode.c_str()));

        const char *vc = &vertexCode[0];
        const char *fc = &fragmentCode[0];

        vertexShader = glCreateShader(GL_VERTEX_SHADER);
        glShaderSource(vertexShader, 1, &(vc), &vertexCodeLength);

        fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
        glShaderSource(fragmentShader, 1, &(fc), &fragmentCodeLength);

        glCompileShader(vertexShader);
        glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &vertexShaderCompiled);
        glGetShaderInfoLog(vertexShader, CONST_INFOLOG_LENGTH, &infoLogLength, infoLog);

        glCompileShader(fragmentShader);
        glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &fragmentShaderCompiled);
        glGetShaderInfoLog(fragmentShader, CONST_INFOLOG_LENGTH, &infoLogLength2, infoLog2);


        if (!vertexShaderCompiled) {
#ifdef __ANDROID__
            __android_log_print(ANDROID_LOG_ERROR, "ShaderManager",
                                "Failed to compile vertex shader: %s", infoLog);
#endif
			std::cout << "Vertex shader compilation failed: " << infoLog << std::endl;
            throw std::runtime_error("Failed to compile vertex shader code!");
        }

        if (!fragmentShaderCompiled) {
#ifdef __ANDROID__
            __android_log_print(ANDROID_LOG_ERROR, "ShaderManager",
                                "Failed to compile fragment shader: %s", infoLog);
#endif
            std::cout << "Fragment shader compilation failed: " << infoLog << std::endl;

            throw std::runtime_error("Failed to compile fragment shader code!");
        }

        shaderProgram = glCreateProgram();

        glAttachShader(shaderProgram, vertexShader);
        glAttachShader(shaderProgram, fragmentShader);

        glLinkProgram(shaderProgram);

        glDeleteShader(vertexShader);
        glDeleteShader(fragmentShader);

        glGetProgramiv(shaderProgram, GL_LINK_STATUS, &programLinked);
        glGetProgramInfoLog(shaderProgram, CONST_INFOLOG_LENGTH, &infoLogLength, infoLog);

        if (!programLinked) {
            shaderProgram = 0;
#ifdef __ANDROID__
            __android_log_print(ANDROID_LOG_ERROR, "ShaderManager",
                                "Failed to link shader program: %s", infoLog);
#endif
            throw std::runtime_error("Failed to link shader program!");
        }

        //================= Parsing all uniforms ================

        int dummySize;            //Dummy
        int dummyLen;            //Dummy
        GLenum dummyEnum;

        int activeUniforms;

        const int CONST_UNIFORM_NAME_LENGTH = 256;
        char uniformName[CONST_UNIFORM_NAME_LENGTH];

        glGetProgramiv(shaderProgram, GL_ACTIVE_UNIFORMS, &activeUniforms);

        for (int i = 0; i < activeUniforms; i++) {
            glGetActiveUniform(shaderProgram, i, CONST_UNIFORM_NAME_LENGTH, &dummyLen, &dummySize,
                               &dummyEnum, uniformName);

            uniformList[uniformName] = glGetUniformLocation(shaderProgram, uniformName);
        }

        //================= Parsing all attributes ================
        int activeAttribs;

        const int CONST_ATTRIB_NAME_LENGTH = 256;
        char attribName[CONST_ATTRIB_NAME_LENGTH];

        glGetProgramiv(shaderProgram, GL_ACTIVE_ATTRIBUTES, &activeAttribs);

        for (int i = 0; i < activeAttribs; i++) {
            glGetActiveAttrib(shaderProgram, i, CONST_ATTRIB_NAME_LENGTH, &dummyLen, &dummySize,
                              &dummyEnum, attribName);
            attribList[attribName] = glGetAttribLocation(shaderProgram, attribName);
        }

#ifdef __ANDROID__
        __android_log_print(ANDROID_LOG_INFO, "ShaderManager",
                            "Shader created successfully, program ID: %u", shaderProgram);
#endif
    }

    ShaderResource::~ShaderResource() {
        if (shaderProgram != 0) {
            glDeleteProgram(shaderProgram);

            shaderProgram = 0;
        }
    }

    GLuint ShaderResource::getShaderProgram() {
        return shaderProgram;
    }


    void ShaderManager::AddShaderFromFiles(const std::string &shaderName,
                                           const std::string &vertexShaderFileName,
                                           const std::string &fragmentShaderFileName,
                                           const std::string &ZIPFileName) {
#ifdef __ANDROID__
        __android_log_print(ANDROID_LOG_INFO, "ShaderManager",
                            "Loading shader: %s", shaderName.c_str());
        __android_log_print(ANDROID_LOG_INFO, "ShaderManager",
                            "Vertex shader: %s", vertexShaderFileName.c_str());
        __android_log_print(ANDROID_LOG_INFO, "ShaderManager",
                            "Fragment shader: %s", fragmentShaderFileName.c_str());
#endif

        std::string vertexShader;
        std::string fragmentShader;

        if (!ZIPFileName.empty()) {

            std::vector<char> vertexShaderData;
            std::vector<char> fragmentShaderData;

            vertexShaderData = readFileFromZIP(vertexShaderFileName, ZIPFileName);
            fragmentShaderData = readFileFromZIP(fragmentShaderFileName, ZIPFileName);

            vertexShader = std::string(vertexShaderData.begin(), vertexShaderData.end());
            fragmentShader = std::string(fragmentShaderData.begin(), fragmentShaderData.end());

        } else {
            vertexShader = readTextFile(vertexShaderFileName);
            fragmentShader = readTextFile(fragmentShaderFileName);
        }

        shaderResourceMap[shaderName] = std::make_shared<ShaderResource>(vertexShader,
                                                                         fragmentShader);
    }

    void ShaderManager::PushShader(const std::string &shaderName) {
        if (shaderStack.size() >= CONST_MAX_SHADER_STACK_SIZE) {
            throw std::runtime_error("Shader stack overflow!");
        }

        if (shaderResourceMap.find(shaderName) == shaderResourceMap.end()) {
            std::cout << "Shader does not exist: " << shaderName << std::endl;
            throw std::runtime_error("Shader does not exist!");
        }

        if (shaderStack.size() > 0)
        {
            auto& prevShaderAttribList = shaderResourceMap[shaderStack.top()]->attribList;
            for (auto& attrib : prevShaderAttribList)
            {
                glDisableVertexAttribArray(attrib.second);
            }
        }

        shaderStack.push(shaderName);

		auto& shaderResource = shaderResourceMap[shaderName];

        glUseProgram(shaderResource->getShaderProgram());

        auto& shaderAttribList = shaderResource->attribList;
        for (auto& attrib : shaderAttribList)
        {
            glEnableVertexAttribArray(attrib.second);
        }
    }


    void ShaderManager::PopShader() {
        if (shaderStack.size() == 0) {
            throw std::runtime_error("Shader stack underflow!");
        }

        auto& prevShaderAttribList = shaderResourceMap[shaderStack.top()]->attribList;
        for (auto& attrib : prevShaderAttribList)
        {
            glDisableVertexAttribArray(attrib.second);
        }

        shaderStack.pop();

        if (shaderStack.size() == 0) {
            glUseProgram(0);
        } else {
            auto& shaderResource = shaderResourceMap[shaderStack.top()];

            glUseProgram(shaderResource->getShaderProgram());

            auto& shaderAttribList = shaderResource->attribList;
            for (auto& attrib : shaderAttribList)
            {
                glEnableVertexAttribArray(attrib.second);
            }
        }
    }

    void ShaderManager::EnableVertexAttribArrays()
    {
        if (shaderStack.size() != 0) {
            auto& shaderResource = shaderResourceMap[shaderStack.top()];

            auto& shaderAttribList = shaderResource->attribList;
            for (auto& attrib : shaderAttribList)
            {
                glEnableVertexAttribArray(attrib.second);
            }
        }
    }

    std::shared_ptr <ShaderResource> ShaderManager::GetCurrentShader() {
        if (shaderStack.size() == 0) {
            throw std::runtime_error("Shader stack underflow!");
        }


        return shaderResourceMap[shaderStack.top()];

    }


    ShaderSetter::ShaderSetter(ShaderManager &inShaderManager, const std::string &shaderName)
            : shaderManager(inShaderManager) {
        shaderManager.PushShader(shaderName);
    }

    ShaderSetter::~ShaderSetter() {
        shaderManager.PopShader();
    }

}