/*
  Copyright (C) 2010-2011  Charles Pence <charles@charlespence.net>
  Copyright (C) 1996-1997  Id Software, Inc.

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "pch.h"
#include "quakedef.h"
#include <SOIL.h>

// FIXME HACK
extern byte menuplyr_pixels[4096];

struct gltexture_t
{
    GLuint texnum;
    std::string identifier;
    unsigned width, height;
    int flags;
};

static GLint gl_max_texture_size;
static std::vector<gltexture_t> texture_list;

static byte gamma_table[256];


void R_InitTextures(void)
{
    glGetIntegerv(GL_MAX_TEXTURE_SIZE, &gl_max_texture_size);
    
    for (int i = 0 ; i < 256 ; i++)
    {
        float f = pow((i + 1) / 256.0f, 0.7);
        
        int r = (int)(f * 255.0f + 0.5f);
        if (r < 0) r = 0;
        if (r > 255) r = 255;
        
        gamma_table[i] = r;
    }
}

void R_ShutdownTextures(void)
{
    texture_list.clear();
}


static GLuint R_CheckTextureCache(const std::string &identifier, unsigned width, unsigned height, int flags)
{
    if (!identifier.length())
        return 0;

    for (std::vector<gltexture_t>::iterator iter = texture_list.begin() ;
         iter != texture_list.end() ; ++iter)
    {
        if ((*iter).identifier != identifier)
            continue;
        if ((*iter).width != width || (*iter).height != height || (*iter).flags != flags)
            continue;
        return (*iter).texnum;
    }

    return 0;
}

static void R_AddToTextureCache(const std::string &identifier, unsigned width, unsigned height, int flags, GLuint texnum)
{
    if (!identifier.length())
        return;

    gltexture_t tex;

    tex.identifier = identifier;
    tex.texnum = texnum;
    tex.width = width;
    tex.height = height;
    tex.flags = flags;

    texture_list.push_back(tex);
}



GLuint R_LoadTextureFromData(const std::string &identifier, byte *data, unsigned width, unsigned height, int flags)
{
    // If these are set, then we allocated a new bunch of data and should free
    // it when we're finished.  (The first is for palette expansion, the second
    // is for scaling.)
    byte *new_data = NULL;
    byte *scaled_data = NULL;
    unsigned scaled_width, scaled_height;
    GLuint ret;

    // Check for a cache hit
    ret = R_CheckTextureCache(identifier, width, height, flags);
    if (ret != 0)
        return ret;

    if (flags & TEXTURE_8BIT)
    {
        // Expand the 8-bit texture data to RGBA
        new_data = (byte *)malloc(width * height * 4);

        for (unsigned i = 0 ; i < width * height ; i++)
        {
            new_data[i*4 + 0] = vid.palette[data[i]][0];
            new_data[i*4 + 1] = vid.palette[data[i]][1];
            new_data[i*4 + 2] = vid.palette[data[i]][2];
            new_data[i*4 + 3] = vid.palette[data[i]][3];
        }

        data = new_data;
    }
    else if (flags & TEXTURE_RGB)
    {
        // Expand RGB to RGBA
        new_data = (byte *)malloc(width * height * 4);

        for (unsigned i = 0 ; i < width * height ; i++)
        {
            new_data[i*4 + 0] = data[i*3 + 0];
            new_data[i*4 + 1] = data[i*3 + 1];
            new_data[i*4 + 2] = data[i*3 + 2];
            new_data[i*4 + 3] = 255;
        }

        data = new_data;
    }
    else if (!(flags & TEXTURE_RGBA))
    {
        Con_Printf("R_LoadTextureFromData: Attempted to load data without specifying format!\n");
        return 0;
    }

    // Scale the texture if needed
    scaled_width = width;
    scaled_height = height;

    if (!QGL_ARB_texture_non_power_of_two)
    {
        for (scaled_width = 1 ; scaled_width < width ; scaled_width<<=1)
            ;
        for (scaled_height = 1 ; scaled_height < height ; scaled_height<<=1)
            ;
    }

    if (scaled_width > (unsigned)gl_max_texture_size)
        scaled_width = gl_max_texture_size;
    if (scaled_height > (unsigned)gl_max_texture_size)
        scaled_height = gl_max_texture_size;

    if (scaled_width != width || scaled_height != height)
    {
        scaled_data = (byte *)malloc(scaled_width * scaled_height * 4);
        gluScaleImage(GL_RGBA, width, height, GL_UNSIGNED_BYTE, data,
            scaled_width, scaled_height, GL_UNSIGNED_BYTE, scaled_data);
        data = scaled_data;
    }
    
    // Gamma-correct the textures, even external files
    for (size_t i = 0 ; i < scaled_width * scaled_height ; i++)
    {
        data[i*4 + 0] = gamma_table[data[i*4 + 0]];
        data[i*4 + 1] = gamma_table[data[i*4 + 1]];
        data[i*4 + 2] = gamma_table[data[i*4 + 2]];
    }

    // Generate a texture
    glGenTextures(1, &ret);
    glBindTexture(GL_TEXTURE_2D, ret);

    // Enable mipmap generation if requested
    if (QGL_SGIS_generate_mipmap)
    {
        if (flags & TEXTURE_MIPMAP)
            glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
        else
            glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_FALSE);

        // Upload the first miplevel
        glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, scaled_width, scaled_height, 0,
            GL_RGBA, GL_UNSIGNED_BYTE, data);
    }
    else
    {
        if (flags & TEXTURE_MIPMAP)
            gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGBA8, scaled_width, scaled_height,
                GL_RGBA, GL_UNSIGNED_BYTE, data);
        else
            glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, scaled_width, scaled_height, 0,
                GL_RGBA, GL_UNSIGNED_BYTE, data);
    }

    // Set the texture modes
    if (flags & TEXTURE_MIPMAP)
    {
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    }
    else
    {
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    }

    if (flags & TEXTURE_CLAMP)
    {
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    }
    else
    {
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    }

    // Free temporary storage
    if (new_data)
        free(new_data);
    if (scaled_data)
        free(scaled_data);

    // Add to the texture cache
    R_AddToTextureCache(identifier, width, height, flags, ret);

    return ret;
}


GLuint R_LoadTextureFromFile(const std::string &filename, int flags)
{
    byte *file, *pixels;
    int filesize;
    int width, height, channels;
    GLuint ret;

    // Load the file
    File fp;
    if (!fp.OpenRead(filename))
    {
        Con_Printf("R_LoadTextureFromFile: Cannot find file %s\n", filename.c_str());
        return 0;
    }
    
    file = fp.Read();
    if (!file)
    {
        Con_Printf("R_LoadTextureFromFile: Cannot read file %s\n", filename.c_str());
        fp.Close();
        return 0;
    }
    
    filesize = fp.Size();
    fp.Close();

    // Get the image using SOIL
    pixels = SOIL_load_image_from_memory(file, filesize, &width, &height,
        &channels, 4);
    if (!pixels)
    {
        Con_Printf("R_LoadTextureFromFile: Cannot load file %s (bad format)?\n", filename.c_str());
        return 0;
    }

    // Clear any type flags
    flags &= ~(TEXTURE_8BIT | TEXTURE_RGB | TEXTURE_RGBA);
    flags |= TEXTURE_RGBA;

    // Pass to our loading function
    ret = R_LoadTextureFromData(filename, pixels, width, height, flags);

    // Free the SOIL pixels and the file
    SOIL_free_image_data(pixels);
    delete[] file;

    return ret;
}


qboolean R_TextureFileExists(const std::string &filebase, std::string &extension)
{
    int i;
    static const char *extensions[] = { ".tga", ".png", ".jpg", ".bmp", NULL };
    std::string filename;

    for (i = 0 ; extensions[i] ; i++)
    {
        filename = filebase;
        filename += extensions[i];

        if (!File::Exists(filename))
            continue;

        extension = extensions[i];
        return true;
    }

    return false;
}



GLuint R_LoadModelTexture(const std::string &modelname, int skinnum, byte *data,
    unsigned width, unsigned height, int flags)
{
    std::string identifier;

    // Create a unique identifier
    identifier = modelname + "_" + Util::lexical_cast<std::string>(skinnum);

    // Fall back to the passed data
    return R_LoadTextureFromData(identifier, data, width, height, flags|TEXTURE_8BIT);
}

GLuint R_LoadBSPTexture(const std::string &mapname, const std::string &texname_original,
    byte *data, unsigned width, unsigned height, int flags)
{
    std::string identifier, texname, filename, extension;
    GLuint ret;

    // Create a unique identifier
    identifier = mapname + "_" + texname;

    // Copy and clean the texture name
    texname = texname_original;
    for (size_t i = 0 ; i < texname.length() ; i++)
    {
        if (texname[i] == '*')
            texname[i] = '#';
    }

    // First, check for "textures/<mapname>/<texname>"
    filename = "textures/" + mapname + "/" + texname;
    if (R_TextureFileExists(filename, extension))
    {
        filename += extension;
        ret = R_LoadTextureFromFile(filename, flags);

        if (ret != 0)
            return ret;
    }

    // Then, check "textures/<texname>"
    filename = "textures/" + texname;
    if (R_TextureFileExists(filename, extension))
    {
        filename += extension;
        ret = R_LoadTextureFromFile(filename, flags);

        if (ret != 0)
            return ret;
    }

    // Fall back to the passed data
    return R_LoadTextureFromData(identifier, data, width, height, flags|TEXTURE_8BIT);
}

GLuint R_LoadWadTexture(const std::string &name, unsigned *wad_width, unsigned *wad_height)
{
    qpic_t *pic;

    // Get the WAD lump
    pic = (qpic_t *)W_GetLumpName(name.c_str());
    if (!pic)
        return 0;

    // Set the width and height
    *wad_width = pic->width;
    *wad_height = pic->height;

    // Fall back to the WAD data
    return R_LoadTextureFromData(name, pic->data, pic->width, pic->height, TEXTURE_8BIT);
}

GLuint R_LoadLumpTexture(const std::string &path, unsigned *file_width, unsigned *file_height)
{
    qpic_t *pic;
    GLuint ret;

    // Get the LMP file from disk
    File fp;
    if (!fp.OpenRead(path))
        return 0;
    
    pic = (qpic_t *)fp.Read();
    if (!pic)
        return 0;
    
    SwapPic(pic);
    fp.Close();

    // Set the width and height
    *file_width = pic->width;
    *file_height = pic->height;

    // FIXME HACK HACK HACK --- we need to keep the bytes for
    // the translatable player picture just for the menu
    // configuration dialog
    if (path == "gfx/menuplyr.lmp")
        memcpy (menuplyr_pixels, pic->data, pic->width*pic->height);

    // Fall back to the LMP data
    ret = R_LoadTextureFromData(path, pic->data, pic->width, pic->height, TEXTURE_8BIT);

    // Free the pic file
    delete[] pic;

    return ret;
}


