/** 
 * (c) 2005 by Carlos H. P. da Silva. 
 * This program is free software; you can redistribute it and/or 
 * modify it under the terms of the GNU General Public License 
 * as published by the Free Software Foundation; either version 2 
 * of the License, or (at your option) any later version. 
 * 
 * This program is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
 * 
 * See the GNU General Public License for more details. 
 * 
 * You should have received a copy of the GNU General Public License 
 * along with this program; if not, write to the Free Software 
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. 
 * --------------------------------------------------------------------------- 
 * lamescript.c - LameScript functions implementation 
 **/ 
#include "quakedef.h"
//#include "lamescript.h" 

int      lsErrorCode = 0; 
/** 
 *  Removes comments, linefeeds 
 *  and trailling spaces from 
 *  original source text (except for 
 *  comma-separated '"' expressions) 
 **/ 
token_t   *clearText (token_t *input) 
{ 
   token_t   *output = NULL; 
   int      count = 0; 
   int      skip = 0; 
   char   *i, *o; 
   int      newLine = 0, comment = 0, comma = 0; 

   if ((input != NULL) || (input->len > 0)) 
   { 
      output = malloc (sizeof(token_t)); 
      output->text = malloc (input->len); 
      memset (output->text, '\0', input->len); 
      output->len = 0; 
      count = input->len; 
      i = input->text; 
      o = output->text; 
      while (count) 
      { 
         skip = 0; 
         switch (*i) 
         { 
            case   '#': 
               if (newLine) 
               { 
                  newLine = 0; 
               } 

               comment = 1; 
            break; 

            case   '"': 
               { 
                  comma = !comma; 
                  i++; 
               } 
            break; 

            case   '\n': 
            case   '\r': 
               if (comment) 
               { 
                  comment = 0; 
               } 

               newLine = 1; 
            break; 

            case   ' ': 
            case   '\t': 
               skip = 1; 
            break; 

            default: 
               if ((*i < 32) || (*i > 126)) 
               { 
                  skip = 1; 
               } 

               newLine = 0; 
         } 

         if ((comment) || (newLine)) 
         { 
            skip = 1; 
         } 
         else if (comma) 
         { 
            skip = 0; 
         } 

         if (!skip) 
         { 
            *o++ = *i; 
            output->len++; 
         } 

         i++; 
         count--; 
      } 
   } 

   return(output); 
} 

/** 
 *  Scans the source text for substrings 
 **/ 
token_t   *getToken (char *src, char separator) 
{ 
   token_t   *t; 
   char   *p; 

   if ((src == NULL) || (strlen(src) < 1)) 
   { 
      return(NULL); 
   } 

   t = malloc (sizeof(token_t)); 
   t->text = NULL; 
   t->len = 0; 
   p = src; 
   while ((*p != '\0') && (*p != separator)) 
   { 
      if (t->text == NULL) 
      { 
         t->text = src; 
      } 

      p++; 
      t->len++; 
   } 

   if (t->text != NULL) 
   { 
      *p = '\0'; 
      return(t); 
   } 
   else 
   { 
      return(NULL); 
   } 
} 

/** 
 *  Scans the source text for pairs key=value; 
 **/ 
pair_t   *getPair (char *src) 
{ 
   pair_t   *p = NULL; 
   char   *c; 

   if (src != NULL) 
   { 
      c = src; 
      p = malloc (sizeof(pair_t)); 
      p->key = getToken (c, '='); 
      if (p->key == NULL) 
      { 
         return (NULL); 
      } 

      c += p->key->len; 
      *c = '\0'; 
      c++; 
      p->values = getToken (c, ';'); 
      if (p->values == NULL) 
      { 
         return (NULL); 
      } 

      c += p->values->len; 
      *c = '\0'; 
      c = p->values->text; 
      p->numValues = 1; 
      while (*c != '\0') 
      { 
         if (*c == ',') 
         { 
            p->numValues++; 
         } 

         c++; 
      } 

      if (!p->key->len) 
      { 
         lsErrorCode = LSERR_PAIRNOKEY; 
         return (NULL); 
      } 
      else if (!p->values->len) 
      { 
         lsErrorCode = LSERR_PAIRNOVALUE; 
         return (NULL); 
      } 
   } 

   return(p); 
} 

