/****************************************************************************************/
/*  PARSER.C                                                                            */
/*                                                                                      */
/*  Author: Timothy Roff	                                                            */
/*  Description:  Shader implementation                                                 */
/*                                                                                      */
/*  The contents of this file are subject to the Jet3D Public License                   */
/*  Version 1.02 (the "License"); you may not use this file except in                   */
/*  compliance with the License. You may obtain a copy of the License at                */
/*  http://www.jet3d.com                                                                */
/*                                                                                      */
/*  Software distributed under the License is distributed on an "AS IS"                 */
/*  basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See                */
/*  the License for the specific language governing rights and limitations              */
/*  under the License.                                                                  */
/*                                                                                      */
/*  This file was not part of the original Jet3D, released December 12, 1999.           */
/*                                                                                      */
/****************************************************************************************/

#include "jeShaderDefs.h"
#include "JeShader_h.h"
#include "finalize.h"
#include "shader_functions.h"
#include "errorlog.h"
#include <Assert.h>


void expected2(const char* expected, const char* got)
{
   char * temp=NULL;
   sprintf(temp, "expected %s but found '%s'", expected, got);

   jeErrorLog_AddString(JE_ERR_SHADER_SCRIPT, "SH_PARSE: ", temp);
}

/*
jeBoolean fogparmsParseFunc(int argc, char* argv[], shader_info_t *info)
{
   const char *red;
   const char *green;
   const char *blue;
   const char *distance_to_opaque;

   // check arguments
   if (argc == 5) {
      red   = argv[1];
      green = argv[2];
      blue  = argv[3];

      distance_to_opaque = argv[4];
   }
   else if ((argc == 7) &&
            (strcmp(argv[1], "(") == 0) &&
            (strcmp(argv[5], ")") == 0)) {

      red   = argv[2];
      green = argv[3];
      blue  = argv[4];

      distance_to_opaque = argv[6];

   }
   else {
      jeShader_Error("usage: fogparms <red> <green> <blue> <distance to opaque>");
      return JE_TRUE;
   } 

   // color
   ParseColor(info->shader->fog_color, red, green, blue);

   // distance to opaque
   shader_number_parse(distance_to_opaque, &info->shader->fog_distance_to_opaque);

   // finish
   info->shader->flags |= SH_FOGPARMS;

   return JE_TRUE;
}*/



/* portalParseFunc:
 ***************************************************************************/
jeBoolean portalParseFunc(int unused0, char* unused1[], shader_info_t *info)
{
	assert(info != NULL);

   (void)unused0;
   (void)unused1;

   info->shader->sort_priority = SH_SORT_PORTAL;
   info->shader->flags |= SH_EXPLICIT_PORTAL;

   return JE_TRUE;
}



//------------------------------------------
//SORTS - not implemented
//------------------------------------------
typedef struct sort_parse_table_s {
   char* name;
   int   pri;
} sort_parse_table_t;



sort_parse_table_t sort_table[] = {
  /* name          priority      */
   { "portal",     SH_SORT_PORTAL     },
   { "sky",        SH_SORT_SKY        },
   { "opaque",     SH_SORT_OPAQUE     },
   { "banner",     SH_SORT_BANNER     },
   { "underwater", SH_SORT_UNDERWATER },
   { "additive",   SH_SORT_ADDITIVE   },
   { "nearest",    SH_SORT_NEAREST    },
   { NULL,         0             }
};



/* sortParseFunc:
 ***************************************************************************/
jeBoolean sortParseFunc(int unused, char* argv[], shader_info_t *info)
{
	int pri;
	assert(info != NULL);


   (void)unused;

   // sort <priority>
   if (isalpha(argv[1][0])) {
      sort_parse_table_t* table;

      table = sort_table;

      while (table->name) {
         if (stricmp(table->name, argv[1]) == 0) {
            info->shader->sort_priority = table->pri;
            break;
         }

         table++;
      }

      if (!table->name) {
         jeShader_Error("unknown priority");
         return JE_TRUE;
      }
   }
   // sort <#>
   else {
      sh_num_t num;

      shader_number_parse(argv[1], &num);
      pri = (int)num.value;

      if (pri < 1) {
         jeShader_Error("priority must be between 1 and 16, clamping to 1");
         pri = 1;
      }
      else if (pri > 16) {
         jeShader_Error("priority must be between 1 and 16, clamping to 16");
         pri = 16;
      }

      info->shader->sort_priority = pri;
   }

   return JE_TRUE;
}

