%{
#include <stdbool.h>    
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct StatementList;
struct Statement;
struct Declaration;
struct Loop;
struct Condition;
struct Expression;
struct Comparator;
struct SymbolTableEntry;
struct SymbolTable;

typedef struct StatementList {
    struct Statement** statements;
    int size;
} StatementList;

typedef struct Statement {
    enum {
        DECLARATION,
        LOOP,
        CONDITION,
        EXPRESSION
    } type;
    union {
        struct Declaration* declaration;
        struct Loop* loop;
        struct Condition* condition;
        struct Expression* expression;
    };
} Statement;

typedef struct Declaration {
    char* type;
    char* identifier;
} Declaration;

typedef struct Loop {
    enum {
        FOR_LOOP,
        WHILE_LOOP
    } loopType;
    char* type;
    struct Condition* condition;
    struct StatementList* statementList;
} Loop;

typedef struct Condition {
    struct Expression* expression1;
    struct Comparator* comparator;
    struct Expression* expression2;
    struct StatementList* statementList;
} Condition;

typedef struct Expression {
    enum {
        IDENTIFIER_EXP,
        NUMBER_EXP,
        STRING_EXP,
        BINARY_EXP
    } expressionType;
    union {
        char* identifier;
        int number;
        char* string;
        struct {
            struct Expression* left;
            struct Expression* right;
            char* operator;
        } binaryExp;
    };
} Expression;

typedef struct Comparator {
    char* type;
} Comparator;

typedef struct {
    char* key;
    void* value;
} SymbolTableEntry;

typedef struct {
    SymbolTableEntry* entries;
    int size;
    int capacity;
} SymbolTable;

SymbolTable *symbol_table;

void executeStatements(StatementList *list);
void executeStatement(Statement *statement);
void executeDeclaration(Declaration *declaration);
void executeLoop(Loop *loop);
void executeCondition(Condition *condition);
bool evaluateCondition(Expression *expression1, Comparator *comparator, Expression *expression2);
void* evaluateExpression(Expression *expression);
struct Expression* createExpression(int expressionType, char* identifier, int number, char* string, struct Expression* left, struct Expression* right, char* operator);
void SymbolTable_init();
void SymbolTable_add(char *key, void *value);
void *SymbolTable_lookup(char *key);
int yylex();
int yyerror(char *s);
%}

%token IDENTIFIER
%token NUMBER
%token INTEGER
%token FLOAT
%token BOOLEAN
%token STRING
%token OR 
%token AND 
%token NOT 
%token EQUALS 
%token NOT_EQUALS 
%token GREATER_THAN 
%token LESS_THAN 
%token GREATER_THAN_EQUAL 
%token LESS_THAN_EQUAL 

%token FOR 
%token WHILE 

%token IF 
%token ELSE 
%token SEMICOLON

%token <string> type
%token VALUE

%left '+' '-'
%left '*' '/'
%left OR
%left AND
%nonassoc EQUALS NOT_EQUALS GREATER_THAN LESS_THAN GREATER_THAN_EQUAL LESS_THAN_EQUAL
%right NOT
%left IDENTIFIER
%left STRING

//Define yyval
%union {
    int number;
    char *string;
    struct StatementList* statement_list;
}

%type <string> IDENTIFIER
%type <number> NUMBER
%type <string> STRING
%type <string> declaration
%type <string> loop
%type <string> condition
%type <bool> expression
%type <string> comparator
%type <statement_list> statement_list

%precedence TYPE_DECLARATION
%%

// Program
program: statement_list | YYEOF ;
// Statement List Action
statement_list: statement_list statement {printf("Statement List: \n");}
              | statement {printf("Statement List: \n");} ;

statement: declaration SEMICOLON
          | loop 
          | condition
          | expression SEMICOLON;

// Declaration Action
declaration: type IDENTIFIER {
    printf("Declared %s of type %s\n", $2, $1);
    // Allocate memory for the new variable
    if (strcmp($1, "int") == 0) {
        int *var = malloc(sizeof(int));
        // Save the pointer in the symbol table
        SymbolTable_add($2, var);
    } else if (strcmp($1, "float") == 0) {
        float *var = malloc(sizeof(float));
        // Save the pointer in the symbol table
        SymbolTable_add($2, var);
    } else if (strcmp($1, "bool") == 0) {
        bool *var = malloc(sizeof(bool));
        // Save the pointer in the symbol table
        SymbolTable_add($2, var);
    } else if (strcmp($1, "string") == 0) {
        char *var = malloc(sizeof(char*));
        // Save the pointer in the symbol table
        SymbolTable_add($2, var);
    }
	$$ = $2;
};

