/* KallistiGL for KallistiOS ##version##

   libgl/gl-api.c
   Copyright (C) 2013-2014 Josh Pearson
   Copyright (C) 2014 Lawrence Sebald

   Some functionality adapted from the original KOS libgl:
   Copyright (C) 2001 Dan Potter
   Copyright (C) 2002 Benoit Miller

   This API implements much but not all of the OpenGL 1.1 for KallistiOS.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "gl.h"
#include "glu.h"
#include "gl-api.h"
#include "gl-sh4.h"
#include "gl-pvr.h"

//====================================================================================================//
//== Local API State Macine Variables ==//

static GLubyte GL_KOS_DEPTH_FUNC  = PVR_DEPTHCMP_GEQUAL;
static GLubyte GL_KOS_DEPTH_WRITE = PVR_DEPTHWRITE_ENABLE;
static GLubyte GL_KOS_BLEND_FUNC  = (PVR_BLEND_ONE << 4) | (PVR_BLEND_ZERO & 0x0F);
static GLubyte GL_KOS_SHADE_FUNC  = PVR_SHADE_GOURAUD;
static GLushort GL_KOS_CULL_FUNC   = PVR_CULLING_NONE;
static GLushort GL_KOS_FACE_FRONT  = GL_CCW;
static GLubyte GL_KOS_SUPERSAMPLE = 0;

static GLuint  GL_KOS_VERTEX_COUNT = 0;
static GLuint  GL_KOS_VERTEX_MODE  = GL_TRIANGLES;
static GLuint  GL_KOS_VERTEX_COLOR = 0xFFFFFFFF;
static GLfloat GL_KOS_VERTEX_UV[2] = { 0, 0 };
//static glTexCoord4f GL_KOS_VERTEX_TEX_COORD = { 0, 0, 0, 1 };

static GLfloat GL_KOS_COLOR_CLEAR[3] = { 0, 0, 0 };

static GLfloat GL_KOS_POINT_SIZE = 0.02;

static pvr_poly_cxt_t GL_KOS_POLY_CXT;

static inline void _glKosFlagsSetTriangleStrip();
static inline void _glKosFlagsSetTriangle();
static inline void _glKosFlagsSetQuad();
static inline void _glKosFinishRect();

//====================================================================================================//
//== API Initialization ==//

//void APIENTRY glKosInit() {
//    _glKosInitPVR();

//    _glKosInitTextures();

//    _glKosInitMatrix();

//    _glKosInitLighting();

//    _glKosInitFrameBuffers();
//}

//====================================================================================================//
//== Blending / Shading functions ==//

//void APIENTRY glShadeModel(GLenum   mode) {
//    switch(mode) {
//        case GL_FLAT:
//            GL_KOS_SHADE_FUNC = PVR_SHADE_FLAT;
//            break;

//        case GL_SMOOTH:
//            GL_KOS_SHADE_FUNC = PVR_SHADE_GOURAUD;
//            break;
//    }
//}

//void APIENTRY glBlendFunc(GLenum sfactor, GLenum dfactor) {
//    GL_KOS_BLEND_FUNC = 0;

//    switch(sfactor) {
//        case GL_ONE:
//            GL_KOS_BLEND_FUNC |= (PVR_BLEND_ONE & 0XF) << 4;
//            break;

//        case GL_ZERO:
//            GL_KOS_BLEND_FUNC |= (PVR_BLEND_ZERO & 0XF) << 4;
//            break;

//        case GL_SRC_COLOR:
//            GL_KOS_BLEND_FUNC |= (PVR_BLEND_SRCALPHA & 0XF) << 4;
//            break;

//        case GL_DST_COLOR:
//            GL_KOS_BLEND_FUNC |= (PVR_BLEND_DESTCOLOR & 0XF) << 4;
//            break;

//        case GL_SRC_ALPHA:
//            GL_KOS_BLEND_FUNC |= (PVR_BLEND_SRCALPHA << 4);
//            break;

//        case GL_DST_ALPHA:
//            GL_KOS_BLEND_FUNC |= (PVR_BLEND_DESTALPHA & 0XF) << 4;
//            break;

//        case GL_ONE_MINUS_SRC_ALPHA:
//            GL_KOS_BLEND_FUNC |= (PVR_BLEND_INVSRCALPHA & 0XF) << 4;
//            break;

//        case GL_ONE_MINUS_DST_ALPHA:
//            GL_KOS_BLEND_FUNC |= (PVR_BLEND_INVDESTALPHA & 0XF) << 4;
//            break;

//        case GL_ONE_MINUS_DST_COLOR:
//            GL_KOS_BLEND_FUNC |= (PVR_BLEND_INVDESTCOLOR & 0XF) << 4;
//            break;
//    }

//    switch(dfactor) {
//        case GL_ONE:
//            GL_KOS_BLEND_FUNC |= (PVR_BLEND_ONE & 0XF);
//            break;

//        case GL_ZERO:
//            GL_KOS_BLEND_FUNC |= (PVR_BLEND_ZERO & 0XF);
//            break;

//        case GL_SRC_COLOR:
//            GL_KOS_BLEND_FUNC |= (PVR_BLEND_SRCALPHA & 0XF);
//            break;

//        case GL_DST_COLOR:
//            GL_KOS_BLEND_FUNC |= (PVR_BLEND_DESTCOLOR & 0XF);
//            break;

//        case GL_SRC_ALPHA:
//            GL_KOS_BLEND_FUNC |= (PVR_BLEND_SRCALPHA & 0XF);
//            break;

//        case GL_DST_ALPHA:
//            GL_KOS_BLEND_FUNC |= (PVR_BLEND_DESTALPHA & 0XF);
//            break;

//        case GL_ONE_MINUS_SRC_ALPHA:
//            GL_KOS_BLEND_FUNC |= (PVR_BLEND_INVSRCALPHA & 0XF);
//            break;

//        case GL_ONE_MINUS_DST_ALPHA:
//            GL_KOS_BLEND_FUNC |= (PVR_BLEND_INVDESTALPHA & 0XF);
//            break;

//        case GL_ONE_MINUS_DST_COLOR:
//            GL_KOS_BLEND_FUNC |= (PVR_BLEND_INVDESTCOLOR & 0XF);
//            break;
//    }
//}

//====================================================================================================//
//== Depth / Clear functions ==//

//void APIENTRY glClear(GLuint mode) {
//    if(mode & GL_COLOR_BUFFER_BIT)
//        pvr_set_bg_color(GL_KOS_COLOR_CLEAR[0], GL_KOS_COLOR_CLEAR[1], GL_KOS_COLOR_CLEAR[2]);
//}

//void APIENTRY glClearColor(float r, float g, float b, float a) {
//    if(r > 1) r = 1;

//    if(g > 1) g = 1;

//    if(b > 1) b = 1;

//    if(a > 1) a = 1;

//    GL_KOS_COLOR_CLEAR[0] = r * a;
//    GL_KOS_COLOR_CLEAR[1] = g * a;
//    GL_KOS_COLOR_CLEAR[2] = b * a;
//}

//== NoOp ==//
//void APIENTRY glClearDepthf(GLfloat depth) {
//    ;
//}

//void APIENTRY glDepthFunc(GLenum func) {
//    switch(func) {
//        case GL_LESS:
//            GL_KOS_DEPTH_FUNC = PVR_DEPTHCMP_GEQUAL;
//            break;

//        case GL_LEQUAL:
//            GL_KOS_DEPTH_FUNC = PVR_DEPTHCMP_GREATER;
//            break;

//        case GL_GREATER:
//            GL_KOS_DEPTH_FUNC = PVR_DEPTHCMP_LEQUAL;
//            break;

//        case GL_GEQUAL:
//            GL_KOS_DEPTH_FUNC = PVR_DEPTHCMP_LESS;
//            break;

//        default:
//            GL_KOS_DEPTH_FUNC = (func & 0x0F);
//    }
//}

//void APIENTRY glDepthMask(GLboolean flag) {
//    GL_KOS_DEPTH_WRITE = (flag == GL_TRUE) ? PVR_DEPTHWRITE_ENABLE : PVR_DEPTHWRITE_DISABLE;
//}

//====================================================================================================//
//== Culling functions ==//

//void APIENTRY glFrontFace(GLenum mode) {
//    switch(mode) {
//        case GL_CW:
//        case GL_CCW:
//            GL_KOS_FACE_FRONT = mode;
//            break;
//    }
//}

//void APIENTRY glCullFace(GLenum mode) {
//    switch(mode) {
//        case GL_FRONT:
//        case GL_BACK:
//        case GL_FRONT_AND_BACK:
//            GL_KOS_CULL_FUNC = mode;
//            break;
//    }
//}

//====================================================================================================//
//== Vertex Attributes Submission Functions ==//

//== Vertex Color Submission ==//

void APIENTRY glColor1ui(GLuint argb) {
    GL_KOS_VERTEX_COLOR = argb;
}

void APIENTRY glColor4ub(GLubyte r, GLubyte  g, GLubyte b, GLubyte a) {
    GL_KOS_VERTEX_COLOR = a << 24 | r << 16 | g << 8 | b;
}


//== Vertex Position Submission Functions ==//

void APIENTRY glRectf(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2) {
    pvr_vertex_t *v = _glKosVertexBufPointer();

    v[0].z = v[3].z = 0;

    mat_trans_single3_nomod(x1, y1, v[0].z, v[0].x, v[0].y, v[0].z);
    mat_trans_single3_nomod(x2, y2, v[3].z, v[3].x, v[3].y, v[3].z);

    _glKosFinishRect();
}

void APIENTRY glRectfv(const GLfloat *v1, const GLfloat *v2) {
    pvr_vertex_t *v = _glKosVertexBufPointer();

    v[0].z = v[3].z = 0;

    mat_trans_single3_nomod(v1[0], v1[1], v[0].z, v[0].x, v[0].y, v[0].z);
    mat_trans_single3_nomod(v2[0], v2[1], v[3].z, v[3].x, v[3].y, v[3].z);

    _glKosFinishRect();
}

void APIENTRY glRecti(GLint x1, GLint y1, GLint x2, GLint y2) {
    return glRectf((GLfloat)x1, (GLfloat)y1, (GLfloat)x2, (GLfloat)y2);
}

void APIENTRY glRectiv(const GLint *v1, const GLint *v2) {
    return glRectfv((const GLfloat *)v1, (const GLfloat *)v2);
}

//====================================================================================================//
//== GL Begin / End ==//

//====================================================================================================//
//== Misc. functions ==//

/* Clamp X to [MIN,MAX]: */


