#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* ================================================================
 *  VM — Machine Virtuelle à pile (mini-Forth simplifié)
 * ================================================================
 *  Registres :
 *    ip  — instruction pointer (compteur ordinal)
 *    sp  — stack pointer       (sommet de pile)
 *  Mémoire :
 *    mem[]   — mémoire de données (256 mots)
 *    stack[] — pile de travail    (128 mots)
 * ================================================================
 *  Jeu d'instructions (postfixé, comme en Forth) :
 *    PUSH v  — empiler la valeur v
 *    DUP     — a → a a
 *    DROP    — a →
 *    SWAP    — a b → b a
 *    OVER    — a b → a b a
 *    ADD     — a b → (b + a)
 *    SUB     — a b → (b - a)
 *    MUL     — a b → (b * a)
 *    NEG     — a → (-a)
 *    INC     — a → (a + 1)
 *    DEC     — a → (a - 1)
 *    EQ      — a b → (b == a)   [rend 0 ou 1]
 *    NE      — a b → (b != a)
 *    GT      — a b → (b >  a)
 *    LT      — a b → (b <  a)
 *    JMP a   — saut inconditionnel vers l'adresse a
 *    JZ  a   — sauter si sommet = 0
 *    JNZ a   — sauter si sommet ≠ 0
 *    LOAD a  — empiler mem[a]
 *    STORE a — depiler → mem[a]
 *    PRINT   — depiler et afficher le nombre
 *    PRTS a  — afficher la chaîne en mémoire à l'adresse a
 *    HALT    — arrêter la machine
 * ================================================================ */

/* ---------------------------------------------------------------
 *  Opcodes
 * --------------------------------------------------------------- */
enum {
    PUSH, DUP, DROP, SWAP, OVER,
    ADD, SUB, MUL, NEG, INC, DEC,
    EQ, NE, GT, LT,
    JMP, JZ, JNZ,
    LOAD, STORE,
    PRINT, PRTS,
    HALT
};

/* ---------------------------------------------------------------
 *  Ressources de la VM
 * --------------------------------------------------------------- */
#define MEM_SIZE   256
#define STACK_SIZE 128

int ip;                    /* instruction pointer                  */
int sp;                    /* stack pointer                        */
int mem[MEM_SIZE];         /* mémoire de données                   */
int stack[STACK_SIZE];     /* pile de travail                      */
int pas_a_pas;             /* mode pas à pas (déverminage)         */

/* ---------------------------------------------------------------
 *  Primitives de pile
 * --------------------------------------------------------------- */
void pousser(int v) {
    if (sp >= STACK_SIZE) {
        fprintf(stderr, "DEBORDEMENT DE PILE (sp=%d)\n", sp);
        exit(1);
    }
    stack[sp++] = v;
}

int depiler(void) {
    if (sp <= 0) {
        fprintf(stderr, "PILE VIDE (ip=%d)\n", ip);
        exit(1);
    }
    return stack[--sp];
}

int sommet(void) {
    if (sp <= 0) {
        fprintf(stderr, "PILE VIDE (ip=%d)\n", ip);
        exit(1);
    }
    return stack[sp - 1];
}

/* ---------------------------------------------------------------
 *  Afficher la pile (pour déverminage)
 * --------------------------------------------------------------- */
void afficher_pile(void) {
    printf("  pile[");
    for (int i = 0; i < sp; i++)
        printf("%s%d", (i == 0) ? "" : " ", stack[i]);
    printf("]");
}

/* ---------------------------------------------------------------
 *  Afficher une instruction (pour déverminage)
 * --------------------------------------------------------------- */
void afficher_instr(int code[], int pos) {
    static const char *nom[] = {
        "PUSH", "DUP", "DROP", "SWAP", "OVER",
        "ADD", "SUB", "MUL", "NEG", "INC", "DEC",
        "EQ", "NE", "GT", "LT",
        "JMP", "JZ", "JNZ",
        "LOAD", "STORE",
        "PRINT", "PRTS", "HALT"
    };
    int op = code[pos];
    if (op == PUSH || op == JMP || op == JZ || op == JNZ ||
        op == LOAD || op == STORE || op == PRTS)
        printf("  %4d:  %-5s %d\n", pos, nom[op], code[pos + 1]);
    else
        printf("  %4d:  %s\n", pos, nom[op]);
}

/* ---------------------------------------------------------------
 *  Exécuter un programme
 * --------------------------------------------------------------- */
void executer(int code[], int taille) {
    ip = 0;
    /* ne pas réinitialiser sp — la valeur initiale est déjà sur la pile */

    while (ip < taille) {
        if (pas_a_pas) {
            afficher_instr(code, ip);
            afficher_pile();
            printf("\n");
            getchar();  /* attendre une touche */
        }

        int instr = code[ip++];

        switch (instr) {

        /* --- Poussée --- */
        case PUSH: pousser(code[ip++]); break;

        /* --- Duplication / suppression / permutation --- */
        case DUP:  { int v = depiler(); pousser(v); pousser(v); break; }
        case DROP: { depiler(); break; }
        case SWAP: { int a = depiler(); int b = depiler(); pousser(a); pousser(b); break; }
        case OVER: { int a = depiler(); int b = depiler(); pousser(b); pousser(a); pousser(b); break; }

        /* --- Arithmétique --- */
        case ADD: { int a = depiler(); int b = depiler(); pousser(b + a); break; }
        case SUB: { int a = depiler(); int b = depiler(); pousser(b - a); break; }
        case MUL: { int a = depiler(); int b = depiler(); pousser(b * a); break; }
        case NEG: { pousser(-depiler()); break; }
        case INC: { pousser(depiler() + 1); break; }
        case DEC: { pousser(depiler() - 1); break; }

        /* --- Comparaisons --- */
        case EQ: { int a = depiler(); int b = depiler(); pousser(b == a); break; }
        case NE: { int a = depiler(); int b = depiler(); pousser(b != a); break; }
        case GT: { int a = depiler(); int b = depiler(); pousser(b >  a); break; }
        case LT: { int a = depiler(); int b = depiler(); pousser(b <  a); break; }

        /* --- Sauts --- */
        case JMP:  { ip = code[ip]; break; }
        case JZ:   { int a = code[ip++]; if (depiler() == 0) ip = a; break; }
        case JNZ:  { int a = code[ip++]; if (depiler() != 0) ip = a; break; }

        /* --- Accès mémoire --- */
        case LOAD:  { int a = code[ip++]; pousser(mem[a]); break; }
        case STORE: { int a = code[ip++]; mem[a] = depiler(); break; }

        /* --- Entrées-sorties --- */
        case PRINT: printf("%d", depiler()); break;
        case PRTS: {
            int a = code[ip++];
            while (mem[a] != 0)
                printf("%c", mem[a++]);
            break;
        }

        /* --- Arrêt --- */
        case HALT: ip = taille; break;

        default:
            fprintf(stderr, "ERREUR : opcode %d inconnu (ip=%d)\n", instr, ip - 1);
            exit(1);
        }
    }
}

