#include <stdio.h>
#include "parcours.h"
#include "syntabs.h"
#include "util.h"

int nbArg = 0;
int nbvariableLocalFonc = 0;
int contexte = C_VARIABLE_GLOBALE; 
int adresseGlobalCourante = 0;
int adresseLocaleCourante = 0;
int adresseArgumentCourant = 0;
int afficheMips = 0;
int creg = 0;

extern dico_ dico;

int tagindice = 0;
void new_tag(char * tmp){
  sprintf(tmp,"e%d",tagindice );
  ++tagindice;
}

void printMips( const char * msg ) {
  if(afficheMips)
    printf("%s\n", msg);
}

int newReg() {
  return creg++%10;
}

void debutFunc() {
  printMips("subi $sp, $sp, 4");
  printMips("sw $fp, 0($sp)");
  printMips("move $fp, $sp");
  printMips("subi $sp, $sp, 4");
  printMips("sw $ra, 0($sp)");
}

void finFuncreturn(int bool){
  printMips("lw $ra, 0($sp)");
  printMips("addi $sp, $sp, 4");
  printMips("lw $fp, 0($sp)");
  printMips("addi $sp, $sp, 4");
  if(!bool){
    printMips("addi $sp, $sp, 4 #ignore valeur de retour");
  }
  printMips("jr $ra");
}
void finFunc() {
  finFuncreturn(0);
}

void empile(char *reg) {
  if(afficheMips) {
    printf("subi $sp, $sp, 4\n");
    printf("sw %s, 0($sp)\n", reg);
  }
}

void depile(char *reg) {
  if(afficheMips) {
    printf("lw %s, 0($sp)\n", reg);
    printf("addi $sp, $sp, 4\n");
  }
}

/*-------------------------------------------------------------------------*/

void parcours_table(n_prog *n)
{
  printMips(".data");
  parcours_l_dec(n->variables);
  printMips(".text");
  printMips("__start:");
  printMips("jal main");
  printMips("li $v0, 10");
  printMips("syscall");

  parcours_l_dec(n->fonctions); 

  int id = rechercheExecutable("main");
  if(id < 0 || dico.tab[id].complement != 0) {
    printf("Fonction main non trouvé\n");
    affiche_dico();
    exit(1);
  }
}

/*-------------------------------------------------------------------------*/

void parcours_l_instr(n_l_instr *n)
{
  if(n) {
    parcours_instr(n->tete);
    parcours_l_instr(n->queue);
  }
}

/*-------------------------------------------------------------------------*/

void parcours_instr(n_instr *n)
{
  if(n) {
    if(n->type == blocInst) parcours_l_instr(n->u.liste);
    else if(n->type == affecteInst) parcours_instr_affect(n);
    else if(n->type == siInst) parcours_instr_si(n);
    else if(n->type == tantqueInst) parcours_instr_tantque(n);
    else if(n->type == faireInst) parcours_instr_faire(n);
    else if(n->type == pourInst) parcours_instr_pour(n);    
    else if(n->type == appelInst) parcours_instr_appel(n);
    else if(n->type == retourInst) parcours_instr_retour(n);
    else if(n->type == ecrireInst) parcours_instr_ecrire(n);
  }
}

/*-------------------------------------------------------------------------*/

void parcours_instr_si(n_instr *n)
{  
  parcours_exp(n->u.si_.test);
  int iRegistre = 0;
  char tmp[250]; 
  char registre[250]; 
  char faux[256];
  char suite[256];

  new_tag(faux);
  new_tag(suite);

  sprintf(registre,"$t%d",iRegistre);
  depile(registre);

  sprintf(tmp,"beq %s $zero %s", registre,faux);
  printMips(tmp);

  parcours_instr(n->u.si_.alors);

  sprintf(tmp,"j %s",suite);
  printMips(tmp);

  sprintf(tmp,"%s:",faux);
  printMips(tmp);

  if(n->u.si_.sinon) {
    parcours_instr(n->u.si_.sinon);
  }

  sprintf(tmp,"%s:",suite);
  printMips(tmp);
}

/*-------------------------------------------------------------------------*/