//void APIENTRY glHint(GLenum target, GLenum mode) {
//    switch(target) {
//        case GL_PERSPECTIVE_CORRECTION_HINT:
//            if(mode == GL_NICEST)
//                GL_KOS_SUPERSAMPLE = 1;
//            else
//                GL_KOS_SUPERSAMPLE = 0;

//            break;
//    }

//}

//====================================================================================================//
//== Internal API Vertex Submission functions ==//

void _glKosVertex3fs(GLfloat x, GLfloat y, GLfloat z) {
    pvr_vertex_t *v = _glKosVertexBufPointer();

    mat_trans_single3_nomod(x, y, z, v->x, v->y, v->z);

    v->u = GL_KOS_VERTEX_UV[0];
    v->v = GL_KOS_VERTEX_UV[1];

    _glKosVertexBufIncrement();

    ++GL_KOS_VERTEX_COUNT;
}

void _glKosVertex3fsv(const GLfloat *xyz) {
    pvr_vertex_t *v = _glKosVertexBufPointer();

    mat_trans_single3_nomod(xyz[0], xyz[1], xyz[2], v->x, v->y, v->z);

    v->u = GL_KOS_VERTEX_UV[0];
    v->v = GL_KOS_VERTEX_UV[1];

    _glKosVertexBufIncrement();

    ++GL_KOS_VERTEX_COUNT;
}