/* ---------------------------------------------------------------
 *  Désassembleur — afficher le code VM en texte lisible
 * --------------------------------------------------------------- */
static const char *nom_opcode(int op) {
    static const char *noms[] = {
        "PUSH", "DUP", "DROP", "SWAP", "OVER",
        "ADD", "SUB", "MUL", "NEG", "INC", "DEC",
        "EQ", "NE", "GT", "LT",
        "JMP", "JZ", "JNZ",
        "LOAD", "STORE",
        "PRINT", "PRTS", "HALT"
    };
    if (op >= 0 && op < HALT + 1)
        return noms[op];
    return "???";
}

void desassembler(int code[], int taille) {
    int i = 0;
    while (i < taille) {
        printf("%4d:  ", i);
        int op = code[i];
        if (op == PUSH || op == JMP || op == JZ  || op == JNZ ||
            op == LOAD || op == STORE || op == PRTS) {
            printf("%-5s %d\n", nom_opcode(op), code[i + 1]);
            i += 2;
        } else {
            printf("%s\n", nom_opcode(op));
            i++;
        }
    }
}

/* ---------------------------------------------------------------
 *  Programme : Factorielle
 * ---------------------------------------------------------------
 *  On définit le programme directement sous forme de tableau
 *  d'entiers.  Les adresses de saut sont données par des
 *  constantes symboliques pour rester lisibles.
 *
 *  Algorithme (assembleur VM) :
 *
 *    STORE N       — mémoriser n
 *    PUSH 1        — res = 1
 *    PUSH 2        — i = 2
 *  LOOP:
 *    DUP           —  i i
 *    LOAD N        —  i i n
 *    GT            —  i (i>n)
 *    JNZ END       —  si i>n, fin
 *    SWAP          —  i res
 *    OVER          —  i res i
 *    MUL           —  i res*i
 *    SWAP          —  res*i i
 *    INC           —  res*i i+1
 *    JMP LOOP
 *  END:
 *    DROP          —  res
 *    PRINT         —  afficher res
 *    HALT
 * --------------------------------------------------------------- */

/* adresses mémoires réservées */
enum { N = 0, MSG = 200 };

/* étiquettes de saut */
enum { LOOP = 6, END = 19 };

int programme[] = {
    /* 0 */  STORE, N,
    /* 2 */  PUSH, 1,
    /* 4 */  PUSH, 2,
    /* 6 */  DUP,
    /* 7 */  LOAD, N,
    /* 9 */  GT,
    /*10 */  JNZ, END,
    /*12 */  SWAP,
    /*13 */  OVER,
    /*14 */  MUL,
    /*15 */  SWAP,
    /*16 */  INC,
    /*17 */  JMP, LOOP,
    /*19 */  DROP,
    /*20 */  PRTS, MSG,
    /*22 */  PRINT,
    /*23 */  HALT,
};

int taille_prog = sizeof(programme) / sizeof(programme[0]);

/* ---------------------------------------------------------------
 *  Initialiser la mémoire avec les chaînes et les zéros
 * --------------------------------------------------------------- */
void initialiser_memoire(void) {
    for (int i = 0; i < MEM_SIZE; i++)
        mem[i] = 0;
    mem[MSG]     = ' ';
    mem[MSG + 1] = '=';
    mem[MSG + 2] = ' ';
    mem[MSG + 3] = 0;
}

/* ---------------------------------------------------------------
 *  Tests
 * --------------------------------------------------------------- */
int main(int argc, char *argv[]) {
    if (argc > 1 && strcmp(argv[1], "--pas-a-pas") == 0)
        pas_a_pas = 1;

    initialiser_memoire();

    printf("=== Désassemblage du programme ===\n");
    desassembler(programme, taille_prog);

    printf("\n=== Factorielle 0 à 12 ===\n");
    long long attendus[] = {1,1,2,6,24,120,720,5040,40320,362880,3628800,39916800,479001600};
    int ok = 0, echec = 0;
    for (int n = 0; n <= 12; n++) {
        sp = 0;
        pousser(n);
        executer(programme, taille_prog);
        int res = stack[0];
        if (res == attendus[n]) {
            printf("  OK   ! %d = %d\n", n, res);
            ok++;
        } else {
            printf("  FAIL ! %d = %d (attendu %lld)\n", n, res, attendus[n]);
            echec++;
        }
    }
    printf("\n  %d passes, %d echoues\n", ok, echec);

    return echec > 0 ? 1 : 0;
}
