/* Pathogen Warrior
 * Copyright 2004 Jetro Lauha - http://iki.fi/jetro/
 *
 * 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 2 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * $Id: gfx.cpp,v 1.7 2004/07/14 15:37:09 tonic Exp $
 * $Revision: 1.7 $
 */

#include <stdlib.h>
#include <stdio.h>
#include "SDL.h"
#include "SDL_opengl.h"
#include "SDL_image.h"
#include "main.h"
#include "gfx.h"


#pragma warning( disable : 4786 )

static inline int power_of_two(int input)
{
    int value = 1;

    while ( value < input ) {
        value <<= 1;
    }
    return value;
}


void clear(float r, float g, float b)
{
    glClearColor(r, g, b, 1);
    glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
}


void pushGLState()
{
    glMatrixMode(GL_TEXTURE);
    glPushMatrix();
    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glPushAttrib(GL_ALL_ATTRIB_BITS);
}


void popGLState()
{
    glPopAttrib();
    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glMatrixMode(GL_TEXTURE);
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);
}


void enter2DMode(INT viewportWidth, INT viewportHeight)
{
    SDL_Surface *screen;
    screen = SDL_GetVideoSurface();

    /* Note, there may be other things you need to change,
     * depending on how you have your OpenGL state set up.
     */
    glPushAttrib(GL_ENABLE_BIT);
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_CULL_FACE);
    glEnable(GL_TEXTURE_2D);

    /* This allows alpha blending of 2D textures with the scene */
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    glViewport(0, 0, viewportWidth, viewportHeight);

    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();

    glOrtho(0.0, (GLdouble)viewportWidth, (GLdouble)viewportHeight, 0.0, 0.0, 1.0);

    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();

//    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    glColor4f(1,1,1,1);
}


void leave2DMode()
{
    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();

    glMatrixMode(GL_PROJECTION);
    glPopMatrix();

    glPopAttrib();
}


GLuint SDL_GL_LoadTexture(SDL_Surface *surface, GLfloat *texcoord,
                          SDL_Rect *srcrect)
{
    GLuint texture;
    int w, h;
    SDL_Surface *image;
    SDL_Rect area;
    SDL_Rect srcr;
    Uint32 saved_flags;
    Uint8 saved_alpha;
    int srcw, srch;

    if (srcrect != NULL)
    {
        srcw = srcrect->w;
        srch = srcrect->h;
    }
    else
    {
        srcw = surface->w;
        srch = surface->h;
    }

    /* Use the surface width and height expanded to powers of 2 */
    w = power_of_two(srcw);
    h = power_of_two(srch);
    texcoord[0] = 0.0f;                 /* Min X */
    texcoord[1] = 0.0f;                 /* Min Y */
    texcoord[2] = (GLfloat)srcw / w;    /* Max X */
    texcoord[3] = (GLfloat)srch / h;    /* Max Y */

    image = SDL_CreateRGBSurface(
        SDL_HWSURFACE | SDL_SRCALPHA,
        w, h,
        32,
#if SDL_BYTEORDER == SDL_LIL_ENDIAN /* OpenGL RGBA masks */
        0x000000FF,
        0x0000FF00,
        0x00FF0000,
        0xFF000000
#else
        0xFF000000,
        0x00FF0000,
        0x0000FF00,
        0x000000FF
#endif
        );
    if ( image == NULL ) {
        return 0;
    }

    /* Save the alpha blending attributes */
    saved_flags = surface->flags&(SDL_SRCALPHA|SDL_RLEACCELOK);
    saved_alpha = surface->format->alpha;
    if ( (saved_flags & SDL_SRCALPHA) == SDL_SRCALPHA ) {
        SDL_SetAlpha(surface, 0, 0);
    }

    /* Copy the surface into the GL texture image */
    area.x = 0;
    area.y = 0;
    area.w = srcw;
    area.h = srch;
    if (srcrect != NULL)
        srcr = *srcrect;
    else
        srcr = area;
    SDL_BlitSurface(surface, &srcr, image, &area);

    /* Restore the alpha blending attributes */
    if ( (saved_flags & SDL_SRCALPHA) == SDL_SRCALPHA ) {
        SDL_SetAlpha(surface, saved_flags, saved_alpha);
    }

    /* Create an OpenGL texture for the image */
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D,
        0,
        GL_RGBA,
        w, h,
        0,
        GL_RGBA,
        GL_UNSIGNED_BYTE,
        image->pixels);
    SDL_FreeSurface(image); /* No longer needed */

    return texture;
}


IMAGE loadImage(char *filename)
{
    IMAGE res;
    SDL_Surface *image;

    image = IMG_Load(filename);
    if (image == NULL)
    {
        errorMessage("Error", "Cannot load image: %s", filename);
        exit(1);
    }
    res.w = image->w;
    res.h = image->h;
    res.glid = SDL_GL_LoadTexture(image, res.uv);
    SDL_FreeSurface(image);

    return res;
}


void freeImage(const IMAGE &image)
{
    glDeleteTextures(1, &image.glid);
}


void drawImage(const IMAGE &img, FLOAT x, FLOAT y,
               FLOAT r, FLOAT g, FLOAT b, FLOAT a)
{
    glColor4f(r, g, b, a);
    glBindTexture(GL_TEXTURE_2D, img.glid);
    glBegin(GL_TRIANGLE_STRIP);
    glTexCoord2f(img.uv[0], img.uv[1]); glVertex2f(x, y);
    glTexCoord2f(img.uv[2], img.uv[1]); glVertex2f(img.w + x, y);
    glTexCoord2f(img.uv[0], img.uv[3]); glVertex2f(x, img.h + y);
    glTexCoord2f(img.uv[2], img.uv[3]); glVertex2f(img.w + x, img.h + y);
    glEnd();
}


