/*
   Projet de C - Riesner
   Calculatrice à Pile
*/

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <math.h>

/* Definitions de constantes */
#define  PILE     5
#define  FALSE    0
#define  TRUE     1

/* Declarations des fonctions: */
void welcome(void);
void init(void);
void uninit(void);
int  search_func(char *s);
int  read_number(char *s);
int  stack_in(long nombre);
void grow_stack(void);
int stack_read(long *nombre);
int  stack_out(long *nombre);
void print_element(long n);
void strinv(char *s);
int  c_print(void);
int  c_dup(void);
int  c_change(void);
int  c_pop(void);
int  c_list(void);
int  c_clear(void);
int  c_help(void);
int  c_quit(void);
int  c_obase(void);
int  c_ibase(void);
int  c_status(void);
int  c_add(void);
int  c_sub(void);
int  c_mul(void);
int  c_div(void);
int  c_mod(void);

/* Declaration de structure */
typedef struct
{
   char  com[10];       /* Commande */
   int   (*func)();     /* Fonction correspondante */
   char  help[60];      /* Descriptif de la fonction */
   char  r1;            /* Touche raccourci  */
   char  r2[10];        /* Raccourci commande */
} TABCOM;
  
/* Declarations des variables globales */
int      fin;
long     *pile;         /* Pointeur de Pile */
int      nmb;           /* Nombre d'elements dans la Pile */
int      pos;           /* Positition dans la Pile */
int      ibase;         /* Base d'entree */
int      obase;         /* Base de sortie */
int      nichiff;       /* Nombre de chiffres suivant la base d'entree */
int      nochiff;       /* Nombre de chiffres suivant la base de sortie */

TABCOM   tabcom[]=
{
 { "add",    c_add,    "Addition",                               '+', "ad"  },
 { "sub",    c_sub,    "Soustraction",                           '-', "su" },
 { "mul",    c_mul,    "Multiplication",                         '*', "mu" },
 { "div",    c_div,    "Division",                               '/', "di" },
 { "mod",    c_mod,    "Modulo",                                 '%', "mo" },
 { "print",  c_print,  "Affiche le sommet de la pile",           ' ', "pr" },
 { "dup",    c_dup,    "Duplique le sommet de la pile",          ' ', "du" },
 { "change", c_change, "Echange sommet/sous-sommet de la pile",  ' ', "ch" },
 { "pop",    c_pop,    "Depile un element et affiche sa valeur", ' ', "po" },
 { "list",   c_list,   "Affiche tous les elements de la pile",   ' ', "l"  },
 { "clear",  c_clear,  "Supprime tous les elements de la pile",  ' ', "cl" },
 { "help",   c_help,   "Affiche le resume des commandes",        '?', "h"  },
 { "quit",   c_quit,   "Quitte le programme",                    ' ', "q"  },
 { "obase",  c_obase,  "Depile un element comme base de sortie", ' ', "o"  },
 { "ibase",  c_ibase,  "Depile un element comme base d'entree",  ' ', "i"  },
 { "status", c_status, "Affiche les parametres",                 ' ', "st" }
};

main()
{
  char  buf[82];
  int   i_func;

  welcome();
  init();

  do
  {
    fprintf(stdout,"> ");
    fgets(buf,80,stdin);
    buf[strlen(buf)-1]='\0';

    /* Recherche de la fonction entree */
    i_func=search_func(buf);
    if (i_func!=-1)
      (*tabcom[i_func].func)();

    /* Si pas de fonction recherche un nombre */
    else if (read_number(buf))
      fprintf(stderr,"Commande inconnue ou mauvais format de nombre\n",buf);
  } while (!fin);

  uninit();
}

void welcome()
{
  fprintf(stdout,"Calculatrice a Pile\n");
  fprintf(stdout,"Christophe Boyanique (boyanique@imac.u-paris2.fr)\n");
  fprintf(stdout,"\n");
}

void init()
{
  fin=FALSE;
  pos=0;

  /* La valeur maximale traitee est LONG_MAX (definie par l'implementation)
     nichiff et nochiff calculent le nombre de chiffres necessaires pour
     afficher cette valeur dans les bases d'entree et de sortie. */
  ibase=10;
  nichiff=1+ceil( log(LONG_MAX) / log(ibase) );
  obase=10;
  nochiff=1+ceil( log(LONG_MAX) / log(obase) );

  /* Allocation dynamique de la pile par defaut a PILE elements */
  pile=NULL;
  nmb=PILE;
  pile=(long *)malloc((long)nmb*sizeof(long));
  if (!pile)
  {
    fprintf(stderr,"Pas de memoire pour la pile.\n");
    exit(EXIT_FAILURE);
  }
}

