/*
 * Mr. 4th Dimention - Allen Webster
 *
 * 21.01.2017
 *
 * Builder for the 4coder_string.h header.
 *
 */

// TOP

// TODO(allen): Make sure to only publish the 4coder_string.h if it builds and passes a series of tests.

#define BUILD_NUMBER_FILE "4coder_string_build_num.txt"

#define GENERATED_FILE "4coder_string.h"
#define INTERNAL_STRING "internal_4coder_string.cpp"

#define BACKUP_FOLDER ".." SLASH ".." SLASH "string_backup"
#define PUBLISH_FOLDER ".." SLASH "4coder_helper"

#include "../4cpp/4cpp_lexer.h"
#define FSTRING_IMPLEMENTATION
#include "../4coder_lib/4coder_string.h"

#include "../4ed_defines.h"
#include "../meta/4ed_meta_defines.h"

#define FTECH_FILE_MOVING_IMPLEMENTATION
#include "../meta/4ed_file_moving.h"
#include "../meta/4ed_meta_parser.cpp"
#include "../meta/4ed_meta_keywords.h"

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

#define V_MAJ_NUM 1
#define V_MIN_NUM 0

#define V_MAJ STR_(V_MAJ_NUM)
#define V_MIN STR_(V_MIN_NUM)

internal char*
parse_next_line(char *str, char *str_end){
    char *ptr = str;
    for (; ptr < str_end && *ptr != '\n'; ++ptr);
    ++ptr;
    return(ptr);
}

internal b32
parse_build_number(char *file_name, i32 *major_out, i32 *minor_out, i32 *build_out){
    b32 result = false;
    String file = file_dump(file_name);
    
    if (file.str != 0){
        *major_out = 0;
        *minor_out = 0;
        *build_out = 0;
        
        char *end_str = file.str + file.size;
        char *major_str = file.str;
        char *minor_str = parse_next_line(major_str, end_str);
        char *build_str = parse_next_line(minor_str, end_str);
        char *ender = parse_next_line(build_str, end_str);
        
        if (major_str < end_str && build_str < end_str && ender < end_str){
            minor_str[-1] = 0;
            build_str[-1] = 0;
            ender[-1] = 0;
            
            *major_out = str_to_int_c(major_str);
            *minor_out = str_to_int_c(minor_str);
            *build_out = str_to_int_c(build_str);
            
            result = true;
        }
        
        free(file.str);
    }
    
    return(result);
}

internal void
save_build_number(char *file_name, i32 major, i32 minor, i32 build){
    FILE *out = fopen(file_name, "wb");
    fprintf(out, "%d\n%d\n%d\n\n\n", major, minor, build);
    fclose(out);
}

///////////////////////////////

//
// Meta Parse Rules
//

internal void
print_function_body_code(String *out, Parse_Context *context, i32 start){
    String pstr = {0}, lexeme = {0};
    Cpp_Token *token = 0;
    
    i32 do_print = 0;
    i32 nest_level = 0;
    i32 finish = false;
    i32 do_whitespace_print = false;
    i32 is_first = true;
    
    for (; (token = get_token(context)) != 0; get_next_token(context)){
        if (do_whitespace_print){
            pstr = str_start_end(context->data, start, token->start);
            append(out, pstr);
        }
        else{
            do_whitespace_print = true;
        }
        
        do_print = true;
        if (token->type == CPP_TOKEN_COMMENT){
            lexeme = get_lexeme(*token, context->data);
            if (check_and_fix_docs(&lexeme)){
                do_print = false;
            }
        }
        else if (token->type == CPP_TOKEN_BRACE_OPEN){
            ++nest_level;
        }
        else if (token->type == CPP_TOKEN_BRACE_CLOSE){
            --nest_level;
            if (nest_level == 0){
                finish = true;
            }
        }
        if (is_first){
            do_print = false;
            is_first = false;
        }
        
        if (do_print){
            pstr = get_lexeme(*token, context->data);
            append(out, pstr);
        }
        
        start = token->start + token->size;
        
        if (finish){
            break;
        }
    }
}

internal void
file_move(char *path, char *file_name){
    fm_copy_file(fm_str(file_name), fm_str(path, "/", file_name));
}

