/*
 * See Licensing and Copyright notice in naev.h
 */

/**
 * @file nlua_var.c
 *
 * @brief Lua Variable module.
 */


#include "nlua_var.h"

#include "naev.h"

#include <stdlib.h>
#include <stdio.h>
#include "nstring.h"
#include <math.h>

#include <lua.h>
#include <lauxlib.h>

#include "nluadef.h"
#include "log.h"
#include "nxml.h"



/* similar to Lua vars, but with less variety */
#define MISN_VAR_NIL    0 /**< Nil type. */
#define MISN_VAR_NUM    1 /**< Number type. */
#define MISN_VAR_BOOL   2 /**< Boolean type. */
#define MISN_VAR_STR    3 /**< String type. */
/**
 * @struct misn_var
 *
 * @brief Contains a mission variable.
 */
typedef struct misn_var_ {
   char* name; /**< Name of the variable. */
   char type; /**< Type of the variable. */
   union {
      double num; /**< Used if type is number. */
      char* str; /**< Used if type is string. */
      int b; /**< Used if type is boolean. */
   } d; /**< Variable data. */
} misn_var;


/*
 * variable stack
 */
static misn_var* var_stack = NULL; /**< Stack of mission variables. */
static int var_nstack      = 0; /**< Number of mission variables. */
static int var_mstack      = 0; /**< Memory size of the mission variable stack. */


/*
 * prototypes
 */
/* static */
static int var_add( misn_var *var );
static void var_free( misn_var* var );
/* externed */
int var_save( xmlTextWriterPtr writer );
int var_load( xmlNodePtr parent );


/* var */
static int var_peek( lua_State *L );
static int var_pop( lua_State *L );
static int var_push( lua_State *L );
static const luaL_Reg var_methods[] = {
   { "peek", var_peek },
   { "pop", var_pop },
   { "push", var_push },
   {0,0}
}; /**< Mission variable Lua methods. */


/**
 * @brief Loads the mission variable Lua library.
 *    @param env Lua environment.
 *    @return 0 on success.
 */
int nlua_loadVar( nlua_env env )
{
   nlua_register(env, "var", var_methods, 0);
   return 0;
}


/**
 * @brief Saves the mission variables.
 *
 *    @param writer XML Writer to use.
 *    @return 0 on success.
 */
int var_save( xmlTextWriterPtr writer )
{
   int i;

   xmlw_startElem(writer,"vars");

   for (i=0; i<var_nstack; i++) {
      xmlw_startElem(writer,"var");

      xmlw_attr(writer,"name","%s",var_stack[i].name);

      switch (var_stack[i].type) {
         case MISN_VAR_NIL:
            xmlw_attr(writer,"type","nil");
            break;
         case MISN_VAR_NUM:
            xmlw_attr(writer,"type","num");
            xmlw_str(writer,"%f",var_stack[i].d.num);
            break;
         case MISN_VAR_BOOL:
            xmlw_attr(writer,"type","bool");
            xmlw_str(writer,"%d",var_stack[i].d.b);
            break;
         case MISN_VAR_STR:
            xmlw_attr(writer,"type","str");
            xmlw_str(writer,"%s",var_stack[i].d.str);
            break;
      }

      xmlw_endElem(writer); /* "var" */
   }

   xmlw_endElem(writer); /* "vars" */

   return 0;
}


/**
 * @brief Loads the vars from XML file.
 *
 *    @param parent Parent node containing the variables.
 *    @return 0 on success.
 */
int var_load( xmlNodePtr parent )
{
   char *str;
   xmlNodePtr node, cur;
   misn_var var;

   var_cleanup();

   node = parent->xmlChildrenNode;

   do {
      if (xml_isNode(node,"vars")) {
         cur = node->xmlChildrenNode;

         do {
            if (xml_isNode(cur,"var")) {
               xmlr_attr(cur,"name",var.name);
               xmlr_attr(cur,"type",str);
               if (strcmp(str,"nil")==0)
                  var.type = MISN_VAR_NIL;
               else if (strcmp(str,"num")==0) {
                  var.type = MISN_VAR_NUM;
                  var.d.num = xml_getFloat(cur);
               }
               else if (strcmp(str,"bool")==0) {
                  var.type = MISN_VAR_BOOL;
                  var.d.b = xml_getInt(cur);
               }
               else if (strcmp(str,"str")==0) {
                  var.type = MISN_VAR_STR;
                  var.d.str = xml_getStrd(cur);
               }
               else { /* super error checking */
                  WARN(_("Unknown var type '%s'"), str);
                  free(var.name);
                  continue;
               }
               free(str);
               var_add( &var );
            }
         } while (xml_nextNode(cur));
      }
   } while (xml_nextNode(node));

   return 0;
}


/**
 * @brief Adds a var to the stack, strings will be SHARED, don't free.
 *
 *    @param new_var Variable to add.
 *    @return 0 on success.
 */
