/*
  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"

// Thanks to MH for this sky-sphere code!

#define SKYGRID_SIZE 16
#define SKYGRID_SIZE_PLUS_1 (SKYGRID_SIZE + 1)
#define SKYGRID_RECIP (1.0f / (SKYGRID_SIZE))
#define SKYSPHERE_NUMVERTS (SKYGRID_SIZE_PLUS_1 * SKYGRID_SIZE_PLUS_1)
#define SKYSPHERE_NUMTRIS (SKYGRID_SIZE * SKYGRID_SIZE * 2)
#define SKYSPHERE_NUMINDEXES (SKYGRID_SIZE * SKYGRID_SIZE * 6)

extern void R_ClipSky (msurface_t *s);

static qboolean r_drawqsky = false;
static qboolean r_skyinitialized = false;

typedef struct skyverts_s
{
    float v[3];
    float st[2];
} skyverts_t;


static skyverts_t r_skyverts[SKYSPHERE_NUMVERTS];
static unsigned short r_skyindexes[SKYSPHERE_NUMINDEXES];
static texture_t *r_skytexture = NULL;

static void R_InitSkySphere (void)
{
    int i, j;
    float a, b, x, ax, ay, v[3], length;
    float dx, dy, dz;
    skyverts_t *ssv = r_skyverts;
    unsigned short *e = r_skyindexes;

    if (r_skyinitialized) return;

    dx = 16;
    dy = 16;
    dz = 16 / 3;

    i = SKYSPHERE_NUMVERTS;
    j = SKYSPHERE_NUMINDEXES;

    for (j = 0; j <= SKYGRID_SIZE; j++)
    {
        a = j * SKYGRID_RECIP;
        ax = cos (a * M_PI * 2);
        ay = -sin (a * M_PI * 2);

        for (i = 0; i <= SKYGRID_SIZE; i++)
        {
            b = i * SKYGRID_RECIP;
            x = cos ((b + 0.5) * M_PI);

            v[0] = ax * x * dx;
            v[1] = ay * x * dy;
            v[2] = -sin ((b + 0.5) * M_PI) * dz;

            // Same calculation as classic Q1 sky but projected onto an actual
            // physical sphere (rather than on flat surfs) and calced as if
            // from an origin of [0,0,0] to prevent the heaving and buckling
            // effect.
            length = 3.0f / sqrt (v[0] * v[0] + v[1] * v[1] + (v[2] * v[2] * 9));

            ssv->st[0] = v[0] * length;
            ssv->st[1] = v[1] * length;
            ssv->v[0] = v[0];
            ssv->v[1] = v[1];
            ssv->v[2] = v[2];

            ssv++;
        }
    }

    for (j = 0; j < SKYGRID_SIZE; j++)
    {
        for (i = 0; i < SKYGRID_SIZE; i++)
        {
            *e++ =  j * SKYGRID_SIZE_PLUS_1 + i;
            *e++ =  j * SKYGRID_SIZE_PLUS_1 + i + 1;
            *e++ = (j + 1) * SKYGRID_SIZE_PLUS_1 + i;

            *e++ =  j * SKYGRID_SIZE_PLUS_1 + i + 1;
            *e++ = (j + 1) * SKYGRID_SIZE_PLUS_1 + i + 1;
            *e++ = (j + 1) * SKYGRID_SIZE_PLUS_1 + i;
        }
    }

    r_skyinitialized = true;
}

/*
=============
R_LoadQSky

A sky texture is 256*128, with the right side being a masked overlay
==============
*/
void R_LoadQSky (texture_t *mt)
{
    int            i, j, p;
    std::string solid_fn, solid_ext, alpha_fn, alpha_ext;
    byte        *src, *rgba;
    byte        trans[128*128][4];
    byte        transpix[4];
    int            r, g, b;

    // Create the vertex buffers
    R_InitSkySphere();

    // Look for external files first
    solid_fn = "textures/" + std::string(mt->name) + "_solid";
    if (R_TextureFileExists(solid_fn, solid_ext))
    {
        alpha_fn = "textures/" + std::string(mt->name) + "_alpha";
        if (R_TextureFileExists(alpha_fn, alpha_ext))
        {
            // Try loading external files
            solid_fn += solid_ext;
            mt->sky_solid = R_LoadTextureFromFile(solid_fn, TEXTURE_MIPMAP);

            alpha_fn += alpha_ext;
            mt->sky_alpha = R_LoadTextureFromFile(alpha_fn, TEXTURE_MIPMAP);

            if (mt->sky_solid && mt->sky_alpha)
                return;
        }
    }

    // Okay, use the map's texture instead
    src = (byte *)(mt + 1);

    // Make an average value for the back to avoid
    // a fringe on the top level (which is stored as black)
    r = g = b = 0;
    for (i=0 ; i<128 ; i++)
    {
        for (j=0 ; j<128 ; j++)
        {
            p = src[i*256 + j + 128];
            rgba = vid.palette[p];

            trans[(i*128) + j][0] = rgba[0];
            trans[(i*128) + j][1] = rgba[1];
            trans[(i*128) + j][2] = rgba[2];
            trans[(i*128) + j][3] = rgba[3];

            r += vid.palette[p][0];
            g += vid.palette[p][1];
            b += vid.palette[p][2];
        }
    }

    transpix[0] = r/(128*128);
    transpix[1] = g/(128*128);
    transpix[2] = b/(128*128);
    transpix[3] = 0;

    mt->sky_solid = R_LoadTextureFromData("", &trans[0][0], 128, 128, TEXTURE_RGBA);

    for (i=0 ; i<128 ; i++)
    {
        for (j=0 ; j<128 ; j++)
        {
            p = src[i*256 + j];
            if (p == 0)
                rgba = transpix;
            else
                rgba = vid.palette[p];

            trans[(i*128) + j][0] = rgba[0];
            trans[(i*128) + j][1] = rgba[1];
            trans[(i*128) + j][2] = rgba[2];
            trans[(i*128) + j][3] = rgba[3];
        }
    }

    mt->sky_alpha = R_LoadTextureFromData("", &trans[0][0], 128, 128, TEXTURE_RGBA);
}