void parcours_instr_tantque(n_instr *n)
{
  char tmp[256];
  char etiquette[256];
  char fin[256];
  char registre[250]; 

  new_tag(etiquette);
  new_tag(fin);

  sprintf(tmp,"%s:",etiquette);
  printMips(tmp);

  parcours_exp(n->u.tantque_.test);

  int iRegistre = 0;
  sprintf(registre,"$t%d",iRegistre);
  depile(registre);
  sprintf(tmp,"beq %s $0 %s", registre,fin);
  printMips(tmp);

  parcours_instr(n->u.tantque_.faire);

  sprintf(tmp,"j %s",etiquette);
  printMips(tmp);
  sprintf(tmp,"%s:",fin);
  printMips(tmp);

}

/*-------------------------------------------------------------------------*/

void parcours_instr_faire(n_instr *n)          
{                                      

  parcours_instr(n->u.faire_.faire);           
  parcours_exp(n->u.faire_.test);              
}                                             

/*-------------------------------------------------------------------------*/

void parcours_instr_pour(n_instr *n)                
{                                                  
  parcours_instr(n->u.pour_.init);
  char tmp[250]; 
  char etiquette[256];
  char fin[256];
  char registre[250]; 

  new_tag(etiquette);
  new_tag(fin);

  sprintf(tmp,"%s:",etiquette);
  printMips(tmp);

  parcours_exp(n->u.pour_.test);                    
  
  int iRegistre = 0;
  sprintf(registre,"$t%d",iRegistre);
  depile(registre);
  sprintf(tmp,"beq %s $zero %s", registre, fin);
  printMips(tmp);

  parcours_instr(n->u.pour_.faire);                 
  parcours_instr(n->u.pour_.incr);                  

  sprintf(tmp,"j %s",etiquette);
  printMips(tmp);
  sprintf(tmp,"%s:",fin);
  printMips(tmp);
}                                                  

/*-------------------------------------------------------------------------*/

void parcours_instr_affect(n_instr *n)
{
  char var[256];
  char tmp[250]; 
  char registre[250]; 

  parcours_exp(n->u.affecte_.exp);
  parcours_var(n->u.affecte_.var,var);
  

  int iRegistre = 0;
  sprintf(registre,"$t%d",iRegistre);
  depile(registre);
  sprintf(tmp,"sw %s, %s",registre, var);
  printMips(tmp);
}

/*-------------------------------------------------------------------------*/

void parcours_instr_appel(n_instr *n)
{
  parcours_appel(n->u.appel);
}
/*-------------------------------------------------------------------------*/

void parcours_appel(n_appel *n)
{
  char tmp[250];

  int id = rechercheExecutable(n->fonction);
  if(id < 0) {
    printf("Fonction %s introuvable\n", n->fonction);
    affiche_dico();
    exit(1);
  }

  printMips("subi $sp, $sp, 4 #Val Retour");

  int nbArg = parcours_l_exp(n->args);


  if(dico.tab[id].complement != nbArg) {
    printf("Nombre Argument différent pour l'appel à %s\n", n->fonction);
    affiche_dico();
    exit(1);
  }

  sprintf(tmp,"jal %s",n->fonction);
  printMips(tmp);
  int i = nbArg * 4;
  if(i != 0) {
    sprintf(tmp,"addi $sp, $sp, %d #désalocation des arguments",i);
    printMips(tmp);
  }
}

/*-------------------------------------------------------------------------*/

void parcours_instr_retour(n_instr *n)
{
  char tmp[250];
  char exec[250];
  int returnAdress = 4 *  (nbArg + 1);
  parcours_exp(n->u.retour_.expression);
  int iRegistre = 0;
  sprintf(tmp,"$t%d",iRegistre);
  depile(tmp);
  sprintf(exec,"sw %s %d($fp) #Adresse de retour",tmp , returnAdress);
  printMips(exec);

  sprintf(tmp,"addi $sp, $sp %d # desallocation variables locales",nbvariableLocalFonc * 4);
  printMips(tmp);
  
  finFuncreturn(1);

}

/*-------------------------------------------------------------------------*/
void parcours_instr_ecrire(n_instr *n)
{
  parcours_exp(n->u.ecrire_.expression);

  char tmp[250];
  int iRegistre = 0;

  sprintf(tmp,"$t%d",iRegistre);
  depile(tmp);
  sprintf(tmp, "move $a0, $t%d",iRegistre);
  printMips(tmp);
  printMips("li $v0, 1");
  printMips("syscall");
  printMips("li  $a0, '\\n'");
  printMips("li  $v0, 11");
  printMips("syscall");   
}

