/****************************************************/
/* 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;

    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, BucketList entry) {
    if (entry == NULL) return;
    fprintf(listing, "%-14s", entry->name);
    switch (entry->symbolKind) {
        case SymbolVar:
            fprintf(listing, "|%-13s", "Var");
            break;
        case SymbolFunc:
            fprintf(listing, "|%-13s", "Func");
            break;
        case SymbolParam:
            fprintf(listing, "|%-13s", "Param");
            break;
    }
    switch (entry->type) {
        case Void:
            fprintf(listing, "|%-11s", "void");
            break;
        case Integer:
            fprintf(listing, "|%-11s", "int");
            break;
        case IntegerArray:
            fprintf(listing, "|%-11s", "int[]");
            break;
    }
    fprintf(listing, "|%-9d|", entry->memloc);
    LineList t = entry->lines;
    while (t != NULL) {
        fprintf(listing, "%d ", t->lineno);
        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) {
            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, entries[i]);
        }

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

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;
    }
}

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;
    }
}

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