/* 
 *
 * GLX Hardware Device Driver for Sun Creator/Creator3D
 * Copyright (C) 2001 David S. Miller
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * DAVID MILLER, OR ANY OTHER CONTRIBUTORS BE LIABLE FOR ANY CLAIM, 
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 
 * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 *
 *    David S. Miller <davem@redhat.com>
 */

#include "main/glheader.h"
#include "api_noop.h"
#include "main/context.h"
#include "light.h"
#include "main/macros.h"
#include "main/imports.h"
#include "main/mtypes.h"
#include "main/simple_list.h"
#include "vtxfmt.h"
#include "ffb_xmesa.h"
#include "ffb_context.h"
#include "ffb_vb.h"
#include "tnl/tnl.h"
#include "tnl/tcontext.h"

#include "ffb_vtxfmt.h"

#define TNL_VERTEX			ffbTnlVertex

#define INTERP_RGBA(t, out, a, b)		\
do {						\
   GLint i;					\
   for ( i = 0 ; i < 4 ; i++ ) {		\
      GLfloat fa = a[i];			\
      GLfloat fb = b[i];			\
      out[i] = LINTERP( t, fa, fb );		\
   }						\
} while (0)

/* Color functions: */

static INLINE void ffb_recalc_base_color(GLcontext *ctx)
{
	ffbContextPtr fmesa = FFB_CONTEXT(ctx);
	struct gl_light *light;

	COPY_3V(fmesa->vtx_state.light.base_color, ctx->Light._BaseColor[0]);
	foreach (light, &ctx->Light.EnabledList) {
		ACC_3V(fmesa->vtx_state.light.base_color,
		       light->_MatAmbient[0]);
	}

	fmesa->vtx_state.light.base_alpha = ctx->Light._BaseAlpha[0];
}

#define GET_CURRENT \
	GET_CURRENT_CONTEXT(ctx);	\
	ffbContextPtr fmesa = FFB_CONTEXT(ctx)

#define CURRENT_COLOR(COMP)		fmesa->vtx_state.current.color[COMP]
#define CURRENT_SPECULAR(COMP)		fmesa->vtx_state.current.specular[COMP]
#define COLOR_IS_FLOAT
#define RECALC_BASE_COLOR(ctx)		ffb_recalc_base_color(ctx)

#define TAG(x)	ffb_##x
#include "tnl_dd/t_dd_imm_capi.h"

/* Normal functions: */

struct ffb_norm_tab {
	void (*normal3f_multi)(GLfloat x, GLfloat y, GLfloat z);
	void (*normal3fv_multi)(const GLfloat *v);
	void (*normal3f_single)(GLfloat x, GLfloat y, GLfloat z);
	void (*normal3fv_single)(const GLfloat *v);
};

static struct ffb_norm_tab norm_tab[0x4];

#define HAVE_HW_LIGHTING 0
#define GET_CURRENT_VERTEX			\
	GET_CURRENT_CONTEXT(ctx);		\
	ffbContextPtr fmesa = FFB_CONTEXT(ctx);	\
	ffbTnlVertexPtr v = fmesa->imm.v0

#define CURRENT_NORMAL		fmesa->vtx_state.current.normal
#define BASE_COLOR		fmesa->vtx_state.light.base_color
#define BASE_ALPHA		fmesa->vtx_state.light.base_alpha
#define VERT_COLOR( COMP )	v->color[COMP]
#define VERT_COLOR_IS_FLOAT

#define IND (0)
#define TAG(x)	ffb_##x
#define PRESERVE_NORMAL_DEFS
#include "tnl_dd/t_dd_imm_napi.h"

#define IND (NORM_RESCALE)
#define TAG(x) ffb_##x##_rescale
#define PRESERVE_NORMAL_DEFS
#include "tnl_dd/t_dd_imm_napi.h"

#define IND (NORM_NORMALIZE)
#define TAG(x) ffb_##x##_normalize
#include "tnl_dd/t_dd_imm_napi.h"

static void ffb_init_norm_funcs(void)
{
	ffb_init_norm();
	ffb_init_norm_rescale();
	ffb_init_norm_normalize();
}