void drawImageScaled(const IMAGE &img,
                     FLOAT x, FLOAT y, FLOAT width, FLOAT height,
                     FLOAT r, FLOAT g, FLOAT b, FLOAT a)
{
    glPushMatrix();
    glColor4f(r, g, b, a);
    glBindTexture(GL_TEXTURE_2D, img.glid);
    glBegin(GL_TRIANGLE_STRIP);
    glTexCoord2f(img.uv[0], img.uv[1]); glVertex2f(x, y);
    glTexCoord2f(img.uv[2], img.uv[1]); glVertex2f(width + x, y);
    glTexCoord2f(img.uv[0], img.uv[3]); glVertex2f(x, height + y);
    glTexCoord2f(img.uv[2], img.uv[3]); glVertex2f(width + x, height + y);
    glEnd();
    glPopMatrix();
}


void drawImageRotatedCentered(const IMAGE &img, FLOAT x, FLOAT y, FLOAT angle,
                              FLOAT r, FLOAT g, FLOAT b, FLOAT a)
{
    glPushMatrix();
    glColor4f(r, g, b, a);
    glBindTexture(GL_TEXTURE_2D, img.glid);
    glTranslatef(x, y, 0);
    glRotatef(angle * 180 / (FLOAT)PI, 0, 0, 1);
    glBegin(GL_TRIANGLE_STRIP);
    glTexCoord2f(img.uv[0], img.uv[1]); glVertex2f(-img.w * 0.5f, -img.h * 0.5f);
    glTexCoord2f(img.uv[2], img.uv[1]); glVertex2f(img.w * 0.5f, -img.h * 0.5f);
    glTexCoord2f(img.uv[0], img.uv[3]); glVertex2f(-img.w * 0.5f, img.h * 0.5f);
    glTexCoord2f(img.uv[2], img.uv[3]); glVertex2f(img.w * 0.5f, img.h * 0.5f);
    glEnd();
    glPopMatrix();
}


void drawImageAdditive(const IMAGE &img, FLOAT x, FLOAT y, FLOAT angle,
                       FLOAT r, FLOAT g, FLOAT b, FLOAT a)
{
    glPushAttrib(GL_COLOR_BUFFER_BIT);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE);
    drawImage(img, x, y, r, g, b, a);
    glPopAttrib();
}



void drawImageRotatedCenteredScaled(const IMAGE &img,
                                    FLOAT x, FLOAT y, FLOAT angle, FLOAT scale,
                                    FLOAT r, FLOAT g,
                                    FLOAT b, FLOAT a)
{
    glPushMatrix();
    glColor4f(r, g, b, a);
    glBindTexture(GL_TEXTURE_2D, img.glid);
    glTranslatef(x, y, 0);
    glScalef(scale, scale, 1);
    glRotatef(angle * 180 / (FLOAT)PI, 0, 0, 1);
    glBegin(GL_TRIANGLE_STRIP);
    glTexCoord2f(img.uv[0], img.uv[1]); glVertex2f(-img.w * 0.5f, -img.h * 0.5f);
    glTexCoord2f(img.uv[2], img.uv[1]); glVertex2f(img.w * 0.5f, -img.h * 0.5f);
    glTexCoord2f(img.uv[0], img.uv[3]); glVertex2f(-img.w * 0.5f, img.h * 0.5f);
    glTexCoord2f(img.uv[2], img.uv[3]); glVertex2f(img.w * 0.5f, img.h * 0.5f);
    glEnd();
    glPopMatrix();
}


void drawImageRotatedCenteredScaledAdditive(const IMAGE &img,
                                            FLOAT x, FLOAT y, FLOAT angle, FLOAT scale,
                                            FLOAT r, FLOAT g,
                                            FLOAT b, FLOAT a)
{
    glPushAttrib(GL_COLOR_BUFFER_BIT);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE);
    drawImageRotatedCenteredScaled(img, x, y, angle, scale, r, g, b, a);
    glPopAttrib();
}


static FLOAT hls2rgbvalue(FLOAT n1, FLOAT n2, FLOAT hue)
{
    if (hue > 360)
        hue -= 360;
    else if (hue < 0)
        hue += 360;
    if (hue < 60)
        return n1 + (n2 - n1) * hue / 60;
    else if (hue < 180)
        return n2;
    else if (hue < 240)
        return n1 + (n2 - n1) * (240 - hue) / 60;
    else
        return n1;
}


// h in [0,360], l and s in [0,1]
void hls2rgb(FLOAT *r, FLOAT *g, FLOAT *b, FLOAT h, FLOAT l, FLOAT s)
{
    FLOAT m1, m2;
    const float threshold = 0.001f;

    if (l <= 0.5f)
        m2 = l * (1.0f + s);
    else
        m2 = l + s - l * s;
    m1 = 2.0f * l - m2;

    if (s < threshold) {
        *r = l;
        *g = l;
        *b = l;
    } else {
        *r = hls2rgbvalue(m1, m2, h + 120.0f);
        *g = hls2rgbvalue(m1, m2, h);
        *b = hls2rgbvalue(m1, m2, h - 120.0f);
    }
}
