/****************************************************/
/* File: analyze.c                                  */
/* Semantic analyzer implementation                 */
/* for the CMinus compiler                          */
/* Yenru0                                           */
/****************************************************/

#include "analyze.h"
#include "globals.h"
#include "symtab.h"


static BucketList func_entry = NULL;
static Scope func_scope;
static TreeNode *func_params[MAX_PARAM_COUNT];
static int func_param_count = 0;

void gen_random_hex_16(char *buffer) {
    char *hex_chars = "0123456789abcdef";
    
    for (int i = 0; i < 16; i++) {
        buffer[i] = hex_chars[rand() % 16];
    }
    buffer[16] = '\0';
}

/* Procedure traverse is a generic recursive 
 * syntax tree traversal routine:
 * it applies preProc in preorder and postProc 
 * in postorder to tree pointed to by t
 */
static void traverse(TreeNode *t,
                     void (*preProc)(TreeNode *),
                     void (*postProc)(TreeNode *)) {
    if (t != NULL) {
        preProc(t);
        {
            int i;
            for (i = 0; i < MAXCHILDREN; i++)
                traverse(t->child[i], preProc, postProc);
        }
        postProc(t);
        traverse(t->sibling, preProc, postProc);
    }
}

/* nullProc is a do-nothing procedure to 
 * generate preorder-only or postorder-only
 * traversals from traverse
 */
static void nullProc(TreeNode *t) {
    if (t == NULL) return;
    else
        return;
}

/* Procedure insertNode inserts 
 * identifiers stored in t into 
 * the symbol table 
 */
static void insertNode(TreeNode *t) {
    switch (t->nodekind) {
        case ExpK:
            switch (t->kind.exp) {
                case IdK:
                case ArrIdK: {
                    BucketList entry = st_lookup(t->attr.name);
                    if (entry == NULL) {
                        fprintf(listing, "Error: undeclared variable \"%s\" is used at line %d\n", t->attr.name, t->lineno);
                        Error = TRUE;

                        entry = st_try_insert(t->attr.name, SymbolVar, Undetermined, t->lineno);
                    } else {
                        st_entry_insert_line(entry, t->lineno);
                    }

                    t->scope = curr_scope();

                } break;
                case CallK: {
                    BucketList entry = st_lookup(t->attr.name);
                    if (entry == NULL) {
                        fprintf(listing, "Error: undeclared function \"%s\" is called at line %d\n", t->attr.name, t->lineno);
                        Error = TRUE;

                        entry = st_try_insert(t->attr.name, SymbolFunc, Undetermined, t->lineno);
                        entry->param_count = -1;
                        entry->returnType = Undetermined;
                    } else {
                        st_entry_insert_line(entry, t->lineno);
                    }

                    t->scope = curr_scope();
                }
                default:
                    break;
            }
            break;
        case StmtK:
            switch (t->kind.stmt) {
                case CompK:
                    if (func_scope != NULL) {
                        
                        push_scope(func_scope);
                        func_scope = NULL;
                        for (int i = 0; i < func_param_count; i++) {
                            TreeNode *param = func_params[i];
                            func_entry->param_types[func_entry->param_count] = param->type;
                            func_entry->param_names[func_entry->param_count] = param->attr.name;
                            func_entry->param_count++;
                            BucketList param_entry = st_lookup_current(param->attr.name);
                            if (param_entry != NULL) {
                                fprintf(listing, "Error: Symbol \"%s\" is redefined at line %d (already defined at line", func_entry->param_names[i], param->lineno);
                                LineList lines = param_entry->lines;
                                while (lines != NULL) {
                                    fprintf(listing, " ");
                                    fprintf(listing, "%d", lines->lineno);
                                    lines = lines->next;
                                }
                                fprintf(listing, ")\n");
                                Error = TRUE;
                                st_entry_insert_line(param_entry, param->lineno);
                            } else {
                                st_try_insert(param->attr.name, SymbolParam, param->type, param->lineno);
                            }
                        }
                        func_entry = NULL;
                        func_scope = NULL;
                        func_param_count = 0;
                    } else {
                        char *parent_scope_name = curr_scope()->name;
                        char *new_name = (char *) malloc(255);
                        snprintf(new_name, 255, "%s.%d", parent_scope_name, curr_scope()->child_count);
                        push_scope(scope_new(new_name));
                    }
                    break;
                default:
                    break;
            }
            break;

        case DeclK:
            switch (t->kind.decl) {
                case FuncK: {
                    BucketList entry = st_lookup(t->attr.name);
                    if (entry != NULL) {
                        fprintf(listing, "Error: Symbol \"%s\" is redefined at line %d (already defined at line", t->attr.name, t->lineno);
                        LineList lines = entry->lines;
                        while (lines != NULL) {
                            fprintf(listing, " ");
                            fprintf(listing, "%d", lines->lineno);
                            lines = lines->next;
                        }
                        st_entry_insert_line(entry, t->lineno);
                        fprintf(listing, ")\n");
                        
                        Error = TRUE;
                        char* random_name = (char *) calloc(1, 20);
                        gen_random_hex_16(random_name);
                        
                        func_entry = st_try_insert(random_name, SymbolFunc, t->type, t->lineno);
                        t->scope = curr_scope();
                        func_scope = scope_new(random_name);
                    } else {
                        func_entry = st_try_insert(t->attr.name, SymbolFunc, t->type, t->lineno);
                        t->scope = curr_scope();
                        func_scope = scope_new(t->attr.name);
                    }
                    

                } break;
                case ArrParamK:
                case NonArrParamK: {
                    if (func_scope != NULL) {
                        if (t->type == Void) {
                            if (t->attr.name != NULL) {
                                fprintf(listing, "Error: The void-type variable is declared at line %d (name : \"%s\")\n", t->lineno, t->attr.name);
                                Error = TRUE;
                            } else {
                                goto insert_param_exit;
                            }
                        }
                        func_params[func_param_count++] = t;
                    }
                insert_param_exit:

                } break;
                case VarK:
                case ArrVarK: {
                    BucketList entry = st_lookup_current(t->attr.name);
                    if (entry != NULL) {// ignore
                        fprintf(listing, "Error: Symbol \"%s\" is redefined at line %d (already defined at line", t->attr.name, t->lineno);
                        LineList lines = entry->lines;
                        while (lines != NULL) {
                            fprintf(listing, " ");
                            fprintf(listing, "%d", lines->lineno);
                            lines = lines->next;
                        }
                        fprintf(listing, ")\n");
                        st_entry_insert_line(entry, t->lineno);
                        Error = TRUE;
                    } else {
                        if (t->type == Void) {
                            fprintf(listing, "Error: The void-type variable is declared at line %d (name : \"%s\")\n", t->lineno, t->attr.name);
                            Error = TRUE;
                        }
                        t->scope = curr_scope();
                        st_try_insert(t->attr.name, SymbolVar, t->type, t->lineno);
                    }
                } break;
                default:
                    break;
            }
            break;
        default:
            break;
    }
}

