//Emilien Royer, Mars 2010
//

#include "header.h"

void
Initialisation ()
{
  R_CO = 0;
  R_ADR = 0;
  R_TMP = 0;
  R_ACC = 0;
}

void
ExecuteInstruction (unsigned char code_op, unsigned char operande)
{
/*
   Recupère le code d'une instruction et l'opérande associée avec, puis execute
   l'instruction correspondante. Voir instruction.c pour le detail des
   operations effectuées.
*/
  switch (code_op)
    {
    case INP:
      Input (operande);
      break;
    case OUT:
      Output (operande);
      break;
    case CLA:
      ClearAndAdd (operande);
      break;
    case STO:
      Store (operande);
      break;
    case ADD:
      Add (operande);
      break;
    case SUB:
      Sub (operande);
      break;
    case SHT:
      Shift ();
      break;
    case JMP:
      Jump (operande);
      break;
    case TAC:
      TestAccContent (operande);
      break;
    case HRS:
      HaltAndReset ();
      break;
    case TMP:
      Temp ();
      break;
    case LTH:
      LessThan (operande);
      break;
    case GTH:
      GreaterThan (operande);
      break;
    case CAL:
      CallSub (operande);
      break;
    case RET:
      Return ();
      break;
    }
}

void
PrintOpCode (unsigned char code_op)
{
  /*
     Recupère le code d'une instruction et l'opérande associée avec, puis execute
     l'instruction correspondante. Voir instruction.c pour le detail des
     operations effectuées.
   */
  switch (code_op)
    {
    case INP:
      printf ("INP");
      break;
    case OUT:
      printf ("OUT");
      break;
    case CLA:
      printf ("CLA");
      break;
    case STO:
      printf ("STO");
      break;
    case ADD:
      printf ("ADD");
      break;
    case SUB:
      printf ("SUB");
      break;
    case SHT:
      printf ("SHT");
      break;
    case JMP:
      printf ("JMP");
      break;
    case TAC:
      printf ("TAC");
      break;
    case HRS:
      printf ("HRS");
      break;
    case TMP:
      printf ("TMP");
      break;
    case LTH:
      printf ("LTH");
      break;
    case GTH:
      printf ("GTH");
      break;
    case CAL:
      printf ("CAL");
      break;
    case RET:
      printf ("RET");
      break;
    }
}

void
Execution (unsigned char beg, char s)
{
  /*
     Execute le programme: L'adresse qui initialise le compteur ordinal est
     fournie en argument (cf LoadInMemory).
   */
  R_CO = beg;
  unsigned int buff;		// entier non signé car 3 demi octets ne peuvent pas
  // êtres contenus dans un octet...
  unsigned char code_op, operande;

  /*
     A chaque passe dans la boucle, on récupere l'entier contenu dans
     l'adresse courante, et grace à des operations de décalage, on extrait
     le code d'opération, puis l'opérande.
   */
  while (1)
    {
      buff = memoire[R_CO];	// Extraction du code d'opération
      code_op = buff >> 8;

      buff = buff << 24;	// Extraction de l'opérande (on opère sur 32 bits, int)
      operande = buff >> 24;

      /*
         Option du mode pas à pas, affiche les différents registres et
         l'action en cours
       */
      if (s)
	{
	  printf ("R_CO: %x\t", R_CO);
	  PrintOpCode (code_op);
	  printf ("  %x\t\t", operande);
	  printf ("R_ACC: %x R_ADR: %x R_TMP: %x\n", R_ACC, R_ADR, R_TMP);
	  getchar ();		// Attend une entrée au clavier avant de continuer
	}
      ExecuteInstruction (code_op, operande);
      R_CO++;
    }
}

