/******************************************************************************
*
* Copyright (C) 2019  Brian Berlin
* 
* 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 3 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, see <http://www.gnu.org/licenses/>
*
******************************************************************************
*
* FILENAME: toml.c
*
* DESCRIPTION:  This contains function declarations for toml config file 
*               handling.  We do our own since since all the parsers out there
*               don't let you emit in the same style you wrote in. 
*               We will keep it simple and just make it so we can read and
*               emit in same style.
*
* AUTHORS:  bberlin
*
* CHANGE LOG
*
*  VER     DATE     CHANGED BY   DESCRIPTION OF CHANGE
* ------  --------  ----------   ------------------------------------
* 0.9.0   01/29/19  bberlin      Creation
******************************************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <stdint.h>
#include "toml.h"

#define TOML_IS_STRING(value) ((value) == TOML_DQ_STRING || (value) == TOML_SQ_STRING \
            || (value) == TOML_TSQ_STRING || (value) == TOML_TDQ_STRING)

#define TOML_IS_NUMBER(value) ((value) == TOML_FLOAT || (value) == TOML_INT)

#define TOML_IS_KEY_OR_TABLE(value) ((value) == TOML_KEY || (value) == TOML_TABLE \
                                     || (value) == TOML_TABLE_ARY)

#define TOML_IS_DATE_TIME(value) ((value) == TOML_DATE || (value) == TOML_TIME)

#define TOML_IS_VALUE(value) (TOML_IS_STRING(value) || TOML_IS_NUMBER(value) || \
                              TOML_IS_DATE_TIME(value) || value == TOML_BOOLEAN)

#define TOML_IN_KEY (g_parser.in_key_ary || g_parser.in_t_squote || \
        g_parser.in_t_dquote)

static t_toml_parser g_parser = {0};

int g_toml_error;

/******************************************************************************
**  Function   :  toml_get_error
**                                                                    
**  Objective  :  This function will return the current error type if set
**                If there is an error line_no will be set to the current
**                line number of the error.
**                                                   
**  Parameters :  line_no - output set with line number of error
**                 
**  return     :  current global error code
******************************************************************************/ 
int toml_get_error ( int *line_no ) {

    if ( line_no != NULL )
        *line_no = g_parser.line_no;

    return g_toml_error;
}

/******************************************************************************
**  Function   :  toml_get_error_string
**                                                                    
**  Objective  :  This function will return the error string based on
**                input error value
**                                                   
**  Parameters :  err_val - value of error to get string for
**                 
**  return     :  error string
******************************************************************************/ 
const char * toml_get_error_string ( e_toml_error err_val ) {

    switch ( err_val ) {
        case TOML_NONE: return "None"; break;
        case TOML_FILE: return "File Not Opened or Found"; break;
        case TOML_INTERNAL_ERROR: return "Module Internal Error"; break;
        case TOML_INVALID_TABLE_DECLARATION: return "Invalid Table Declaration"; break;
        case TOML_INVALID_KEY_DECLARATION: return "Invalid Key Declaration"; break;
        case TOML_INVALID_KEY: return "Invalid Key"; break;
        case TOML_INVALID_ARRAY: return "Invalid Array"; break;
        case TOML_INVALID_ARRAY_MIXED_TYPES: return "Invalid Mixed Types"; break;
        default: return "Unknown Error"; break;
    }
}

/******************************************************************************
**  Function   :  toml_allocate_string
**                                                                    
**  Objective  :  This function will malloc memory for the input string
**                and copy it in. If no_newline is set, it will search from 
**                the end for the newline and end the string there.
**                                                   
**  Parameters :  input - string to copy into malloc'd memory
**                no_newline - if set, find newline and replace with NULL
**                 
**  return     :  pointer to new string on success, NULL on failure
******************************************************************************/ 
char * toml_allocate_string ( const char *input, char no_newline ) {

    char *tmp;
    char *line;

    if (!input)
        return NULL;

    line = malloc(strlen(input) + 1);

    if ( !line ) {
        g_toml_error = TOML_INTERNAL_ERROR;
        return NULL;
    }

    strcpy ( line, input );
    if ( no_newline ) {
        tmp = strchr( line, '\n' );
        if ( tmp )
            *tmp = '\0';
    }

    return line;
}

/******************************************************************************
**  Function   :  toml_allocate_table_header
**                                                                    
**  Objective  :  This function will get the string from the bracketed table
**                header.  It assumes start is just after the opening bracket.
**                It removes spaces, then allocates and returns the string.
**                                                   
**  Parameters :  start - input string just after bracket(s)
**                 
**  return     :  pointer to new string on success, NULL on failure
******************************************************************************/ 
char * toml_allocate_table_header ( char *start ) {

    char *value;
    char *tmp;
    char *end;
    int i = 0;

    end = strchr(start, ']');
    if ( !end ) {
        g_toml_error = TOML_INTERNAL_ERROR;
        return NULL;
    }
    else 
        end--;

    value = malloc(end-start+2);

    if ( !value ) {
        g_toml_error = TOML_INTERNAL_ERROR;
        return NULL;
    }


    tmp = start;
    while (tmp <= end) {
        if ( isspace(*tmp) ) {
            ++tmp;
            continue;
        }
        value[i++] = *tmp;
        ++tmp;
    }
    value[i] = '\0';

    return value;
}

/******************************************************************************
**  Function   :  toml_allocate_indent
**                                                                    
**  Objective  :  This function will check for spacing at beginning of line
**                and allocate a string to hold that initial spacing. 
**                                                   
**  Parameters :  line - check for spacing at beginning of this string
**                 
**  return     :  pointer to new string on success, NULL on failure or no space
******************************************************************************/ 
char * toml_allocate_indent ( char *line ) {

    int size;
    char *indent;
    char *start = line;

    while(isspace(*line)) {
        ++line;
    }

    size = line - start;

    if (!size)
        return NULL;

    indent = malloc(size+1);

    if ( !indent ) {
        g_toml_error = TOML_INTERNAL_ERROR;
        return NULL;
    }

    strncpy(indent, start, size);
    indent[size] = '\0';

    return indent;
}

/******************************************************************************
**  Function   :  toml_is_legal_key
**                                                                    
**  Objective  :  This function will determine if the key is already in this 
**                table.  It also checks if there is already a dotted key
**                with this at beginning which is not legal
**                                                   
**  Parameters :  table - check children of this node for key
**                key   - key to check for
**                 
**  return     :  1 if key is legal and not found, otherwise 0
******************************************************************************/ 
int toml_is_legal_key ( t_toml_node *table, char *key ) {

    t_toml_node *node = table->child;
    int size; 

    if ( key == NULL )
        return 0;

    while ( node ) {

        if ( !strncmp ( key, node->value, size=strlen(key) ) ) {
            if (node->value[size] == '\0' || node->value[size] == '.')
                return 0;
        }
        else if ( !strncmp ( node->value, key, size=strlen(node->value) ) ) {
            if (key[size] == '\0' || key[size] == '.')
                return 0;
        }

        node = node->next;
    }

    return 1;
}

/******************************************************************************
**  Function   :  toml_init_node
**                                                                    
**  Objective  :  This function will initialize all pointers to NULL and other
**                values to defaults.
**                                                   
**  Parameters :  node - node to initialize
**                 
**  return     :  NONE
******************************************************************************/ 
void toml_init_node ( t_toml_node *node ) {

    if (!node)
        return;

    node->type = TOML_UNKNOWN;
    node->value = NULL;
    node->indent_initial = NULL;
    node->indent_to_equals = NULL;
    node->to_newline = NULL;
    node->values_per_line = 1;
    node->has_ending_backslash = 0;
    node->child = NULL;
    node->parent = NULL;
    node->next = NULL;
    node->previous = NULL;
}

/******************************************************************************
**  Function   :  toml_allocate_child
**                                                                    
**  Objective  :  This function will append a new allocated node to the child
**                list of the input node
**                                                   
**  Parameters :  node - node to add child to.
**                 
**  return     :  newly allocated/initialized node on success, NULL on fail
******************************************************************************/ 
t_toml_node * toml_allocate_child ( t_toml_node *node ) {

    t_toml_node *parent = node;
    t_toml_node *previous = NULL;

    if (node->child) {
        node = node->child;
        while (node->next)
            node = node->next;
        node->next = malloc(sizeof(t_toml_node));
        previous = node;
        node = node->next;
    }
    else {
        node->child = malloc(sizeof(t_toml_node));
        node = node->child;
    }

    if ( !node ) {
        g_toml_error = TOML_INTERNAL_ERROR;
        return NULL;
    }

    toml_init_node ( node );

    node->parent = parent;
    node->previous = previous;

    return node;

} /* end toml_allocate_child */