/** 
 *  Scans the source text for sections 
 **/ 
section_t   *getSection (char *src) 
{ 
   section_t   *s = NULL; 
   char      *p; 
   int         c; 

   if (src != NULL) 
   { 
      s = malloc (sizeof(section_t));          
      s->numPairs = 0; 
      p = src; 
      c = 0; 
      s->name = src; 
      while ((*p != '\0') && (*p != '{')) 
      { 
         p++; 
         c++; 
      } 

      *p = '\0'; 
      p++; 
      c = 0; 
      s->data = p; 
      while ((*p != '\0') && (*p != '}')) 
      { 
         p++; 
         c++; 
      } 

      *p = '\0'; 
   } 

   return(s); 
} 


/** 
 *  Loads a LameScript file 
 *  Several checkings are made 
 *  TODO: make a global error var and 
 *  report errors using it 
 **/ 
ls_t   *loadLameScript (vfsfile_t *script) 
{ 
   token_t      *t; 
   section_t   *s;
   ls_t      *result = NULL; 
   int         i, j, k, r, count = 0; 
   char      *p; 

   lsErrorCode = 0; 
   t = malloc(sizeof(token_t)); 
   t->len = VFS_GETLEN(script);
   t->text = malloc (t->len); 
//   fread (t->text, t->len, 1, script); 
   VFS_READ (script, t->text, t->len);
//   fclose (script); 
   VFS_CLOSE (script);

   // clean the script text 
   t = clearText (t); 

   // makes a copy because getSection destroys 
   // the text buffer contents 
   p = malloc (t->len + 1); 
   sprintf (p, "%s\0", t->text); 

   // determine the number of sections 
   i = 0; 
   count = 0; 
   while (p[i] != '\0') 
   { 
      s = getSection (&p[i]); 
      if (strlen(s->name) == 0) 
      { 
         lsErrorCode = LSERR_SECTNONAME; 
         return(NULL); 
      } 

      if (strlen(s->data) == 0) 
      { 
         lsErrorCode = LSERR_SECTNODATA; 
         return(NULL); 
      } 

      i += strlen(s->name) + strlen(s->data) + 2; 
      count++; 
   } 

   if (!count) 
   { 
      lsErrorCode = LSERR_NOSECTIONS; 
      return(NULL); 
   } 

   // releases the copy 
   free (p); 
   result = malloc (sizeof(ls_t)); 
   result->sections = malloc (sizeof(section_t *) * count); 
   result->numSections = count; 
   count = 0; 
   i = 0; 
   while (t->text[i] != '\0') 
   { 
      result->sections[count] = getSection(&t->text[i]); 

      // check for duplicated sections 
      for (r = 0; r < count; r++) 
      { 
         if (!strcmp(result->sections[count]->name, result->sections[r]->name)) 
         { 
            lsErrorCode = LSERR_REDEFSECTION; 
            return (NULL); 
         } 
      } 


      i += strlen(result->sections[count]->name) + strlen(result->sections[count]->data) + 2; 
      // counts all pairs into the section 
      p = result->sections[count]->data; 
      j = 0; 
      while (*p != '\0') 
      { 
         if (*p == '=') 
         { 
            j++; 
         } 

         p++; 
      } 

      if (!j) 
      { 
         lsErrorCode = LSERR_SECTBADDATA; 
         return (NULL); 
      } 

      result->sections[count]->pairs = malloc (sizeof(pair_t *) * j); 
      result->sections[count]->numPairs = j; 
      j = 0; 
      k = 0; 
      while (result->sections[count]->data[k] != '\0') 
      { 
         if 
         ( 
            ((result->sections[count]->pairs[j] = getPair (&result->sections[count]->data[k])) == NULL) || 
            (result->sections[count]->pairs[j]->key->len == 0) || 
            (result->sections[count]->pairs[j]->values->len == 0) 
         ) 
         { 
            lsErrorCode = LSERR_SECTBADDATA; 
            return (NULL); 
         } 
          
         for (r = 0; r < j; r++) 
         { 
            if (!strcmp(result->sections[count]->pairs[j]->key->text, result->sections[count]->pairs[r]->key->text)) 
            { 
               lsErrorCode = LSERR_REDEFKEY; 
               return (NULL); 
            } 
         } 

         k += result->sections[count]->pairs[j]->key->len + result->sections[count]->pairs[j]->values->len + 2; 
         j++; 
      } 


      count++; 
   } 

   return(result); 
} 

