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

extern void R_ClipSky (msurface_t *s);

static qboolean skybox_loaded = false;
static GLuint skybox_textures[6];

static qboolean r_drawskybox = false;


void R_LoadSkybox(void)
{
    int i;
    std::string name, extension;
    const char *suf[6] = {"rt", "bk", "lf", "ft", "up", "dn"};

    if (skybox_loaded)
        return;

    for (i=0 ; i<6 ; i++)
    {
        name = "gfx/env/" + Cvar_VariableString("r_skyname") + suf[i];
        if (!R_TextureFileExists(name, extension))
            return;

        name += extension;
        skybox_textures[i] = R_LoadTextureFromFile(name, TEXTURE_MIPMAP);
    }

    skybox_loaded = true;
}


static vec3_t skyclip[6] = {
    {1,1,0},
    {1,-1,0},
    {0,-1,1},
    {0,1,1},
    {1,0,1},
    {-1,0,1}
};

static int st_to_vec[6][3] = {
    {3,-1,2},
    {-3,1,2},

    {1,3,2},
    {-1,-3,2},

    {-2,-1,3},
    {2,-1,-3}
};

static int vec_to_st[6][3] = {
    {-2,3,1},
    {2,3,-1},

    {1,3,2},
    {-1,3,-2},

    {-2,-1,3},
    {-2,1,-3}
};

static float skymins[2][6], skymaxs[2][6];

static void R_DrawSkyPolygon (int nump, vec3_t vecs)
{
    int        i,j;
    vec3_t    v, av;
    float    s, t, dv;
    int        axis;
    float    *vp;

    // decide which face it maps to
    VectorCopy (vec3_origin, v);
    for (i=0, vp=vecs ; i<nump ; i++, vp+=3)
    {
        VectorAdd (vp, v, v);
    }
    av[0] = fabs(v[0]);
    av[1] = fabs(v[1]);
    av[2] = fabs(v[2]);
    if (av[0] > av[1] && av[0] > av[2])
    {
        if (v[0] < 0)
            axis = 1;
        else
            axis = 0;
    }
    else if (av[1] > av[2] && av[1] > av[0])
    {
        if (v[1] < 0)
            axis = 3;
        else
            axis = 2;
    }
    else
    {
        if (v[2] < 0)
            axis = 5;
        else
            axis = 4;
    }

    // project new texture coords
    for (i=0 ; i<nump ; i++, vecs+=3)
    {
        j = vec_to_st[axis][2];
        if (j > 0)
            dv = vecs[j - 1];
        else
            dv = -vecs[-j - 1];

        j = vec_to_st[axis][0];
        if (j < 0)
            s = -vecs[-j -1] / dv;
        else
            s = vecs[j-1] / dv;
        j = vec_to_st[axis][1];
        if (j < 0)
            t = -vecs[-j -1] / dv;
        else
            t = vecs[j-1] / dv;

        if (s < skymins[0][axis])
            skymins[0][axis] = s;
        if (t < skymins[1][axis])
            skymins[1][axis] = t;
        if (s > skymaxs[0][axis])
            skymaxs[0][axis] = s;
        if (t > skymaxs[1][axis])
            skymaxs[1][axis] = t;
    }
}

#define    MAX_CLIP_VERTS    64
static void R_ClipSkyPolygon (int nump, vec3_t vecs, int stage)
{
    float    *norm;
    float    *v;
    qboolean    front, back;
    float    d, e;
    float    dists[MAX_CLIP_VERTS];
    int        sides[MAX_CLIP_VERTS];
    vec3_t    newv[2][MAX_CLIP_VERTS];
    int        newc[2];
    int        i, j;

    if (nump > MAX_CLIP_VERTS-2)
        Sys_Error ("R_ClipSkyPolygon: MAX_CLIP_VERTS");
    if (stage == 6)
    {
        // fully clipped, so draw it
        R_DrawSkyPolygon (nump, vecs);
        return;
    }

    front = back = false;
    norm = skyclip[stage];
    for (i=0, v = vecs ; i<nump ; i++, v+=3)
    {
        d = DotProduct (v, norm);
        if (d > ON_EPSILON)
        {
            front = true;
            sides[i] = SIDE_FRONT;
        }
        else if (d < ON_EPSILON)
        {
            back = true;
            sides[i] = SIDE_BACK;
        }
        else
            sides[i] = SIDE_ON;
        dists[i] = d;
    }

    if (!front || !back)
    {
        // not clipped
        R_ClipSkyPolygon (nump, vecs, stage+1);
        return;
    }

    // clip it
    sides[i] = sides[0];
    dists[i] = dists[0];
    VectorCopy (vecs, (vecs+(i*3)) );
    newc[0] = newc[1] = 0;

    for (i=0, v = vecs ; i<nump ; i++, v+=3)
    {
        switch (sides[i])
        {
        case SIDE_FRONT:
            VectorCopy (v, newv[0][newc[0]]);
            newc[0]++;
            break;
        case SIDE_BACK:
            VectorCopy (v, newv[1][newc[1]]);
            newc[1]++;
            break;
        case SIDE_ON:
            VectorCopy (v, newv[0][newc[0]]);
            newc[0]++;
            VectorCopy (v, newv[1][newc[1]]);
            newc[1]++;
            break;
        }

        if (sides[i] == SIDE_ON || sides[i+1] == SIDE_ON || sides[i+1] == sides[i])
            continue;

        d = dists[i] / (dists[i] - dists[i+1]);
        for (j=0 ; j<3 ; j++)
        {
            e = v[j] + d*(v[j+3] - v[j]);
            newv[0][newc[0]][j] = e;
            newv[1][newc[1]][j] = e;
        }
        newc[0]++;
        newc[1]++;
    }

    // continue
    R_ClipSkyPolygon (newc[0], newv[0][0], stage+1);
    R_ClipSkyPolygon (newc[1], newv[1][0], stage+1);
}