void _glKosVertex3ft(GLfloat x, GLfloat y, GLfloat z) {
    pvr_vertex_t *v = _glKosVertexBufPointer();

    mat_trans_single3_nomod(x, y, z, v->x, v->y, v->z);

    v->u = GL_KOS_VERTEX_UV[0];
    v->v = GL_KOS_VERTEX_UV[1];
    v->argb  = GL_KOS_VERTEX_COLOR;

    _glKosVertexBufIncrement();

    ++GL_KOS_VERTEX_COUNT;
}

void _glKosVertex3ftv(const GLfloat *xyz) {
    pvr_vertex_t *v = _glKosVertexBufPointer();

    mat_trans_single3_nomod(xyz[0], xyz[1], xyz[2], v->x, v->y, v->z);

    v->u = GL_KOS_VERTEX_UV[0];
    v->v = GL_KOS_VERTEX_UV[1];
    v->argb  = GL_KOS_VERTEX_COLOR;

    _glKosVertexBufIncrement();

    ++GL_KOS_VERTEX_COUNT;
}

void _glKosVertex3fc(GLfloat x, GLfloat y, GLfloat z) {
    pvr_vertex_t *v = _glKosClipBufPointer();

    v->x = x;
    v->y = y;
    v->z = z;
    v->u = GL_KOS_VERTEX_UV[0];
    v->v = GL_KOS_VERTEX_UV[1];
    v->argb  = GL_KOS_VERTEX_COLOR;

    _glKosClipBufIncrement();

    ++GL_KOS_VERTEX_COUNT;
}