void uninit()
{
  if (pile)
    free((void *)pile); /* Libere la memoire de la pile */
}

/* Cette fonction recherche dans une chaine de caracteres une commande ou
   son raccourci et retourne le numero de la fonction associee ou -1 */
int search_func(char *s)
{
  int  i;

  /* sizeof(tabcom)/sizeof(TABCOM) represente le nombre d'elements du
     tableau tabcom (fonction+descriptif+raccourci) */
  for (i=0;i<sizeof(tabcom)/sizeof(TABCOM);i++)
  {
    /* Regarde si la chaine d'entree fait 1 caractere, si la touche rapide
       de la fonction est differente de ' 'la compare */
    if (strlen(s)==1 && tabcom[i].r1!=' ' && *s==tabcom[i].r1)
      return i;

    /* Compare la chaine d'entree avec le raccourci de la commande */
    if (!strncmp(s,tabcom[i].r2,strlen(tabcom[i].r2)))
      return i;
  }
  return -1;
}

int read_number(char *s)
{
  char  alpha[]="0123456789ABCDEF";
  int   trans[]={0,1,2,3,4,5,6,7,8,9,0xA,0xB,0xC,0xD,0xE,0xF};
  char  *p=s;
  long  nombre=0;
  int   signe=+1;
  int   valid;
  int   ok=FALSE;
  int   i;

  /* Si le premier caractere est un '-' on note le signe */
  if (*p=='-')
  {
    signe=-1;
    p++;
  }

  while (p && valid)
  {
    valid=FALSE;
    /* Compare le chiffre avec le debut du tableau de reference (elements
       dont la position est inferieure a la base */
    for (i=0;i<ibase;i++)
      if (toupper(*p)==alpha[i])
      {
        ok=TRUE;
        valid=TRUE;
        nombre=nombre*ibase+trans[i];
      }
    p++;
  }
  nombre*=signe;

  /* Si ok on empile le resultat */
  if (ok)
    stack_in(nombre);

  return !ok;
}

int stack_in(long nombre)
{
  /* Si la pile est pleine essaie de l'agrandir */
  if (pos==nmb)
    grow_stack();

  /* Si l'agrandissement echoue -> erreur */
  if (pos==nmb)
  {
    fprintf(stderr,"Pile pleine\n");
    return -1;
  }
  else
  {
    /* Empile le nombre et augmente le compteur */
    pile[pos]=nombre;
    pos++;
  }
  return 0;
}

void grow_stack()
{
  long  *new;

  /* Alloue de la memoire pour une nouvelle pile. Chaque reallocation
     augmente de PILE le nombre d'elements */
  new=(long *)malloc((long)(nmb+PILE)*sizeof(long));
  if (new)
  {
    /* Copie l'ancienne pile sur la nouvelle */
    memcpy((char *)new,(char *)pile,(long)(nmb)*sizeof(long));
    nmb+=PILE;
    /* Libere l'ancienne pile */
    free((void *)pile);
    /* Regle le pointeur sur la nouvelle pile */
    pile=new;
  }
}

int stack_read(long *nombre)
{
  /* Lit le dernier element de la pile sans le depiler */
  if (pos<1)
  {
    fprintf(stderr,"Pile vide\n");
    return 1;
  }
  else
    *nombre=pile[pos-1];
  return 0;
}

int stack_out(long *nombre)
{
  /* Lit le dernier element et le depile */
  if (pos<1)
  {
    fprintf(stderr,"Pile vide\n");
    return 1;
  }
  else
  {
    pos--;
    *nombre=pile[pos];
  }
  return 0;
}

void print_element(long n)
{
  char  alpha[]="0123456789ABCDEF";
  char  buf[10];
  char  fmt[10];
  int   signe=+1;

  /* Si le nombre est negatif on le rend positif et on retient son signe */
  if (n<0)
  {
    signe*=-1;
    n*=-1;
  }

  buf[0]='\0';
  do
  {
    strncat(buf,&alpha[n%obase],1);
    n/=obase;
  } while (n!=0);
  strinv(buf);
  if (signe<0)
    strcat(buf," -");
  else
    strcat(buf," +");

  sprintf(fmt,"%%%is",nochiff);
  fprintf(stdout,fmt,buf);
}

