
#include "TinyGL/gl_zgl.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>


#define X v[0]
#define Y v[1]
#define Z v[2]
#define W v[3]


void glopNormal( GLContext *c, GLParam *p ){
    V3 v;

    v.X = p[1].f;
    v.Y = p[2].f;
    v.Z = p[3].f;

    c->current_normal.X = v.X;
    c->current_normal.Y = v.Y;
    c->current_normal.Z = v.Z;
    c->current_normal.W = 0;
}

void glopTexCoord( GLContext *c, GLParam *p ){
    c->current_tex_coord.X = p[1].f;
    c->current_tex_coord.Y = p[2].f;
    c->current_tex_coord.Z = p[3].f;
    c->current_tex_coord.W = p[4].f;
}

void glopEdgeFlag( GLContext *c, GLParam *p ){
    c->current_edge_flag = p[1].i;
}

void glopColor( GLContext *c, GLParam *p ){

    c->current_color.X      = p[1].f;
    c->current_color.Y      = p[2].f;
    c->current_color.Z      = p[3].f;
    c->current_color.W      = p[4].f;
    c->longcurrent_color[0] = p[5].ui;
    c->longcurrent_color[1] = p[6].ui;
    c->longcurrent_color[2] = p[7].ui;

    if (c->color_material_enabled) {
		GLParam q[7];
		q[0].op = OP_Material;
		q[1].i  = c->current_color_material_mode;
		q[2].i  = c->current_color_material_type;
		q[3].f  = p[1].f;
		q[4].f  = p[2].f;
		q[5].f  = p[3].f;
		q[6].f  = p[4].f;
		glopMaterial( c, q );
    }
}


void gl_eval_viewport( GLContext * c ){
    GLViewport *v;
    float       zsize = (1 << (ZB_Z_BITS + ZB_POINT_Z_FRAC_BITS));

    v = &c->viewport;

    v->trans.X = (float)(   ((v->xsize - 0.5) / 2) + v->xmin  );
    v->trans.Y = (float)(   ((v->ysize - 0.5) / 2) + v->ymin  );
    v->trans.Z = (float)(   ((   zsize - 0.5) / 2) + ((1 << ZB_POINT_Z_FRAC_BITS)) / 2  );

    v->scale.X = (float)(   ( v->xsize - 0.5) / 2             );
    v->scale.Y = (float)(  -( v->ysize - 0.5) / 2             );
    v->scale.Z = (float)(  -((   zsize - 0.5) / 2)            );
}

void glopBegin( GLContext *c, GLParam *p ){
    int type;
    M4  tmp;

    assert( c->in_begin == 0 );

    type = p[1].i;
    c->begin_type = type;
    c->in_begin   = 1;
    c->vertex_n   = 0;
    c->vertex_cnt = 0;

    if (c->matrix_model_projection_updated) {

	if (c->lighting_enabled) {
	    /* precompute inverse modelview */
	    gl_M4_Inv(&tmp, c->matrix_stack_ptr[0]);
	    gl_M4_Transpose(&c->matrix_model_view_inv, &tmp);
	} else {
	    float *m = &c->matrix_model_projection.m[0][0];
	    /* precompute projection matrix */
	    gl_M4_Mul(&c->matrix_model_projection,
		      c->matrix_stack_ptr[1],
		      c->matrix_stack_ptr[0]);
	    /* test to accelerate computation */
	    c->matrix_model_projection_no_w_transform = 0;
	    if (m[12] == 0.0 && m[13] == 0.0 && m[14] == 0.0)
		c->matrix_model_projection_no_w_transform = 1;
	}

	/* test if the texture matrix is not Identity */
	c->apply_texture_matrix = !gl_M4_IsId(c->matrix_stack_ptr[2]);

	c->matrix_model_projection_updated = 0;
    }
    /*  viewport */
    if (c->viewport.updated) {
	gl_eval_viewport(c);
	c->viewport.updated = 0;
    }
    /* triangle drawing functions */
    if (c->render_mode == GL_SELECT) {
	c->draw_triangle_front = gl_draw_triangle_select;
	c->draw_triangle_back = gl_draw_triangle_select;
    } else {
	switch (c->polygon_mode_front) {
	case GL_POINT:
	    c->draw_triangle_front = gl_draw_triangle_point;
	    break;
	case GL_LINE:
	    c->draw_triangle_front = gl_draw_triangle_line;
	    break;
	default:
	    c->draw_triangle_front = gl_draw_triangle_fill;
	    break;
	}

	switch (c->polygon_mode_back) {
	case GL_POINT:
	    c->draw_triangle_back = gl_draw_triangle_point;
	    break;
	case GL_LINE:
	    c->draw_triangle_back = gl_draw_triangle_line;
	    break;
	default:
	    c->draw_triangle_back = gl_draw_triangle_fill;
	    break;
	}
    }
}

