#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdint.h>

#include <compat/strl.h>
#include <string/stdstring.h>

#include "scope.h"

union number
{
   int   val_int;
   float val_dec;
};

typedef struct generator
{
   bool         is_decimal;
   union number value;
   union number increment;
   int          shift;
} generator_t;

struct param
{
   char        *name;
   char        *value;
   generator_t *generator;
   param_t     *prev;
   int          level;
};

static void param_deinit(param_t *param)
{
   free(param->generator);
   free(param->value);
   free(param->name);
}

static param_t *param_find(scope_t *scope, const char *name, int level)
{
   param_t *param = scope->param;

   while (param && param->level >= level)
   {
      if (string_is_equal(param->name, name))
         return param;
      param = param->prev;
   }

   return NULL;
}

void scope_init(scope_t *scope)
{
   scope->level          = 0;

   scope->param          = NULL;
   scope->elements       = NULL;
   scope->elements_count = 0;
   scope->groups         = NULL;
   scope->groups_count   = 0;
}

void scope_deinit(scope_t *scope)
{
   int i;
   param_t *param;
   param_t *prev;

   for (i = 0; i < scope->elements_count; ++i)
      element_deinit(&scope->elements[i]);
   free(scope->elements);

   for (i = 0; i < scope->groups_count; ++i)
      view_deinit(&scope->groups[i]);
   free(scope->groups);

   for (param = scope->param; param; param = prev)
   {
      prev = param->prev;
      param_deinit(param);
      free(param);
   }
}

void scope_push(scope_t *scope)
{
   ++scope->level;
}

void scope_pop(scope_t *scope)
{
   param_t *param;

   --scope->level;

   while ((param = scope->param))
   {
      if (param->level <= scope->level)
         break;

      scope->param = param->prev;
      param_deinit(param);
      free(param);
   }
}

void scope_repeat(scope_t *scope)
{
   param_t *param;

   for (
         param = scope->param;
         param && param->level >= scope->level;
         param = param->prev)
   {
      generator_t *gen;
      if ((gen = param->generator))
      {
         char tmp[SCOPE_BUFFER_SIZE];
         tmp[0] = '\0';

         if (gen->is_decimal)
         {
            gen->value.val_dec += gen->increment.val_dec;
            if (gen->shift > 0)
               gen->value.val_dec = (float)((int)gen->value.val_dec << gen->shift);
            else if (gen->shift < 0)
               gen->value.val_dec = (float)((int)gen->value.val_dec >> -gen->shift);
            snprintf(tmp, sizeof(tmp), "%f", gen->value.val_dec);
         }
         else
         {
            gen->value.val_int += gen->increment.val_int;
            if(gen->shift > 0)
               gen->value.val_int <<= gen->shift;
            else if (gen->shift < 0)
               gen->value.val_int >>= -gen->shift;
            snprintf(tmp, sizeof(tmp), "%d", gen->value.val_int);
         }

         string_set(&param->value, tmp);
      }
   }
}


void scope_param(scope_t *scope, const char *name, const char *value)
{
   param_t *param;
   char *eval_name  = string_init(scope_eval(scope, name));
   char *eval_value = string_init(scope_eval(scope, value));

   if ((param = param_find(scope, eval_name, scope->level)))
   {
      free(param->value);
      param->value = eval_value;
   }
   else
   {
      param        = (param_t*)malloc(sizeof(param_t));
      param->name  = string_init(name);
      param->value = eval_value;
      param->generator = NULL;
      param->level = scope->level;
      param->prev = scope->param;
      scope->param = param;
   }

   free(eval_name);
}

void scope_generator(scope_t *scope, const char *name,
      const char *start, const char *increment,
      const char *lshift, const char *rshift)
{
   char *e_val;
   char *e_inc;
   generator_t *gen;
   param_t *param;
   char *e_name     = string_init(scope_eval(scope, name));

   if (param_find(scope, e_name, scope->level))
   {
      free(e_name);
      return;
   }

   e_val            = string_init(scope_eval(scope, start));
   e_inc            = string_init(scope_eval(scope, increment));

   gen              = (generator_t*)malloc(sizeof(generator_t));

   param            = (param_t*)malloc(sizeof(param_t));
   param->name      = string_init(e_name);
   param->value     = string_init(e_val);
   param->generator = gen;
   param->level     = scope->level;
   param->prev      = scope->param;
   scope->param     = param;

   gen->is_decimal  = is_decimal(e_val) | is_decimal(e_inc);

   if (gen->is_decimal)
   {
      gen->value.val_dec     = get_dec(e_val);
      gen->increment.val_dec = get_dec(e_inc);
   }
   else
   {
      gen->value.val_int     = get_int(e_val);
      gen->increment.val_int = get_int(e_inc);
   }

   gen->shift                = 0;

   if (lshift)
      gen->shift            += get_int(scope_eval(scope, lshift));

   if (rshift)
      gen->shift            -= get_int(scope_eval(scope, rshift));

   free(e_inc);
   free(e_val);
   free(e_name);
}

const char *scope_eval(scope_t *scope, const char *src)
{
   const char* next;
   bool in_var;
   char tmp[SCOPE_BUFFER_SIZE];

   if (!src)
      return NULL;

   scope->eval[0] = '\0';
   next           = src;

   while (next[0] != '\0')
   {
      const char *cur = next;

      if ((in_var = (next[0] == '~')))
         ++cur;

      next = strchr(cur, '~');

      if (next && next != cur)
      {
         size_t len = next - cur;

         if (in_var)
         {
            param_t *param;

            strncpy(tmp, cur, len);
            tmp[len] = '\0';

            if ((param = param_find(scope, tmp, 0)))
               strlcat(scope->eval, param->value,
                     sizeof(scope->eval));
            else
               strlcat(scope->eval, tmp, sizeof(scope->eval));

            ++next;
         }
         else
            strncat(scope->eval, cur, len);
      }
      else
      {
         if (in_var)
            --cur;
         strlcat(scope->eval, cur, sizeof(scope->eval));
         break;
      }
   }

   return scope->eval;
}

element_t *scope_add_element(scope_t *scope)
{
   element_t *elem;

   vec_size((void**)&scope->elements,
         sizeof(element_t), ++scope->elements_count);

   elem = &scope->elements[scope->elements_count - 1];
   element_init(elem, NULL, 0);

   return elem;
}

element_t *scope_find_element(scope_t *scope, const char *name)
{
   unsigned i;

   for (i = 0; i < scope->elements_count; ++i)
   {
      if (string_is_equal(name, scope->elements[i].name))
         return &scope->elements[i];
   }

   return NULL;
}

view_t *scope_add_group(scope_t *scope)
{
   view_t *group;

   vec_size((void**)&scope->groups, sizeof(view_t), ++scope->groups_count);

   group = &scope->groups[scope->groups_count - 1];
   view_init(group, NULL);

   return group;
}

view_t *scope_find_group(scope_t *scope, const char *name)
{
   unsigned i;

   for (i = 0; i < scope->groups_count; ++i)
   {
      if (string_is_equal(name, scope->groups[i].name))
         return &scope->groups[i];
   }

   return NULL;
}
