/**
 * \file atifragshader.c
 * \author David Airlie
 * Copyright (C) 2004  David Airlie   All Rights Reserved.
 *
 * 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 AIRLIE 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.
 */

#include "glheader.h"
#include "context.h"
#include "hash.h"
#include "imports.h"
#include "macros.h"
#include "enums.h"
#include "mtypes.h"
#include "atifragshader.h"

#define MESA_DEBUG_ATI_FS 0

extern struct program _mesa_DummyProgram;

static void
new_inst(struct ati_fragment_shader *prog)
{
   prog->Base.NumInstructions++;
}

#if MESA_DEBUG_ATI_FS
static char *
create_dst_mod_str(GLuint mod)
{
   static char ret_str[1024];

   _mesa_memset(ret_str, 0, 1024);
   if (mod & GL_2X_BIT_ATI)
      _mesa_strncat(ret_str, "|2X", 1024);

   if (mod & GL_4X_BIT_ATI)
      _mesa_strncat(ret_str, "|4X", 1024);

   if (mod & GL_8X_BIT_ATI)
      _mesa_strncat(ret_str, "|8X", 1024);
   if (mod & GL_HALF_BIT_ATI)
      _mesa_strncat(ret_str, "|HA", 1024);
   if (mod & GL_QUARTER_BIT_ATI)
      _mesa_strncat(ret_str, "|QU", 1024);
   if (mod & GL_EIGHTH_BIT_ATI)
      _mesa_strncat(ret_str, "|EI", 1024);

   if (mod & GL_SATURATE_BIT_ATI)
      _mesa_strncat(ret_str, "|SAT", 1024);

   if (_mesa_strlen(ret_str) == 0)
      _mesa_strncat(ret_str, "NONE", 1024);
   return ret_str;
}

static char *atifs_ops[] = {"ColorFragmentOp1ATI", "ColorFragmentOp2ATI", "ColorFragmentOp3ATI", 
			    "AlphaFragmentOp1ATI", "AlphaFragmentOp2ATI", "AlphaFragmentOp3ATI" };

static void debug_op(GLint optype, GLuint arg_count, GLenum op, GLuint dst,
		     GLuint dstMask, GLuint dstMod, GLuint arg1,
		     GLuint arg1Rep, GLuint arg1Mod, GLuint arg2,
		     GLuint arg2Rep, GLuint arg2Mod, GLuint arg3,
		     GLuint arg3Rep, GLuint arg3Mod)
{
  char *op_name;

  op_name = atifs_ops[(arg_count-1)+(optype?3:0)];
  
  fprintf(stderr, "%s(%s, %s", op_name, _mesa_lookup_enum_by_nr(op),
	      _mesa_lookup_enum_by_nr(dst));
  if (!optype)
    fprintf(stderr, ", %d", dstMask);
  
  fprintf(stderr, ", %s", create_dst_mod_str(dstMod));
  
  fprintf(stderr, ", %s, %s, %d", _mesa_lookup_enum_by_nr(arg1),
	      _mesa_lookup_enum_by_nr(arg1Rep), arg1Mod);
  if (arg_count>1)
    fprintf(stderr, ", %s, %s, %d", _mesa_lookup_enum_by_nr(arg2),
	      _mesa_lookup_enum_by_nr(arg2Rep), arg2Mod);
  if (arg_count>2)
    fprintf(stderr, ", %s, %s, %d", _mesa_lookup_enum_by_nr(arg3),
	      _mesa_lookup_enum_by_nr(arg3Rep), arg3Mod);

  fprintf(stderr,")\n");

}
#endif

GLuint GLAPIENTRY
_mesa_GenFragmentShadersATI(GLuint range)
{
   GLuint first;
   GLuint i;
   GET_CURRENT_CONTEXT(ctx);

   first = _mesa_HashFindFreeKeyBlock(ctx->Shared->Programs, range);
   for (i = 0; i < range; i++) {
      _mesa_HashInsert(ctx->Shared->Programs, first + i, &_mesa_DummyProgram);
   }

   return first;
}