/*-------------------------------------------------------------------------*/

int parcours_l_exp(n_l_exp *n)
{
  if(n){
    parcours_exp(n->tete);
    return 1 + parcours_l_exp(n->queue);
  }
  return 0;
}

/*-------------------------------------------------------------------------*/

void parcours_exp(n_exp *n)
{
  if(n->type == varExp) parcours_varExp(n);
  else if(n->type == opExp) parcours_opExp(n);
  else if(n->type == intExp) parcours_intExp(n);
  else if(n->type == appelExp) parcours_appelExp(n);
  else if(n->type == lireExp) parcours_lireExp(n);
}

/*-------------------------------------------------------------------------*/

void parcours_varExp(n_exp *n)
{
  char var[256];
  char tmp[256];

  parcours_var(n->u.var, var);

  int iRegistre = 0;
  sprintf(tmp, "lw $t%d, %s", iRegistre, var);
  printMips(tmp);
  sprintf(tmp, "$t%d", iRegistre);
  empile(tmp);
}

/*-------------------------------------------------------------------------*/
void parcours_opExp(n_exp *n) 
{
  char tmp[256];

  int iRegistre = 0;
  int iRegistre2 = 1;
  int iRegistre3 = 2;
  

  if(n->u.opExp_.op == plus) {
    parcours_exp(n->u.opExp_.op1);
    parcours_exp(n->u.opExp_.op2);
    sprintf(tmp, "$t%d", iRegistre3);
    depile(tmp);
    sprintf(tmp, "$t%d", iRegistre2);
    depile(tmp);

    sprintf(tmp,"add $t%d, $t%d, $t%d",iRegistre, iRegistre2, iRegistre3);
    printMips(tmp);
  } else if(n->u.opExp_.op == moins){
    parcours_exp(n->u.opExp_.op1);
    parcours_exp(n->u.opExp_.op2);
    sprintf(tmp, "$t%d", iRegistre3);
    depile(tmp);
    sprintf(tmp, "$t%d", iRegistre2);
    depile(tmp);

    sprintf(tmp,"sub $t%d, $t%d, $t%d",iRegistre, iRegistre2, iRegistre3);
    printMips(tmp);
  } else if(n->u.opExp_.op == fois) {
    parcours_exp(n->u.opExp_.op1);
    parcours_exp(n->u.opExp_.op2);
    sprintf(tmp, "$t%d", iRegistre3);
    depile(tmp);
    sprintf(tmp, "$t%d", iRegistre2);
    depile(tmp);

    sprintf(tmp,"mult  $t%d, $t%d", iRegistre2, iRegistre3);
    printMips(tmp);
    sprintf(tmp,"mflo $t%d",iRegistre);
    printMips(tmp);
  } else if(n->u.opExp_.op == divise) {
    parcours_exp(n->u.opExp_.op1);
    parcours_exp(n->u.opExp_.op2);
    sprintf(tmp, "$t%d", iRegistre3);
    depile(tmp);
    sprintf(tmp, "$t%d", iRegistre2);
    depile(tmp);

    sprintf(tmp,"div  $t%d, $t%d", iRegistre2, iRegistre3);
    printMips(tmp);
    sprintf(tmp,"mflo $t%d",iRegistre);
    printMips(tmp);
  } else if(n->u.opExp_.op == egal){
    parcours_exp(n->u.opExp_.op1);
    parcours_exp(n->u.opExp_.op2);

    sprintf(tmp, "$t%d", iRegistre3);
    depile(tmp);
    sprintf(tmp, "$t%d", iRegistre2);
    depile(tmp);

    char etiquette[256];
    new_tag(etiquette);
    sprintf(tmp,"li $t%d, 1",iRegistre);
    printMips(tmp);
    sprintf(tmp,"beq $t%d, $t%d %s",iRegistre2, iRegistre3, etiquette);
    printMips(tmp);
    sprintf(tmp,"li $t%d, 0",iRegistre);
    printMips(tmp);
    sprintf(tmp,"%s:", etiquette);
    printMips(tmp);
  } else if(n->u.opExp_.op == diff) {
    parcours_exp(n->u.opExp_.op1);
    parcours_exp(n->u.opExp_.op2);
    sprintf(tmp, "$t%d", iRegistre3);
    depile(tmp);
    sprintf(tmp, "$t%d", iRegistre2);
    depile(tmp);

    char etiquette[256];
    new_tag(etiquette);
    sprintf(tmp,"li $t%d, 0",iRegistre);
    printMips(tmp);
    sprintf(tmp,"beq $t%d, $t%d %s",iRegistre2, iRegistre3, etiquette);
    printMips(tmp);
    sprintf(tmp,"li $t%d, 1",iRegistre);
    printMips(tmp);
    sprintf(tmp,"%s:", etiquette);
    printMips(tmp);
  }else if(n->u.opExp_.op == inf){
    parcours_exp(n->u.opExp_.op1);
    parcours_exp(n->u.opExp_.op2);
    sprintf(tmp, "$t%d", iRegistre3);
    depile(tmp);
    sprintf(tmp, "$t%d", iRegistre2);
    depile(tmp);
    char etiquette[256];
    new_tag(etiquette);
    sprintf(tmp,"li $t%d, 1",iRegistre);
    printMips(tmp);
    sprintf(tmp,"blt $t%d, $t%d %s",iRegistre2, iRegistre3, etiquette);
    printMips(tmp);
    sprintf(tmp,"li $t%d, 0",iRegistre);
    printMips(tmp);
    sprintf(tmp,"%s:", etiquette);
    printMips(tmp);
  }else if(n->u.opExp_.op == infeg){  
    parcours_exp(n->u.opExp_.op1);
    parcours_exp(n->u.opExp_.op2);
    sprintf(tmp, "$t%d", iRegistre3);
    depile(tmp);
    sprintf(tmp, "$t%d", iRegistre2);
    depile(tmp);

    char etiquette[256];
    new_tag(etiquette);
    sprintf(tmp,"li $t%d, 1",iRegistre);
    printMips(tmp);
    sprintf(tmp,"ble $t%d, $t%d %s",iRegistre2, iRegistre3, etiquette);
    printMips(tmp);
    sprintf(tmp,"li $t%d, 0",iRegistre);
    printMips(tmp);
    sprintf(tmp,"%s:", etiquette);
    printMips(tmp);
  }else if(n->u.opExp_.op == ou){
    char faux1[256]; 
    char faux2[256]; 
    char vrai[256];
    char empile[256];
    new_tag(faux1);
    new_tag(faux2);
    new_tag(vrai);
    new_tag(empile);
    
    parcours_exp(n->u.opExp_.op1);
    sprintf(tmp, "$t%d", iRegistre2);
    depile(tmp);
    sprintf(tmp,"beq $zero, $t%d, %s",iRegistre2,faux1);
    printMips(tmp);
    sprintf(tmp,"j %s",vrai);
    printMips(tmp);
    sprintf(tmp,"%s :",faux1);
    printMips(tmp);
    
    parcours_exp(n->u.opExp_.op2);
    sprintf(tmp, "$t%d", iRegistre2);
    depile(tmp);
    sprintf(tmp,"beq $zero $t%d %s",iRegistre2,faux2);
    sprintf(tmp, "%s :", vrai);
    printMips(tmp);
    sprintf(tmp,"li $t%d 1",iRegistre);
    printMips(tmp);
    sprintf(tmp,"j %s",empile);
    printMips(tmp);
    sprintf(tmp,"%s :",faux2);
    printMips(tmp);
    sprintf(tmp,"li $t%d 0",iRegistre);
    printMips(tmp);
    sprintf(tmp,"%s : ", empile);
    printMips(tmp);
  } else if(n->u.opExp_.op == et) {
    char empile[256];
    char faux[256];
    new_tag(empile);
    new_tag(faux);

    parcours_exp(n->u.opExp_.op1);
    sprintf(tmp, "$t%d", iRegistre2);
    depile(tmp);
    sprintf(tmp,"li $t%d, 1",iRegistre);
    printMips(tmp);
    sprintf(tmp,"beq $zero, $t%d, %s",iRegistre2,faux);
    printMips(tmp);
    
    parcours_exp(n->u.opExp_.op2);
    sprintf(tmp, "$t%d", iRegistre3);
    depile(tmp);
    sprintf(tmp,"beq $zero, $t%d, %s",iRegistre3,faux);
    printMips(tmp);
    
    sprintf(tmp,"j %s", empile);
    printMips(tmp);

    sprintf(tmp," %s :",faux);
    printMips(tmp);
    sprintf(tmp,"li $t%d 0",iRegistre);
    printMips(tmp);

    sprintf(tmp ," %s :",empile);

    printMips(tmp);

  } else if(n->u.opExp_.op == non) {
    parcours_exp(n->u.opExp_.op1);
    char fin[256];
    new_tag(fin);
    sprintf(tmp, "$t%d", iRegistre3);
    depile(tmp);
    sprintf(tmp,"li $t%d 1",iRegistre);
    printMips(tmp);
    sprintf(tmp,"beq $t%d, $zero %s",iRegistre3,fin);
    printMips(tmp);
    sprintf(tmp,"li $t%d 0",iRegistre);
    printMips(tmp);
    sprintf(tmp, "%s :",fin);
    printMips(tmp);
  }  
  sprintf(tmp, "$t%d", iRegistre);
  empile(tmp);
}