static void choose_normals(void)
{
	GET_CURRENT_CONTEXT(ctx);
	GLuint index;

	if (ctx->Light.Enabled) {
		if (ctx->Transform.Normalize) {
			index = NORM_NORMALIZE;
		} else if (!ctx->Transform.RescaleNormals &&
			   ctx->_ModelViewInvScale != 1.0) {
			index = NORM_RESCALE;
		} else {
			index = 0;
		}

		if (ctx->Light.EnabledList.next == ctx->Light.EnabledList.prev) {
			SET_Normal3f(ctx->Exec, norm_tab[index].normal3f_single);
			SET_Normal3fv(ctx->Exec, norm_tab[index].normal3fv_single);
		} else {
			SET_Normal3f(ctx->Exec, norm_tab[index].normal3f_multi);
			SET_Normal3fv(ctx->Exec, norm_tab[index].normal3fv_multi);
		}
	} else {
		SET_Normal3f(ctx->Exec, _mesa_noop_Normal3f);
		SET_Normal3fv(ctx->Exec, _mesa_noop_Normal3fv);
	}
}

static void ffb_choose_Normal3f(GLfloat x, GLfloat y, GLfloat z)
{
	choose_normals();
	CALL_Normal3f(GET_DISPATCH(), (x, y, z));
}

static void ffb_choose_Normal3fv(const GLfloat *v)
{
	choose_normals();
	CALL_Normal3fv(GET_DISPATCH(), (v));
}

/* Vertex functions: */

#define GET_CURRENT_VERTEX			\
	GET_CURRENT_CONTEXT(ctx);		\
	ffbContextPtr fmesa = FFB_CONTEXT(ctx);	\
	ffbTnlVertexPtr v = fmesa->imm.v0

#define CURRENT_VERTEX		v->obj
#define SAVE_VERTEX		fmesa->imm.save_vertex(ctx, v)

#define TAG(x)	ffb_##x
#include "tnl_dd/t_dd_imm_vapi.h"

struct ffb_vert_tab {
	void (*save_vertex)(GLcontext *ctx, ffbTnlVertexPtr v);
	void (*interpolate_vertex)(GLfloat t,
				   ffbTnlVertex *O,
				   const ffbTnlVertex *I,
				   const ffbTnlVertex *J);
};

static struct ffb_vert_tab vert_tab[0xf];

#define VTX_NORMAL	0x0
#define VTX_RGBA	0x1

#define LOCAL_VARS \
	ffbContextPtr fmesa = FFB_CONTEXT(ctx)

#define CURRENT_COLOR			fmesa->vtx_state.current.color
#define COLOR_IS_FLOAT
#define FLUSH_VERTEX			fmesa->imm.flush_vertex( ctx, v );

#define IND	(VTX_NORMAL)
#define TAG(x)	ffb_##x##_NORMAL
#define PRESERVE_VERTEX_DEFS
#include "tnl_dd/t_dd_imm_vertex.h"

#define IND	(VTX_RGBA)
#define TAG(x)	ffb_##x##_RGBA
#include "tnl_dd/t_dd_imm_vertex.h"

static void ffb_init_vert_funcs( void )
{
	ffb_init_vert_NORMAL();
	ffb_init_vert_RGBA();
}

#define LOCAL_VARS							\
	ffbContextPtr fmesa = FFB_CONTEXT(ctx)

#define GET_INTERP_FUNC							\
	ffb_interp_func interp = fmesa->imm.interp

#define FLUSH_VERTEX			fmesa->imm.flush_vertex
#define IMM_VERTEX( V )			fmesa->imm.V
#define IMM_VERTICES( n )		fmesa->imm.vertices[n]

#define EMIT_VERTEX_USES_HWREGS

/* XXX Implement me XXX */
#define EMIT_VERTEX_TRI(VTX0, VTX1, VTX2)	\
	do { } while (0)
#define EMIT_VERTEX_LINE(VTX0, VTX1)		\
	do { } while (0)
#define EMIT_VERTEX_POINT(VTX0)			\
	do { } while (0)

#define TAG(x)	ffb_##x
#include "tnl_dd/t_dd_imm_primtmp.h"