/* Inverse une chaine de caractere */
void strinv(char *s)
{
  char  *p=s;
  char  c;

  while (*p != '\0')
    ++p;
  --p;
  while (s<p)
  {
    c=*s;
    *s++=*p;
    *p--=c;
  }
}

int c_print()
{
  long  nombre;

  if (!stack_read(&nombre))
  {
    fprintf(stdout,"%3i: ",pos);
    print_element(nombre);
    fprintf(stdout,"\n");
    return 0;
  }
  return 1;
}

int c_dup()
{
  if (pos<1)
  {
    fprintf(stderr,"Pile vide\n");
    return 1;
  }
  else
    stack_in(pile[pos-1]);
  return 0;
}

int c_change()
{
  long  swap;

  if (pos<1)
    fprintf(stderr,"Pile vide\n");
  else if (pos<2)
    fprintf(stderr,"La Pile n'a qu'un seul element\n");
  else
  {
    swap=pile[pos-1];
    pile[pos-1]=pile[pos-2];
    pile[pos-2]=swap;
    return 0;
  }
  return 1;
}

int c_pop()
{
  long  nombre;

  if (!stack_out(&nombre))
  {
    fprintf(stdout,"%3i: ",pos+1);
    print_element(nombre);
    fprintf(stdout,"\n");
    return 0;
  }
  return 1;
}

int c_list()
{
  int  i;

  for (i=0;i<pos;i++)
  {
    fprintf(stdout,"%3i: ",i+1);
    print_element(pile[i]);
    fprintf(stdout,"\n");
  }
  return 0;
}

int c_clear()
{
  if (pos>0)
    pos=0;
  return 0;
}

int c_help()
{
  int  i;

  fprintf(stdout,"Commandes valides:\n");

  for (i=0;i<sizeof(tabcom)/sizeof(TABCOM);i++)
    fprintf(stdout,"   %-8s %-3s  %c  %-60s\n",
      tabcom[i].com,tabcom[i].r2,tabcom[i].r1,tabcom[i].help);
  return 0;
}

int c_quit()
{
  fin=TRUE;
  return 0;
}

int c_obase()
{
  long  res;

  if (!stack_out(&res))
  {
    if (res>1L && res<17L)
    {
      obase=(int)res;
      nochiff=1+ceil( log(LONG_MAX) / log(obase) );
      return 0;
    }
    else
      fprintf(stderr,"Base 2 a 16 uniquement\n");
  }
  return 1;
}

int c_ibase()
{
  long  res;

  if (!stack_out(&res))
  {
    if (res>1L && res<17L)
    {
      ibase=(int)res;
      nichiff=1+ceil( log(LONG_MAX) / log(ibase) );
      return 0;
    }
    else
      fprintf(stderr,"Base 2 a 16 uniquement\n");
  }
  return 1;
}

int c_status()
{
  fprintf(stdout,"Status:\n");
  fprintf(stdout,"   Taille de la Pile:  %i\n",nmb);
  fprintf(stdout,"   Base d'entree:      %i\n",ibase);
  fprintf(stdout,"   Base de sortie:     %i\n",obase);
  return 0;
}

int c_add(void)
{
  long  a,b;
  long  res;

  if (!stack_out(&b))
    if (!stack_out(&a))
    {
      res=a+b;
      if (!stack_in(res))
      {
        c_print();
        return 0;
      }
    }
  return 1;
}

int c_sub(void)
{
  long  a,b;
  long  res;

  if (!stack_out(&b))
    if (!stack_out(&a))
    {
      res=a-b;
      if (!stack_in(res))
      {
         c_print();
         return 0;
      }
    }
  return 1;
}

int c_mul(void)
{
  long  a,b;
  long  res;

  if (!stack_out(&b))
    if (!stack_out(&a))
    {
      res=a*b;
      if (!stack_in(res))
      {
        c_print();
        return 0;
      }
    }
  return 1;
}

int c_div(void)
{
  long  a,b;
  long  res;

  if (!stack_out(&b))
    if (!stack_out(&a))
    {
      if (b)
      {
        res=a/b;
        if (!stack_in(res))
        {
          c_print();
          return 0;
        }
      }
      else
        printf("Division par 0!\n");
    }
  return 1;
}

int c_mod(void)
{
  long  a,b;
  long  res;

  if (!stack_out(&b))
    if (!stack_out(&a))
    {
      if (b)
      {
        res=a%b;
        if (!stack_in(res))
        {
          c_print();
          return 0;
        }
      }
      else
        printf("Division par 0!\n");
    }
  return 1;
}