/******************************************************************************
**  Function   :  toml_allocate_node
**                                                                    
**  Objective  :  This function will append a new allocated node to the end
**                of the input node list
**                                                   
**  Parameters :  node - node to add after, will search to end of list
**                 
**  return     :  newly allocated/initialized node on success, NULL on fail
******************************************************************************/ 
t_toml_node * toml_allocate_node ( t_toml_node *node ) {

    while (node->next)
        node = node->next;

    node->next = malloc(sizeof(t_toml_node));

    if ( !(node->next) ) {
        g_toml_error = TOML_INTERNAL_ERROR;
        return NULL;
    }

    toml_init_node ( node->next );

    node->next->parent = node->parent;
    node->next->previous = node;
    node = node->next;

    return node;

} /* end toml_allocate_node */

/******************************************************************************
**  Function   :  toml_allocate_standalone_node
**                                                                    
**  Objective  :  This function allocates and initializes a node, but does
**                not attach it to anything
**                                                   
**  Parameters :  NONE
**                 
**  return     :  newly allocated/initialized node on success, NULL on fail
******************************************************************************/ 
t_toml_node * toml_allocate_standalone_node ( ) {

    t_toml_node *node;

    node = malloc(sizeof(t_toml_node));

    if ( !(node) ) {
        g_toml_error = TOML_INTERNAL_ERROR;
        return NULL;
    }

    toml_init_node ( node );

    return node;

} /* end toml_allocate_standalone_node */

/******************************************************************************
**  Function   :  toml_allocate_insert_node
**                                                                    
**  Objective  :  This function allocates and inserts node just before the
**                input node.
**                                                   
**  Parameters :  node - node to insert new node before
**                 
**  return     :  newly allocated/initialized node on success, NULL on fail
******************************************************************************/ 
t_toml_node * toml_allocate_insert_node ( t_toml_node *node ) {

    t_toml_node *new_node;

    if (!node)
        return NULL;

    new_node = toml_allocate_standalone_node();

    if ( !(new_node) )
        return NULL;

    if (node->previous)
        node->previous->next = new_node;

    if (node->parent) {
        if (node->parent->child == node)
            node->parent->child = new_node;
    }

    new_node->previous = node->previous;
    new_node->next = node;
    new_node->parent = node->parent;
   
    node->previous = new_node;

    return new_node;

} /* end toml_allocate_insert_node */

/******************************************************************************
**  Function   :  toml_allocate_append_node
**                                                                    
**  Objective  :  This function allocates and appends node just after the
**                input node.
**                                                   
**  Parameters :  node - node to append new node after
**                 
**  return     :  newly allocated/initialized node on success, NULL on fail
******************************************************************************/ 
t_toml_node * toml_allocate_append_node ( t_toml_node *node ) {

    t_toml_node *new_node;

    if (!node)
        return NULL;

    new_node = toml_allocate_standalone_node();

    if ( !(new_node) )
        return NULL;

    if (node->next)
        node->next->previous = new_node;

    new_node->previous = node;
    new_node->next = node->next;
    new_node->parent = node->parent;
   
    node->next = new_node;

    return new_node;

} /* end toml_allocate_append_node */

static void toml_write_escaped_dq_string ( FILE *fp, const char *value ) {

    const unsigned char *p = (const unsigned char *)(value ? value : "");

    while ( *p ) {
        switch ( *p ) {
            case '\b': fputs ( "\\b", fp ); break;
            case '\t': fputs ( "\\t", fp ); break;
            case '\n': fputs ( "\\n", fp ); break;
            case '\f': fputs ( "\\f", fp ); break;
            case '\r': fputs ( "\\r", fp ); break;
            case '"':  fputs ( "\\\"", fp ); break;
            case '\\': fputs ( "\\\\", fp ); break;
            default:
                fputc ( *p, fp );
                break;
        }
        ++p;
    }
}

/******************************************************************************
**  Function   :  toml_is_legal_comment
**                                                                    
**  Objective  :  This function checks that there is only a comment left
**                in line. If so allocate a child as type COMMENT and return it
**                                                   
**  Parameters :  line   - string to check and ensure it is a comment
**                parent - node to add comment to as a child
**                 
**  return     :  newly allocated/initialized node on success, NULL on fail
******************************************************************************/ 
t_toml_node * toml_is_legal_comment ( char *line, t_toml_node *parent ) {

    t_toml_node *node = NULL;
    char *tmp = line;

    while ( *tmp != '\n' && *tmp != '\0' ) {
        if(isspace(*tmp)) {
            ++tmp;
            continue;
        }

        if (*tmp != '#') {
            g_toml_error = TOML_INVALID_KEY;
            return NULL;
        }
        else {
            if (parent->type != TOML_COMMENT)
                node = toml_allocate_child ( parent );
            else
                node = parent;
            node->type = TOML_COMMENT;
            node->value = toml_allocate_string(line, 1);
            break;
        }
        ++tmp;
    } 

    return node;

} /* end toml_is_legal_comment */

/******************************************************************************
**  Function   :  toml_free_single_node
**                                                                    
**  Objective  :  This function frees allocated memory for any strings
**                including the value for the input node.  It does not free
**                any linked nodes.  It will unlink itself from list and then
**                free itself.
**                                                   
**  Parameters :  node - node to free strings for and to free itself
**                 
**  return     :  NONE
******************************************************************************/ 
void toml_free_single_node ( t_toml_node *node ) {

    if (node->value) free(node->value);
    if (node->indent_initial) free(node->indent_initial);
    if (node->indent_to_equals) free(node->indent_to_equals);
    if (node->to_newline) free(node->to_newline);

    if (node->parent && node->parent->child == node)
        node->parent->child = node->next;

    if (node->previous)
        node->previous->next = node->next;

    if (node->next)
        node->next->previous = node->previous;

    free(node);

} /* end toml_free_single_node */

/******************************************************************************
**  Function   :  toml_free_node_tree
**                                                                    
**  Objective  :  This function frees the entire tree (both children and next)
**                from the given node
**                                                   
**  Parameters :  node - node start freeing all nodes in tree from
**                 
**  return     :  NONE
******************************************************************************/ 
void toml_free_node_tree ( t_toml_node *node ) {

    if ( !node )
        return;

    if (node->child) {
        toml_free_node_tree(node->child);
    }

    if (node->next) {
        toml_free_node_tree(node->next);
    }

    toml_free_single_node(node);

} /* end toml_free_node_tree */

/******************************************************************************
**  Function   :  toml_reset_parser_bools
**                                                                    
**  Objective  :  This function resets boolean variables used in parsing to 0
**                Typically done at start of parse and for new key
**                                                   
**  Parameters :  NONE
**                 
**  return     :  NONE
******************************************************************************/
void toml_reset_parser_bools ( ) {

    g_parser.in_t_dquote = 0;
    g_parser.in_t_squote = 0;
    g_parser.in_key_ary = 0;

} /* end toml_reset_parser_bools */

/******************************************************************************
**  Function   :  toml_get_time
**                                                                    
**  Objective  :  This function tests that the input date has a valid time.
**                                                   
**  Parameters :  date  - string to check for a valid time
**                value - value to hold allocated time string
**                rest  - if not NULL return rest of string after time here
**                 
**  return     :  TOML_TIME type or 0 if not valid time
******************************************************************************/
static int toml_is_value_terminator ( char c ) {

    return c == '\0' || c == '\n' || isspace((unsigned char)c) ||
           c == '#' || c == ',' || c == ']';
}

static int toml_copy_token ( char *start, char *end, char **value ) {

    int size;

    if ( !value )
        return 0;

    size = end - start;
    *value = malloc(size + 1);
    if ( !*value ) {
        g_toml_error = TOML_INTERNAL_ERROR;
        return -1;
    }

    strncpy ( *value, start, size );
    (*value)[size] = '\0';

    return 0;
}

int toml_get_time ( char *date, char **value, char **rest ) {

    int hour;
    int minute;
    int second;
    char *start;
    char *tmp;

    while ( isspace((unsigned char)*date) ) date++;

    start = date;

    if ( !isdigit((unsigned char)date[0]) || !isdigit((unsigned char)date[1]) ||
         date[2] != ':' ||
         !isdigit((unsigned char)date[3]) || !isdigit((unsigned char)date[4]) ||
         date[5] != ':' ||
         !isdigit((unsigned char)date[6]) || !isdigit((unsigned char)date[7]) )
        return 0;

    hour = (date[0] - '0') * 10 + (date[1] - '0');
    minute = (date[3] - '0') * 10 + (date[4] - '0');
    second = (date[6] - '0') * 10 + (date[7] - '0');
    if ( hour > 23 || minute > 59 || second > 60 )
        return 0;

    tmp = date + 8;

    if ( *tmp == '.' ) {
        ++tmp;
        if ( !isdigit((unsigned char)*tmp) )
            return 0;
        while ( isdigit((unsigned char)*tmp) )
            ++tmp;
    }

    if ( !toml_is_value_terminator(*tmp) )
        return 0;

    if ( rest )
        *rest = tmp;

    if ( toml_copy_token(start, tmp, value) )
        return 0;

    return TOML_TIME;

} /* toml_get_time */