//===============================================================



static void R_DrawSkySphere(void)
{
    float speedscale;

    if (!r_drawqsky) return;
    if (!r_skytexture || !r_skytexture->sky) return;

    glEnableClientState (GL_VERTEX_ARRAY);
    glVertexPointer (3, GL_FLOAT, sizeof (skyverts_t), r_skyverts->v);

    glClientActiveTexture(GL_TEXTURE0);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    glTexCoordPointer(2, GL_FLOAT, sizeof (skyverts_t), r_skyverts->st);

    glBindTexture(GL_TEXTURE_2D, r_skytexture->sky_solid);

    glActiveTexture(GL_TEXTURE0);
    glMatrixMode (GL_TEXTURE);
    speedscale = cl.time * 8;
    speedscale -= (int) speedscale & ~127;
    speedscale /= 128.0f;

    glLoadIdentity ();
    glTranslatef (speedscale, speedscale, 0);

    // Same texture coords on TMU1, different texture matrix
    glActiveTexture(GL_TEXTURE1);
    glEnable(GL_TEXTURE_2D);

    glClientActiveTexture(GL_TEXTURE1);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    glTexCoordPointer(2, GL_FLOAT, sizeof (skyverts_t), r_skyverts->st);

    glBindTexture(GL_TEXTURE_2D, r_skytexture->sky_alpha);
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);

    glMatrixMode(GL_TEXTURE);
    speedscale = cl.time * 16;
    speedscale -= (int) speedscale & ~127;
    speedscale /= 128.0f;

    glLoadIdentity ();
    glTranslatef (speedscale, speedscale, 0);

    // Sky is drawn as an radius 5 sphere clamped to the extreme end of the
    // depth range, and it follows the viewpoint around
    glDepthRange(1.0f, 1.0f);
    glDepthMask(GL_FALSE);
    glDepthFunc(GL_GEQUAL);

    glMatrixMode(GL_MODELVIEW);
    glPushMatrix ();
    glTranslatef (r_refdef.origin[0], r_refdef.origin[1], r_refdef.origin[2]);

    glDrawElements (GL_TRIANGLES, SKYSPHERE_NUMINDEXES, GL_UNSIGNED_SHORT, r_skyindexes);

    glDisable(GL_TEXTURE_2D);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);

    glMatrixMode(GL_TEXTURE);
    glLoadIdentity();

    glClientActiveTexture(GL_TEXTURE0);
    glActiveTexture(GL_TEXTURE0);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);

    glMatrixMode(GL_TEXTURE);
    glLoadIdentity();

    glMatrixMode(GL_MODELVIEW);

    glPopMatrix();
    glDepthRange(0.0f, 1.0f);
    glDepthMask(GL_TRUE);
    glDepthFunc(GL_LEQUAL);

    glDisableClientState(GL_VERTEX_ARRAY);

    // for the next frame
    r_drawqsky = false;
}


/*
=================
R_AddToQSky
=================
*/
static void R_AddToQSky (msurface_t *s)
{
    if (!s->texinfo || !s->texinfo->texture)
        return;

    r_drawqsky = true;
    r_skytexture = s->texinfo->texture;

    R_ClipSky(s);
}



void R_DrawQSky(void)
{
    int i;
    msurface_t *s;
    texture_t *t;

    // Draw the sky chains (i.e., write out their depth buffer values)
    for (i=0 ; i<cl.worldmodel->numtextures ; i++)
    {
        t = cl.worldmodel->textures[i];
        if (!t || !t->sky)
            continue;

        // Load the sky texture if it hasn't been already (a cvar change,
        // possibly)
        if (!t->sky_solid || !t->sky_alpha)
        {
            R_LoadQSky(t);
            if (!t->sky_solid || !t->sky_alpha)
                continue;
        }

        s = t->texturechain;
        if (!s || s->flags & SURF_DRAWTURB)
            continue;

        R_AddToQSky(s);

        t->texturechain = NULL;
    }

    // Draw the sky sphere
    R_DrawSkySphere();
}

