#include "assembler.h"
#include <ctype.h>

/* Détermine si une ligne contient une instruction (toutes sauf les lignes
   vides et les commentaires) */
bool is_useful_line(const char *line)
{
    while(isspace(*line))
        line++;
    return (*line != 0 && *line != '#');
}

/* Liste des registres RISC-V par nom */
const char *register_names[32] = {
    "zero", "ra", "sp",  "gp",  "tp", "t0", "t1", "t2",
    "s0",   "s1", "a0",  "a1",  "a2", "a3", "a4", "a5",
    "a6",   "a7", "s2",  "s3",  "s4", "s5", "s6", "s7",
    "s8",   "s9", "s10", "s11", "t3", "t4", "t5", "t6",
};

/* Analyse le texte d'un argument (et fait avancer le pointeur)
  - line_ptr: Pointeur vers le texte, passé par adresse
  - argument: Pointeur vers la valeur de l'argument (remplie si un argument
    est trouvé)
  Renvoie true si un argument est trouvé, false sinon.

  Exemples:
    "x3xyz"    -> true   (line_ptr -> "xyz", argument -> 3)
    " , +42,5" -> true   (line_ptr -> ",5",  argument -> 42)
    "gpx"      -> true   (line_ptr -> "x",   argument -> 3)
    "oops"     -> false  (line_ptr -> "oops") */
bool parse_argument(const char **line_ptr, int *argument)
{
    const char *line = *line_ptr;
    while(isspace(*line) || *line == ',' || *line == '(' || *line == ')')
        line++;

    /* Registres explicites: x0, x1, ... x31. On saute le 'x' et on tombe
       dans le cas des entiers explicites ci-dessous */
    if(line[0] == 'x' && isdigit(line[1]))
        line++;

    /* Entiers explicites: 12, +380, -23... */
    if(isdigit(line[0]) ||
       ((line[0] == '+' || line[0] == '-') && isdigit(line[1]))) {
        bool negative = (line[0] == '-');
        line += !isdigit(line[0]);

        *argument = 0;
        while(isdigit(*line)) {
            *argument = (*argument * 10) + (*line - '0');
            line++;
        }

        *argument = negative ? -*argument : *argument;
        *line_ptr = line;
        return true;
    }

    /* "Jolis" noms de registres: zero, sp, a0, a1, ... */
    for(int i = 0; i < 32; i++) {
        int len = strlen(register_names[i]);
        /* Il faut que la chaîne commence par le nom du registre et soit suivie
           d'un caractère non-alphanumérique. Par exemple:
             "zero,"  -> OK pour "zero" (suivi par ",")
             "s11)"   -> OK pour "s11"  (suivi par ")")
             "s11)"   -> NON pour "s1"  (suivi par "1") */
        if(!strncmp(line, register_names[i], len) && !isalnum(line[len])) {
            *argument = i;
            *line_ptr = line + len;
            return true;
        }
    }

    return false;
}

/* Analyse le texte de l'instruction et isole son nom et ses arguments */
struct instruction parse_instruction(const char *line)
{
    struct instruction insn = { 0 };

    /* Lecture de l'opcode et normalisation en minuscules */
    while(isspace(*line))
        line++;
    for(int i = 0; isalpha(*line) && i < 15; i++, line++)
        insn.name[i] = tolower(*line);

    printf(":: '%s'", insn.name);

    /* Lecture des arguments */
    int i = 0;
    while(i < 3 && parse_argument(&line, &insn.args[i])) {
        printf(" %d", insn.args[i]);
        i++;
    }
    printf("\n");

    return insn;
}

void assemble(FILE *fp_in, FILE *fp_out)
{
    char *line = NULL;
    size_t n = 0;
    int insn_number = 1;

    /* Lecture du fichier ligne-par-ligne */
    while(getline(&line, &n, fp_in) != -1) {
        if(!is_useful_line(line))
            continue;

        printf("[%d] %s", insn_number, line);
        struct instruction insn = parse_instruction(line);
        uint32_t code = encode(&insn);
        printf(":: %08x\n", code);
        fprintf(fp_out, "%08x\n", code);
        insn_number++;
    }

    free(line);
}