/* coords, tranformation , clip code and projection */
/* TODO : handle all cases */
static /*inline*/ void gl_vertex_transform( GLContext * c, GLVertex * v ){
    float *m;
    V4 *n;

    if (c->lighting_enabled) {
	/* eye coordinates needed for lighting */

	m = &c->matrix_stack_ptr[0]->m[0][0];
	v->ec.X = (v->coord.X * m[0] + v->coord.Y * m[1] +
		   v->coord.Z * m[2] + m[3]);
	v->ec.Y = (v->coord.X * m[4] + v->coord.Y * m[5] +
		   v->coord.Z * m[6] + m[7]);
	v->ec.Z = (v->coord.X * m[8] + v->coord.Y * m[9] +
		   v->coord.Z * m[10] + m[11]);
	v->ec.W = (v->coord.X * m[12] + v->coord.Y * m[13] +
		   v->coord.Z * m[14] + m[15]);

	/* projection coordinates */
	m = &c->matrix_stack_ptr[1]->m[0][0];
	v->pc.X = (v->ec.X * m[0] + v->ec.Y * m[1] +
		   v->ec.Z * m[2] + v->ec.W * m[3]);
	v->pc.Y = (v->ec.X * m[4] + v->ec.Y * m[5] +
		   v->ec.Z * m[6] + v->ec.W * m[7]);
	v->pc.Z = (v->ec.X * m[8] + v->ec.Y * m[9] +
		   v->ec.Z * m[10] + v->ec.W * m[11]);
	v->pc.W = (v->ec.X * m[12] + v->ec.Y * m[13] +
		   v->ec.Z * m[14] + v->ec.W * m[15]);

	m = &c->matrix_model_view_inv.m[0][0];
	n = &c->current_normal;

	v->normal.X = (n->X * m[0] + n->Y * m[1] + n->Z * m[2]);
	v->normal.Y = (n->X * m[4] + n->Y * m[5] + n->Z * m[6]);
	v->normal.Z = (n->X * m[8] + n->Y * m[9] + n->Z * m[10]);

	if (c->normalize_enabled) {
	    gl_V3_Norm(&v->normal);
	}
    } else {
	/* no eye coordinates needed, no normal */
	/* NOTE: W = 1 is assumed */
	m = &c->matrix_model_projection.m[0][0];

	v->pc.X = (v->coord.X * m[0] + v->coord.Y * m[1] +
		   v->coord.Z * m[2] + m[3]);
	v->pc.Y = (v->coord.X * m[4] + v->coord.Y * m[5] +
		   v->coord.Z * m[6] + m[7]);
	v->pc.Z = (v->coord.X * m[8] + v->coord.Y * m[9] +
		   v->coord.Z * m[10] + m[11]);
	if (c->matrix_model_projection_no_w_transform) {
	    v->pc.W = m[15];
	} else {
	    v->pc.W = (v->coord.X * m[12] + v->coord.Y * m[13] +
		       v->coord.Z * m[14] + m[15]);
	}
    }

    v->clip_code = gl_clipcode(v->pc.X, v->pc.Y, v->pc.Z, v->pc.W);
}