// Loop Action
loop: FOR '(' type IDENTIFIER ':' type IDENTIFIER ')' statement_list SEMICOLON {
    printf("Loop: for (%s) {\n", $3);
    
    Loop* myLoop = malloc(sizeof(Loop));
    myLoop->loopType = FOR_LOOP;
    myLoop->type = "for";  // Assuming "for" is the loop type
    
    // Set the loop conditions based on $3 and $6
    myLoop->condition = malloc(sizeof(Condition));
    myLoop->condition->expression1 = (struct Expression*)$3;  // Assuming $3 contains the first expression
    myLoop->condition->expression2 = (struct Expression*)$6;  // Assuming $6 contains the second expression
    
    myLoop->statementList = $9;  // Assuming $9 contains the statement list
    
    // Execute the loop body
    executeLoop(myLoop);
}
| WHILE '(' type condition ')' statement_list SEMICOLON {
    printf("Loop: while (%s) {\n", $3);
    
    Loop* myLoop = malloc(sizeof(Loop));
    myLoop->loopType = WHILE_LOOP;
    myLoop->type = "while";  // Assuming "while" is the loop type
    
    myLoop->condition = (struct Condition*)$4;  // Assuming $4 contains the loop condition
    
    myLoop->statementList = $6;  // Assuming $6 contains the statement list
    
    // Execute the loop body
    executeLoop(myLoop);
};

// Condition Action
condition: IF'('IDENTIFIER comparator IDENTIFIER')' statement_list SEMICOLON {
    // Evaluate the expression
    Condition* myCondition = malloc(sizeof(Condition));
    executeCondition(myCondition);
    bool result = evaluateCondition((struct Expression*)$3, (struct Comparator*)$4, (struct Expression*)$5);

    if (result) {
        // Execute the statement list
        executeStatements((struct StatementList*)$7);
    } else {
        printf("Condition: %s\n", $3);
    }
};

// Expression Action
expression: IDENTIFIER {
	printf("Expression: %s\n", $1);
	// Lookup the variable in the symbol table
	void *var = SymbolTable_lookup($1);
	// Return the result of the expression
	return (int)var;
}
          | NUMBER {
	printf("Expression: %d\n", $1);
	// Return the result of the expression
	return $1;
}
          | STRING {
	printf("Expression: %s\n", $1);
	// Return the result
	return $1;
}
          | expression '+' expression {
	printf("Addition: %d + %d\n", $1, $3);
	// Add the two operands
	return $1 + $3;
} 
          | expression '-' expression {
	printf("Subtraction: %d - %d\n", $1, $3);
	// Subtract the second operand from the first
	return $1 - $3;
} 
          | expression '*' expression {
	printf("Multiplication: %d * %d\n", $1, $3);
	// Multiply the two operands
	return $1 * $3;
}
          | expression '/' expression {
	printf("Division: %d / %d\n", $1, $3);
	// Check for division by zero
	if ($3 == 0) {
		fprintf(stderr, "Error: division by zero\n");
		exit(1);
	}
	// Divide the first operand by the second
	return $1 / $3;
}; 

// Comparator Action
comparator: OR {printf("Comparator: ||\n");}
          | AND {printf("Comparator: &&\n");}
          | NOT {printf("Comparator: !\n");}
          | EQUALS {printf("Comparator: ==\n");}
          | NOT_EQUALS {printf("Comparator: !=\n");}
          | GREATER_THAN {printf("Comparator: >\n");}

          | LESS_THAN {printf("Comparator: <\n");}

          | GREATER_THAN_EQUAL {printf("Comparator: >=\n");}

          | LESS_THAN_EQUAL {printf("Comparator: <=\n");} ;

%%

// Helper Functions
void executeStatements(StatementList *list) {
	// Execute each statement in the list
	for (int i=0; i < list->size; i++) {
		executeStatement(list->statements[i]);
	}
}

void executeStatement(Statement *statement) {
	if (statement->type == DECLARATION) {
		executeDeclaration(statement->declaration);
	} else if (statement->type == LOOP) {
		executeLoop(statement->loop);
	} else if (statement->type == CONDITION) {
		executeCondition(statement->condition);
	}
}

void executeDeclaration(Declaration *declaration) {
	// Allocate memory for the new variable
	if (strcmp(declaration->type, "int") == 0) {
		int *var = malloc(sizeof(int));
		// Save the pointer in the symbol table
		SymbolTable_add(declaration->identifier, var);
	} else if (strcmp(declaration->type, "float") == 0) {
		float *var = malloc(sizeof(float));
		// Save the pointer in the symbol table
		SymbolTable_add(declaration->identifier, var);
	} else if (strcmp(declaration->type, "bool") == 0) {
		bool *var = malloc(sizeof(bool));
		// Save the pointer in the symbol table
		SymbolTable_add(declaration->identifier, var);
	} else if (strcmp(declaration->type, "string") == 0) {
		char *var = malloc(sizeof(char*));
		// Save the pointer in the symbol table
		SymbolTable_add(declaration->identifier, var);
	}
}