/******************************************************************************
**  Function   :  toml_get_date
**                                                                    
**  Objective  :  This function tests that the input date is a valid date
**                                                   
**  Parameters :  date  - string to check for a valid time
**                value - value to hold allocated time string
**                rest  - if not NULL return rest of string after time here
**                 
**  return     :  TOML_DATE type or 0 if not valid time
******************************************************************************/
int toml_get_date ( char *date, char **value, char **rest ) {

    int i;
    char *start;
    char *tmp;
    char *date_end;

    while ( isspace((unsigned char)*date) ) date++;

    start = date;

    /*  Check for valid initial date */
    for ( i = 0; i < 10; ++i ) {
        if ( date[i] == '\0' || date[i] == '\n' )
            return 0;

        if ( i == 4 || i == 7 ) {
            if ( date[i] != '-' )
                return 0;
        }
        else {
            if ( !isdigit(date[i]) )
                return 0;
        }
    }
    
    /*  if a space then this is date only, we can exit */
    if ( toml_is_value_terminator(date[i]) ) {
        if ( rest )
            *rest = &(date[i]);
        if ( toml_copy_token(start, &(date[i]), value) )
            return 0;
        return TOML_DATE;
    }

    /*  Now test for time */
    if ( date[i] != 'T' && date[i] != 't' )
        return 0;

    tmp = &date[++i];
    if ( !toml_get_time(tmp, NULL, &date_end) )
        return 0;

    if ( rest )
        *rest = date_end;

    /* Ok use rest from time to put together date and time */
    if ( toml_copy_token(start, date_end, value) )
        return 0;

    return TOML_DATE;

} /* end toml_get_date */

/******************************************************************************
**  Function   :  toml_find_key_header
**                                                                    
**  Objective  :  This function reads the input line up to the equals sign 
**                and determines the key name
**                                                   
**  Parameters :  line        - string to check for a valid key name
**                post_indent - store spacing between key name and equals
**                equals      - if not NULL return pointer to equals sign
**                 
**  return     :  key name if success, otherwise NULL
******************************************************************************/
char * toml_find_key_header ( char *line, char **post_indent, char **equals ) {

    char *start = line;
    char *key;
    char *ptr = line;
    int in_dquote = 0, in_squote = 0;
    int i;

    /* return if in key or quote meaning key value */
    if ( TOML_IN_KEY )
        return NULL;

    /* We assume this should be a key, figure out if quoted */
    if (line[0] == '\"') {
        in_dquote = 1;
        ++line;
    }
    else if (line[0] == '\'') {
        in_squote = 1;
        ++line;
    }

    /* 
     * Read to the equals sign, checking for valid characters
     */
    while ( *(line) != '=' || (in_dquote || in_squote) ) {

        /* Basically make sure not in quote when hitting equals */
        if ( *line == '\"' && *(line-1) != '\\' )
            in_dquote = in_dquote ? 0 : 1;

        if ( *line == '\'' && *(line-1) != '\\' )
            in_squote = in_squote ? 0 : 1;

        /* if not in quote, check for valid characters */
        else if ( !in_dquote && !in_squote) {
            if ( !isalnum(*line) && *line != '-' && *line != '_' && 
                    *line != '.' && !isspace(*line)) {
                return NULL;
            }
        }

        if (*line == '\0')
            return NULL;
        if (*line == '\n')
            return NULL;

        line++;

    } /* while not hitting valid equals sign */

    /* if we didn't find valid equals this is illegal */
    if (*line != '=')
        return NULL;

    /* If callers passes a char**, then lets fill it with equals position */
    if ( equals )
        *equals = line;

    /* 
     * if we made it here, we have a valid key 
     *   malloc the memory and copy in, getting rid of white space
     */
    i = -1;
    key = malloc(line-start+2);
    while ( ptr < line ) {
        if ( isspace(*ptr) ) {
            ptr++;
            continue;
        } 

        key[++i] = *ptr;
        ptr++;
    }

    key[++i] = '\0';

    /* 
     * If callers passs a post indent ptr, lets fill it with whitespace
     * between key and equals
     */
    if ( post_indent ) {

        char *tmp = --line;

        /* count back to key header */
        while (isspace(*line)) {
            line--;
        }

        if (line == tmp)
            *post_indent = NULL;

        else {
            *post_indent = malloc(tmp-line+1);
            strncpy(*post_indent,line+1,tmp-line);
            (*post_indent)[tmp-line] = '\0';
        }
    }

    return key;

} /* end toml_find_key_header */

/******************************************************************************
**  Function   :  toml_get_valid_number
**                                                                    
**  Objective  :  This function tests that the input string contains a number
**                                                   
**  Parameters :  string  - string to check for a valid number (float or int)
**                value   - value to hold allocated number string
**                value_d - if not NULL store decimal value if found
**                value_f - if not NULL store float value if found
**                rest    - if not NULL store rest of string after number
**                 
**  return     :  TOML Number type or 0 if no valid number found
******************************************************************************/
int toml_get_valid_number ( char *string, char **value, int64_t *value_d, 
                            double *value_f, char **rest ) {

    char *leftover;
    char *newstring;
    char *start;
    char *token_end;
    int i = -1;
    int is_float = 0;
    int64_t val_d;
    double val_f;
    int sign_offset = 0;
    int is_hex = 0;

    while(isspace((unsigned char)*string)) string++;

    start = string;
    token_end = string;

    while ( !toml_is_value_terminator(*token_end) )
        ++token_end;

    if ( token_end == start )
        return TOML_UNKNOWN;

    /* 
     * Copy over string without underscores and determine if float
     */
    newstring = malloc((token_end - start) + 1);
    if ( !newstring ) {
        g_toml_error = TOML_INTERNAL_ERROR;
        return TOML_UNKNOWN;
    }

    while ( string < token_end ) {
        if ( *string == '_' ) {
            ++string;
            continue;
        }
        newstring[++i] = *string;
        ++string;
    }

    newstring[++i] = '\0';

    if ( newstring[0] == '+' || newstring[0] == '-' )
        sign_offset = 1;

    is_hex = newstring[sign_offset] == '0' &&
        (newstring[sign_offset + 1] == 'x' ||
         newstring[sign_offset + 1] == 'X');

    if ( strchr(newstring, '.') ||
         (!is_hex && (strchr(newstring, 'e') || strchr(newstring, 'E'))) ||
         !strcmp(&newstring[sign_offset], "inf") ||
         !strcmp(&newstring[sign_offset], "nan") )
        is_float = 1;

    /*
     * Verify it can be converted to type and store if desired
     */
    errno = 0;
    if (is_float) {
        val_f = strtof(newstring, &leftover);
        if (value_f)
            *value_f = val_f;
    }
    else {
        val_d = strtoll(newstring, &leftover, 0);
        if (value_d)
            *value_d = val_d;
    }

    /*
     * Check for errors or and return type
     */
    if (leftover == newstring || *leftover != '\0' || errno) {
        free(newstring);
        return TOML_UNKNOWN;
    }

    if ( rest )
        *rest = token_end;

    free(newstring);

    /* Store string representation of number */
    if ( toml_copy_token(start, token_end, value) )
        return TOML_UNKNOWN;

    return is_float ? TOML_FLOAT : TOML_INT;

} /* end toml_get_valid_number */