void _glKosVertex3fcv(const GLfloat *xyz) {
    pvr_vertex_t *v = _glKosClipBufPointer();

    v->x = xyz[0];
    v->y = xyz[1];
    v->z = xyz[2];
    v->u = GL_KOS_VERTEX_UV[0];
    v->v = GL_KOS_VERTEX_UV[1];
    v->argb  = GL_KOS_VERTEX_COLOR;

    _glKosClipBufIncrement();

    ++GL_KOS_VERTEX_COUNT;
}

/* GL_POINTS */
GLvoid _glKosVertex3fp(GLfloat x, GLfloat y, GLfloat z) {
    pvr_vertex_t *v = _glKosVertexBufPointer();

    mat_trans_single3_nomod(x - GL_KOS_POINT_SIZE, y + GL_KOS_POINT_SIZE, z,
                            v[0].x, v[0].y, v[0].z);
    mat_trans_single3_nomod(x + GL_KOS_POINT_SIZE, y - GL_KOS_POINT_SIZE, z,
                            v[3].x, v[3].y, v[3].z);

    _glKosFinishRect();
}

GLvoid _glKosVertex3fpv(const GLfloat *xyz) {
    pvr_vertex_t *v = _glKosVertexBufPointer();

    mat_trans_single3_nomod(xyz[0] - GL_KOS_POINT_SIZE, xyz[1] + GL_KOS_POINT_SIZE, xyz[2],
                            v[0].x, v[0].y, v[0].z);
    mat_trans_single3_nomod(xyz[0] + GL_KOS_POINT_SIZE, xyz[1] - GL_KOS_POINT_SIZE, xyz[2],
                            v[3].x, v[3].y, v[3].z);

    _glKosFinishRect();
}

static inline void _glKosFinishRect() {
    pvr_vertex_t *v = _glKosVertexBufPointer();

    v[0].argb = v[1].argb = v[2].argb = v[3].argb = GL_KOS_VERTEX_COLOR;

    v[0].flags = v[1].flags = v[2].flags = PVR_CMD_VERTEX;
    v[3].flags = PVR_CMD_VERTEX_EOL;

    v[1].x = v[0].x;
    v[1].y = v[3].y;
    v[1].z = v[3].z;

    v[2].x = v[3].x;
    v[2].y = v[0].y;
    v[2].z = v[0].z;

    _glKosVertexBufAdd(4);

    GL_KOS_VERTEX_COUNT += 4;
}

