/****************************************************/
/* File: symtab.c                                   */
/* Symbol table implementation for the TINY compiler*/
/* (allows only one symbol table)                   */
/* Symbol table is implemented as a chained         */
/* hash table                                       */
/****************************************************/

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

Scope scope_stack[MAX_SCOPE_DEPTH];
int scope_stack_top = -1;
Scope scope_global;// no sibling no parent

void st_init(void) {
    scope_global = scope_new("global");
    scope_global->depth = 0;
    scope_stack_top = 0;
    scope_stack[scope_stack_top] = scope_global;
}

/* SHIFT is the power of two used as multiplier
   in hash function  */
#define SHIFT 4

/* the hash function */
static int hash(char *key) {
    int temp = 0;
    int i = 0;
    while (key[i] != '\0') {
        temp = ((temp << SHIFT) + key[i]) % SYMTAB_SIZE;
        ++i;
    }
    return temp;
}

Scope scope_new(char *scope_name) {// it
    Scope new_scope = (Scope) malloc(sizeof(struct Scope));
    new_scope->name = scope_name;
    new_scope->depth = -1;
    new_scope->parent = NULL;
    new_scope->child = NULL;
    new_scope->child_last = NULL;
    new_scope->next_sibling = NULL;
    int i;
    for (i = 0; i < SYMTAB_SIZE; ++i) {
        new_scope->hashTable[i] = NULL;
    }
    new_scope->child_count = 0;
    new_scope->location = 0;
    return new_scope;
}

void pop_scope(void) {
    if (scope_stack_top == -1) {// empty
        return;
    } else {
        scope_stack[scope_stack_top] = NULL;
        scope_stack_top--;
    }
}

void push_scope(Scope scope) {
    if (scope_stack_top == MAX_SCOPE_DEPTH - 1) {// full
        return;
    } else {
        Scope before = curr_scope();
        if (before->child == NULL) {
            before->child = scope;
            before->child_last = scope;
        } else {
            before->child_last->next_sibling = scope;
            before->child_last = scope;
        }
        scope->parent = before;
        scope->depth = before->depth + 1;
        before->child_count++;
        scope_stack_top++;
        scope_stack[scope_stack_top] = scope;
    }
}

Scope curr_scope(void) {
    if (scope_stack_top == -1) {
        return NULL;
    } else {
        return scope_stack[scope_stack_top];
    }
}

BucketList st_try_insert(char *name, SymbolKind symbolkind, ExpType type, int lineno) {
    int h = hash(name);
    Scope scope = curr_scope();
    BucketList *hashTable = scope->hashTable;
    BucketList l = hashTable[h];
    while ((l != NULL) && (strcmp(name, l->name) != 0))
        l = l->next;
    if (l == NULL) { /* variable not yet in table */
        l = (BucketList) malloc(sizeof(struct BucketListEntry));
        l->name = name;
        l->symbolKind = symbolkind;
        l->lines = (LineList) malloc(sizeof(struct LineListEntry));
        l->lines->lineno = lineno;
        l->symbolKind = symbolkind;
        if (symbolkind == SymbolFunc) {
            l->type = Void;
            l->returnType = type;
            l->param_count = 0;
        } else {
            l->type = type;
            l->returnType = type;
        }

        l->lines->next = NULL;
        l->memloc = scope->location;
        scope->location++;
        l->next = hashTable[h];
        hashTable[h] = l;
    } else { /* found in table, so just add line number */
        LineList t = l->lines;
        while (t->next != NULL)
            t = t->next;
        t->next = (LineList) malloc(sizeof(struct LineListEntry));
        t->next->lineno = lineno;
        t->next->next = NULL;
    }
    return l;
}

void st_entry_insert_line(BucketList entry, int lineno) {
    if (entry == NULL) return;
    LineList t = entry->lines;
    while (t->next != NULL)
        t = t->next;
    t->next = (LineList) malloc(sizeof(struct LineListEntry));
    t->next->lineno = lineno;
    t->next->next = NULL;
}

BucketList st_lookup_current(char *name) {
    int h = hash(name);
    Scope scope = curr_scope();
    BucketList *hashTable = scope->hashTable;
    BucketList l = hashTable[h];
    while ((l != NULL) && (strcmp(name, l->name) != 0))
        l = l->next;

    return l;
}

BucketList st_lookup(char *name) {
    int h = hash(name);
    Scope scope = curr_scope();
    while (scope != NULL) {
        BucketList *hashTable = scope->hashTable;
        BucketList l = hashTable[h];
        while ((l != NULL) && (strcmp(name, l->name) != 0))
            l = l->next;
        if (l != NULL) {
            return l;
        }
        scope = scope->parent;
    }
    return NULL; /* not found */
}

BucketList st_lookup_from(char *name, Scope scope) {
    int h = hash(name);
    while (scope != NULL) {
        BucketList *hashTable = scope->hashTable;
        BucketList l = hashTable[h];
        while ((l != NULL) && (strcmp(name, l->name) != 0))
            l = l->next;
        if (l != NULL) {
            return l;
        }
        scope = scope->parent;
    }
    return NULL; /* not found */
}