void GLAPIENTRY
_mesa_BindFragmentShaderATI(GLuint id)
{
   struct program *prog;
   GET_CURRENT_CONTEXT(ctx);
   struct ati_fragment_shader *curProg = ctx->ATIFragmentShader.Current;

   FLUSH_VERTICES(ctx, _NEW_PROGRAM);

   if (curProg->Base.Id == id) {
      return;
   }

   if (curProg->Base.Id != 0) {
      curProg->Base.RefCount--;
      if (curProg->Base.RefCount <= 0) {
	 _mesa_HashRemove(ctx->Shared->Programs, id);
      }
   }

   /* Go bind */
   if (id == 0) {
      prog = ctx->Shared->DefaultFragmentShader;
   }
   else {
      prog = (struct program *) _mesa_HashLookup(ctx->Shared->Programs, id);
      if (!prog || prog == &_mesa_DummyProgram) {
	 /* allocate a new program now */
	 prog = ctx->Driver.NewProgram(ctx, GL_FRAGMENT_SHADER_ATI, id);
	 if (!prog) {
	    _mesa_error(ctx, GL_OUT_OF_MEMORY, "glBindFragmentShaderATI");
	    return;
	 }
	 _mesa_HashInsert(ctx->Shared->Programs, id, prog);
      }

   }

   /* do actual bind */
   ctx->ATIFragmentShader.Current = (struct ati_fragment_shader *) prog;

   ASSERT(ctx->ATIFragmentShader.Current);
   if (prog)
      prog->RefCount++;

   /*if (ctx->Driver.BindProgram)
      ctx->Driver.BindProgram(ctx, target, prog); */
}

void GLAPIENTRY
_mesa_DeleteFragmentShaderATI(GLuint id)
{
   GET_CURRENT_CONTEXT(ctx);

   if (id != 0) {
      struct program *prog = (struct program *)
	 _mesa_HashLookup(ctx->Shared->Programs, id);
      if (prog == &_mesa_DummyProgram) {
	 _mesa_HashRemove(ctx->Shared->Programs, id);
      }
      else if (prog) {
	 if (ctx->ATIFragmentShader.Current &&
	     ctx->ATIFragmentShader.Current->Base.Id == id) {
	    _mesa_BindFragmentShaderATI(0);
	 }
      }
#if 0
      if (!prog->DeletePending) {
	 prog->DeletePending = GL_TRUE;
	 prog->RefCount--;
      }
      if (prog->RefCount <= 0) {
	 _mesa_HashRemove(ctx->Shared->Programs, id);
	 ctx->Driver.DeleteProgram(ctx, prog);
      }
#else
      /* The ID is immediately available for re-use now */
      _mesa_HashRemove(ctx->Shared->Programs, id);
      prog->RefCount--;
      if (prog->RefCount <= 0) {
         ctx->Driver.DeleteProgram(ctx, prog);
      }
#endif
   }
}

void GLAPIENTRY
_mesa_BeginFragmentShaderATI(void)
{
   GET_CURRENT_CONTEXT(ctx);

   /* malloc the instructions here - not sure if the best place but its
      a start */
   ctx->ATIFragmentShader.Current->Instructions =
      (struct atifs_instruction *)
      _mesa_calloc(sizeof(struct atifs_instruction) * MAX_NUM_PASSES_ATI *
		   MAX_NUM_INSTRUCTIONS_PER_PASS_ATI * 2);

   ctx->ATIFragmentShader.Current->cur_pass = 0;
   ctx->ATIFragmentShader.Compiling = 1;
}

void GLAPIENTRY
_mesa_EndFragmentShaderATI(void)
{
   GET_CURRENT_CONTEXT(ctx);
#if MESA_DEBUG_ATI_FS
   struct ati_fragment_shader *curProg = ctx->ATIFragmentShader.Current;
   GLint i;
#endif

   ctx->ATIFragmentShader.Compiling = 0;
   ctx->ATIFragmentShader.Current->NumPasses = ctx->ATIFragmentShader.Current->cur_pass;
   ctx->ATIFragmentShader.Current->cur_pass=0;
#if MESA_DEBUG_ATI_FS
   for (i = 0; i < curProg->Base.NumInstructions; i++) {
      GLuint op0 = curProg->Instructions[i].Opcode[0];
      GLuint op1 = curProg->Instructions[i].Opcode[1];
      const char *op0_enum = op0 > 5 ? _mesa_lookup_enum_by_nr(op0) : "0";
      const char *op1_enum = op1 > 5 ? _mesa_lookup_enum_by_nr(op1) : "0";
      GLuint count0 = curProg->Instructions[i].ArgCount[0];
      GLuint count1 = curProg->Instructions[i].ArgCount[1];

      fprintf(stderr, "%2d %04X %s %d %04X %s %d\n", i, op0, op0_enum, count0,
	      op1, op1_enum, count1);
   }
#endif
}