static int var_add( misn_var *new_var )
{
   int i;

   if (var_nstack+1 > var_mstack) { /* more memory */
      var_mstack += 64; /* overkill ftw */
      var_stack = realloc( var_stack, var_mstack * sizeof(misn_var) );
   }

   /* check if already exists */
   for (i=0; i<var_nstack; i++)
      if (strcmp(new_var->name,var_stack[i].name)==0) { /* overwrite */
         var_free( &var_stack[i] );
         var_stack[i] = *new_var;
         return 0;
      }

   var_stack[var_nstack] = *new_var;
   var_nstack++;

   return 0;
}


/**
 * @brief Mission variable Lua bindings.
 *
 * Mission variables are similar to Lua variables, but are conserved for each
 *  player across all the missions.  They are good for storing campaign or
 *  other global values.
 *
 * Typical usage would be:
 * @code
 * v = var.peek( "es_misn" ) -- Get the value
 * if v == nil then -- Doesn't exist, so create
 *    var.push( "es_misn", 1 )
 * else
 *    var.push( "es_misn", v+1 ) -- Increment value
 * end
 * @endcode
 *
 * @luamod var
 */
/**
 * @brief Checks to see if a mission var exists.
 *
 *    @param str Name of the mission var.
 *    @return 1 if it exists, 0 if it doesn't.
 */
int var_checkflag( char* str )
{
   int i;

   for (i=0; i<var_nstack; i++)
      if (strcmp(var_stack[i].name,str)==0)
         return 1;
   return 0;
}
/**
 * @brief Gets the mission variable value of a certain name.
 *
 *    @luatparam string name Name of the mission variable to get.
 *    @luareturn The value of the mission variable which will depend on what type
 *             it is.
 * @luafunc peek( name )
 */
static int var_peek( lua_State *L )
{
   int i;
   const char *str;

   /* Get the parameter. */
   str = luaL_checkstring(L,1);

   for (i=0; i<var_nstack; i++)
      if (strcmp(str,var_stack[i].name)==0) {
         switch (var_stack[i].type) {
            case MISN_VAR_NIL:
               lua_pushnil(L);
               break;
            case MISN_VAR_NUM:
               lua_pushnumber(L,var_stack[i].d.num);
               break;
            case MISN_VAR_BOOL:
               lua_pushboolean(L,var_stack[i].d.b);
               break;
            case MISN_VAR_STR:
               lua_pushstring(L,var_stack[i].d.str);
               break;
         }
         return 1;
      }

   return 0;
}
/**
 * @brief Pops a mission variable off the stack, destroying it.
 *
 * This does not give you any value and destroys it permanently (or until recreated).
 *
 *    @luatparam string name Name of the mission variable to pop.
 * @luafunc pop( name )
 */
static int var_pop( lua_State *L )
{
   int i;
   const char* str;

   NLUA_CHECKRW(L);

   str = luaL_checkstring(L,1);

   for (i=0; i<var_nstack; i++)
      if (strcmp(str,var_stack[i].name)==0) {
         var_free( &var_stack[i] );
         memmove( &var_stack[i], &var_stack[i+1], sizeof(misn_var)*(var_nstack-i-1) );
         var_nstack--;
         return 0;
      }

   /*NLUA_DEBUG("Var '%s' not found in stack", str);*/
   return 0;
}
/**
 * @brief Creates a new mission variable.
 *
 * This will overwrite existing vars, so it's a good way to update the values
 *  of different mission variables.
 *
 *    @luatparam string name Name to use for the new mission variable.
 *    @luaparam value Value of the new mission variable.  Accepted types are:
 *                  nil, bool, string or number.
 * @luafunc push( name, value )
 */
static int var_push( lua_State *L )
{
   const char *str;
   misn_var var;

   NLUA_CHECKRW(L);

   str = luaL_checkstring(L,1);

   /* store appropriate data */
   if (lua_isnil(L,2))
      var.type = MISN_VAR_NIL;
   else if (lua_isnumber(L,2)) {
      var.type = MISN_VAR_NUM;
      var.d.num = (double) lua_tonumber(L,2);
   }
   else if (lua_isboolean(L,2)) {
      var.type = MISN_VAR_BOOL;
      var.d.b = lua_toboolean(L,2);
   }
   else if (lua_isstring(L,2)) {
      var.type = MISN_VAR_STR;
      var.d.str = strdup( lua_tostring(L,2) );
   }
   else {
      NLUA_INVALID_PARAMETER(L);
      return 0;
   }
   /* Set name. */
   var.name = strdup(str);
   var_add( &var );

   return 0;
}
/**
 * @brief Frees a mission variable.
 *
 *    @param var Mission variable to free.
 */
static void var_free( misn_var* var )
{
   switch (var->type) {
      case MISN_VAR_STR:
         if (var->d.str!=NULL) {
            free(var->d.str);
            var->d.str = NULL;
         }
         break;
      case MISN_VAR_NIL:
      case MISN_VAR_NUM:
      case MISN_VAR_BOOL:
         break;
   }

   if (var->name!=NULL) {
      free(var->name);
      var->name = NULL;
   }
}
/**
 * @brief Cleans up all the mission variables.
 */
void var_cleanup (void)
{
   int i;
   for (i=0; i<var_nstack; i++)
      var_free( &var_stack[i] );

   if (var_stack!=NULL)
      free( var_stack );
   var_stack   = NULL;
   var_nstack  = 0;
   var_mstack  = 0;
}