/******************************************************************************
**  Function   :  toml_get_valid_string
**                                                                    
**  Objective  :  This function tests that the input sting contains a valid
**                TOML string.  This could be single or double quotes, or
**                could be start of multi-line string. It also check for
**                valid continuing of a multi-line string.
**                                                   
**  Parameters :  string  - string to check for a valid TOML string
**                after_backquote - if backslash used at start of multiline
**                value   - value to hold allocated number string
**                rest    - if not NULL store rest of string after valid string
**                 
**  return     :  actual TOML string type or TOML_UNKNOWN if not found
******************************************************************************/
int toml_get_valid_string ( char *string, int *after_backquote, char **value, 
                            char **rest ) {

    char *start = string;
    char *end = NULL;
    int i = 0;
    int in_dquote = 0;
    int in_squote = 0;
    int backslash_found = 0;
    char * backslash_last = 0;
    int type = TOML_UNKNOWN;
    /* 
     * If we aren't in a quote, this is new
     */
    if (!g_parser.in_t_squote && !g_parser.in_t_dquote) {

        if (!strncmp(string,"'''",3)) {
            g_parser.in_t_squote = 1;
            type = TOML_TSQ_STRING;
        }
        else if (!strncmp(string,"\"\"\"",3)) {
            g_parser.in_t_dquote = 1;
            type = TOML_TDQ_STRING;
        }
        else if (*string == '\"') {
            in_dquote = 1;
            type = TOML_DQ_STRING;
        }
        else if (*string == '\'') {
            in_squote = 1;
            type = TOML_SQ_STRING;
        }

        /* if starting triple quote, check for immediate newline */
        if ( g_parser.in_t_dquote || g_parser.in_t_squote ) {
            string += 3;
            if ( *string == '\n' ) {
                *after_backquote = 0;
                if (value)
                    *value = calloc(1,1);
                if (rest) {
                    *rest = malloc(2);
                    (*rest)[0] = '\n';
                    (*rest)[1] = '\0';
                }
                return g_parser.in_t_dquote ? TOML_TDQ_STRING : TOML_TSQ_STRING;
            }
        }
        else
            string += 1;
    }

    /* 
     * If we are in multi-line, was backquote used on last line
     */
    else {
        if (*after_backquote) {
            while(isspace(*string))
                string++;
        }

        type =  g_parser.in_t_dquote ? TOML_TDQ_STRING : TOML_TSQ_STRING;
    }

    start = string;

    if (value)
        *value = malloc(strlen(string)+1);

    /*
     * Go through the string storing values as needed
     *    include the newline if found unless backslash found
     */
    while ( *string != '\0' ) {
        int consuming_escape = (in_dquote || g_parser.in_t_dquote) && backslash_found;

        if (in_dquote) {
            if (*string == '\"' && *(string-1) != '\\') {
                in_dquote = 0;
                end = string+1;
                type = TOML_DQ_STRING;
                if (value && *value)
                    (*value)[i] = '\0';
                break;
            }

        }
        else if (in_squote) {
            if (*string == '\'') {
                in_squote = 0;
                end = string+1;
                type = TOML_SQ_STRING;
                if (value && *value)
                    (*value)[i] = '\0';
                break;
            }
        }
        else if (g_parser.in_t_dquote) {
            if ( !strncmp(string,"\"\"\"",3) ) {
                g_parser.in_t_dquote = 0;
                end = string+3;
                type = TOML_TDQ_STRING;
                if (value && *value)
                    (*value)[i] = '\0';
                break;
            }
        }
        else if (g_parser.in_t_squote) {
            if ( !strncmp(string,"'''",3) ) {
                g_parser.in_t_squote = 0;
                end = string+3;
                type = TOML_TSQ_STRING;
                if (value && *value)
                    (*value)[i] = '\0';
                break;
            }
        }

        /* Set value, if double quote, check for escaping */
        /* FIXME: unicode */
        if (value && *value) {
            int wrote_value = 1;

            if ( consuming_escape ) {
                switch ( *string ) {
                    case 'b': (*value)[i] = '\b';break;
                    case 't': (*value)[i] = '\t';break;
                    case 'n': (*value)[i] = '\n';break;
                    case 'f': (*value)[i] = '\f';break;
                    case 'r': (*value)[i] = '\r';break;
                    case '\"': (*value)[i] = '\"';break;
                    case '\\': (*value)[i] = '\\';break;
                    default: 
                        if (in_dquote)
                            return TOML_UNKNOWN;
                        else {
                            (*value)[i] = '\\'; 
                             if (isspace(*string)) 
                                 backslash_last = &((*value)[i]);
                        }
                        break;
                }
            }
            else if ( (in_dquote || g_parser.in_t_dquote) && *string == '\\' ) {
                wrote_value = 0;
            }
            else {
                (*value)[i] = *string;

                /* A lone backslash not followed by all space is illegal */
                if ( backslash_last && !isspace(*string) ) {
                    free(*value);
                    *value = NULL;
                    return TOML_UNKNOWN;
                }
            }

            if ( wrote_value )
                ++i;
        }

        if (consuming_escape)
            backslash_found = 0;
        else if (*string == '\\')
            backslash_found = 1;
        else
            backslash_found = 0;

        ++string;

    } /* end while looking through string */

    if ( in_dquote || in_squote ) {
        *value = NULL;
        return TOML_UNKNOWN;
    }

    /* If string ended, set the leftover pointer */
    if ( end ) {
        if (rest)
            *rest = end;
    }

    /* If not multiline and we did not end, we have error */
    else if ( g_parser.in_t_squote == 0 && g_parser.in_t_dquote == 0 ) {
        if(value && *value)
            free(*value);
        return TOML_UNKNOWN;
    }

    /* Else multi-line, check for a backquote */
    else {
        if ( value && *value )
            (*value)[i] = '\0';
        if(backslash_last) {
            *after_backquote = 1;
            *backslash_last = '\0';
        }
        if ( rest )
            *rest = string;
    }

    return type;

} /* end toml_get_valid_string */

/******************************************************************************
**  Function   :  toml_search_for_values
**                                                                    
**  Objective  :  This function searches the string for valid values, this
**                can be called multiple times per line in the case of arrays
**                and comments after values.
**                                                   
**  Parameters :  to_check - string to check for values
**                last_node - current end of list
**                leftover  - after searching values, return rest of string
**                 
**  return     :  node if created, NULL otherwise
******************************************************************************/ 
t_toml_node * toml_search_for_values ( char *to_check, t_toml_node *last_node, 
                                       char **leftover ) {

    t_toml_node *node;
    char *start;
    int has_backslash = 0;
    int in_multi_quote = 0;
    int size;
    int type;

    start = to_check;

    while(isspace(*to_check)) ++to_check;

    if ( *to_check == '\n' || *to_check == '\0' )
        return NULL;

    /* Allocate node based on if this key is a child */
    if ( last_node->type == TOML_KEY || last_node->type == TOML_ARRAY ) {
            //last_node->type == TOML_TDQ_STRING && g_parser.in_t_dquote || 
            //last_node->type == TOML_TSQ_STRING && g_parser.in_t_squote ) {
        node = toml_allocate_child ( last_node );
    }
    else {
        node = toml_allocate_node ( last_node );
    }

    if ( !node ) {
        g_toml_error = TOML_INTERNAL_ERROR;
        return NULL;
    }

    /* Store the indent */
    if ( size = to_check - start ) {
        node->indent_initial = malloc(size+1);
        strncpy ( node->indent_initial, start, size );
        node->indent_initial[size] = '\0';
    }

    /*
     * Let's search for stuff
     */

    /* Do string first in case of multi-line */
    if ( g_parser.in_t_dquote || g_parser.in_t_squote || 
            *to_check == '\"' || *to_check =='\'' ) {

        if ( g_parser.in_t_dquote || g_parser.in_t_squote ) {
            has_backslash = last_node->has_ending_backslash;
            in_multi_quote = 1;
        }

        node->type = toml_get_valid_string ( to_check, 
                &has_backslash, &(node->value), leftover );

        if ( node->type == TOML_UNKNOWN) {
            g_toml_error = TOML_INVALID_KEY;
            return NULL;
        }

        if ( g_parser.in_t_squote || g_parser.in_t_dquote ) {
            node->has_ending_backslash = has_backslash;

            /* We started multi quote, change node to this */
            if (!in_multi_quote)
                g_parser.current_node = node;
        }

        /* We ended multi quote, change node to parent */
        else if ( in_multi_quote ) { 
            g_parser.current_node = node->parent;
        }
    }

    /* Comment */
    else if ( *to_check == '#' ) {
        node->type = TOML_COMMENT;
        node = toml_is_legal_comment(to_check, node);
        if ( g_toml_error ) {
            g_toml_error = TOML_INVALID_KEY;
            return NULL;
        }
        *leftover = start + strlen(start);
    }

    /* Comma */
    else if ( *to_check == ',' ) {
        if ( !g_parser.in_key_ary ) {
            g_toml_error = TOML_INVALID_KEY;
            return NULL;
        }
        node->type = TOML_COMMA;
        node->value = toml_allocate_string ( ",", 0 );
        *leftover = to_check + 1;
    }

    /* Time or Date before number*/
    else if ( (type = toml_get_date(to_check, &(node->value), leftover)) ||
              (type = toml_get_time(to_check, &(node->value), leftover)) ) {

        node->type = type;
    }

    /* Number */
    else if ( isdigit((unsigned char)*to_check) || *to_check == '-' ||
              *to_check == '+' || !strncmp(to_check, "inf", 3) ||
              !strncmp(to_check, "nan", 3) ) {
        node->type = toml_get_valid_number ( to_check, &(node->value), NULL, NULL, 
                                             leftover );
        if ( node->type == TOML_UNKNOWN) {
            g_toml_error = TOML_INVALID_KEY;
            return NULL;
        }
    }

    /* Boolean */
    else if ( !strncmp(to_check,"true",4) ) {
        node->type = TOML_BOOLEAN;
        *leftover = to_check + 4;
        node->value = toml_allocate_string ( "true", 0 );
    }
    else if ( !strncmp(to_check,"false",5) ) {
        node->type = TOML_BOOLEAN;
        *leftover = to_check + 5;
        node->value = toml_allocate_string ( "false", 0 );
    }

    /* Array */
    else if (*to_check == '[') {
        g_parser.in_key_ary++;
        node->type = TOML_ARRAY;
        g_parser.current_node = node;
        *leftover = to_check + 1;
    }

    else if (*to_check == ']') {
        g_parser.in_key_ary--;
        g_parser.current_node = g_parser.current_node->parent;
        node->type = TOML_ARRAY_END;
        if ( g_parser.in_key_ary < 0 ) {
            g_toml_error = TOML_INVALID_ARRAY;
            return NULL;
        }
        *leftover = to_check + 1;
    }

    /* Else invalid */
    else {
        g_toml_error = TOML_INVALID_KEY;
        return NULL;
    }

    /*
     * If in an array and not first element, must have a previous comma
     *   and must be same as previous elements
     */
    if ( g_parser.in_key_ary && (node->previous != NULL) && 
            TOML_IS_VALUE(node->type) ) {

        int comma_found = 0;
        t_toml_node *tmp_node = node->previous;

        while ( tmp_node != NULL ) {
            if ( tmp_node->type == TOML_COMMA )
                comma_found = 1;

            if ( TOML_IS_VALUE(tmp_node->type) ) {
                if ( node->type != tmp_node->type ) {
                    g_toml_error = TOML_INVALID_ARRAY_MIXED_TYPES;
                    return NULL;
                }
            }

            tmp_node = tmp_node->previous;
        }

        if ( !comma_found ) {
            g_toml_error = TOML_INVALID_ARRAY;
            return NULL;
        }
    }

    return node;

} /* end toml_search_for_values */

