/**************************************************************************************
 * Chaga compiler                                                                     *
 * Copyright (C) 2026 Robert James Bruce                                              *
 *                                                                                    *
 * This file is part of the Chaga compiler.                                           *
 *                                                                                    *
 * The Chaga compiler is free software: you can redistribute it and/or modify it      *
 * under the terms of the GNU General Public License as published by the              *
 * Free Software Foundation, either version 3 of the License, or (at your option) any *
 * later version.                                                                     *
 *                                                                                    *
 * The Chaga compiler is distributed in the hope that it will be useful, but WITHOUT  *
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or              *
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License               *
 * for more details.                                                                  *
 *                                                                                    *
 * You should have received a copy of the GNU General Public License                  *
 * along with the Chaga compiler. If not, see <https://www.gnu.org/licenses/>.        *
 **************************************************************************************/
#include "./include/chaga_compiler-main.h"

#define CHAGA_COMPILER_VERSION "0.5.4"

int main (int argc, char *argv[])
{
  token_node      *token_list;
  token_node      *begin_token, *end_token, *token_element;
  cst_node        *concrete_syntax_tree;
  ast_node        *abstract_syntax_tree;
  symtab_node     *symbol_table;
  asmblr_node     *assembler_program;
  stackframe_node *stackframe_cross_reference_table;
  char            input_filename[MAX_FILE_PATH_LENGTH + MAX_FILENAME_LENGTH + 1], assembler_input_filename[MAX_FILE_PATH_LENGTH + MAX_FILENAME_LENGTH + 1]; // allocate one additional byte as NULL termainator.
  char            assembler_object_filename[MAX_FILE_PATH_LENGTH + MAX_FILENAME_LENGTH + 1], executable_output_filename[MAX_FILE_PATH_LENGTH + MAX_FILENAME_LENGTH + 1]; // allocate one additional byte as NULL termainator.
  char            test_expression[2048], *file_buffer, **byte;
  size_t          file_buffer_size, argv_string_length;
  FILE            *fp;

  file_buffer          = NULL;
  token_list           = NULL;
  concrete_syntax_tree = NULL;
  abstract_syntax_tree = NULL;
  symbol_table         = NULL;
  assembler_program    = NULL;
  stackframe_cross_reference_table = NULL;
  memset (&input_filename[0], 0, sizeof(input_filename));
  memset (&assembler_input_filename[0], 0, sizeof(assembler_input_filename));
  memset (&assembler_object_filename[0], 0, sizeof(assembler_object_filename));
  memset (&executable_output_filename[0], 0, sizeof(executable_output_filename));
  if (argc > 1)
  {
    argv_string_length = strlen(argv[1]);
    if (argv_string_length >= 3)
    {
      if (argv_string_length < (MAX_FILENAME_LENGTH - 4))
      {
        strncpy (&input_filename[0], argv[1], sizeof(input_filename));
        strncpy (&assembler_input_filename[0], argv[1], sizeof(assembler_input_filename));
        //strncat (&assembler_input_filename[0], ".s", sizeof(assembler_input_filename));
        strcat (&assembler_input_filename[0], ".s");
        strncpy (&assembler_object_filename[0], argv[1], sizeof(assembler_object_filename));
        strcat (&assembler_object_filename[0], ".o");
        strncpy (&executable_output_filename[0], argv[1], sizeof(executable_output_filename));
        strcat (&executable_output_filename[0], ".elf");
      }
      else
      {
        strncpy (&input_filename[0], argv[1], sizeof(input_filename)-4);
        strncpy (&assembler_input_filename[0], argv[1], sizeof(assembler_input_filename)-4);
        strcat (&assembler_input_filename[0], ".s");
        strncpy (&assembler_object_filename[0], argv[1], sizeof(assembler_object_filename)-4);
        strcat (&assembler_object_filename[0], ".o");
        strncpy (&executable_output_filename[0], argv[1], sizeof(executable_output_filename)-4);
        strcat (&executable_output_filename[0], ".elf");
      }
    }
    else
    {
      if (argv_string_length == 1)
      {
        byte = &argv[1];
        if (**byte == '.')
        {
          printf ("Invalid input filename.\n");
          return (EXIT_FAILURE);
        }
        else
        {
          input_filename[0] = **byte;
          assembler_input_filename[0] = **byte;
          strcat (&assembler_input_filename[0], ".s");
          assembler_object_filename[0] = **byte;
          strcat (&assembler_object_filename[0], ".o");
          executable_output_filename[0] = **byte;
          strcat (&executable_output_filename[0], ".elf");
        }
      }
      else
      {
        if (argv_string_length == 2)
        {
          byte = &argv[1];
          if ((**byte == '.') && (*(*byte+1) == '.'))
          {
            printf ("Invalid input filename.\n");
            return (EXIT_FAILURE);
          }
          else
          {
            strncpy (&input_filename[0], argv[1], sizeof(input_filename));
            strncpy (&assembler_input_filename[0], argv[1], sizeof(assembler_input_filename));
            strcat (&assembler_input_filename[0], ".s");
            strncpy (&assembler_object_filename[0], argv[1], sizeof(assembler_object_filename));
            strcat (&assembler_object_filename[0], ".o");
            strncpy (&executable_output_filename[0], argv[1], sizeof(executable_output_filename));
            strcat (&executable_output_filename[0], ".elf");
          }
        }
        else
        {
          printf ("Invalid input filename.\n");
          return (EXIT_FAILURE);
        }
      }
    }
  }
  else
  {
    printf ("Chaga compiler version " CHAGA_COMPILER_VERSION "\n\n");
    printf ("Copyright (C) 2026 Robert James Bruce\n");
    printf ("This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\nThis program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.\nYou should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.\n");
    return (EXIT_SUCCESS);
  }
  file_buffer_size = 0;
  file_buffer = read_input_file (&input_filename[0], &file_buffer_size);
  if (file_buffer == NULL)
  {
    printf ("Failed to read file %s\n", &input_filename[0]);
    return (EXIT_FAILURE);
  }
  else
  {
//
//  *******************************************************
//  * Remove comments from the input file before parsing. *
//  *******************************************************
    purge_comments (file_buffer, file_buffer_size);
//
//  ***********************************************************************
//  * Convert the input file into a series of lexical tokens for parsing. *
//  ***********************************************************************
    token_list = tokenize_input_file (file_buffer, file_buffer_size);
//
// **********************************************************
// * Debuggging purposes: Display the tokenized input file. *
// **********************************************************
    if (token_list != NULL)
    {
      //printf ("---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n\n");
      //printf ("TOKEN LIST:\n\n");
      //display_token_list (token_list);
      //display_token_list_with_line_numbers (token_list);
      //printf ("\n");
    }
//
//  ***************************************************************
//  * Parse the tokenized input file into a series of statements. *
//  ***************************************************************
    concrete_syntax_tree = create_concrete_syntax_tree (token_list);
//
// **********************************************************
// * Debuggging purposes: display the concrete syntax tree. *
// **********************************************************
    if (concrete_syntax_tree != NULL)
    {
      //printf ("---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n\n");
      //printf ("CONCRETE SYNTAX TREE:\n\n");
      //display_concrete_syntax_tree (concrete_syntax_tree);
      //printf ("\n");
    }
//
// *****************************************************************************************************************************
// * Flag programming statements in the Concrete Syntax Tree that could be optimized further to reduce unnecessary operations. *
// *****************************************************************************************************************************
    if (concrete_syntax_tree != NULL)
    {
      //concrete_syntax_tree = set_compiler_optimization_flags_in_concrete_syntax_tree (concrete_syntax_tree);
    }
// ****************************************************
// * Display Concrete Syntax Tree optimization flags. *
// ****************************************************
    if (concrete_syntax_tree != NULL)
    {
      //printf ("---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n\n");
      //printf ("EXPRESSIONS THAT CAN BE EVALUATED AT COMPILER TIME BEFORE ASSIGNMENT:\n\n");
      //display_optimization_flags_in_concrete_syntax_tree (concrete_syntax_tree);
    }
//
//  **************************
//  * Create a symbol table. *
//  **************************
    symbol_table = create_symbol_table (concrete_syntax_tree);
//
//  ***********************************
//  * Create an abstract_syntax tree. *
//  ***********************************
    if (concrete_syntax_tree != NULL)
    {
      abstract_syntax_tree = create_abstract_syntax_tree (concrete_syntax_tree, symbol_table, NULL);
    }
//
// **********************************************************
// * Debuggging purposes: display the abstract syntax tree. *
// **********************************************************
    if (abstract_syntax_tree != NULL)
    {
      //printf ("---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n\n");
      //printf ("ABSTRACT SYNTAX TREE:\n\n");
      //display_abstract_syntax_tree (abstract_syntax_tree);
      //printf ("\n");
    }
//
// **********************************************************************************************************************************************************************************
// * Examine each subroutine to determine if any intermediate numerical expressions need to be computed on-the-fly during run-time with temporary variables. Examples of on-the-fly *
// * numerical expressions:                                                                                                                                                         *
// *                                                                                                                                                                                *
// * EXAMPLE 1: if (i > foo + bar - 5):  In this situation, we must compute the result of (foo + bar - 5). The evaluation of the expression will be stored in a temporary variable. *
// * EXAMPLE 2: while (i > bar + 5):     In this sitatuoon, we must compute the result of (bar + 5). The evaluation of the expression will be stored in a temporary variable.       *
// * EXAMPLE 3: set bar = (1 + foo * 5): In this situation, we must compute the result of (1 + foo * 5). The evaluation of the expression will be stored in a temporary variable.   *
// * EXAMPLE 4: set bar = -foo:          In this situation, we must copy value of foo to a temporary variable then invert the sign of the temporary variable before copying to bar. *
// *                                                                                                                                                                                *
// * NOTE: The abstract syntax tree is also modified by pointing the temporary variables in symbol table to the token in the abstract syntax tree.                                  *
// **********************************************************************************************************************************************************************************
    if ((abstract_syntax_tree != NULL) && (symbol_table != NULL))
    {
      symbol_table = add_temporary_variables_to_symbol_table_to_evaluate_numerical_expressions (symbol_table, abstract_syntax_tree);
    }
//
// **************************************************
// * Debuggging purposes: display the symbol table. *
// **************************************************
    if (symbol_table != NULL)
    {
      //printf ("---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n\n");
      //printf ("SYMBOL TABLE:\n\n");
      //display_symbol_table (symbol_table);
      //printf ("\n");
    }
//
//  *********************************************************************************************************************************
//  * Create stackframe cross reference table.                                                                                      *
//  * This table creates a cross-reference between variables in the symbol table and their respective locations on the stack frame. *
//  *********************************************************************************************************************************
    stackframe_cross_reference_table = create_stackframe_cross_reference_table_for_main_only (symbol_table); 
    if (stackframe_cross_reference_table != NULL)
    {
      //printf ("---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\n\n");
      //printf ("STACKFRAME CROSS REFERENCE TABLE:\n\n");
      //display_stackframe_cross_reference_table (stackframe_cross_reference_table);
      //printf ("\n");
    }
//  *************************************
//  * Create asssembly language output. *
//  *************************************
    assembler_program = create_assembler_program (abstract_syntax_tree, symbol_table, stackframe_cross_reference_table);
    if (assembler_program != NULL)
    {
      fp = fopen (&assembler_input_filename[0], "wb");
      if (fp != NULL)
      {
        write_assembler_output_to_file (fp, assembler_program);
        fclose (fp);
        create_executable_output_file_from_x86_assembler_file (&assembler_input_filename[0], &assembler_object_filename[0], &executable_output_filename[0]);
      }
      else
      {
        printf ("Failed to save to save assembler output to file: %s\n", &assembler_input_filename[0]);
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// !! TODO: FREE all the tables I've created! !!
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        return (EXIT_FAILURE);
      }
    }
  }
  if (file_buffer != NULL)
  {
    free (file_buffer);
  }

// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// !! TODO: FREE all the tables I've created! !!
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  exit (SUCCESS); 
}
