#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "store.h"

/*
 * This is the "data store" module for Mush.
 * It maintains a mapping from variable names to values.
 * The values of variables are stored as strings.
 * However, the module provides functions for setting and retrieving
 * the value of a variable as an integer.  Setting a variable to
 * an integer value causes the value of the variable to be set to
 * a string representation of that integer.  Retrieving the value of
 * a variable as an integer is possible if the current value of the
 * variable is the string representation of an integer.
 */

/**
 * @brief  Get the current value of a variable as a string.
 * @details  This function retrieves the current value of a variable
 * as a string.  If the variable has no value, then NULL is returned.
 * Any string returned remains "owned" by the data store module;
 * the caller should not attempt to free the string or to use it
 * after any subsequent call that would modify the value of the variable
 * whose value was retrieved.  If the caller needs to use the string for
 * an indefinite period, a copy should be made immediately.
 *
 * @param  var  The variable whose value is to be retrieved.
 * @return  A string that is the current value of the variable, if any,
 * otherwise NULL.
 */
char *store_get_string(char *var) {
    if (!var)
        return NULL;
    store_data *data = store_get_data(var);
    if (data)
        return data->value;
    return NULL;
}

/**
 * @brief  Get the current value of a variable as an integer.
 * @details  This retrieves the current value of a variable and
 * attempts to interpret it as an integer.  If this is possible,
 * then the integer value is stored at the pointer provided by
 * the caller.
 *
 * @param  var  The variable whose value is to be retrieved.
 * @param  valp  Pointer at which the returned value is to be stored.
 * @return  If the specified variable has no value or the value
 * cannot be interpreted as an integer, then -1 is returned,
 * otherwise 0 is returned.
 */
int store_get_int(char *var, long *valp) {
    if (!var || !valp)
        return -1;
    store_data *data = store_get_data(var);
    if (data) {
        // is NULL
        char *ptr = data->value;
        if (!ptr)
            return -1;

        // find whether negative or not
        int sign = 1;
        if (*ptr == '-')
            sign = -1, ptr ++;

        long ans = 0;
        while (*ptr)
        {
            // cannot be interpreted as an integer
            if(*ptr > '9' || *ptr < '0')
                return -1;
            ans = ans * 10 +(*(ptr++) - '0');
        }
        *valp = ans * sign;
    }
    return 0;
}

/**
 * @brief  Set the value of a variable as a string.
 * @details  This function sets the current value of a specified
 * variable to be a specified string.  If the variable already
 * has a value, then that value is replaced.  If the specified
 * value is NULL, then any existing value of the variable is removed
 * and the variable becomes un-set.  Ownership of the variable and
 * the value strings is not transferred to the data store module as
 * a result of this call; the data store module makes such copies of
 * these strings as it may require.
 *
 * @param  var  The variable whose value is to be set.
 * @param  val  The value to set, or NULL if the variable is to become
 * un-set.
 */
int store_set_string(char *var, char *val) {
    if (!var)
        return -1;
    
    store_data* data = store_get_data(var);
    // if want to unset a value that does not exist
    if (!data && !val)
        return -1;

    // unset is val is NULL
    if (!val)
    {
        store_remove_data(data);
        return 0;
    }
    
    // set the data to val if data exists
    if (data)
    {
        char *tmp = data->value;
        data->value = store_strcpy(val);
        free(tmp);
        return 0;
    }
    
    // create a new data with the value and insert it
    data = malloc(sizeof(store_data));
    data->name = store_strcpy(var);
    data->value = store_strcpy(val);
    store_add_data(data);
    return 0;
}

/**
 * @brief  Set the value of a variable as an integer.
 * @details  This function sets the current value of a specified
 * variable to be a specified integer.  If the variable already
 * has a value, then that value is replaced.  Ownership of the variable
 * string is not transferred to the data store module as a result of
 * this call; the data store module makes such copies of this string
 * as it may require.
 *
 * @param  var  The variable whose value is to be set.
 * @param  val  The value to set.
 */
int store_set_int(char *var, long val) {
    if (!var)
        return -1;
    // if val is 0, set var to 0
    if (val == 0)
        return store_set_string(var, "0");
    
    char *str, *tmp, *ptr;
    tmp = malloc(sizeof(char));
    int is_negative = 0, size = 0;

    // check whether val is negative
    if (val < 0)
        is_negative = 1;
    
    // get a string with reversed direction
    while (val)
    {
        int remainder = val % 10;
        if (is_negative)
            remainder *= -1;
        tmp[size] = ('0' + remainder);
        tmp = realloc(tmp, (++size + 1) * sizeof(char));
        val /= 10;
    }

    // alloc memory to str
    if (is_negative) {
        str = malloc((size+2)*sizeof(char));
        ptr = str + 1;
        str[0] = '-';
    }
    else {
        str = malloc((size+1)*sizeof(char));
        ptr = str;
    }

    // reverse the string back
    for (size_t i = 0; i < size; i++)
        ptr[i] = tmp[size - i - 1];
    
    ptr[size] = '\0';

    // put it in data
    int return_val = store_set_string(var, str);

    free(str);
    free(tmp);
    
    return return_val;
}

/**
 * @brief  Print the current contents of the data store.
 * @details  This function prints the current contents of the data store
 * to the specified output stream.  The format is not specified; this
 * function is intended to be used for debugging purposes.
 *
 * @param f  The stream to which the store contents are to be printed.
 */
void store_show(FILE *f) {
    store_data* ptr = store_head->next;

    fprintf(f, "{");
    while (ptr != store_head)
    {
        fprintf(f, "%s=%s", ptr->name, ptr->value);
        ptr = ptr->next;
        if ((ptr != store_head))
            fprintf(f, ", ");
        
    }
    fprintf(f, "}\n");
}

void store_init() {
    store_head = malloc(sizeof(store_data));
    store_head->name = NULL;
    store_head->value = NULL;
    store_head->next = store_head;
    store_head->prev = store_head;
}

store_data* store_get_data(char* var) {
    store_data* ptr = store_head->next;

    while (ptr != store_head)
    {
        if (strcmp(ptr->name, var) == 0)
            return ptr;

        ptr = ptr->next;
    }

    return NULL;
}

void store_remove_data(store_data* data) {
    data->prev->next = data->next;
    data->next->prev = data->prev;
    if (data->name)
        free(data->name);
    if (data->value)
        free(data->value);
    free(data);
}

void store_add_data(store_data* data) {
    data->next = store_head->next;
    data->prev = store_head;
    data->next->prev = data;
    data->prev->next = data;
}

void store_fini() {
    while (store_head->next != store_head)
        store_remove_data(store_head->next);

    free(store_head);
    store_head = NULL;
}

char* store_strcpy(char *str) {
    size_t size = 0;
    char *result = malloc(sizeof(char));
    while (*str)
    {
        result[size] = *(str++);
        result = realloc(result, (++size + 1) * sizeof(char));
    }
    result[size] = '\0';
    return result;
}