/*
 * Copyright (C) 2012 
 *     Dale Weiler
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is furnished to do
 * so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
#include "gmqcc.h"

/* compile-time constant for type constants */
typedef struct {
    char *name;
    int   type;
    float value[3];
    char *string; /* string value if constant is string literal */
} constant;
VECTOR_MAKE(constant, compile_constants);

void compile_constant_debug() {
    int iter = 0;
    for(; iter < compile_constants_elements; iter++) {
        constant *c = &compile_constants_data[iter];
        switch(c->type) {
            case TYPE_FLOAT:  printf("constant: %s FLOAT   %f\n",       c->name, c->value[0]);                           break;
            case TYPE_VECTOR: printf("constant: %s VECTOR {%f,%f,%f}\n",c->name, c->value[0], c->value[1], c->value[2]); break;
            case TYPE_STRING: printf("constant: %s STRING  %s\n",       c->name, c->string); break;
            case TYPE_VOID:   printf("constant: %s VOID    %s\n",       c->name, c->string); break;
        }
    }
}

/*
 * Generates a parse tree out of the lexees generated by the lexer.  This
 * is where the tree is built.  This is where valid check is performed.
 */
int parse_gen(struct lex_file *file) {    
    int     token = 0;
    while ((token = lex_token(file)) != ERROR_LEX && file->length >= 0) {
        switch (token) {
            case TOKEN_TYPEDEF: {
                char *f; /* from */
                char *t; /* to   */
                
                token = lex_token(file); 
                token = lex_token(file); f = util_strdup(file->lastok);
                token = lex_token(file); 
                token = lex_token(file); t = util_strdup(file->lastok);
                
                typedef_add(file, f, t);
                mem_d(f);
                mem_d(t);
                
                token = lex_token(file);
                if (token == ' ')
                    token = lex_token(file);
                    
                if (token != ';')
                    error(file, ERROR_PARSE, "Expected a `;` at end of typedef statement");
                    
                token = lex_token(file);
                break;
            }
            
            case TOKEN_VOID:   goto fall;
            case TOKEN_STRING: goto fall;
            case TOKEN_VECTOR: goto fall;
            case TOKEN_ENTITY: goto fall;
            case TOKEN_FLOAT:  goto fall;
            {
            fall:;
                char *name = NULL;
                int   type = token; /* story copy */
                
                /* skip over space */
                token = lex_token(file);
                if (token == ' ')
                    token = lex_token(file);
                
                /* save name */
                name = util_strdup(file->lastok);
                
                /* skip spaces */
                token = lex_token(file);
                if (token == ' ')
                    token = lex_token(file);
                    
                if (token == ';') {
                    /*
                     * Definitions go to the defs table, they don't have
                     * any sort of data with them yet.
                     */
                } else if (token == '=') {
                    token = lex_token(file);
                    if (token == ' ')
                        token = lex_token(file);
                    
                    /* strings are in file->lastok */
                    switch (type) {
                        case TOKEN_VOID:
                            error(file, ERROR_PARSE, "Cannot assign value to type void\n");
                            
                        /* TODO: Validate (end quote), strip quotes for constant add, name constant */
                        case TOKEN_STRING:
                            if (*file->lastok != '"')
                                error(file, ERROR_PARSE, "Expected a '\"' (quote) for string constant\n");
                            /* add the compile-time constant */
                            compile_constants_add((constant){
                                .name   = util_strdup(name),
                                .type   = TYPE_STRING,
                                .value  = {0,0,0},
                                .string = util_strdup(file->lastok)
                            });
                            break;
                        /* TODO: name constant, old qc vec literals, whitespace fixes, name constant */
                        case TOKEN_VECTOR: {
                            float compile_calc_x = 0;
                            float compile_calc_y = 0;
                            float compile_calc_z = 0;
                            int   compile_calc_d = 0; /* dot?        */
                            int   compile_calc_s = 0; /* sign (-, +) */
                            
                            char  compile_data[1024];
                            char *compile_eval = compile_data;
                            
                            if (token != '{')
                                error(file, ERROR_PARSE, "Expected initializer list {} for vector constant\n");    
                            
                            /*
                             * This parses a single vector element: x,y & z.  This will handle all the
                             * complicated mechanics of a vector, and can be extended as well.  This
                             * is a rather large macro, and is #undef'd after it's use below.
                             */
                            #define PARSE_VEC_ELEMENT(NAME, BIT)                                                                                                           \
                                token = lex_token(file);                                                                                                                   \
                                if (token == ' ')                                                                                                                          \
                                    token = lex_token(file);                                                                                                               \
                                if (token == '.')                                                                                                                          \
                                    compile_calc_d = 1;                                                                                                                    \
                                if (!isdigit(token) && !compile_calc_d && token != '+' && token != '-')                                                                    \
                                    error(file, ERROR_PARSE,"Invalid constant initializer element %c for vector, must be numeric\n", NAME);                                \
                                if (token == '+')                                                                                                                          \
                                    compile_calc_s = '+';                                                                                                                  \
                                if (token == '-' && !compile_calc_s)                                                                                                       \
                                    compile_calc_s = '-';                                                                                                                  \
                                while (isdigit(token) || token == '.' || token == '+' || token == '-') {                                                                   \
                                    *compile_eval++ = token;                                                                                                               \
                                    token           = lex_token(file);                                                                                                     \
                                    if (token == '.' && compile_calc_d) {                                                                                                  \
                                        error(file, ERROR_PARSE, "Invalid constant initializer element %c for vector, must be numeric.\n", NAME);                          \
                                        token = lex_token(file);                                                                                                           \
                                    }                                                                                                                                      \
                                    if ((token == '-' || token == '+') && compile_calc_s) {                                                                                \
                                        error(file, ERROR_PARSE, "Invalid constant initializer sign for vector element %c\n", NAME);                                       \
                                        token = lex_token(file);                                                                                                           \
                                    }                                                                                                                                      \
                                    else if (token == '.' && !compile_calc_d)                                                                                              \
                                        compile_calc_d = 1;                                                                                                                \
                                    else if (token == '-' && !compile_calc_s)                                                                                              \
                                        compile_calc_s = '-';                                                                                                              \
                                    else if (token == '+' && !compile_calc_s)                                                                                              \
                                        compile_calc_s = '+';                                                                                                              \
                                }                                                                                                                                          \
                                if (token == ' ')                                                                                                                          \
                                    token = lex_token(file);                                                                                                               \
                                if (NAME != 'z') {                                                                                                                         \
                                    if (token != ',' && token != ' ')                                                                                                      \
                                        error(file, ERROR_PARSE, "invalid constant initializer element %c for vector (missing spaces, or comma delimited list?)\n", NAME); \
                                } else if (token != '}') {                                                                                                                 \
                                    error(file, ERROR_PARSE, "Expected `}` on end of constant initialization for vector\n");                                               \
                                }                                                                                                                                          \
                                compile_calc_##BIT = atof(compile_data);                                                                                                   \
                                compile_calc_d = 0;                                                                                                                        \
                                compile_calc_s = 0;                                                                                                                        \
                                compile_eval   = &compile_data[0];                                                                                                         \
                                memset(compile_data, 0, sizeof(compile_data))
                            
                            /*
                             * Parse all elements using the macro above.
                             * We must undef the macro afterwards.
                             */
                            PARSE_VEC_ELEMENT('x', x);
                            PARSE_VEC_ELEMENT('y', y);
                            PARSE_VEC_ELEMENT('z', z);
                            #undef PARSE_VEC_ELEMENT
                            
                            /* Check for the semi-colon... */
                            token = lex_token(file);
                            if (token == ' ')
                                token = lex_token(file);
                            if (token != ';')
                                error(file, ERROR_PARSE, "Expected `;` on end of constant initialization for vector\n");
                                
                            /* add the compile-time constant */
                            compile_constants_add((constant){
                                .name   = util_strdup(name),
                                .type   = TYPE_VECTOR,
                                .value  = {
                                    [0] = compile_calc_x,
                                    [1] = compile_calc_y,
                                    [2] = compile_calc_z
                                },
                                .string = NULL
                            });
                            break;
                        }
                            
                        case TOKEN_ENTITY:
                        case TOKEN_FLOAT: /*TODO: validate, constant generation, name constant */
                            if (!isdigit(token))
                                error(file, ERROR_PARSE, "Expected numeric constant for float constant\n");
                            compile_constants_add((constant){
                                .name   = util_strdup(name),
                                .type   = TOKEN_FLOAT,
                                .value  = {0,0,0},
                                .string = NULL
                            });
                            break;
                    }
                } else if (token == '(') {
                    printf("FUNCTION ??\n");
                }
                mem_d(name);
            }
                
            /*
             * From here down is all language punctuation:  There is no
             * need to actual create tokens from these because they're already
             * tokenized as these individual tokens (which are in a special area
             * of the ascii table which doesn't conflict with our other tokens
             * which are higer than the ascii table.)
             */
            case '#':
                token = lex_token(file); /* skip '#' */
                if (token == ' ')
                    token = lex_token(file);
                /*
                 * If we make it here we found a directive, the supported
                 * directives so far are #include.
                 */
                if (strncmp(file->lastok, "include", sizeof("include")) == 0) {
                    /*
                     * We only suport include " ", not <> like in C (why?)
                     * because the latter is silly.
                     */
                    while (*file->lastok != '"' && token != '\n')
                        token = lex_token(file);
                    if (token == '\n')
                        return error(file, ERROR_PARSE, "Invalid use of include preprocessor directive: wanted #include \"file.h\"\n");
                        
                    char            *copy = util_strdup(file->lastok);
                    struct lex_file *next = lex_include(file,   copy);
                    
                    if (!next) {
                        error(file, ERROR_INTERNAL, "Include subsystem failure\n");
                        exit (-1);
                    }
                    compile_constants_add((constant) {
                            .name   = "#include",
                            .type   = TYPE_VOID,
                            .value  = {0,0,0},
                            .string = copy
                    });
                    parse_gen(next);
                    mem_d    (copy);
                    lex_close(next);
                }
                /* skip all tokens to end of directive */
                while (token != '\n')
                    token = lex_token(file);
                break;
                
            case LEX_IDENT:
                token = lex_token(file);
                break;
        }
    }
    compile_constant_debug();
    lex_reset(file);
    /* free constants */
    {
		size_t i = 0;
		for (; i < compile_constants_elements; i++) {
			mem_d(compile_constants_data[i].name);
			mem_d(compile_constants_data[i].string);
		}
		mem_d(compile_constants_data);
	}
    return 1;
}