jeBoolean match(JE_SHADER_TOKEN id, shader_info_t *info)
{
	assert(info != NULL);

   if (id == info->lookahead) {
      info->lookahead = jeShader_Scan(info);
      return JE_TRUE;
   }
   else {
      switch(id) {
         case JE_SHADER_TOKEN_STRING:
            expected("string", info->token.string);
            break;
         case JE_SHADER_TOKEN_EOF:
            expected("<EOF>", info->token.string);
            break;
         case '{':
            expected("{", info->token.string);
            break;
         case '}':
            expected("}", info->token.string);
            break;
         default:
            // assert: this should never happen
            expected("ERROR 404: ", info->token.string);
            break;
      }

      return JE_FALSE;
   }
}

//=====================================================================================
//	DispatchCommandParser
//=====================================================================================
void DispatchCommandParser(int argc, char* argv[JE_SHADER_MAX_ARGS], shader_info_t *info)
{
	command_parse_table_t* command;
	assert(info != NULL);

   for (command = command_table; ; command++) {
      if (!command->name) {
         jeShader_Error("unrecognized command '%s'", argv[0]);
         return;
      }

      if (stricmp(command->name, argv[0]) == 0) {
         if (command->is_ignored) {
         }
         else if (argc > command->max) {
            jeShader_Error("too many arguments for '%s'", argv[0]);
         }
         else if (argc < command->min) {
            jeShader_Error("too few arguments for '%s'", argv[0]);
         }
         else {
            command->Parse(argc, argv, info);
         }

         return;
      }
   }
}

//=====================================================================================
//	ParseArguments
//=====================================================================================
jeBoolean ParseArguments(int* pargc, char* pargv[JE_SHADER_MAX_ARGS], shader_info_t *info)
{
	assert(info != NULL);

   *pargc = 1;
   strncpy(pargv[0], info->token.string, JE_SHADER_TOKEN_LEN);

   match(JE_SHADER_TOKEN_STRING, info);

   while (!info->token.is_newline) {
      if (*pargc < JE_SHADER_MAX_ARGS) {
         strncpy(pargv[*pargc], info->token.string, JE_SHADER_TOKEN_LEN);
		 (*pargc)++;
      }

      if (!match(JE_SHADER_TOKEN_STRING, info)) return JE_FALSE;
   }

   return JE_TRUE;
}


typedef struct arg_s {
   char string[JE_SHADER_TOKEN_LEN+1];
} arg_t;


//=====================================================================================
//	parse_shader_command
//=====================================================================================
jeBoolean parse_shader_command(shader_info_t *info)
{
	int argc;
	arg_t argv_str[JE_SHADER_MAX_ARGS];
	
	char* argv[JE_SHADER_MAX_ARGS] = {
      argv_str[0].string,
      argv_str[1].string,
      argv_str[2].string,
      argv_str[3].string,
      argv_str[4].string,
      argv_str[5].string,
      argv_str[6].string,
      argv_str[7].string,
      argv_str[8].string,
      argv_str[9].string,
      argv_str[10].string,
	  argv_str[11].string,
      argv_str[12].string,
	  argv_str[13].string,
	  argv_str[14].string,
	  argv_str[15].string,
	  argv_str[16].string,
	  argv_str[17].string,
	  argv_str[18].string,
	  argv_str[19].string,
   };

	assert(info != NULL);

	if (ParseArguments(&argc, argv, info)) {
      DispatchCommandParser(argc, argv, info);
      return JE_TRUE;
   }
   else {
      return JE_FALSE;
   }
}


//=====================================================================================
//	initalize_stage
//=====================================================================================
void initialize_stage( shader_info_t *info)
{
	assert(info != NULL);

   if (info->shader->stage_count < JE_SHADER_MAX_STAGES) {
      info->stage = info->shader->stages[info->shader->stage_count];
   }
   else {
      if (info->shader->stage_count == JE_SHADER_MAX_STAGES) {
         jeShader_Error("number of stages exceeds JE_SHADER_MAX_STAGES (%d) in shader '%s'", JE_SHADER_MAX_STAGES, info->shader->name);
      }

      info->stage = info->ignored_stage;
   }

   memset(&info->stage, 0, sizeof(sh_stage_t));
}