static void printEntry(FILE *listing, Scope scope, BucketList entry) {
    if (entry == NULL) return;
    fprintf(listing, "%-13s", entry->name);
    fprintf(listing, "  ");
    switch (entry->symbolKind) {
        case SymbolVar:
            fprintf(listing, "%-11s", "Variable");
            break;
        case SymbolFunc:
            fprintf(listing, "%-11s", "Function");
            break;
        case SymbolParam:
            fprintf(listing, "%-11s", "Variable");
            break;
    }
    fprintf(listing, "  ");
    ExpType type;
    if (entry->symbolKind == SymbolFunc) {
        type = entry->returnType;
    } else {
        type = entry->type;
    }
    switch (type) {
        case Void:
            fprintf(listing, "%-13s", "void");
            break;
        case Integer:
            fprintf(listing, "%-13s", "int");
            break;
        case IntegerArray:
            fprintf(listing, "%-13s", "int[]");
            break;
        case Undetermined:
            fprintf(listing, "%-13s", "undetermined");
    }
    //
    fprintf(listing, "  ");
    fprintf(listing, "%-12s", scope->name);
    //
    fprintf(listing, "  ");
    fprintf(listing, "%-8d", entry->memloc);
    fprintf(listing, "  ");
    LineList t = entry->lines;
    while (t != NULL) {
        fprintf(listing, "%3d", t->lineno);
        fprintf(listing, "  ");
        t = t->next;
    }
    fprintf(listing, "\n");
}

static void printScopeTable(FILE *listing, Scope scope) {
    if (scope == NULL) return;
    int count = 0;
    for (int i = 0; i < SYMTAB_SIZE; ++i) {
        BucketList l = scope->hashTable[i];
        while (l != NULL) {
            printEntry(listing, scope, l);
            count++;
            l = l->next;
        }
    }
    /*
    if (count > 0) {
        BucketList *entries = (BucketList *) malloc(sizeof(BucketList) * count);
        int idx = 0;
        for (int i = 0; i < SYMTAB_SIZE; ++i) {
            BucketList l = scope->hashTable[i];
            while (l != NULL) {
                entries[idx++] = l;
                l = l->next;
            }
        }

        for (int i = 1; i < count; ++i) {
            BucketList key = entries[i];
            int j = i - 1;
            while (j >= 0 && entries[j]->memloc > key->memloc) {
                entries[j + 1] = entries[j];
                --j;
            }
            entries[j + 1] = key;
        }

        for (int i = 0; i < count; ++i) {
            printEntry(listing, scope, entries[i]);
        }

        free(entries);
    }
        */
    //fprintf(listing, "\n");
}

static void printScope(FILE *listing, Scope scope) {
    if (scope == NULL) return;
    fprintf(listing, "Scope Name: %s, Depth: %d\n", scope->name, scope->depth);
    fprintf(listing, "-----------------------------------------\n");
    fprintf(listing, "Variable Name  Symbol Kind  Type       Location  Line Numbers\n");
    fprintf(listing, "------------------------------------------------------------\n");

    printScopeTable(listing, scope);
}

static void printScopeRecursive(FILE *listing, Scope scope) {
    if (scope == NULL) return;

    printScope(listing, scope);

    Scope child = scope->child;
    while (child != NULL) {
        printScopeRecursive(listing, child);
        child = child->next_sibling;
    }
}

static void printScopeTree(FILE *listing) {
    if (scope_global == NULL) return;

    Scope current_scope = scope_global;

    printScopeRecursive(listing, current_scope);
}

static void printScopeTableRecursive(FILE *listing, Scope scope) {
    if (scope == NULL) return;

    printScopeTable(listing, scope);

    Scope child = scope->child;
    while (child != NULL) {
        printScopeTableRecursive(listing, child);
        child = child->next_sibling;
    }
}

static void printFunctionTableRecursive(FILE *listing, Scope scope) {
    for (int i = 0; i < SYMTAB_SIZE; ++i) {
        BucketList entry = scope->hashTable[i];
        while (entry != NULL) {
            if (entry->symbolKind == SymbolFunc) {
                fprintf(listing, "%-13s", entry->name);
                fprintf(listing, "  ");
                switch (entry->returnType) {
                    case Void:
                        fprintf(listing, "%-13s", "void");
                        break;
                    case Integer:
                        fprintf(listing, "%-13s", "int");
                        break;
                    case IntegerArray:
                        fprintf(listing, "%-13s", "int[]");
                        break;
                    case Undetermined:
                        fprintf(listing, "%-13s", "undetermined");
                }
                if (entry->param_count == -1) {
                    fprintf(listing, "                  ");
                    fprintf(listing, "%-14s", "undetermined");
                    entry = entry->next;
                    fprintf(listing, "\n");
                    continue;
                } else if (entry->param_count == 0) {
                    fprintf(listing, "                  ");
                    fprintf(listing, "%-14s", "void");
                    entry = entry->next;
                    fprintf(listing, "\n");
                    continue;
                }
                fprintf(listing, "  \n");
                for (int j = 0; j < entry->param_count; j++) {
                    fprintf(listing, "-              -            ");
                    fprintf(listing, "  %-14s", entry->param_names[j]);
                    fprintf(listing, "  ");
                    switch (entry->param_types[j]) {
                        case Void:
                            fprintf(listing, "%-14s", "void");
                            break;
                        case Integer:
                            fprintf(listing, "%-14s", "int");
                            break;
                        case IntegerArray:
                            fprintf(listing, "%-14s", "int[]");
                            break;
                        case Undetermined:
                            fprintf(listing, "%-14s", "undetermined");
                    }
                    fprintf(listing, "\n");
                }
            }
            entry = entry->next;
        }
    }

    Scope child = scope->child;
    while (child != NULL) {
        printFunctionTableRecursive(listing, child);
        child = child->next_sibling;
    }
}