/** 
 *  Returns the section index into the script 
 *  or -1 if this section does not exist 
 **/ 
int      hasSection (ls_t *script, char *target) 
{ 
   int   r; 

   if (script != NULL) 
   { 
      if (script->sections != NULL) 
      { 
         for (r = 0; r < script->numSections; r++) 
         { 
            if (script->sections[r]->name != NULL) 
            { 
               if (!strcmp (target, script->sections[r]->name)) 
               { 
                  return r; 
               } 
            } 
         } 
      } 
   } 

   return -1; 
} 

/** 
 * Returns the index to the given section/key combination 
 * or -1 if there is no such combination 
 **/ 
int      hasKey (ls_t *script, char *section, char *target) 
{ 
   int sect;
   int r; 
   sect = hasSection (script, section); 
   if (sect != -1) 
   { 
      if (script->sections[sect]->pairs != NULL) 
      { 
         for (r = 0; r < script->sections[sect]->numPairs; r++) 
         { 
            if (!strcmp (target, script->sections[sect]->pairs[r]->key->text)) 
            {
				//found it
               return r; 
            } 
         } 
      } 
   } 

   return -1; 
} 

// hasKey2 when we already have the sectnum like in getValue
int      hasKey2 (ls_t *script, int sectnum, char *target) 
{ 
	int r;

	if (sectnum != -1)
	{ 
		if (script->sections[sectnum]->pairs != NULL) 
		{ 
			for (r = 0; r < script->sections[sectnum]->numPairs; r++) 
			{ 
				if (!strcmp (target, script->sections[sectnum]->pairs[r]->key->text)) 
				{ 
					//found it
					return r;
				} 
			} 
		} 
	} 

	return -1; 
} 

/** 
 * Returns the value for the desired section and key 
 * Allows to select one into several values associated to a key 
 **/ 
char   *getValue (ls_t *script, char *section, char *key, int index) 
{ 
   int is, ik, i, c; 
   char *result = NULL; 

   is = hasSection (script, section); 
   ik = hasKey (script, section, key); 
   if ((is != -1) && (ik != -1)) 
   { 
      if (index > script->sections[is]->pairs[ik]->numValues) 
      { 
         return (NULL); 
      } 

      i = 0; 
      c = 0; 
      while (i < script->sections[is]->pairs[ik]->values->len) 
      { 
         if (c == index) 
         { 
            c = script->sections[is]->pairs[ik]->values->len - i + 1; 
            result = malloc (c); 
            memset (result, '\0', c); 
            c = 0; 
            while 
            ( 
               (script->sections[is]->pairs[ik]->values->text[i] != ',') && 
               (script->sections[is]->pairs[ik]->values->text[i] != ';') && 
               (script->sections[is]->pairs[ik]->values->text[i] != '\0') 
            ) 
            { 
               result[c++] = script->sections[is]->pairs[ik]->values->text[i++]; 
            } 

            break; 
         } 
         else if (script->sections[is]->pairs[ik]->values->text[i] == ',') 
         { 
            c++;             
         } 

         i++; 
      } 
   } 

   return (result); 
} 

/** 
 *  Checks if a value exists for the 
 *  specified section/key combination. 
 *  Wildcards (*) are supported only 
 *  in the value list, not the target 
 **/ 
int      hasValue (ls_t *script, char *section, char *key, char *value) 
{ 
   char      *listItem; 
   int         result = -1; 
   unsigned   len, i, count = 0; 

   listItem = getValue (script, section, key, count); 
   while ((listItem != NULL) && (result == -1)) 
   { 
      len = strlen (listItem); 
      if (strlen (value) < len) 
      { 
         len = strlen (value); 
      } 

      i = 0; 
      while (i < len) 
      { 
         if (listItem[i] == '*') 
         { 
            result = count; 
            i = len; 
         } 
         else if (listItem[i] != value[i]) 
         { 
            i = len; 
         } 
         else if (i == len - 1) 
         { 
            result = count; 
         } 

         i++; 
      } 

      listItem = getValue (script, section, key, ++count); 
   } 

   return (result); 
} 