//=====================================================================================
//	parse_shader_stage
//=====================================================================================
jeBoolean parse_shader_stage(shader_info_t *info)
{
	assert(info != NULL);

   match('{', info);
   initialize_stage(info);

   while (info->lookahead != '}') {
      if (info->lookahead == JE_SHADER_TOKEN_STRING) {
         parse_shader_command(info);
      }
      else {
         expected("stage command", info->token.string);
         return JE_FALSE;
      }
   }

   finalize_stage(info);
   match('}', info);

   return JE_TRUE;
}


//=====================================================================================
//	parse_shader_type
//=====================================================================================
jeBoolean parse_shader_type(shader_info_t *info)
{
	assert(info != NULL);

	match('{', info);

	while (info->lookahead != '}')
	{

		if (info->lookahead == JE_SHADER_TOKEN_STRING)
		{
			parse_shader_command(info);
		}
		
		else
		{
			expected("type command", info->token.string);
			return JE_FALSE;
		}
	}

	match('}', info);
	
	return JE_TRUE;
}

//=====================================================================================
//	initialize_shader
//=====================================================================================
jeBoolean initialize_shader(const char* name, shader_info_t *info)
{

	shader_t *shader;

	assert(info != NULL);
   
	shader = sh_alloc();
	
	if (shader == NULL) return JE_FALSE;

	memset(shader, 0, sizeof(shader_t));
	strcpy(shader->name, name);

	info->shader = shader;

   return JE_TRUE;
}

//=====================================================================================
//	parse_shader
//=====================================================================================
jeBoolean parse_shader(jeShader_ApproveFunc_t approve, shader_info_t *info)
{
	char name[JE_SHADER_TOKEN_LEN+1];

	assert(info != NULL);
   
   strncpy(name, info->token.string, JE_SHADER_TOKEN_LEN);
   name[JE_SHADER_TOKEN_LEN] = '\0';

   match(JE_SHADER_TOKEN_STRING, info);

   if (strlen(name) <= JE_SHADER_NAME_LEN) {
      if (approve && !approve(name)) return JE_FALSE;

      if (!initialize_shader(name, info)) return JE_FALSE;

	  #ifdef _DEBUG
	  jeShader_Error("parsing shader: ", name);
	  #endif
   }
   else {
	   jeShader_Error("shader name too long", name);
	   return JE_FALSE;
   }

   if (info->lookahead == '{')
   {
      match('{', info);
	  while (info->lookahead != '}')
	  {
         if (info->lookahead == '{')
		 {
            if (!parse_shader_stage(info)) return JE_FALSE;
         }
         else if (info->lookahead == JE_SHADER_TOKEN_STRING)
		 {
            if (!parse_shader_command(info)) return JE_FALSE;
         }
         else
		 {
            expected("global command or stage", info->token.string);
            return JE_FALSE;
         }
      }

      match('}', info);
      finalize_shader(info);
      return JE_TRUE;
   }
   else {
      expected("beginning of shader body", info->token.string);
      return JE_FALSE;
   }
}

//=====================================================================================
//	jeShader_Parse
//=====================================================================================
size_t jeShader_Parse(jeShader_CompileFunc_t compile, jeShader_ApproveFunc_t approve, shader_info_t *info)
{

   int count = 0;

   	assert(info != NULL);

#ifdef _DEBUG
	jeShader_Error("Begining Scan");
#endif
	jeShader_BeginScan(info);
   info->parse_stop = JE_FALSE;

   #ifdef _DEBUG
	jeShader_Error("Setting Lookahead");
	#endif

   info->lookahead  = jeShader_Scan(info);

   while (info->lookahead != JE_SHADER_TOKEN_EOF) {
      if (info->lookahead == JE_SHADER_TOKEN_STRING) {
         #ifdef _DEBUG
		jeShader_Error("Entering parse_shader");
		#endif

		  if (parse_shader(approve, info)) {
            if (compile) compile(info->shader);

            jeShader_AllocKeep();
            count++;

            if (info->parse_stop) {
               return count;
            }
            else {
               continue;
            }
         }
      }
      else {
         expected("shader name", info->token.string);
      }

      jeShader_Skip(info);
   }

   match(JE_SHADER_TOKEN_EOF, info);

   jeShader_EndScan(info);
   info->lookahead = 0;

   return count;
}

//=====================================================================================
//	jeShader_Parse_stop
//=====================================================================================
void jeShader_Parse_stop( shader_info_t *info)
{
	assert(info != NULL);

   info->parse_stop = JE_TRUE;
}