/*--------------------------------  -----------------------------------------*/

void parcours_intExp(n_exp *n)
{
  int iRegistre = 0;
  
  char tmp[256]; 
  sprintf(tmp, "li $t%d, %d", iRegistre, n->u.entier);
  printMips(tmp);
  sprintf(tmp, "$t%d", iRegistre);
  empile(tmp);
}

/*-------------------------------------------------------------------------*/
void parcours_lireExp(n_exp *n)
{
  printMips("li $v0 5");
  printMips("syscall");

  int iRegistre = 0;
  char tmp[256];

  sprintf(tmp, "move $t%d, $v0", iRegistre);
  printMips(tmp);
  sprintf(tmp, "$t%d", iRegistre);
  empile(tmp);
}

/*-------------------------------------------------------------------------*/

void parcours_appelExp(n_exp *n)
{
  parcours_appel(n->u.appel);
}

/*-------------------------------------------------------------------------*/

void parcours_l_dec(n_l_dec *n)
{
  if( n ){
    parcours_dec(n->tete);
    parcours_l_dec(n->queue);
  }
}

/*-------------------------------------------------------------------------*/

void parcours_dec(n_dec *n)
{

  if(n){
    if(n->type == foncDec) {
      parcours_foncDec(n);
    }
    else if(n->type == varDec) {
      parcours_varDec(n);
    }
    else if(n->type == tabDec) { 
      parcours_tabDec(n);
    }
  }
}