void _glKosTransformClipBuf(pvr_vertex_t *v, GLuint verts) {
    register float __x  __asm__("fr12");
    register float __y  __asm__("fr13");
    register float __z  __asm__("fr14");

    while(verts--) {
        __x = v->x;
        __y = v->y;
        __z = v->z;

        mat_trans_fv12();

        v->x = __x;
        v->y = __y;
        v->z = __z;
        ++v;
    }
}

static inline void _glKosVertexSwap(pvr_vertex_t *v1, pvr_vertex_t *v2) {
    pvr_vertex_t tmp = *v1;
    *v1 = *v2;
    *v2 = * &tmp;
}

static inline void _glKosFlagsSetQuad() {
    pvr_vertex_t *v = (pvr_vertex_t *)_glKosVertexBufPointer() - GL_KOS_VERTEX_COUNT;
    GLuint i;

    for(i = 0; i < GL_KOS_VERTEX_COUNT; i += 4) {
        _glKosVertexSwap(v + 2, v + 3);
        v->flags = (v + 1)->flags = (v + 2)->flags = PVR_CMD_VERTEX;
        (v + 3)->flags = PVR_CMD_VERTEX_EOL;
        v += 4;
    }
}

static inline void _glKosFlagsSetTriangle() {
    pvr_vertex_t *v = (pvr_vertex_t *)_glKosVertexBufPointer() - GL_KOS_VERTEX_COUNT;
    GLuint i;

    for(i = 0; i < GL_KOS_VERTEX_COUNT; i += 3) {
        v->flags = (v + 1)->flags = PVR_CMD_VERTEX;
        (v + 2)->flags = PVR_CMD_VERTEX_EOL;
        v += 3;
    }
}

static inline void _glKosFlagsSetTriangleStrip() {
    pvr_vertex_t *v = (pvr_vertex_t *)_glKosVertexBufPointer() - GL_KOS_VERTEX_COUNT;
    GLuint i;

    for(i = 0; i < GL_KOS_VERTEX_COUNT - 1; i++) {
        v->flags = PVR_CMD_VERTEX;
        v++;
    }

    v->flags = PVR_CMD_VERTEX_EOL;
}

//====================================================================================================//
//== GL KOS PVR Header Parameter Compilation Functions ==//

static inline void _glKosApplyDepthFunc() {
    if(_glKosEnabledDepthTest())
        GL_KOS_POLY_CXT.depth.comparison = GL_KOS_DEPTH_FUNC;
    else
        GL_KOS_POLY_CXT.depth.comparison = PVR_DEPTHCMP_ALWAYS;

    GL_KOS_POLY_CXT.depth.write = GL_KOS_DEPTH_WRITE;
}

static inline void _glKosApplyScissorFunc() {
    if(_glKosEnabledScissorTest())
        GL_KOS_POLY_CXT.gen.clip_mode = PVR_USERCLIP_INSIDE;
}

static inline void _glKosApplyFogFunc() {
    if(_glKosEnabledFog())
        GL_KOS_POLY_CXT.gen.fog_type = PVR_FOG_TABLE;
}

static inline void _glKosApplyCullingFunc() {
    if(_glKosEnabledCulling()) {
        if(GL_KOS_CULL_FUNC == GL_BACK) {
            if(GL_KOS_FACE_FRONT == GL_CW)
                GL_KOS_POLY_CXT.gen.culling = PVR_CULLING_CCW;
            else
                GL_KOS_POLY_CXT.gen.culling = PVR_CULLING_CW;
        }
        else if(GL_KOS_CULL_FUNC == GL_FRONT) {
            if(GL_KOS_FACE_FRONT == GL_CCW)
                GL_KOS_POLY_CXT.gen.culling = PVR_CULLING_CCW;
            else
                GL_KOS_POLY_CXT.gen.culling = PVR_CULLING_CW;
        }
    }
    else
        GL_KOS_POLY_CXT.gen.culling = PVR_CULLING_NONE;
}