static void printFunctionTable(FILE *listing) {
    if (scope_global == NULL) return;

    printFunctionTableRecursive(listing, scope_global);
}

static void printGlobalSymbols(FILE *listing) {
    if (scope_global == NULL) return;

    for (int i = 0; i < SYMTAB_SIZE; ++i) {
        BucketList entry = scope_global->hashTable[i];
        while (entry != NULL) {
            fprintf(listing, "%-13s  ", entry->name);

            switch (entry->symbolKind) {
                case SymbolVar:
                    fprintf(listing, "%-11s  ", "Variable");
                    break;
                case SymbolFunc:
                    fprintf(listing, "%-11s  ", "Function");
                    break;
                case SymbolParam:
                    fprintf(listing, "%-11s  ", "Variable");
                    break;
            }
            ExpType type;
            if (entry->symbolKind == SymbolFunc) {
                type = entry->returnType;
            } else {
                type = entry->type;
            }
            switch (type) {
                case Void:
                    fprintf(listing, "%-13s", "void");
                    break;
                case Integer:
                    fprintf(listing, "%-13s", "int");
                    break;
                case IntegerArray:
                    fprintf(listing, "%-13s", "int[]");
                    break;
                case Undetermined:
                    fprintf(listing, "%-13s", "undetermined");
            }
            fprintf(listing, "\n");
            entry = entry->next;
        }
    }
}

static void printScopeTableAdvancedRecursive(FILE *listing, Scope scope) {
    if (scope == NULL) return;
    int no_sym = TRUE;
    for (int i = 0; i < SYMTAB_SIZE; ++i) {
        BucketList entry = scope->hashTable[i];
        while (entry != NULL) {
            no_sym = FALSE;
            fprintf(listing, "%-12s", scope->name);
            fprintf(listing, "  ");
            fprintf(listing, "%-12d", scope->depth);
            fprintf(listing, "  ");
            fprintf(listing, "%-13s", entry->name);
            fprintf(listing, "  ");
            switch (entry->type) {
                case Void:
                    fprintf(listing, "%-11s", "void");
                    break;
                case Integer:
                    fprintf(listing, "%-11s", "int");
                    break;
                case IntegerArray:
                    fprintf(listing, "%-11s", "int[]");
                    break;
                case Undetermined:
                    fprintf(listing, "%-11s", "undetermined");
            }
            fprintf(listing, "\n");
            entry = entry->next;
        }
    }
    if (!no_sym) fprintf(listing, "\n");

    Scope child = scope->child;
    while (child != NULL) {
        printScopeTableAdvancedRecursive(listing, child);
        child = child->next_sibling;
    }
}

static void printScopeTableAdvanced(FILE *listing) {
    if (scope_global == NULL) return;

    Scope current_scope;

    current_scope = scope_global->child;

    while (current_scope != NULL) {
        printScopeTableAdvancedRecursive(listing, current_scope);
        current_scope = current_scope->next_sibling;
    }
}

void printSymTab(FILE *listing) {
    Scope curr = scope_global;
    fprintf(listing, "\n\n< Symbol Table >\n");
    fprintf(listing, " Symbol Name   Symbol Kind   Symbol Type    Scope Name   Location  Line Numbers\n");
    fprintf(listing, "-------------  -----------  -------------  ------------  --------  ------------\n");
    printScopeTableRecursive(listing, curr);

    fprintf(listing, "\n\n< Functions >\n");
    fprintf(listing, "Function Name   Return Type   Parameter Name  Parameter Type\n");
    fprintf(listing, "-------------  -------------  --------------  --------------\n");
    printFunctionTable(listing);

    fprintf(listing, "\n\n< Global Symbols >\n");
    fprintf(listing, " Symbol Name   Symbol Kind   Symbol Type\n");
    fprintf(listing, "-------------  -----------  -------------\n");
    printGlobalSymbols(listing);

    fprintf(listing, "\n\n< Scopes >\n");
    fprintf(listing, " Scope Name   Nested Level   Symbol Name   Symbol Type\n");
    fprintf(listing, "------------  ------------  -------------  -----------\n");
    printScopeTableAdvanced(listing);
}