/******************************************************************************
**  Function   :  toml_search_multi_values
**                                                                    
**  Objective  :  This function searches the input line for values
**                                                   
**  Parameters :  line     - string to check for values
**                key_node - key created that will have child value(s)
**                 
**  return     :  0 on success, error type if failure
******************************************************************************/ 
int toml_search_multi_values ( char *line, t_toml_node *key_node ) {

    char *leftover = NULL;
    t_toml_node *node;
    t_toml_node *test_node;
    t_toml_node *final_node;

    /* 
     * Search for initial value
     */
    node = toml_search_for_values(line, key_node, &leftover);

    if ( g_toml_error )
        return g_toml_error;

    if ( node == NULL )
        return 0;

    final_node = node;

    /* If we are in array, check for more */
    if (g_parser.in_key_ary) {
        while ( test_node = toml_search_for_values(leftover, node, &leftover) ) {
            final_node = test_node;
            if ( g_toml_error )
                return g_toml_error;
        }
    }

    /* If not in array, only allow comment */
    else {
        if ( !(test_node = toml_is_legal_comment(leftover, node)) ) {
            if (g_toml_error)
                return TOML_INVALID_KEY;
        }
        else {
            return 0;
        }
    }

    /* If in array or multi-line indicate end of line */

    if(leftover && strlen(leftover)) {
        final_node->to_newline = toml_allocate_string ( leftover, 0 );
    }
    else {
        final_node->to_newline = toml_allocate_string ( "\0", 0 );
    }

    return 0;
}

/******************************************************************************
**  Function   :  toml_start_key
**                                                                    
**  Objective  :  This function begins searching for key values when we find
**                a valid key
**                                                   
**  Parameters :  equals - pointer to the equals sign in the string
**                key_node - parent key node to find child values for
**                 
**  return     :  0 on success, error code on failure
******************************************************************************/ 
int toml_start_key ( char *equals, t_toml_node *key_node ) {

    int type;
    char *start;
    char *indent = NULL;

    start = ++equals;

    if (*equals == '\0' || *equals == '\n')
        return TOML_INVALID_KEY;

    toml_reset_parser_bools();

    return toml_search_multi_values ( equals, key_node );
}

/******************************************************************************
**  Function   :  toml_parse
**                                                                    
**  Objective  :  This function reads through the lines of the file and fills
**                in the toml tree.
**                                                   
**  Parameters :  filename
**                 
**  return     :  0 on success, error code on failure
******************************************************************************/ 
int toml_parse ( t_toml_node *root ) {

    char line[257];
    char *newline;
    char *tmp;
    char *start;
    char *equals_ptr;
    char *indent_ptr;
    char *tmp_value;
    int size;
    int type;

    toml_reset_parser_bools();
    g_parser.current_node = root;
    g_parser.current_table = root;
    g_parser.line_no = 0;
    g_toml_error = 0;

    /*
     * Read in line by line 
     */
    while (fgets(line, sizeof(line), g_parser.fp)) {

        g_parser.line_no++;
        equals_ptr = 0;

        /* Check for error just in case */
        if ( g_toml_error )
            return g_toml_error;

        /* 
         * Move past leading whitespace 
         */
        int i = 0;
        while(isspace(line[i])) i++;

        /* If nothing but newline, check for multi-line string and move on */
        if (line[i] == '\0' || line[i] == '\n') {
            type = g_parser.current_node->type;
            if ( type == TOML_TSQ_STRING || type == TOML_TDQ_STRING ) {
                g_parser.current_node = toml_allocate_node ( g_parser.current_node );
                g_parser.current_node->type = type;
                g_parser.current_node->value = toml_allocate_string(line, 0);
            }
            else {
                if (g_parser.current_node->type == TOML_TABLE)
                    g_parser.current_node = toml_allocate_child ( g_parser.current_node );
                else
                    g_parser.current_node = toml_allocate_node ( g_parser.current_node );
                g_parser.current_node->type = TOML_NEWLINE;
                g_parser.current_node->value = toml_allocate_string(line, 1);
            }

            continue;
        }

        /* 
         * if in a key, search for values
         */
        if ( TOML_IN_KEY ) {

            if ( toml_search_multi_values ( line, g_parser.current_node ) )
                return g_toml_error;
        }

        /* 
         * if # is first non whitespace, it's its own comment 
         */
        else if ( line[i] == '#'  ) {
            if (g_parser.current_node->type == TOML_TABLE)
                g_parser.current_node = toml_allocate_child ( g_parser.current_node );
            else
                g_parser.current_node = toml_allocate_node ( g_parser.current_node );
            g_parser.current_node->type = TOML_COMMENT;
            g_parser.current_node->value = toml_allocate_string(line, 1);

            /* This is really just a flag to make sure we know it is its own line */
            g_parser.current_node->to_newline = toml_allocate_string(line, 1);
        }

        /* 
         * Test if a table or table array 
         */
        else if ( line[i] == '[' ) {

            
            /* Determine type and put line after brackets */
            if ( line[++i] == '[' ) {
                type = TOML_TABLE_ARY;
                ++i;
            }
            else {
                type = TOML_TABLE;
            }

            /* Find table header without whitespace */
            tmp_value = toml_allocate_table_header( &(line[i]) );
            if ( !tmp_value || strlen(tmp_value) == 0 ) {
                if ( tmp_value )
                    free(tmp_value);
                g_toml_error = TOML_INVALID_TABLE_DECLARATION; 
                return g_toml_error;
            }

            /* Figure out where we belong in tree */
            t_toml_node *tmp_node = g_parser.current_table;
            while ( tmp_node != root ) {

                size = strlen(tmp_node->value);
                if ( size && !strncmp(tmp_node->value, tmp_value, size) &&
                        strlen(tmp_value) > size && tmp_value[size] == '.' ) {
                    break;
                }

                tmp_node = tmp_node->parent;
            }

            g_parser.current_node = toml_allocate_child ( tmp_node );
            g_parser.current_table = g_parser.current_node;
            g_parser.current_node->type = type;
            g_parser.current_node->indent_initial = toml_allocate_indent(line);
            g_parser.current_node->value = tmp_value;

            tmp = strchr(&(line[i]), ']');
            if (!tmp || 
                (g_parser.current_node->type == TOML_TABLE_ARY && tmp[1] != ']') ) {
                g_toml_error = TOML_INVALID_TABLE_DECLARATION; 
                return g_toml_error;
            }

            if (tmp[1] == ']') ++tmp;

            /* Make sure the only thing left is a comment */
            if ( !toml_is_legal_comment(tmp+1, g_parser.current_node) ) {
                if ( g_toml_error ) {
                    g_toml_error = TOML_INVALID_TABLE_DECLARATION; 
                    return g_toml_error;
                }
                g_parser.current_node = toml_allocate_child ( g_parser.current_node );
                g_parser.current_node->type = TOML_NEWLINE;
                g_parser.current_node->value = toml_allocate_string(tmp+1, 1);
            }

        } /* end table header or array */

        /* 
         * Test for a  key  
         */
        else {

            /* Get key name and pointer to equals position */
            char *key = toml_find_key_header ( &line[i], &indent_ptr, &equals_ptr );

            int key_legal = toml_is_legal_key ( g_parser.current_table, key );

            /* if no equals or key header fails or key not good, error */
            if (!equals_ptr || !key || !key_legal) {

                if(key)
                    free(key);

                //if(indent_ptr)
                //    free(indent_ptr);

                return (g_toml_error = TOML_INVALID_KEY); 
            }

            /* Search for repeat of this name*/
            //if (g_parser.current_node->type == TOML_TABLE)
                g_parser.current_node = toml_allocate_child ( g_parser.current_table );
            //else
            //    g_parser.current_node = toml_allocate_node ( g_parser.current_node );

            g_parser.current_node->type = TOML_KEY;
            g_parser.current_node->value = key;
            g_parser.current_node->indent_initial = toml_allocate_indent(line);
            g_parser.current_node->indent_to_equals = indent_ptr;

            /* Now work the value from equals */
            if (g_toml_error = toml_start_key ( equals_ptr, g_parser.current_node ) )
                return g_toml_error;

        } /* end test for key */

    } /* end while reading lines */

    return g_toml_error;

} /* end toml_parse */