/*-------------------------------------------------------------------------*/

void parcours_foncDec(n_dec *n)
{
  char tmp[256];
  sprintf(tmp, "%s :", n->nom);
  printMips(tmp);
  debutFunc();

  int i = rechercheExecutable(n->nom);
  if( i >= 0) {
    printf("Fonction %s déjà déclaré\n", n->nom);
    affiche_dico();
    exit(1);
  }

  int id = ajouteIdentificateur(n->nom,  contexte, T_FONCTION, 0, -1);
  entreeFonction();
  contexte = C_ARGUMENT;
  parcours_l_dec(n->u.foncDec_.param);

  int j = 0;
  for(int i = dico.sommet - 1; i >= 0; i--){
    if(dico.tab[i].classe != C_ARGUMENT)
      break;
    j++;
  }
  nbArg = j;
  i = id;
  int nbvariableLocale = 0;
  
  
  dico.tab[id].complement = j;

  contexte = C_VARIABLE_LOCALE;
  parcours_l_dec(n->u.foncDec_.variables);
  while (i+1 < dico.sommet && (dico.tab[i+1].classe == C_VARIABLE_LOCALE 
    || dico.tab[i+1].classe == C_ARGUMENT)){
    if(dico.tab[i+1].classe == C_VARIABLE_LOCALE)
      ++nbvariableLocale;
    ++i;
  }
  nbvariableLocalFonc = nbvariableLocale;

  parcours_instr(n->u.foncDec_.corps);
  sprintf(tmp,"addi $sp, $sp %d # desallocation variables locales",nbvariableLocale * 4);

  printMips(tmp);

  affiche_dico();
  sortieFonction();
  finFunc();
}