void GLAPIENTRY
_mesa_PassTexCoordATI(GLuint dst, GLuint coord, GLenum swizzle)
{
   GET_CURRENT_CONTEXT(ctx);
   struct ati_fragment_shader *curProg = ctx->ATIFragmentShader.Current;
   GLint ci;
   struct atifs_instruction *curI;

   if (ctx->ATIFragmentShader.Current->cur_pass==1)
     ctx->ATIFragmentShader.Current->cur_pass=2;

   new_inst(curProg);
   ci = curProg->Base.NumInstructions - 1;
   /* some validation 
      if ((swizzle != GL_SWIZZLE_STR_ATI) ||
      (swizzle != GL_SWIZZLE_STQ_ATI) ||
      (swizzle != GL_SWIZZLE_STR_DR_ATI) ||
      (swizzle != GL_SWIZZLE_STQ_DQ_ATI))
    */

   /* add the instructions */
   curI = &curProg->Instructions[ci];

   curI->Opcode[0] = ATI_FRAGMENT_SHADER_PASS_OP;
   curI->DstReg[0].Index = dst;
   curI->SrcReg[0][0].Index = coord;
   curI->DstReg[0].Swizzle = swizzle;

#if MESA_DEBUG_ATI_FS
   _mesa_debug(ctx, "%s(%s, %s, %s)\n", __FUNCTION__,
	       _mesa_lookup_enum_by_nr(dst), _mesa_lookup_enum_by_nr(coord),
	       _mesa_lookup_enum_by_nr(swizzle));
#endif
}

void GLAPIENTRY
_mesa_SampleMapATI(GLuint dst, GLuint interp, GLenum swizzle)
{
   GET_CURRENT_CONTEXT(ctx);
   struct ati_fragment_shader *curProg = ctx->ATIFragmentShader.Current;
   GLint ci;
   struct atifs_instruction *curI;

   if (ctx->ATIFragmentShader.Current->cur_pass==1)
     ctx->ATIFragmentShader.Current->cur_pass=2;


   new_inst(curProg);

   ci = curProg->Base.NumInstructions - 1;
   /* add the instructions */
   curI = &curProg->Instructions[ci];

   curI->Opcode[0] = ATI_FRAGMENT_SHADER_SAMPLE_OP;
   curI->DstReg[0].Index = dst;
   curI->DstReg[0].Swizzle = swizzle;

   curI->SrcReg[0][0].Index = interp;

#if MESA_DEBUG_ATI_FS
   _mesa_debug(ctx, "%s(%s, %s, %s)\n", __FUNCTION__,
	       _mesa_lookup_enum_by_nr(dst), _mesa_lookup_enum_by_nr(interp),
	       _mesa_lookup_enum_by_nr(swizzle));
#endif
}

static void
_mesa_FragmentOpXATI(GLint optype, GLuint arg_count, GLenum op, GLuint dst,
		     GLuint dstMask, GLuint dstMod, GLuint arg1,
		     GLuint arg1Rep, GLuint arg1Mod, GLuint arg2,
		     GLuint arg2Rep, GLuint arg2Mod, GLuint arg3,
		     GLuint arg3Rep, GLuint arg3Mod)
{
   GET_CURRENT_CONTEXT(ctx);
   struct ati_fragment_shader *curProg = ctx->ATIFragmentShader.Current;
   GLint ci;
   struct atifs_instruction *curI;

   if (ctx->ATIFragmentShader.Current->cur_pass==0)
     ctx->ATIFragmentShader.Current->cur_pass=1;

   /* decide whether this is a new instruction or not ... all color instructions are new */
   if (optype == 0)
      new_inst(curProg);

   ci = curProg->Base.NumInstructions - 1;

   /* add the instructions */
   curI = &curProg->Instructions[ci];

   curI->Opcode[optype] = op;

   curI->SrcReg[optype][0].Index = arg1;
   curI->SrcReg[optype][0].argRep = arg1Rep;
   curI->SrcReg[optype][0].argMod = arg1Mod;
   curI->ArgCount[optype] = arg_count;

   if (arg2) {
      curI->SrcReg[optype][1].Index = arg2;
      curI->SrcReg[optype][1].argRep = arg2Rep;
      curI->SrcReg[optype][1].argMod = arg2Mod;
   }

   if (arg3) {
      curI->SrcReg[optype][2].Index = arg3;
      curI->SrcReg[optype][2].argRep = arg3Rep;
      curI->SrcReg[optype][2].argMod = arg3Mod;
   }

   curI->DstReg[optype].Index = dst;
   curI->DstReg[optype].dstMod = dstMod;
   curI->DstReg[optype].dstMask = dstMask;
   
#if MESA_DEBUG_ATI_FS
   debug_op(optype, arg_count, op, dst, dstMask, dstMod, arg1, arg1Rep, arg1Mod, arg2, arg2Rep, arg2Mod, arg3, arg3Rep, arg3Mod);
#endif

}