void glopVertex( GLContext *c, GLParam *p ){
    GLVertex *v;
    int       n, i, cnt;

    assert( c->in_begin != 0 );

    n = c->vertex_n;
    cnt = c->vertex_cnt;
    cnt++;
    c->vertex_cnt = cnt;

    /* quick fix to avoid crashes on large polygons */
    if (n >= c->vertex_max) {
	GLVertex *newarray;
	c->vertex_max <<= 1;	/* just double size */
	newarray = (GLVertex*)malloc(sizeof(GLVertex) * c->vertex_max);
	if( !newarray ){
	    gl_fatal_error("unable to allocate GLVertex array.\n");
	}
	memcpy(newarray, c->vertex, n * sizeof(GLVertex));
	free(c->vertex);
	c->vertex = newarray;
    }
    /* new vertex entry */
    v = &c->vertex[n];
    n++;

    v->coord.X = p[1].f;
    v->coord.Y = p[2].f;
    v->coord.Z = p[3].f;
    v->coord.W = p[4].f;

    gl_vertex_transform(c, v);

    /* color */

    if (c->lighting_enabled) {
	gl_shade_vertex(c, v);
    } else {
	v->color = c->current_color;
    }

    /* tex coords */

    if (c->texture_2d_enabled) {
	if (c->apply_texture_matrix) {
	    gl_M4_MulV4(&v->tex_coord, c->matrix_stack_ptr[2], &c->current_tex_coord);
	} else {
	    v->tex_coord = c->current_tex_coord;
	}
    }
    /* precompute the mapping to the viewport */
    if (v->clip_code == 0)
	gl_transform_to_viewport(c, v);

    /* edge flag */

    v->edge_flag = c->current_edge_flag;

    switch (c->begin_type) {
    case GL_POINTS:
	gl_draw_point(c, &c->vertex[0]);
	n = 0;
	break;

    case GL_LINES:
	if (n == 2) {
	    gl_draw_line(c, &c->vertex[0], &c->vertex[1]);
	    n = 0;
	}
	break;
    case GL_LINE_STRIP:
    case GL_LINE_LOOP:
	if (n == 1) {
	    c->vertex[2] = c->vertex[0];
	} else if (n == 2) {
	    gl_draw_line(c, &c->vertex[0], &c->vertex[1]);
	    c->vertex[0] = c->vertex[1];
	    n = 1;
	}
	break;

    case GL_TRIANGLES:
	if (n == 3) {
	    gl_draw_triangle(c, &c->vertex[0], &c->vertex[1], &c->vertex[2]);
	    n = 0;
	}
	break;
    case GL_TRIANGLE_STRIP:
	if (cnt >= 3) {
	    int n1, n2, n3;
	    if (n == 3)
		n = 0;
	    n1 = (n) % 3;
	    n2 = (n + 1) % 3;
	    n3 = (n + 2) % 3;
	    gl_draw_triangle(c, &c->vertex[n1], &c->vertex[n2], &c->vertex[n3]);
	}
	break;
    case GL_TRIANGLE_FAN:
	if (n == 3) {
	    gl_draw_triangle(c, &c->vertex[0], &c->vertex[1], &c->vertex[2]);
	    c->vertex[1] = c->vertex[2];
	    n = 2;
	}
	break;

    case GL_QUADS:
	if (n == 4) {
	    c->vertex[2].edge_flag = 0;
	    gl_draw_triangle(c, &c->vertex[0], &c->vertex[1], &c->vertex[2]);
	    c->vertex[2].edge_flag = 1;
	    c->vertex[0].edge_flag = 0;
	    gl_draw_triangle(c, &c->vertex[0], &c->vertex[2], &c->vertex[3]);
	    n = 0;
	}
	break;

    case GL_QUAD_STRIP:
	if (n == 4) {
	    gl_draw_triangle(c, &c->vertex[0], &c->vertex[1], &c->vertex[2]);
	    gl_draw_triangle(c, &c->vertex[1], &c->vertex[3], &c->vertex[2]);
	    for (i = 0; i < 2; i++)
		c->vertex[i] = c->vertex[i + 2];
	    n = 2;
	}
	break;
    case GL_POLYGON:
	break;
    default:
	gl_fatal_error("glBegin: type %x not handled\n", c->begin_type);
    }

    c->vertex_n = n;
}

void glopEnd( GLContext * c, GLParam * param ){
    assert( c->in_begin == 1 );

    if( c->begin_type == GL_LINE_LOOP ){
		if( c->vertex_cnt >= 3 ){
			gl_draw_line(c, &c->vertex[0], &c->vertex[2]);
		}
    }else if( c->begin_type == GL_POLYGON ){
		int i = c->vertex_cnt;
		while( i >= 3 ){
			i--;
		    gl_draw_triangle( c, &c->vertex[i], &c->vertex[0], &c->vertex[i - 1] );
		}
    }
    c->in_begin = 0;
}