int main(){
    META_BEGIN();
    fm_init_system();
    
    // NOTE(allen): Parse the internal string file.
    char *string_files[] = { INTERNAL_STRING, 0 };
    Meta_Unit string_unit = compile_meta_unit(".", string_files, ExpandArray(meta_keywords));
    
    if (string_unit.parse == 0){
        Assert(!"Missing one or more input files!");
    }
    
    // NOTE(allen): Parse the version counter file
    i32 major_number = 0;
    i32 minor_number = 0;
    i32 build_number = 0;
    b32 parsed_version_counter = parse_build_number(BUILD_NUMBER_FILE, &major_number, &minor_number, &build_number);
    Assert(parsed_version_counter);
    
    if (V_MAJ_NUM < major_number){
        Assert(!"major version mismatch");
    }
    else if (V_MAJ_NUM > major_number){
        major_number = V_MAJ_NUM;
        minor_number = V_MIN_NUM;
        build_number = 0;
    }
    else{
        if (V_MIN_NUM < minor_number){
            Assert(!"minor version mismatch");
        }
        else if (V_MIN_NUM > minor_number){
            minor_number = V_MIN_NUM;
            build_number = 0;
        }
    }
    
    // NOTE(allen): String Library
    String out = str_alloc(10 << 20);
    
    Cpp_Token *token = 0;
    i32 start = 0;
    
    Parse parse = string_unit.parse[0];
    Parse_Context pcontext = setup_parse_context(parse);
    
    for (; (token = get_token(&pcontext)) != 0; get_next_token(&pcontext)){
        if (!(token->flags & CPP_TFLAG_PP_BODY) &&
            token->type == CPP_TOKEN_IDENTIFIER){
            String lexeme = get_lexeme(*token, pcontext.data);
            if (match(lexeme, "FSTRING_BEGIN")){
                start = token->start + token->size;
                break;
            }
        }
    }
    
    append(&out, "/*\n");
    
    append(&out, GENERATED_FILE " - Version "V_MAJ"."V_MIN".");
    append_int_to_str(&out, build_number);
    append(&out, "\n");
    
    append(&out, STANDARD_DISCLAIMER);
    append(&out,
           "To include implementation: #define FSTRING_IMPLEMENTATION\n"
           "To use in C mode: #define FSTRING_C\n");
    
    append(&out, "*/\n");
    
    String pstr = {0};
    i32 do_whitespace_print = true;
    
    for(;(token = get_next_token(&pcontext)) != 0;){
        if (do_whitespace_print){
            pstr = str_start_end(pcontext.data, start, token->start);
            append(&out, pstr);
        }
        else{
            do_whitespace_print = true;
        }
        
        String lexeme = get_lexeme(*token, pcontext.data);
        
        i32 do_print = true;
        if (match(lexeme, "FSTRING_DECLS")){
            append(&out, "#if !defined(FCODER_STRING_H)\n#define FCODER_STRING_H\n\n");
            do_print = false;
            
            local_persist i32 RETURN_PADDING = 16;
            local_persist i32 SIG_PADDING = 35;
            
            for (i32 j = 0; j < string_unit.set.count; ++j){
                char line_[2048];
                String line = make_fixed_width_string(line_);
                Item_Node *item = string_unit.set.items + j;
                
                if (item->t == Item_Function){
                    append          (&line, item->ret);
                    append_padding  (&line, ' ', SIG_PADDING);
                    append          (&line, item->name);
                    append          (&line, item->args);
                    append          (&line, ";\n");
                }
                else if (item->t == Item_Macro){
                    append          (&line, "#ifndef ");
                    append_padding  (&line, ' ', 10);
                    append          (&line, item->name);
                    append_s_char   (&line, '\n');
                    
                    append          (&line, "# define ");
                    append_padding  (&line, ' ', 10);
                    append          (&line, item->name);
                    append          (&line, item->args);
                    append_s_char   (&line, ' ');
                    append          (&line, item->body);
                    append_s_char   (&line, '\n');
                    
                    append          (&line, "#endif");
                    append_s_char   (&line, '\n');
                }
                else{
                    InvalidCodePath;
                }
                
                append(&out, line);
            }
            
            append(&out, "\n#endif\n");
            
            // NOTE(allen): C++ overload definitions
            append(&out, "\n#if !defined(FSTRING_C) && !defined(FSTRING_GUARD)\n\n");
            
            for (i32 j = 0; j < string_unit.set.count; ++j){
                char line_space[2048];
                String line = make_fixed_width_string(line_space);
                
                Item_Node *item = &string_unit.set.items[j];
                
                if (item->t == Item_Function){
                    String cpp_name = item->cpp_name;
                    if (cpp_name.str != 0){
                        Argument_Breakdown breakdown = item->breakdown;
                        
                        append     (&line, item->ret);
                        append_padding(&line, ' ', SIG_PADDING);
                        append     (&line, cpp_name);
                        append     (&line, item->args);
                        if (match(item->ret, "void")){
                            append(&line, "{(");
                        }
                        else{
                            append(&line, "{return(");
                        }
                        append    (&line, item->name);
                        append_s_char(&line, '(');
                        
                        if (breakdown.count > 0){
                            for (i32 i = 0; i < breakdown.count; ++i){
                                if (i != 0){
                                    append_s_char(&line, ',');
                                }
                                append(&line, breakdown.args[i].param_name);
                            }
                        }
                        else{
                            append(&line, "void");
                        }
                        
                        append(&line, "));}\n");
                        
                        append(&out, line);
                    }
                }
            }
            
            append(&out, "\n#endif\n");
        }
        
        else if (match(lexeme, "API_EXPORT_MACRO")){
            token = get_next_token(&pcontext);
            if (token && token->type == CPP_TOKEN_COMMENT){
                token = get_next_token(&pcontext);
                if (token && token->type == CPP_PP_DEFINE){
                    for (;(token = get_next_token(&pcontext)) != 0;){
                        if (!(token->flags & CPP_TFLAG_PP_BODY)){
                            break;
                        }
                    }
                    if (token != 0){
                        get_prev_token(&pcontext);
                    }
                    do_print = false;
                    do_whitespace_print = false;
                }
            }
        }
        
        else if (match(lexeme, "API_EXPORT") || match(lexeme, "API_EXPORT_INLINE")){
            if (!(token->flags & CPP_TFLAG_PP_BODY)){
                if (match(lexeme, "API_EXPORT_INLINE")){
                    append(&out, "#if !defined(FSTRING_GUARD)\n");
                }
                else{
                    append(&out, "#if defined(FSTRING_IMPLEMENTATION)\n");
                }
                print_function_body_code(&out, &pcontext, start);
                append(&out, "\n#endif");
                do_print = false;
            }
        }
        
        else if (match(lexeme, "CPP_NAME")){
            Cpp_Token *token_start = token;
            i32 has_cpp_name = false;
            
            token = get_next_token(&pcontext);
            if (token && token->type == CPP_TOKEN_PARENTHESE_OPEN){
                token = get_next_token(&pcontext);
                if (token && token->type == CPP_TOKEN_IDENTIFIER){
                    token = get_next_token(&pcontext);
                    if (token && token->type == CPP_TOKEN_PARENTHESE_CLOSE){
                        has_cpp_name = true;
                        do_print = false;
                    }
                }
            }
            
            if (!has_cpp_name){
                token = set_token(&pcontext, token_start);
            }
        }
        
        else if (token->type == CPP_TOKEN_COMMENT){
            if (check_and_fix_docs(&lexeme)){
                do_print = false;
            }
        }
        
        else if (token->type == CPP_PP_INCLUDE){
            token = get_next_token(&pcontext);
            if (token && token->type == CPP_PP_INCLUDE_FILE){
                lexeme = get_lexeme(*token, pcontext.data);
                lexeme.size -= 2;
                lexeme.str += 1;
                
                char space[512];
                String str = make_fixed_width_string(space);
                append(&str, lexeme);
                terminate_with_null(&str);
                String dump = file_dump(str.str);
                if (dump.str){
                    append(&out, dump);
                }
                else{
                    lexeme.size += 2;
                    lexeme.str -= 1;
                    append(&out, "#error Could not find ");
                    append(&out, lexeme);
                    append(&out, "\n");
                }
                free(dump.str);
            }
            
            do_print = false;
        }
        
        if ((token = get_token(&pcontext)) != 0){
            if (do_print){
                pstr = get_lexeme(*token, pcontext.data);
                append(&out, pstr);
            }
            start = token->start + token->size;
        }
    }
    pstr = str_start_end(pcontext.data, start, parse.code.size);
    append(&out, pstr);
    
    fm_write_file(GENERATED_FILE, out.str, out.size);
    out.size = 0;
    
    // NOTE(allen): Publish the new file.  (Would like to be able to automatically test the result before publishing).
    {
        fm_make_folder_if_missing(BACKUP_FOLDER SLASH V_MAJ SLASH V_MIN);
        file_move(BACKUP_FOLDER SLASH V_MAJ SLASH V_MIN, INTERNAL_STRING);
        file_move(BACKUP_FOLDER SLASH V_MAJ SLASH V_MIN, GENERATED_FILE);
        //file_move(PUBLISH_FOLDER, GENERATED_FILE);
        fm_delete_file(GENERATED_FILE);
        printf("published "GENERATED_FILE": v%d.%d.%d\n", major_number, minor_number, build_number);
        save_build_number(BUILD_NUMBER_FILE, major_number, minor_number, build_number + 1);
    }
    
    META_FINISH();
}

// BOTTOM