/* Bzzt: Material changes are lost on fallback. */
static void ffb_Materialfv(GLenum face, GLenum pname,
			   const GLfloat *params)
{
	GET_CURRENT_CONTEXT(ctx);

	_mesa_noop_Materialfv( face, pname, params );
	ffb_recalc_base_color( ctx );
}

/* Fallback functions: */

static void ffb_do_fallback(GLcontext *ctx)
{
	ffbContextPtr fmesa = FFB_CONTEXT(ctx);
	struct ffb_current_state *current = &fmesa->vtx_state.current;

	/* Tell tnl to restore its exec vtxfmt, rehook its driver callbacks
	 * and revive internal state that depended on those callbacks:
	 */
	_tnl_wakeup_exec(ctx);

	/* Replay enough vertices that the current primitive is continued
	 * correctly:
	 */
	if (fmesa->imm.prim != PRIM_OUTSIDE_BEGIN_END )
		CALL_Begin(GET_DISPATCH(), (fmesa->imm.prim));

	if (ctx->Light.Enabled) {
		/* Catch ColorMaterial */
		CALL_Color4fv(GET_DISPATCH(), (ctx->Current.Color));
		CALL_Normal3fv(GET_DISPATCH(), (current->normal));
	} else {
		CALL_Color4fv(GET_DISPATCH(), (current->color));
	}
}

#define PRE_LOOPBACK( FUNC ) do {	\
   GET_CURRENT_CONTEXT(ctx);		\
   ffb_do_fallback( ctx );		\
} while (0)

#define TAG(x) ffb_fallback_##x
#include "vtxfmt_tmp.h"

static void ffb_Begin(GLenum prim)
{
	GET_CURRENT_CONTEXT(ctx);
	ffbContextPtr fmesa = FFB_CONTEXT(ctx);

	if (prim > GL_POLYGON) {
		_mesa_error( ctx, GL_INVALID_ENUM, "glBegin" );
		return;
	}

	if (fmesa->imm.prim != PRIM_OUTSIDE_BEGIN_END) {
		_mesa_error( ctx, GL_INVALID_OPERATION, "glBegin" );
		return;
	}

	ctx->Driver.NeedFlush |= (FLUSH_STORED_VERTICES |
				  FLUSH_UPDATE_CURRENT);

	fmesa->imm.prim = prim;
	fmesa->imm.v0 = &fmesa->imm.vertices[0];
	fmesa->imm.save_vertex = ffb_save_vertex_RGBA;
	fmesa->imm.flush_vertex = ffb_flush_tab[prim];

	/* XXX Lock hardware, update FBC, PPC, DRAWOP, etc. XXX */
}

static void ffb_End(void)
{
	GET_CURRENT_CONTEXT(ctx);
	ffbContextPtr fmesa = FFB_CONTEXT(ctx);

	if (fmesa->imm.prim == PRIM_OUTSIDE_BEGIN_END) {
		_mesa_error( ctx, GL_INVALID_OPERATION, "glEnd" );
		return;
	}

	fmesa->imm.prim = PRIM_OUTSIDE_BEGIN_END;

	ctx->Driver.NeedFlush &= ~(FLUSH_STORED_VERTICES |
				   FLUSH_UPDATE_CURRENT);

	/* XXX Unlock hardware, etc. */
}