/******************************************************************************
**  Function   :  toml_parse_file                                            
**                                                                    
**  Objective  :  This function opens and parses the input filename.  It returns
**                a valid toml tree when successful or NULL on failure. 
**                The caller can check the error code and message after.
**                                                   
**  Parameters :  filename
**                 
**  return     :  toml_tree pointer on success, NULL on failure
******************************************************************************/ 
t_toml_node * toml_parse_file ( char *filename ) {

    t_toml_node *tmp_node;
    int error;

    /*
     * First make sure we can open the file
     */
    if ((g_parser.fp = fopen(filename,"r")) == NULL) {
        g_parser.line_no = 0;
        g_toml_error = TOML_FILE;
        return NULL;
    }

    /* Every parsed document owns a standalone root. */
    tmp_node = toml_allocate_standalone_node();
    if ( !tmp_node ) {
        fclose(g_parser.fp);
        return NULL;
    }

    tmp_node->value = toml_allocate_string ( filename, 0 );
    tmp_node->type = TOML_TABLE;

    /*
     * Initialize the Parser and begin
     */
    error = toml_parse ( tmp_node );

    /*
     * Close the file and return our parsed tree
     */
    fclose(g_parser.fp);

    if ( error ) {
        toml_free_node_tree ( tmp_node );
        return NULL;
    }
    else
        return tmp_node;

} /* end toml_parse_file */

/******************************************************************************
**  Function   :  toml_write_node                                            
**                                                                    
**  Objective  :  This function writes the node to a file while calling itself
**                to write next in line and children of the node.  One call
**                                                   
**  Parameters :  fp   - file pointer to write to
**                node - node to write including next and children
**                 
**  return     :  None
******************************************************************************/ 
void toml_write_node ( FILE *fp, t_toml_node *node ) {

    char *to_write;
    char temp[257];

    /* Put initial whitespace before node */
    if (node->indent_initial)
        fputs ( node->indent_initial, fp ); 

    switch ( node->type ) {

        case TOML_NEWLINE:
        case TOML_COMMENT: 
            fputs ( node->value, fp ); 
            if ( !strchr(node->value, '\n') )
                fputs ( "\n", fp ); 
            break; 

        case TOML_TABLE: 
        case TOML_TABLE_ARY: 
            fputs ( "[", fp);
            if ( node->type == TOML_TABLE_ARY )
                fputs ( "[", fp);
            fputs ( node->value, fp ); 
            fputs ( "]", fp);
            if ( node->type == TOML_TABLE_ARY )
                fputs ( "]", fp);
            if ( node->child && node->child->type == TOML_COMMENT ) {
                if (node->child->to_newline) 
                    fputs ( "\n", fp);
            }
            break; 

        case TOML_KEY: 
            fputs ( node->value, fp ); 
            if ( node->indent_to_equals )
                fputs ( node->indent_to_equals, fp ); 
            fputs ( "=", fp);
            break; 

        case TOML_ARRAY: 
            fputs ( "[", fp);
            break;

        case TOML_ARRAY_END: 
            fputs ( "]", fp);
            break;

        case TOML_DQ_STRING: 
            fputs("\"", fp);
            toml_write_escaped_dq_string ( fp, node->value );
            fputs("\"", fp);
            break;
        case TOML_SQ_STRING: 
            fputs( "\'", fp);
            fputs ( node->value, fp ); 
            fputs( "\'", fp);
            break;
        case TOML_TDQ_STRING:
            if ( !node->previous || node->previous->type != node->type )
                fputs( "\"\"\"", fp);
            if ( node->value )
                fputs ( node->value, fp );
            if ( !node->next || node->next->type != node->type )
                fputs( "\"\"\"", fp);
            break;
        case TOML_TSQ_STRING:
            if ( !node->previous || node->previous->type != node->type )
                fputs( "\'\'\'", fp);
            if ( node->value )
                fputs ( node->value, fp );
            if ( !node->next || node->next->type != node->type )
                fputs( "\'\'\'", fp);
            break;

        case TOML_BOOLEAN: 
        case TOML_COMMA: 
        case TOML_FLOAT: 
        case TOML_INT: 
        case TOML_DATE: 
        case TOML_TIME: 
            fputs ( node->value, fp ); 
            break;
    }

    if ( node->to_newline && node->type != TOML_COMMENT )
        fputs ( node->to_newline, fp ); 

    if ( node->child ) {
        toml_write_node ( fp, node->child );
    }

    if ( node->next ) {
        toml_write_node ( fp, node->next );
    }

} /* end toml_write_node */

/******************************************************************************
**  Function   :  toml_write_file                                            
**                                                                    
**  Objective  :  This function opens the file for writing and writes the 
**                data from the tree
**                                                   
**  Parameters :  tree     - toml node tree with data
**                filename - filename to write to
**                 
**  return     :  0 on success, otherwise failure
******************************************************************************/ 
int toml_write_file ( t_toml_node *tree, const char *filename ) {

    t_toml_node *tmp_node;
    int status = 0;

    if (tree == NULL || tree->child == NULL) {
        return -1;
    }

    /*
     * First make sure we can open the file
     */
    if (filename == NULL)
        g_parser.fp = stdout;
    else {
        if ((g_parser.fp = fopen(filename,"w")) == NULL) {
            g_toml_error = TOML_FILE;
            return -1;
        }
    }

    /*
     * Run through the tree
     */
    toml_write_node ( g_parser.fp, tree->child );

    /* close file if not stdout */
    if ( g_parser.fp != stdout )
        fclose(g_parser.fp);

    return status;

} /* end toml_write_file */

/******************************************************************************
**  Function   :  toml_search_for_node                                            
**                                                                    
**  Objective  :  This function starts with node searching next and children. 
**                Typically key_tab and table_tag will be the same value
**                coming into the first call.  Then when a partial match is 
**                made due to a dotted key, key_tag will contain the leftover
**                that could match a child.  table_tag wont change.
**                                                   
**  Parameters :  node     - toml node tree with data
**                key_tag   - will get replaced as dotted tables found
**                table_tag - contains full name to try and match
**                 
**  return     :  matched node on success, otherwise NULL
******************************************************************************/ 
t_toml_node * toml_search_for_node ( t_toml_node *node, const char *key_tag, 
                                     const char *table_tag ) {

    int size;
    t_toml_node *result_node;

    if ( !node || !key_tag )
        return NULL;

    /* Search this line of nodes from the input */
    while ( node ) {

        /* Is this the exact string */
        if ( !strcmp(key_tag, node->value) || !strcmp(table_tag, node->value) )
            return node;

        /* Check for part string */
        size = strlen(node->value);

        if ( !strncmp(node->value, key_tag, size) || 
                !strncmp(node->value, table_tag, size) ) {

            /* if we have part of it, check children */
            if ( strlen(table_tag) > size+1 && table_tag[size] == '.' ) {

                result_node =  toml_search_for_node ( node->child, 
                        &(table_tag[size+1]), table_tag );

                if ( result_node )
                    return result_node;

            } /* end it could be in our child tree */

        } /* end if part of tag found */

        node = node->next;
    }

    return NULL;
}
  
/******************************************************************************
**  Function   :  toml_find_node                                            
**                                                                    
**  Objective  :  This function calls searches for a node starting with the
**                child of the input node.  This is so we can input root
**                and not have it search all node trees.
**                                                   
**  Parameters :  node - parent node to search from (starts from child)
**                tag  - node name to find
**                 
**  return     :  found node on success, otherwise NULL
******************************************************************************/ 
t_toml_node * toml_find_node ( t_toml_node *node, const char *tag ) {

    if ( !node )
        return NULL;

    node = node->child;

    return toml_search_for_node ( node, tag, tag );
}

/******************************************************************************
**  Function   :  toml_get_key_value
**                                                                    
**  Objective  :  This function will first find the node based on tag name
**                input.  Then it will return the value of the key found
**                meaning the child of the key name
**                                                   
**  Parameters :  node - parent node to search from (starts from child)
**                tag  - node name to find
**                 
**  return     :  0 on success, otherwise failure
******************************************************************************/ 
char * toml_get_key_value ( t_toml_node *node, const char *tag ) {

    t_toml_node *tmp_node = toml_find_node ( node, tag );

    if ( tmp_node && tmp_node->child ) {
        return tmp_node->child->value;
    }

    return NULL;
}

/******************************************************************************
**  Function   :  toml_set_value
**                                                                    
**  Objective  :  This function will first find the node based on tag name
**                input.  Then it will set the value of the key (the child of
**                the key node name).
**                                                   
**  Parameters :  node  - parent node to search from (starts from child)
**                tag   - node name to find
**                value - value to set to
**                 
**  return     :  0 on success, otherwise failure
******************************************************************************/ 
int toml_set_value ( t_toml_node *node, const char *tag, const char *value ) {

    t_toml_node *tmp_node = toml_find_node ( node, tag );

    /* First child should be value if not an array */
    if ( tmp_node && tmp_node->child ) {
        tmp_node = tmp_node->child;
        if ( tmp_node->type == TOML_ARRAY )
            return -1;
        if ( tmp_node->value )
            free(tmp_node->value);
        tmp_node->value = toml_allocate_string ( value, 0 );

        return 0;
    }

    return -1;
}