/*-------------------------------------------------------------------------*/

void parcours_varDec(n_dec *n)
{
  if(rechercheDeclarative(n->nom) >= 0) {
    printf("Variable %s déjà déclaré\n", n->nom);
    affiche_dico();
    exit(1);
  }

  if (contexte == C_VARIABLE_GLOBALE) {
    ajouteIdentificateur(n->nom,  contexte, T_ENTIER, adresseGlobalCourante, -1);
    char tmp[250];
    sprintf(tmp, "$%s: .space 4", n->nom);
    printMips(tmp);
    adresseGlobalCourante += 4;
  } else if (contexte == C_VARIABLE_LOCALE) {
    printMips("subi $sp, $sp, 4");
    ajouteIdentificateur(n->nom,  contexte, T_ENTIER, adresseLocaleCourante, -1);
    adresseLocaleCourante += 4;
  } else if (contexte == C_ARGUMENT) {
    ajouteIdentificateur(n->nom,  contexte, T_ENTIER, adresseArgumentCourant, -1);
    adresseArgumentCourant += 4;
  } else {
    printf("Wtf ? %s\n", n->nom);
    affiche_dico();
    exit(1);
  }

}

/*-------------------------------------------------------------------------*/
void parcours_tabDec(n_dec *n)
{
  char texte[100]; // Max. 100 chars nom tab + taille
  sprintf(texte, "%s[%d]", n->nom, n->u.tabDec_.taille);

  if(rechercheDeclarative(n->nom) >= 0) {
    printf("Tableau %s déjà déclaré\n", n->nom);
    affiche_dico();
    exit(1);
  }

  if (contexte == C_VARIABLE_GLOBALE) {
    ajouteIdentificateur(n->nom,  contexte, T_TABLEAU_ENTIER, adresseGlobalCourante, n->u.tabDec_.taille);
    char tmp[250];
    sprintf(tmp, "$%s: .space %d", n->nom, 4*n->u.tabDec_.taille);
    printMips(tmp);
    adresseGlobalCourante += 4*n->u.tabDec_.taille;
  } else {
    printf("Wtf ? %s\n", n->nom);
    affiche_dico();
    exit(1);
  }

}

/*-------------------------------------------------------------------------*/
void parcours_var(n_var *n , char * var)
{
  if(n->type == simple) {
    parcours_var_simple(n, var);
  }
  else if(n->type == indicee) {
    parcours_var_indicee(n, var);
  }
}

/*-------------------------------------------------------------------------*/
void parcours_var_simple(n_var *n, char * var)
{
  int i = rechercheExecutable(n->nom);
  if( i < 0) {
    printf("Variable %s non déclaré\n", n->nom);
    affiche_dico();
    exit(1);
  } else if (dico.tab[i].type == T_TABLEAU_ENTIER) {
    printf("Indice tableau %s est attendu\n", n->nom);
    affiche_dico();
    exit(1);
  }

  if(dico.tab[i].classe == C_VARIABLE_GLOBALE) {
    sprintf(var, "$%s", n->nom);
  } 
  else if (dico.tab[i].classe == C_ARGUMENT) {    
    int adress = dico.tab[i].adresse;
    int result = 4 * nbArg  - adress;
    sprintf(var, "%d($fp) #variable argument %s",result,n->nom);
  }
  else if (dico.tab[i].classe == C_VARIABLE_LOCALE){
    int adresse = -1 *(8 + dico.tab[i].adresse);
    sprintf(var, "%d($fp) #variable locale", adresse);
  }
}

/*-------------------------------------------------------------------------*/
void parcours_var_indicee(n_var *n, char * var)
{
  int i = rechercheExecutable(n->nom);
  if( i < 0) {
    printf("Tableau %s non déclaré\n", n->nom);
    affiche_dico();
    exit(1);
  } else if (dico.tab[i].type == T_ENTIER) {
    printf("%s n'est pas un Tableau\n", n->nom);
    affiche_dico();
    exit(1);
  }
  parcours_exp( n->u.indicee_.indice );
  depile ("$t4");
  printMips("add $t4,$t4,$t4");
  printMips("add $t4,$t4,$t4");
  sprintf(var,"$%s($t4)",n->nom);
}
/*-------------------------------------------------------------------------*/