void ffbInitTnlModule(GLcontext *ctx)
{
	ffbContextPtr fmesa = FFB_CONTEXT(ctx);
	GLvertexformat *vfmt = &(fmesa->imm.vtxfmt);

	/* Work in progress... */
	return;

	ffb_init_norm_funcs();
	ffb_init_vert_funcs();

	/* start by initializing to no-op functions */
	_mesa_noop_vtxfmt_init(vfmt);

	/* Handled fully in supported states: */
	vfmt->ArrayElement		= NULL;		/* FIXME: ... */
	vfmt->Color3f			= ffb_choose_Color3f;
	vfmt->Color3fv			= ffb_choose_Color3fv;
	vfmt->Color3ub			= ffb_choose_Color3ub;
	vfmt->Color3ubv			= ffb_choose_Color3ubv;
	vfmt->Color4f			= ffb_choose_Color4f;
	vfmt->Color4fv			= ffb_choose_Color4fv;
	vfmt->Color4ub			= ffb_choose_Color4ub;
	vfmt->Color4ubv			= ffb_choose_Color4ubv;
	vfmt->FogCoordfvEXT		= ffb_FogCoordfvEXT;
	vfmt->FogCoordfEXT		= ffb_FogCoordfEXT;
	vfmt->Materialfv		= ffb_Materialfv;
	vfmt->MultiTexCoord1fARB	= ffb_fallback_MultiTexCoord1fARB;
	vfmt->MultiTexCoord1fvARB	= ffb_fallback_MultiTexCoord1fvARB;
	vfmt->MultiTexCoord2fARB	= ffb_fallback_MultiTexCoord2fARB;
	vfmt->MultiTexCoord2fvARB	= ffb_fallback_MultiTexCoord2fvARB;
	vfmt->MultiTexCoord3fARB	= ffb_fallback_MultiTexCoord3fARB;
	vfmt->MultiTexCoord3fvARB	= ffb_fallback_MultiTexCoord3fvARB;
	vfmt->MultiTexCoord4fARB	= ffb_fallback_MultiTexCoord4fARB;
	vfmt->MultiTexCoord4fvARB	= ffb_fallback_MultiTexCoord4fvARB;
	vfmt->Normal3f			= ffb_choose_Normal3f;
	vfmt->Normal3fv			= ffb_choose_Normal3fv;
	vfmt->SecondaryColor3ubEXT	= ffb_SecondaryColor3ubEXT;
	vfmt->SecondaryColor3ubvEXT	= ffb_SecondaryColor3ubvEXT;
	vfmt->SecondaryColor3fEXT	= ffb_SecondaryColor3fEXT;
	vfmt->SecondaryColor3fvEXT	= ffb_SecondaryColor3fvEXT;
	vfmt->TexCoord1f		= ffb_fallback_TexCoord1f;
	vfmt->TexCoord1fv		= ffb_fallback_TexCoord1fv;
	vfmt->TexCoord2f		= ffb_fallback_TexCoord2f;
	vfmt->TexCoord2fv		= ffb_fallback_TexCoord2fv;
	vfmt->TexCoord3f		= ffb_fallback_TexCoord3f;
	vfmt->TexCoord3fv		= ffb_fallback_TexCoord3fv;
	vfmt->TexCoord4f		= ffb_fallback_TexCoord4f;
	vfmt->TexCoord4fv		= ffb_fallback_TexCoord4fv;

	vfmt->Vertex2f			= ffb_Vertex2f;
	vfmt->Vertex2fv			= ffb_Vertex2fv;
	vfmt->Vertex3f			= ffb_Vertex3f;
	vfmt->Vertex3fv			= ffb_Vertex3fv;
	vfmt->Vertex4f			= ffb_Vertex4f;
	vfmt->Vertex4fv			= ffb_Vertex4fv;

	vfmt->Begin			= ffb_Begin;
	vfmt->End			= ffb_End;

	vfmt->DrawArrays = NULL;
	vfmt->DrawElements = NULL;

	/* Active but unsupported -- fallback if we receive these:
	 *
	 * All of these fallbacks can be fixed with additional code, except
	 * CallList, unless we build a play_immediate_noop() command which
	 * turns an immediate back into glBegin/glEnd commands...
	 */
	vfmt->CallList = ffb_fallback_CallList;
	vfmt->EvalCoord1f = ffb_fallback_EvalCoord1f;
	vfmt->EvalCoord1fv = ffb_fallback_EvalCoord1fv;
	vfmt->EvalCoord2f = ffb_fallback_EvalCoord2f;
	vfmt->EvalCoord2fv = ffb_fallback_EvalCoord2fv;
	vfmt->EvalMesh1 = ffb_fallback_EvalMesh1;
	vfmt->EvalMesh2 = ffb_fallback_EvalMesh2;
	vfmt->EvalPoint1 = ffb_fallback_EvalPoint1;
	vfmt->EvalPoint2 = ffb_fallback_EvalPoint2;

	vfmt->prefer_float_colors = GL_TRUE;

	fmesa->imm.prim = PRIM_OUTSIDE_BEGIN_END;

	/* THIS IS A HACK! */
	_mesa_install_exec_vtxfmt( ctx, vfmt );
}