/*
=================
R_AddToSkybox
=================
*/
static void R_AddToSkybox(msurface_t *s)
{
    msurface_t *fa;
    int i;
    vec3_t verts[MAX_CLIP_VERTS];

    r_drawskybox = true;

    // Write the sky polygons out to the Z-buffer
    R_ClipSky(s);

    // Calculate vertex values for sky box
    for (fa = s ; fa ; fa = fa->texturechain)
    {
        for (i=0 ; i<fa->polys->numverts ; i++)
        {
            VectorSubtract(fa->polys->verts[i], r_refdef.origin, verts[i]);
        }

        R_ClipSkyPolygon(fa->polys->numverts, verts[0], 0);
    }
}



static void R_MakeSkyVec(float s, float t, int axis)
{
    vec3_t        v, b;
    int            j, k;

    b[0] = s*2048;
    b[1] = t*2048;
    b[2] = 2048;

    for (j=0 ; j<3 ; j++)
    {
        k = st_to_vec[axis][j];
        if (k < 0)
            v[j] = -b[-k - 1];
        else
            v[j] = b[k - 1];
        v[j] += r_refdef.origin[j];
    }

    // avoid bilerp seam
    s = (s+1)*0.5;
    t = (t+1)*0.5;

    if (s < 1.0/512)
        s = 1.0/512;
    else if (s > 511.0/512)
        s = 511.0/512;
    if (t < 1.0/512)
        t = 1.0/512;
    else if (t > 511.0/512)
        t = 511.0/512;

    t = 1.0 - t;
    glTexCoord2f (s, t);
    glVertex3fv (v);
}



void R_DrawSkybox(void)
{
    int i;
    msurface_t *s;
    texture_t *t;
    const int skytexorder[6] = {0,2,1,3,4,5};

    // Load the skybox if it hasn't been already (cvar change)
    if (!skybox_loaded)
    {
        R_LoadSkybox();
        if (!skybox_loaded)
            return;
    }

    for (i=0 ; i<6 ; i++)
    {
        skymins[0][i] = skymins[1][i] = 9999;
        skymaxs[0][i] = skymaxs[1][i] = -9999;
    }

    // Write out depth buffer values and compute skybox vectors
    for (i=0 ; i<cl.worldmodel->numtextures ; i++)
    {
        t = cl.worldmodel->textures[i];
        if (!t || !t->sky)
            continue;
        s = t->texturechain;
        if (!s || s->flags & SURF_DRAWTURB)
            continue;

        R_AddToSkybox(s);

        t->texturechain = NULL;
    }

    // Draw the sky box
    if (!r_drawskybox)
        return;

    glDepthRange(1.0f, 1.0f);
    glDepthMask(GL_FALSE);
    glDepthFunc(GL_GEQUAL);

    for (i=0 ; i<6 ; i++)
    {
        if (skymins[0][i] >= skymaxs[0][i] ||
            skymins[1][i] >= skymaxs[1][i])
            continue;

        glBindTexture(GL_TEXTURE_2D, skybox_textures[skytexorder[i]]);

        glBegin (GL_QUADS);
        R_MakeSkyVec (skymins[0][i], skymins[1][i], i);
        R_MakeSkyVec (skymins[0][i], skymaxs[1][i], i);
        R_MakeSkyVec (skymaxs[0][i], skymaxs[1][i], i);
        R_MakeSkyVec (skymaxs[0][i], skymins[1][i], i);
        glEnd ();
    }

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

    // Reset for the next frame
    r_drawskybox = false;
}