unsigned char
LoadInMemory (char *file_name)
{
/*
   Charge le code executable contenu dans le fichier binaire indiqué en argument
   dans la mémoire de l'ordinapoche.
   De plus, renvoit l'adresse à laquelle le programme s'execute (variable beg).
*/
  int boucle = 0;
  unsigned int saisie;
  unsigned int buff, tempo;
  unsigned char beg;
  FILE *file;

  /* on ouvre le fichier binaire en lecture, constitué de la forme suivante:
     Chaque octet contient 2 mots (codés sur 4 bits, le maximum est donc 15).
     Par exemple, pour l'instruction ordinapoche INPUT 0x58, nous avons donc
     0 5 8, qui se présente sous la forme d'un premier octet: 00000101 et d'un
     deuxieme octet: 1000????. (???? étant le premier mot de la prochaine
     instruction) il s'agit donc de decomposer les octets pour recuperer ces
     mots.
   */
  printf ("%s\n", file_name);
  file = fopen (file_name, "rb");

  buff = fgetc (file);
  R_CO = buff;
  beg = buff;

  printf ("R_CO: %x\n", R_CO);

  /* Le principe de la boucle est le suivant: 
     Il y a deux cas à differencier, prenons l'exemple suivant:
     a b c
     d e f
     g h i
     1) Le premier octet lu contiendra les mots a et b, ce qui est insuffisant
     pour executer une instruction car il nous manque c pour avoir l'opérande.
     Il nous faut donc lire l'octet suivant, qui contient c et d. On sépare
     donc c, et on execute l'instruction. On garde d de côté pour le prochain
     passage dans la boucle.
     2) deuxieme passage: Nous avons déjà le code de l'instruction, il reste à
     connaitre l'opérande. Il suffit donc de lire un octet. Le prochain passage
     sera identique au premier, d'ou le switch avec un indice modulo 2.
   */

  while (saisie != EOF)
    {
      switch (boucle % 2)
	{

	case 0:
	  buff = fgetc (file);
	  buff = buff << 4;
	  tempo = fgetc (file);
	  saisie = tempo;
	  buff = buff + (tempo >> 4);
	  memoire[R_CO] = buff;
	  break;

	case 1:
	  buff = tempo << 28;
	  buff = buff >> 20;
	  tempo = fgetc (file);
	  buff = buff + tempo;
	  saisie = tempo;
	  memoire[R_CO] = buff;
	  break;
	}
      boucle++;
      R_CO++;
    }
  printf ("\n");
  fclose (file);

  return beg;
}

void
Dump (char *file_name)
{
  /*
     Crée un fichier "logs" qui contient une version texte du code executable
     Pour comprendre les mécanismes, se referer aux commentaires de la
     fonction LoadInMemory.
   */
}

void
ReadMemory (char *file_name, unsigned char end)
{
/*
   Fonction de débugage, permet de lire la mémoire à partir d'une adresse donnée
   afin de verifier qu'il n'y a pas eu de problème lors du chargement en mémoire
   du code.
*/
  FILE *file;
  unsigned int buff;
  unsigned int tempo;

  file = fopen (file_name, "rb");
  buff = fgetc (file);
  R_CO = buff;
  printf ("R_CO: %d\n", R_CO);

  while (end > 0)
    {
      buff = memoire[R_CO];
      printf ("%d\t", buff);
      printf ("%x|", buff >> 8);
      tempo = buff << 24;
      tempo = tempo >> 24;
      printf ("%x", tempo);
      printf ("\n");
      R_CO++;
      end--;
    }

  fclose (file);
}

void
PrintHelp ()
{
/*
    Affiche sur la sortie standart les différentes options de lancement du
    programme
*/
  printf ("\nOptions available:\n");
  printf ("\t-s (Step mode, useful for debugging purpose)\n");
  printf
    ("\t-d (Dump an ASCII text file from a binary, not yet available)\n");
  printf ("\n");
}

int
main (int argc, char *argv[])
{
  int opt;
  char s = 0;
  char *optlist = "hsd";
  unsigned char beg;

  if (argc < 2)
    {
      printf ("Usage: %s Binaryfilename\n\n", argv[0]);
      printf ("\t-h (Help)\n");
      exit (1);
    }

  while ((opt = getopt (argc, argv, optlist)) > 0)
    switch (opt)
      {
      case 'h':
	PrintHelp ();
	exit (1);
      case 's':
	s = 1;
	break;
      case 'd':
	Dump (argv[argc - 1]);
	exit (1);
      default:
	printf ("Unkown option: %c\n", opt);
	printf ("\t-h (Help)\n");
	exit (1);
      }

  Initialisation ();
  beg = LoadInMemory (argv[argc - 1]);
  printf ("\n");
  Execution (beg, s);
  return 0;
}