static inline void _glKosApplyBlendFunc() {
    if(_glKosEnabledBlend()) {
        GL_KOS_POLY_CXT.blend.src = (GL_KOS_BLEND_FUNC & 0xF0) >> 4;
        GL_KOS_POLY_CXT.blend.dst = (GL_KOS_BLEND_FUNC & 0x0F);
    }
}

void _glKosCompileHdr() {
    pvr_poly_hdr_t *hdr = _glKosVertexBufPointer();

    pvr_poly_cxt_col(&GL_KOS_POLY_CXT, _glKosList() * 2);

    GL_KOS_POLY_CXT.gen.shading = GL_KOS_SHADE_FUNC;

    _glKosApplyDepthFunc();

    _glKosApplyScissorFunc();

    _glKosApplyFogFunc();

    _glKosApplyCullingFunc();

    _glKosApplyBlendFunc();

    pvr_poly_compile(hdr, &GL_KOS_POLY_CXT);

    _glKosVertexBufIncrement();
}
//====================================================================================================//
//== Internal GL KOS API State Functions ==//

GLuint _glKosBlendSrcFunc() {
    switch((GL_KOS_BLEND_FUNC & 0xF0) >> 4) {
        case PVR_BLEND_ONE:
            return GL_ONE;

        case PVR_BLEND_ZERO:
            return GL_ZERO;

        case PVR_BLEND_DESTCOLOR:
            return GL_DST_COLOR;

        case PVR_BLEND_SRCALPHA:
            return GL_SRC_ALPHA;

        case PVR_BLEND_DESTALPHA:
            return GL_DST_ALPHA;

        case PVR_BLEND_INVSRCALPHA:
            return GL_ONE_MINUS_SRC_ALPHA;

        case PVR_BLEND_INVDESTALPHA:
            return GL_ONE_MINUS_DST_ALPHA;

        case PVR_BLEND_INVDESTCOLOR:
            return GL_ONE_MINUS_DST_COLOR;
    }

    return 0;
}

GLuint _glKosBlendDstFunc() {
    switch(GL_KOS_BLEND_FUNC & 0xF) {
        case PVR_BLEND_ONE:
            return GL_ONE;

        case PVR_BLEND_ZERO:
            return GL_ZERO;

        case PVR_BLEND_DESTCOLOR:
            return GL_DST_COLOR;

        case PVR_BLEND_SRCALPHA:
            return GL_SRC_ALPHA;

        case PVR_BLEND_DESTALPHA:
            return GL_DST_ALPHA;

        case PVR_BLEND_INVSRCALPHA:
            return GL_ONE_MINUS_SRC_ALPHA;

        case PVR_BLEND_INVDESTALPHA:
            return GL_ONE_MINUS_DST_ALPHA;

        case PVR_BLEND_INVDESTCOLOR:
            return GL_ONE_MINUS_DST_COLOR;
    }

    return 0;
}

GLubyte _glKosCullFaceMode() {
    return GL_KOS_CULL_FUNC;
}

GLubyte _glKosCullFaceFront() {
    return GL_KOS_FACE_FRONT;
}

GLuint _glKosDepthFunc() {
    switch(GL_KOS_DEPTH_FUNC) {
        case PVR_DEPTHCMP_GEQUAL:
            return GL_LESS;

        case PVR_DEPTHCMP_GREATER:
            return GL_LEQUAL;

        case PVR_DEPTHCMP_LEQUAL:
            return GL_GREATER;

        case PVR_DEPTHCMP_LESS:
            return GL_GEQUAL;

        default:
            return GL_NEVER + GL_KOS_DEPTH_FUNC;
    }
}

GLubyte _glKosDepthMask() {
    return (GL_KOS_DEPTH_WRITE == PVR_DEPTHWRITE_ENABLE) ? GL_TRUE : GL_FALSE;
}

GLuint _glKosVertexColor() {
    return GL_KOS_VERTEX_COLOR;
}