/******************************************************************************
**  Function   :  toml_add_generic_node
**                                                                    
**  Objective  :  This function will add and allocate a child node of the
**                input.  The caller sets the type and value.  This is useful
**                for adding newlines and comments
**                                                   
**  Parameters :  table  - parent node to add child to
**                type   - type to use (typically newline or comment)
**                value  - value to set to
**                initial_indent - initial spacing before value
**                 
**  return     :  created node on success, otherwise NULL
******************************************************************************/ 
t_toml_node * toml_add_generic_node ( t_toml_node *table, e_toml_type type, 
                    const char *value, const char *indent_initial ) {

    t_toml_node *tmp_node;

    tmp_node = toml_allocate_child ( table );
    if (!tmp_node)
        return NULL;

    tmp_node->type = type;
    tmp_node->value = toml_allocate_string ( value ? value : "", 0 );
    tmp_node->indent_initial = toml_allocate_string ( indent_initial, 0 );

    return tmp_node;

} /* end toml_add_generic_node */

/******************************************************************************
**  Function   :  toml_add_key
**                                                                    
**  Objective  :  This function will add a key tag and value under the input
**                table.
**                                                   
**  Parameters :  table  - parent node to add child to
**                key    - the key name
**                type   - type to use (any that come back in TOML_IS_VALUE)
**                value  - value to set to
**                initial_indent - initial spacing before value
**                initial_to_equals - spacing between key name and = sign
**                 
**  return     :  0 on success, otherwise error
******************************************************************************/ 
/* Insert a new child node before the first TOML_TABLE or TOML_TABLE_ARY child,
   so that scalar keys always appear before sub-table headers in the output. */
static t_toml_node * toml_allocate_child_before_tables ( t_toml_node *table ) {

    t_toml_node *child = table->child;
    t_toml_node *before = NULL;

    while ( child ) {
        if ( child->type == TOML_TABLE || child->type == TOML_TABLE_ARY ) {
            before = child;
            break;
        }
        child = child->next;
    }

    if ( !before )
        return toml_allocate_child ( table );

    t_toml_node *new_node = malloc ( sizeof(t_toml_node) );
    if ( !new_node ) {
        g_toml_error = TOML_INTERNAL_ERROR;
        return NULL;
    }
    toml_init_node ( new_node );
    new_node->parent = table;
    new_node->next = before;
    new_node->previous = before->previous;

    if ( before->previous )
        before->previous->next = new_node;
    else
        table->child = new_node;

    before->previous = new_node;

    return new_node;
}

int toml_add_key ( t_toml_node *table, const char *key, e_toml_type type,
                   const char *value, const char *indent_initial,
                   const char *indent_to_equals ) {

    t_toml_node *tmp_node;

    /* first see if the key exists, if so then then set value */
    tmp_node = toml_find_node ( table, key );
    if ( tmp_node )
        return toml_set_value ( table, key, value );

    /* allocate for new key, inserting before any sub-table headers */
    tmp_node = toml_allocate_child_before_tables ( table );
    if (!tmp_node)
        return -1;

    tmp_node->type = TOML_KEY;
    tmp_node->value = toml_allocate_string ( key, 0 );
    tmp_node->indent_initial = toml_allocate_string ( indent_initial, 0 );
    tmp_node->indent_to_equals = toml_allocate_string ( indent_to_equals, 0 );

    /* allocate for the value */
    tmp_node = toml_allocate_child ( tmp_node );
    if (!tmp_node)
        return -1;

    tmp_node->type = type;
    tmp_node->value = toml_allocate_string ( value, 0 );
    tmp_node->indent_initial = toml_allocate_string ( indent_to_equals, 0 );
    tmp_node->to_newline = toml_allocate_string ( "\n", 0 );

    return 0;

} /* end toml_add_key */

/******************************************************************************
**  Function   :  toml_add_table
**                                                                    
**  Objective  :  This function will add a table to parent node node.  
**                                                   
**  Parameters :  parent - parent node to add table to
**                name   - the table name
**                type   - either TOML_TABLE or TOML_TABLE_ARY 
**                initial_indent - initial spacing before value
**                 
**  return     :  0 on success, otherwise error
******************************************************************************/ 
t_toml_node * toml_add_table ( t_toml_node *parent, const char *name, e_toml_type type,
                     const char *indent_initial ) {

    t_toml_node *tmp_node;
    t_toml_node *result_node;

    /* First, if not a table array, reject an existing node with this name. */
    if ( type != TOML_TABLE_ARY ) {
        tmp_node = toml_find_node ( parent, name );
        if ( tmp_node )
            return NULL;
    }

    /* Append table */
    tmp_node = toml_allocate_child ( parent );
    if (!tmp_node)
        return NULL;

    tmp_node->type = type;
    tmp_node->value = toml_allocate_string ( name, 0 );
    tmp_node->indent_initial = toml_allocate_string ( indent_initial, 0 );

    result_node = tmp_node;

    /* Append Newline */
    tmp_node = toml_allocate_child ( tmp_node );
    if (!tmp_node)
        return NULL;

    tmp_node->type = TOML_NEWLINE;
    tmp_node->value = toml_allocate_string ( "\n", 0 );

    return result_node;

} /* toml_add_table */

/******************************************************************************
**  Function   :  toml_get_num_array_elements
**                                                                    
**  Objective  :  This function will count the number of elements in an array
**                                                   
**  Parameters :  node - this should be the key name or array node
**                 
**  return     :  number of elements found
******************************************************************************/ 
int toml_get_num_array_elements ( t_toml_node *node ) {

    int elements = 0;

    if (!node)
        return 0;

    if ( node->type == TOML_KEY )
        node = node->child;

    if ( node && node->type == TOML_ARRAY )
        node = node->child;

    while ( node ) {
        if ( TOML_IS_VALUE(node->type) ) {
            elements++;
        }
        node = node->next;
    }

    return elements;

} /*end toml_get_num_array_elements */

/******************************************************************************
**  Function   :  toml_start_array_with_value
**                                                                    
**  Objective  :  This function will setup an array key and place value as 
**                first value
**                                                   
**  Parameters :  node  - this shuold be the parent of the array
**                tag   - array key name may need to be full name with table
**                type  - type of input value and assign this for array
**                value - already formatted string with value
**                indent_initial - spacing from last comma to this value
**                indent_to_equals - spacing from key name to equals.  Also
**                                   used for spacing between array ends
**                 
**  return     :  0 on success, otherwise error
******************************************************************************/ 
int toml_start_array_with_value ( t_toml_node *node, const char *tag, int type, 
                                    const char *value, const char *indent_initial, 
                                    const char *indent_to_equals ) {

    t_toml_node *tmp_node;

    /* Create new key, inserting before any sub-table headers */
    tmp_node = toml_allocate_child_before_tables ( node );
    if (!tmp_node)
        return -1;

    tmp_node->type = TOML_KEY;
    tmp_node->value = toml_allocate_string ( tag, 0 );
    tmp_node->indent_initial = toml_allocate_string ( indent_initial, 0 );
    tmp_node->indent_to_equals = toml_allocate_string ( indent_to_equals, 0 );

    /* Add TOML_ARRAY node for '[' */
    tmp_node = toml_allocate_child ( tmp_node );
    if (!tmp_node)
        return -1;
    tmp_node->type = TOML_ARRAY;
    tmp_node->indent_initial = toml_allocate_string ( indent_to_equals, 0 );

    /* allocate for the value */
    tmp_node = toml_allocate_child ( tmp_node );
    if (!tmp_node)
        return -1;

    tmp_node->type = type;
    tmp_node->value = toml_allocate_string ( value, 0 );
    tmp_node->indent_initial = toml_allocate_string ( indent_to_equals, 0 );

    /* Add TOML_ARRAY_END node for ']' */
    tmp_node = toml_allocate_node ( tmp_node );
    if (!tmp_node)
        return -1;
    tmp_node->type = TOML_ARRAY_END;
    tmp_node->indent_initial = toml_allocate_string ( indent_to_equals, 0 );
    tmp_node->to_newline = toml_allocate_string ( "\n", 0 );

    return 0;

} /* end toml_start_array_with_value */

