/*******************************************************************
** This code is part of Breakout.
**
** Breakout is free software: you can redistribute it and/or modify
** it under the terms of the CC BY 4.0 license as published by
** Creative Commons, either version 4 of the License, or (at your
** option) any later version.
******************************************************************/
#include "resource_manager.hpp"

#include <iostream>
#include <sstream>
#include <fstream>

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

namespace bookr {

// Instantiate static variables
std::map<std::string, Texture2D>    ResourceManager::Textures;
std::map<std::string, Shader>       ResourceManager::Shaders;
SpriteRenderer  *ResourceManager::sprite_renderer;
TextRenderer    *ResourceManager::ui_text_renderer;

Shader ResourceManager::LoadShader(const GLchar *vShaderFile, const GLchar *fShaderFile, const GLchar *gShaderFile, std::string name,
    bool fromFile, const int vertLength, const int fragLength)
{
    if (fromFile)
        Shaders[name] = loadShaderFromFile(vShaderFile, fShaderFile, gShaderFile);
    else
        Shaders[name] = loadShaderFromBuffer(vShaderFile, fShaderFile, gShaderFile, vertLength, fragLength);
    return Shaders[name];
}


Shader ResourceManager::GetShader(std::string name)
{
    return Shaders[name];
}

Texture2D ResourceManager::LoadTexture(const GLchar *file, GLboolean alpha, std::string name, bool fromFile, int size)
{
    if (fromFile)
        Textures[name] = loadTextureFromFile(file, alpha);
    else
        Textures[name] = loadTextureFromBuffer(file, alpha, size);
    return Textures[name];
}

Texture2D ResourceManager::GetTexture(std::string name)
{
    return Textures[name];
}

void ResourceManager::Clear()
{
    // (Properly) delete all shaders    
    for (auto iter : Shaders)
        glDeleteProgram(iter.second.ID);
    // (Properly) delete all textures
    for (auto iter : Textures)
        glDeleteTextures(1, &iter.second.ID);

    delete sprite_renderer;
    delete ui_text_renderer;
}

Shader ResourceManager::loadShaderFromFile(const GLchar *vShaderFile, const GLchar *fShaderFile, const GLchar *gShaderFile)
{
    // 1. Retrieve the vertex/fragment source code from filePath
    std::string vertexCode;
    std::string fragmentCode;
    std::string geometryCode;
    try
    {
        // Open files
        std::ifstream vertexShaderFile(vShaderFile);
        std::ifstream fragmentShaderFile(fShaderFile);
        std::stringstream vShaderStream, fShaderStream;
        // Read file's buffer contents into streams
        vShaderStream << vertexShaderFile.rdbuf();
        fShaderStream << fragmentShaderFile.rdbuf();
        // close file handlers
        vertexShaderFile.close();
        fragmentShaderFile.close();
        // Convert stream into string
        vertexCode = vShaderStream.str();
        fragmentCode = fShaderStream.str();
        // If geometry shader path is present, also load a geometry shader
        if (gShaderFile != nullptr)
        {
            std::ifstream geometryShaderFile(gShaderFile);
            std::stringstream gShaderStream;
            gShaderStream << geometryShaderFile.rdbuf();
            geometryShaderFile.close();
            geometryCode = gShaderStream.str();
        }
    }
    catch (std::exception e)
    {
        std::cout << "ERROR::SHADER: Failed to read shader files" << std::endl;
    }
    const GLchar *vShaderCode = vertexCode.c_str();
    const GLchar *fShaderCode = fragmentCode.c_str();
    const GLchar *gShaderCode = geometryCode.c_str();
    // 2. Now create shader object from source code
    Shader shader;
    shader.Compile(vShaderCode, fShaderCode, gShaderFile != nullptr ? gShaderCode : nullptr, vertexCode.size(), fragmentCode.size());
    return shader;
}

Shader ResourceManager::loadShaderFromBuffer(const GLchar *vShaderFile, const GLchar *fShaderFile, const GLchar *gShaderFile,
    const int vertLength, const int fragLength)
{
    Shader shader;
    shader.Compile(vShaderFile, fShaderFile, gShaderFile != nullptr ? gShaderFile : nullptr, vertLength, fragLength);
    return shader;
}

Texture2D ResourceManager::loadTextureFromFile(const GLchar *file, GLboolean alpha)
{
    // Create Texture object
    Texture2D texture;
    if (alpha)
    {
        texture.Internal_Format = GL_RGBA;
        texture.Image_Format = GL_RGBA;
    }
    // Load image
    int width, height, nrChannels;
    unsigned char *image = stbi_load(file, &width, &height, &nrChannels, 0);
    // Now generate texture
    texture.Generate(width, height, image);
    // And finally free image data
    stbi_image_free(image);
    return texture;
}

Texture2D ResourceManager::loadTextureFromBuffer(const GLchar *file, GLboolean alpha, int size)
{
    // Create Texture object
    Texture2D texture;
    if (alpha)
    {
        texture.Internal_Format = GL_RGBA;
        texture.Image_Format = GL_RGBA;
    }
    // Load image
    int width, height, nrChannels;
    stbi_uc* image = stbi_load_from_memory((const stbi_uc*)file, size, &width, &height, &nrChannels, 4);
    // Now generate texture
    texture.Generate(width, height, image);
    // And finally free image data
    stbi_image_free(image);
    return texture;
}

SpriteRenderer* ResourceManager::getSpriteRenderer()
{
    return sprite_renderer;
}

SpriteRenderer* ResourceManager::CreateSpriteRenderer(Shader shader)
{
    if (sprite_renderer == nullptr)
        sprite_renderer = new SpriteRenderer(shader);

    return sprite_renderer;
}

TextRenderer* ResourceManager::getTextRenderer()
{
    return ui_text_renderer;
}

bool ResourceManager::setTextRenderer(TextRenderer* default_text_renderer)
{
    if (ui_text_renderer == nullptr) {
        ui_text_renderer = default_text_renderer;
        return true;
    }
    return false;
}

TextRenderer* ResourceManager::CreateTextRenderer(int width, int height)
{
    if (ui_text_renderer == nullptr) {
        ui_text_renderer = new TextRenderer(width, height);
        ui_text_renderer->Load("data/fonts/NotoSans-Regular.ttf", 28);
    }

    return ui_text_renderer;
}

}