void executeLoop(Loop *loop) {
	// Evaluate the condition
	bool result = evaluateCondition(loop->condition);
	while (result) {
		// Execute the loop body
		executeStatements(loop->statementList);
		// Re-evaluate the condition
		result = evaluateCondition(loop->condition);
	}
}

void executeCondition(Condition *condition) {
	// Evaluate the expression
	bool result = evaluateCondition(condition->expression1, condition->comparator, condition->expression2);
	if (result) {
		// Execute the statement list
		executeStatements(condition->statementList);
	}
}

bool evaluateCondition(Expression *expression1, Comparator *comparator, Expression *expression2) {
	// Get the values of the expressions
	void *val1 = evaluateExpression(expression1);
	void *val2 = evaluateExpression(expression2);
	// Compare the values
	if (strcmp(comparator->type, "OR") == 0) {
		return val1 || val2;
	} else if (strcmp(comparator->type, "AND") == 0) {
		return val1 && val2;
	} else if (strcmp(comparator->type, "NOT") == 0) {
		return !val1;
	} else if (strcmp(
comparator->type, "EQUALS") == 0) {
		return val1 == val2;
	} else if (strcmp(comparator->type, "NOT_EQUALS") == 0) {
		return val1 != val2;
	} else if (strcmp(comparator->type, "GREATER_THAN") == 0) {
		return val1 > val2;
	} else if (strcmp(comparator->type, "LESS_THAN") == 0) {
		return val1 < val2;
	} else if (strcmp(comparator->type, "GREATER_THAN_EQUAL") == 0) {
		return val1 >= val2;
	} else if (strcmp(comparator->type, "LESS_THAN_EQUAL") == 0) {
		return val1 <= val2;
	}
	return false;
}

void* evaluateExpression(Expression *expression) {
	if (expression->expressionType == IDENTIFIER_EXP) {
		// Lookup the variable in the symbol table
		return SymbolTable_lookup(expression->identifier);
	} else if (expression->expressionType == NUMBER_EXP) {
		// Return the number
		return expression->number;
	} else if (expression->expressionType == STRING_EXP) {
		// Return the string
		return expression->string;
	}
	return NULL;
}

struct Expression* createExpression(int expressionType, char* identifier, int number, char* string, struct Expression* left, struct Expression* right, char* operator) {
    struct Expression* expr = malloc(sizeof(struct Expression));
    expr->expressionType = expressionType;
    
    switch (expressionType) {
        case IDENTIFIER_EXP:
            expr->identifier = identifier;
            break;
        case NUMBER_EXP:
            expr->number = number;
            break;
        case STRING_EXP:
            expr->string = string;
            break;
        case BINARY_EXP:
            expr->binaryExp.left = left;
            expr->binaryExp.right = right;
            expr->binaryExp.operator = operator;
            break;
    }
    
    return expr;
}

void SymbolTable_init() {
// Initialize the symbol table
symbol_table = malloc(sizeof(SymbolTable));
symbol_table->size = 0;
symbol_table->capacity = 16;
symbol_table->entries = malloc(symbol_table->capacity * sizeof(SymbolTableEntry));
}

void SymbolTable_add(char *key, void *value) {
// Resize the symbol table if necessary
if (symbol_table->size == symbol_table->capacity) {
	symbol_table->capacity *= 2;
	symbol_table->entries = realloc(symbol_table->entries, symbol_table->capacity * sizeof(SymbolTableEntry));
	}
// Add the entry to the symbol table
SymbolTableEntry *entry = &(symbol_table->entries[symbol_table->size++]);
entry->key = key;
entry->value = value;
}

void *SymbolTable_lookup(char *key) {
    // Lookup the entry in the symbol table
    for (int i=0; i < symbol_table->size; i++) {
	    if (strcmp(symbol_table->entries[i].key, key) == 0) {
		    return symbol_table->entries[i].value;
	    }
    // The variable is not defined
    fprintf(stderr, "Error: variable '%s' is not defined\n", key);
    exit(1);
    }
}

int main(void) {
	SymbolTable_init();
	yyparse();
	return 0;
}

int yyerror(char *s) {
    fprintf(stderr, "Error: %s\n", s);
    return 0;
}