void GLAPIENTRY
_mesa_ColorFragmentOp1ATI(GLenum op, GLuint dst, GLuint dstMask,
			  GLuint dstMod, GLuint arg1, GLuint arg1Rep,
			  GLuint arg1Mod)
{
   _mesa_FragmentOpXATI(ATI_FRAGMENT_SHADER_COLOR_OP, 1, op, dst, dstMask,
			dstMod, arg1, arg1Rep, arg1Mod, 0, 0, 0, 0, 0, 0);
}

void GLAPIENTRY
_mesa_ColorFragmentOp2ATI(GLenum op, GLuint dst, GLuint dstMask,
			  GLuint dstMod, GLuint arg1, GLuint arg1Rep,
			  GLuint arg1Mod, GLuint arg2, GLuint arg2Rep,
			  GLuint arg2Mod)
{
   _mesa_FragmentOpXATI(ATI_FRAGMENT_SHADER_COLOR_OP, 2, op, dst, dstMask,
			dstMod, arg1, arg1Rep, arg1Mod, arg2, arg2Rep,
			arg2Mod, 0, 0, 0);
}

void GLAPIENTRY
_mesa_ColorFragmentOp3ATI(GLenum op, GLuint dst, GLuint dstMask,
			  GLuint dstMod, GLuint arg1, GLuint arg1Rep,
			  GLuint arg1Mod, GLuint arg2, GLuint arg2Rep,
			  GLuint arg2Mod, GLuint arg3, GLuint arg3Rep,
			  GLuint arg3Mod)
{
   _mesa_FragmentOpXATI(ATI_FRAGMENT_SHADER_COLOR_OP, 3, op, dst, dstMask,
			dstMod, arg1, arg1Rep, arg1Mod, arg2, arg2Rep,
			arg2Mod, arg3, arg3Rep, arg3Mod);
}

void GLAPIENTRY
_mesa_AlphaFragmentOp1ATI(GLenum op, GLuint dst, GLuint dstMod, GLuint arg1,
			  GLuint arg1Rep, GLuint arg1Mod)
{
   _mesa_FragmentOpXATI(ATI_FRAGMENT_SHADER_ALPHA_OP, 1, op, dst, 0, dstMod,
			arg1, arg1Rep, arg1Mod, 0, 0, 0, 0, 0, 0);
}

void GLAPIENTRY
_mesa_AlphaFragmentOp2ATI(GLenum op, GLuint dst, GLuint dstMod, GLuint arg1,
			  GLuint arg1Rep, GLuint arg1Mod, GLuint arg2,
			  GLuint arg2Rep, GLuint arg2Mod)
{
   _mesa_FragmentOpXATI(ATI_FRAGMENT_SHADER_ALPHA_OP, 2, op, dst, 0, dstMod,
			arg1, arg1Rep, arg1Mod, arg2, arg2Rep, arg2Mod, 0, 0,
			0);
}

void GLAPIENTRY
_mesa_AlphaFragmentOp3ATI(GLenum op, GLuint dst, GLuint dstMod, GLuint arg1,
			  GLuint arg1Rep, GLuint arg1Mod, GLuint arg2,
			  GLuint arg2Rep, GLuint arg2Mod, GLuint arg3,
			  GLuint arg3Rep, GLuint arg3Mod)
{
   _mesa_FragmentOpXATI(ATI_FRAGMENT_SHADER_ALPHA_OP, 3, op, dst, 0, dstMod,
			arg1, arg1Rep, arg1Mod, arg2, arg2Rep, arg2Mod, arg3,
			arg3Rep, arg3Mod);
}

void GLAPIENTRY
_mesa_SetFragmentShaderConstantATI(GLuint dst, const GLfloat * value)
{
   GET_CURRENT_CONTEXT(ctx);
   GLuint dstindex = dst - GL_CON_0_ATI;
   struct ati_fragment_shader *curProg = ctx->ATIFragmentShader.Current;

   COPY_4V(curProg->Constants[dstindex], value);
}