/******************************************************************************
**  Function   :  toml_insert_array_value_by_index
**                                                                    
**  Objective  :  This function will insert a value into the array at the 
**                input index.  If index is -1, add to the end
**                                                   
**  Parameters :  node - this should be the parent of the array
**                tag  - array key name may need to be full name with table
**                value - already formatted string with value
**                indent_initial - spacing from last comma to this value
**                comma_to newline - this is set if value is last in row
**                prev_indent_initial - indent to comma
**                index - where to insert new value
**                 
**  return     :  0 on success, otherwise error
******************************************************************************/ 
int toml_insert_array_value_by_index ( t_toml_node *node, const char *tag, 
                                    const char *value, const char *indent_initial, 
                                    const char *comma_to_newline, 
                                    const char *prev_indent_initial, int index ) {

    int elements = 0;
    int had_values;
    int type;
    t_toml_node *ary_node = toml_find_node ( node, tag );
    t_toml_node *tmp_node;
    t_toml_node *new_node = NULL;

    if ( !ary_node )
        return -1;

    /* Cycle through array nodes, until we find index */
    /*  if index = -1, we just go to end  */
    tmp_node = ary_node->child;
    if (tmp_node)
        tmp_node = tmp_node->child;

    if ( !tmp_node )
        return -1;

    had_values = toml_get_num_array_elements ( ary_node ) > 0;

    /* Determine the value type from existing elements.
     * If the array is empty (first child is ARRAY_END after all values were
     * deleted), scan siblings for a real value type.  Fall back to DQ_STRING
     * since all arrays in this codebase use string values. */
    type = tmp_node->type;
    if ( !TOML_IS_VALUE(type) ) {
        t_toml_node *scan = tmp_node;
        while ( scan ) {
            if ( TOML_IS_VALUE(scan->type) ) {
                type = scan->type;
                break;
            }
            scan = scan->next;
        }
        if ( !TOML_IS_VALUE(type) )
            type = TOML_DQ_STRING;
    }

    while ( tmp_node ) {

        if ( elements == index ||
                (tmp_node->type == TOML_ARRAY_END && index == -1) ) {

            /* first create the new node to hold value */
            new_node = toml_allocate_insert_node(tmp_node);
            if (tmp_node->indent_initial)
                free(tmp_node->indent_initial);
            tmp_node->indent_initial = toml_allocate_string ( prev_indent_initial, 0 );

            if (!new_node)
                return -1;

            new_node->value = toml_allocate_string ( value, 0 );
            new_node->type = type;
            new_node->indent_initial = toml_allocate_string ( indent_initial, 0 );

            /* Add a separator only when there is already another array value. */
            if ( had_values ) {
                if ( index == -1 )
                    new_node = toml_allocate_insert_node(new_node);
                else
                    new_node = toml_allocate_insert_node(tmp_node);

                if (!new_node)
                    return -1;

                new_node->type = TOML_COMMA;
                new_node->value = toml_allocate_string ( ", ", 0 );
                if ( comma_to_newline ) {
                    new_node->to_newline = toml_allocate_string ( comma_to_newline, 0 );
                }
            }

            break;
        }

        tmp_node = tmp_node->next;

        if ( tmp_node && TOML_IS_VALUE(tmp_node->type) ) {
            elements++;
        }
    } 

    if (new_node)
        return 0;
    else
        return -1;

} /* end toml_insert_array_value_by_index */

/******************************************************************************
**  Function   :  toml_delete_node_and_children
**                                                                    
**  Objective  :  This function will delete a key or table and all their 
**                children
**                                                   
**  Parameters :  parent - parent node to find our node under
**                name   - name of node to find
**                 
**  return     :  0 on success, otherwise key not found
******************************************************************************/ 
int toml_delete_node_and_children ( t_toml_node *parent, const char *name) {

    t_toml_node *tmp_node;

    /* find the key or table, delete the child tree and then key */
    tmp_node = toml_find_node ( parent, name );
    if ( tmp_node ) {
        toml_free_node_tree(tmp_node->child);
        toml_free_single_node(tmp_node);
        return 0;
    }

    return -1;

} /* end toml_delete_node_and_children */

/******************************************************************************
**  Function   :  toml_delete_array_value_by_index
**                                                                    
**  Objective  :  This function will delete the value at specified index
**                If index is -1, delete last value.
**                                                   
**  Parameters :  node - this shuold be the parent of the array
**                tag  - array key name may need to be full name with table
**                index - where to delete value
**                 
**  return     :  0 on success, otherwise error
******************************************************************************/ 
int toml_delete_array_value_by_index ( t_toml_node *node, const char *tag, 
                                       int index ) {

    int current_index = -1;
    t_toml_node *ary_node = toml_find_node ( node, tag );
    t_toml_node *tmp_node;
    t_toml_node *value_node = NULL;
    t_toml_node *comma_node = NULL;

    if ( !ary_node )
        return -1;

    /* Cycle through array nodes, until we find index */
    /*  if index = -1, we just go to end  */
    tmp_node = ary_node->child;
    if ( tmp_node )
        tmp_node = tmp_node->child;

    if ( !tmp_node )
        return -1;

    /* Locate a real value node.  Non-value nodes such as commas must never
     * satisfy an array index. */
    while ( tmp_node ) {
        if ( TOML_IS_VALUE(tmp_node->type) ) {
            value_node = tmp_node;
            current_index++;
            if ( index != -1 && current_index == index )
                break;
        }
        tmp_node = tmp_node->next;
    }

    if ( index != -1 && (!value_node || current_index != index) )
        return -1;

    if ( index == -1 && !value_node )
        return -1;

    /* Prefer removing the comma after the deleted value; if this is the last
     * value, fall back to the comma before it.  That keeps the remaining array
     * syntactically valid for first, middle, and last deletions alike. */
    tmp_node = value_node->next;
    while ( tmp_node && tmp_node->type != TOML_ARRAY_END ) {
        if ( tmp_node->type == TOML_COMMA ) {
            comma_node = tmp_node;
            break;
        }
        if ( TOML_IS_VALUE(tmp_node->type) )
            break;
        tmp_node = tmp_node->next;
    }

    if ( !comma_node ) {
        tmp_node = value_node->previous;
        while ( tmp_node ) {
            if ( tmp_node->type == TOML_COMMA ) {
                comma_node = tmp_node;
                break;
            }
            if ( TOML_IS_VALUE(tmp_node->type) )
                break;
            tmp_node = tmp_node->previous;
        }
    }

    toml_free_single_node(value_node);

    if ( comma_node )
        toml_free_single_node(comma_node);

    return 0;

} /* end toml_delete_array_value_by_index */

/******************************************************************************
**  Function   :  toml_get_key_array_values_start
**                                                                    
**  Objective  :  This function will get the node starting the array
**                                                   
**  Parameters :  node - this should be the parent of the array
**                tag  - array key name may need to be full name with table
**                 
**  return     :  value as string on success, otherwise NULL
******************************************************************************/ 
t_toml_node* toml_get_key_array_values_start ( t_toml_node *node, 
                                                 const char *tag ) {

    t_toml_node *tmp_node;

    if ( tag )
        tmp_node = toml_find_node ( node, tag );
    else 
        tmp_node = node; //if no tag, assume this is the array

    if ( !tmp_node )
        return NULL;

    /* Verify we have an array */
    if ( !(tmp_node->child) || tmp_node->child->type != TOML_ARRAY )
        return NULL;

    tmp_node = tmp_node->child->child;

    return tmp_node;

} /* end toml_get_key_array_values_start */

/******************************************************************************
**  Function   :  toml_get_value_by_index_from_array_node
**                                                                    
**  Objective  :  This function will get the array value at a specified index
**                input index.  If index not valid, NULL returned
**                                                   
**  Parameters :  node - this should be the node starting the values
**                index - which value to get
**                 
**  return     :  value as string on success, otherwise NULL
******************************************************************************/ 
char * toml_get_value_by_index_from_array_node ( t_toml_node *node, int index ) {

    int curr_index = -1;

    while ( node ) {
        if ( !TOML_IS_VALUE(node->type) || ++curr_index != index ) {
            node = node->next;
            continue;
        }

        return node->value;
    }

    return NULL;

} /* end toml_get_value_by_index_from_array_node */

/******************************************************************************
**  Function   :  toml_get_array_value_by_index
**                                                                    
**  Objective  :  This function will get the array value at a specified index
**                input index.  If index not valid, NULL returned
**                                                   
**  Parameters :  node - this should be the parent of the array
**                tag  - array key name may need to be full name with table
**                index - which value to get
**                 
**  return     :  value as string on success, otherwise NULL
******************************************************************************/ 
char * toml_get_array_value_by_index ( t_toml_node *node, const char *tag, 
                                       int index ) {

    int curr_index = -1;
    t_toml_node *tmp_node = toml_get_key_array_values_start ( node, tag );
    if ( !tmp_node )
        return NULL;

    return toml_get_value_by_index_from_array_node ( tmp_node, index );

} /* end toml_get_array_value_by_index */

/******************************************************************************
**  Function   :  toml_get_table_array_by_key
**                                                                    
**  Objective  :  This function will get the table array based on the key and
**                key value entered.  This is the way to get a specific
**                table array
**                                                   
**  Parameters :  node  - this should on same level as array (e.g. the root
**                        node's child)
**                table - name of the table array
**                key   - the key name to match the table by
**                tab   - key value to use to match the table by
**                 
**  return     :  table array node on success, otherwise NULL
******************************************************************************/ 
t_toml_node * toml_get_table_array_by_key ( t_toml_node *node, const char *table, 
        const char *key, const char *tag ) {

    int found = 0;
    t_toml_node *result_node, *key_node;
    char *result;

    result_node = node;

    while ( !found && result_node ) {
        result_node = toml_search_for_node ( result_node, table, table );
        if ( result_node ) {
            result = toml_get_key_value ( result_node, key );
            if( result && tag && !strcmp(result, tag) )
                return result_node;
        }
        else
            break;

        result_node = result_node->next;
    }

    return NULL;

} /* end toml_get_table_array_by_key */