static void afterNode(TreeNode *t) {
    if (t->nodekind == StmtK && t->kind.stmt == CompK) {
        pop_scope();
    }
}

/* Function buildSymtab constructs the symbol 
 * table by preorder traversal of the syntax tree
 */
void buildSymtab(TreeNode *syntaxTree) {
    st_init();
    BucketList entry;
    entry = st_try_insert("input", SymbolFunc, Integer, 0);
    entry->param_count = 0;
    // entry->returnType = Integer; /* not know */

    entry = st_try_insert("output", SymbolFunc, Void, 0);
    entry->returnType = Void;
    entry->param_types[0] = Integer;
    entry->param_names[0] = "value";
    entry->param_count = 1;

    push_scope(scope_new("output"));
    st_try_insert("value", SymbolParam, Integer, 0);
    pop_scope();
    traverse(syntaxTree, insertNode, afterNode);
    if (TraceAnalyze) {
        printSymTab(listing);
    }
}

static void typeError(TreeNode *t, char *message) {
    fprintf(listing, "Type error at line %d: %s\n", t->lineno, message);
    Error = TRUE;
}

static void beforeCheckNode(TreeNode *t) {
    if (t->nodekind == DeclK && t->kind.decl == FuncK) {
        func_entry = st_lookup(t->attr.name);
    }
}
static void checkNode(TreeNode *t) {
    switch (t->nodekind) {
        case ExpK:
            switch (t->kind.exp) {
                case OpK: {
                    TreeNode *left = t->child[0];
                    TreeNode *right = t->child[1];
                    if (left->type != Integer || right->type != Integer) {
                        fprintf(listing, "Error: invalid operation at line %d\n", t->lineno);
                        Error = TRUE;
                        t->type = Undetermined;
                    } else {
                        t->type = Integer;
                    }
                } break;
                case ConstK:
                    t->type = Integer;
                    break;
                case IdK: {
                    BucketList entry = st_lookup_from(t->attr.name, t->scope);
                    t->type = entry->type;
                } break;
                case ArrIdK: {
                    BucketList entry = st_lookup_from(t->attr.name, t->scope);
                    if (entry->type != IntegerArray) {
                        fprintf(listing, "Error: Invalid array indexing at line %d (name : \"%s\"). indexing can only be allowed for int[] variables\n", t->lineno, t->attr.name);
                        Error = TRUE;
                    }
                    if (t->child[0]->type != Integer) {
                        fprintf(listing, "Error: Invalid array indexing at line %d (name : \"%s\"). indicies should be integer\n", t->lineno, t->attr.name);
                        Error = TRUE;
                    }
                    t->type = Integer;
                } break;

                case AssignK: {
                    TreeNode *left = t->child[0];
                    TreeNode *right = t->child[1];
                    if (left->type == Integer && right->type == Integer) {

                    } else if (left->type == IntegerArray && right->type == IntegerArray) {

                    } else {
                        fprintf(listing, "Error: invalid assignment at line %d\n", t->lineno);
                        Error = TRUE;
                    }

                    t->type = right->type;
                } break;
                case CallK: {
                    BucketList entry = st_lookup_from(t->attr.name, t->scope);// not null

                    if (entry->symbolKind != SymbolFunc) {
                        fprintf(listing, "Error: Invalid function call at line %d (name : \"%s\")\n", t->lineno, t->attr.name);
                        Error = TRUE;
                        goto check_callk_after;
                    }

                    TreeNode *arg = t->child[0];

                    int i = 0;// 파라미터 인덱스
                    if (entry->param_count == -1) {
                        fprintf(listing, "Error: Invalid function call at line %d (name : \"%s\")\n", t->lineno, t->attr.name);
                        Error = TRUE;
                        goto check_callk_after;
                    }
                    while (arg != NULL && i < entry->param_count) {
                        if (arg->type != entry->param_types[i]) {
                            fprintf(listing, "Error: Invalid function call at line %d (name : \"%s\")\n", t->lineno, t->attr.name);
                            Error = TRUE;
                            goto check_callk_after;
                        }

                        arg = arg->sibling;
                        i++;
                    }

                    if (arg != NULL) {
                        fprintf(listing, "Error: Invalid function call at line %d (name : \"%s\")\n", t->lineno, t->attr.name);
                        Error = TRUE;
                    } else if (i < entry->param_count) {
                        fprintf(listing, "Error: Invalid function call at line %d (name : \"%s\")\n", t->lineno, t->attr.name);
                        Error = TRUE;
                    }

                check_callk_after:

                    t->type = entry->returnType;

                } break;
                default:
                    break;
            }
            break;
        case StmtK:
            switch (t->kind.stmt) {
                case ReturnK: {
                    if (func_entry == NULL) {
                        fprintf(listing, "Error: Invalid return at line %d\n", t->lineno);
                        Error = TRUE;
                    }
                    TreeNode *retval = t->child[0]; /* nullalbe */
                    if (func_entry->returnType == Void) {
                        if (retval != NULL) {
                            fprintf(listing, "Error: Invalid return at line %d\n", t->lineno);
                            Error = TRUE;
                        }
                    } else {
                        if (retval == NULL) {
                            fprintf(listing, "Error: Invalid return at line %d\n", t->lineno);
                            Error = TRUE;
                        } else if (retval->type != func_entry->returnType) {
                            fprintf(listing, "Error: Invalid return at line %d\n", t->lineno);
                            Error = TRUE;
                        }
                    }
                } break;
                case IterK: {
                    TreeNode *condition = t->child[0];
                    if (condition->type != Integer) {
                        fprintf(listing, "Error: invalid condition at line %d\n", t->child[0]->lineno);
                        Error = TRUE;
                    }
                } break;
                case IfK: {
                    TreeNode *condition = t->child[0];
                    if (condition->type != Integer) {
                        fprintf(listing, "Error: invalid condition at line %d\n", t->child[0]->lineno);
                        Error = TRUE;
                    }
                } break;
                default:
                    break;
            }
            break;

        case DeclK:
            switch (t->kind.decl) {

                case FuncK:
                    func_entry = NULL;
                    break;
                default:
                    break;
            }
        default:
            break;
    }
}

/* Procedure typeCheck performs type checking 
 * by a postorder syntax tree traversal
 */
void typeCheck(TreeNode *syntaxTree) {
    traverse(syntaxTree, beforeCheckNode, checkNode);
}
