/*
 * Taki sobie smieszny pseudo emulatorek asma rodziny x86
 * Jakub Wartak ( vnull@pcnet.com.pl ) 2005
 * 
 * TODO: 
 * + uwzglednienie roznych rejestrow ( 8,16,32 bit i oper na nich )
 * + modularyzacja logiczna na kilka *.c i *.h ( rej, cmd, parsing )
 * + poprawne wczytywanie w mov liczb binarnych (bez podawania zer na poczatku)
 *
 * gcc -O0 -g -ggdb -Wall sym.c -lm
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// dla pow()
#include <math.h>


/* Tak zeby cmd_jmp() i pochodne mogly manipulowac flow-em programu */
static FILE *input;

static int work = 1;

enum {
    M_SCRIPT = 0,
    M_CLI 
};

static int workmode = M_CLI;

/* wew. rejestr stanu, dla test/j* ( skoki warunkowe ) */
static int reg_state = 0;

/* wartosci jakie moze przyjmowac rejestr skokow */
enum {
    ST_UNINIT = 0,
    ST_EQUAL,
    ST_LESS,
    ST_MORE
};

/* rejestry */
struct s_reg {
    char *name;
    /* ile rejestr ma miec bitow */
    int bits;
    int *data;
} regs[] = {
    { "al", 8, NULL },
    { "bl", 8, NULL },
    { "cl", 8, NULL },
    { "dl", 8, NULL },
    { NULL, 0, NULL },
};

#define ALLOW_ANY_ARGS -1

struct s_cmd {
    char *name;
    char *help;
    /* minimalna wymagana ilosc argumentow dla instrukcji */
    int min_args;
    void (*f)(int ac, char **av);
};

struct s_cmd cmds[];

void init_regs()
{
    struct s_reg *r = &regs[0];
    
    while(r != NULL && r->name != NULL) {
        if(r->data == NULL) {
            r->data = malloc(sizeof(int) * r->bits);
            if(r->data == NULL) {
                printf("oom %s\n", r->name);
                exit(0);
            }
            memset(r->data, 0, sizeof(int) * r->bits);
        }
        r++;
    }
}

void destroy_regs()
{
    struct s_reg *r = &regs[0];

    while(r != NULL && r->name != NULL) {
        if(r->data != NULL) {
            free(r->data);
            r->data = NULL;
        }
        r++;
    }
}

struct s_reg *lookup_reg(char *n)
{
    struct s_reg *r = &regs[0];
    
    // rejestry=> %nazwa_rej
    if(n == NULL || (*n++ != '%')) {
        return NULL;
    }

    while(r != NULL && r->name != NULL) {
        if(!strcmp(n, r->name)) {
            return r;
        }

        r++;
    }
    return NULL;
}

#define GET_REG(X,num) \
    X = lookup_reg(av[num]); \
    if(X == NULL) { \
        printf("%% Zly rejestr ( %s )\n", av[num]); \
        if(workmode == M_SCRIPT) \
            exit(1); \
        return; \
    } 

int conv_bin2dec(int t[], int len)
{
    int i, w = 0;
    for(i = len - 1; i >= 0; i--) {
        int pos = len - i - 1;
        w += t[i] * pow(2, pos);
    }
    return w;
}

int conv_dev2bin(const int dec, int *ret, int ret_items)
{
    int i = dec;

    while(i >= 0) {
        i %= 2;
    }
    
    return 0;
}

/* Pokazuje wartosci rejestrow i rej. wew. */
void cmd_show(int ac, char **av)
{
    if(!ac) {
        // wypisujemy wszystko
        struct s_reg *r = &regs[0];
        while(r != NULL && r->name != NULL) {
            int i;
            printf("%%%s=%d=", r->name, conv_bin2dec(r->data, r->bits));
            for(i=0; i<r->bits; i++) {
                printf("%d", r->data[i]);
            }
            printf("\n");
            r++;
        }
    }
    printf("Internal state register is 0x%X\n", reg_state);
    return;
}

void cmd_and(int ac, char **av) 
{
    struct s_reg *co1, *co2;
    int i;

    GET_REG(co1, 0);
    GET_REG(co2, 1);

    for(i = 0; i < co1->bits && i < co2->bits; i++) {
        int *b1=&co1->data[i], *b2=&co2->data[i];

        if(*b1 == 1 && *b2 == 1) {
            *b1 = 1;
        } else {
            *b1 = 0;
        }
    }

}

void cmd_or(int ac, char **av)
{
    struct s_reg *co1, *co2;
    int i;
    
    GET_REG(co1, 0);
    GET_REG(co2, 1); 

    for(i = 0; i < co1->bits && i < co2->bits; i++) {
        int *b1=&co1->data[i], *b2=&co2->data[i];

        if(*b1 == 1 || *b2 == 1) {
            *b1 = 1;
        } else {
            *b1 = 0;
        }
    }
}


/* Wyjatkowo dla "mov" 2-gi rejestr moze byc liczba */
void cmd_mov(int ac, char **av)
{
    int i;
    struct s_reg *r1, *r2;

    GET_REG(r1, 0);
    
    if(av[1] != NULL && *av[1] != '%') {
        char *d = av[1], *dptr = d;
        // hmm, czyzby liczba ( binarnie tylko ) ?
        if(*dptr++ == '!' && *dptr >= '0' && *dptr <= '1') {
            r2 = malloc(sizeof(struct s_reg)); 
            if(r2 == NULL) {
                printf("whoops! oom ;( ( r2 in cmd_mov )\n");
                exit(-1);
            }
            
            // FIXME?
            r2->bits = 8;
            r2->data = malloc(sizeof(int) * r2->bits);
            if(r2->data == NULL) {
                printf("whoops #2 r2->Data in cmd_mov\n");
                exit(-1);
            }
            memset(r2->data, 0, sizeof(int) * r2->bits);

            {
                int len = strlen(dptr), i;
                for(i = 0; i < len && i < r2->bits; i++) {
                    r2->data[i] = dptr[i] - '0';
                    //printf("debug: i=%d, dptr[%d]=%d\n", i, i, r2->data[i]);
                }

                /* Kopiuje r2 do r1, FIXME: memcpy byloby lepsze */
                for(i = r2->bits; i >= 0; i--) {
                    r1->data[i] = r2->data[i];
                }
            }

            free(r2->data);
            free(r2);
            
            return;
            
        } else
            GET_REG(r2, 1);
    } else {
        GET_REG(r2, 1);
    }

    for(i = 0; i < r1->bits && i < r2->bits; i++) {
        r1->data[i] = r2->data[i];
    }
}   

void cmd_test(int ac, char **av)
{
    int i;
    struct s_reg *r1, *r2;
    GET_REG(r1, 0);
    GET_REG(r2, 1);

    for (i=0; i < r1->bits && i < r2->bits; i++) {
        int *d1=&r1->data[i], *d2=&r2->data[i];
        if(*d1 < *d2) {
            // r1 jest mniejszy od r2
            reg_state = ST_LESS;
            return;
        } else if(*d1 > *d2) {
            reg_state = ST_MORE;
            return;
        }
        
    }
    reg_state = ST_EQUAL;
}

void generic_jmp(char *l)
{
    char s[16];

    if(workmode == M_CLI) {
        printf("%% Skoki nie dzialaja w trybie interaktywnym\n");
        return;
    }
    strcpy(s, "@");
    strncat(s, l, sizeof(s)-3);
    strcat(s, ":");

    //printf("Searching for %s\n", s);

    /* Przewijamy plik/skrypt wej. i szukamy naszego upragnionego labelka */
    rewind(input);
    while(!feof(input)) {
        char in[80];
        memset(in, 0, sizeof(in));

        fgets(in, sizeof(in)-1, input);
        // ostatni znak \13
        in[strlen(in)-1] = '\0';
        if(!strcasecmp(s, in)) {
            //printf("%% Found %s\n", s);
            return;
        }
    }
    printf("%% Unknown label %s\n", s);
    exit(2);
}

void cmd_jmp(int ac, char **av)
{
    generic_jmp(av[0]);
}

void cmd_je(int ac, char **av)
{
    if(reg_state == ST_EQUAL) {
        generic_jmp(av[0]);
    }
}

void cmd_jne(int ac, char **av)
{
    if(reg_state != ST_EQUAL) {
        generic_jmp(av[0]);
    }
}

void cmd_inc(int ac, char **av)
{
    int i;
    struct s_reg *r;
    GET_REG(r, 0);
    
    for(i = r->bits - 1; i >= 0; i--) {
        if(r->data[i] == 0) {
            r->data[i] = 1;
            break;
        } else {
            // == 1
            r->data[i] = 0;
        }
    }
}

void cmd_add(int ac, char **av)
{
   // struct s_reg *r1 = lookup_reg("%al");
    //struct s_reg *r2 = lookup_reg("%bl");
    
}

void cmd_help(int ac, char **av)
{
    struct s_cmd *l = &cmds[0];

    printf("\n\n");
    while(l != NULL && l->name != NULL) {
        printf("%10s%50s\n", l->name, l->help);
        l++;
    } 
}

void cmd_print(int ac, char **av)
{
    int i;
    for(i = 0; i < ac; i++) {
        printf("%s ", av[i]);
    }
    printf("\n");
}

void cmd_quit(int ac, char **av)
{
    work = 0;
}

void cmd_nop(int ac, char **av)
{
}

struct s_cmd cmds[] = {
    { "",    "no-operation", 0, cmd_nop },
    { ";",   "komentarz", 0, cmd_nop },
    { "#",   "komentarz", 0, cmd_nop },
    { "nop", "nop", 0, cmd_nop },
    { "and", "and r1 r2 ==> r1=r1 & r2", 2, cmd_and },
    { "or",  "or r1 r2 ==> r1=r1 | r2", 2, cmd_or },
    { "mov", "mov r1 r2 ==> r1=r2", 2, cmd_mov },
    { "test", "test r1 r2 ==> st_register( >, <, == )", 2, cmd_test },
    { "jmp", "jmp label ==> skacze do label", 1, cmd_jmp },
    { "je", "je label ==> skacze do label gdy ST_EQUAL", 1, cmd_je },
    { "jne", "jne label ==> skacze do label gdy !ST_EQUAL", 1, cmd_jne },
    { "inc", "inc r1 ==> r1=r1+1", 1, cmd_inc },
    { "add", "add r1 r2 ==> r1=r1+r2", 2, cmd_add },
    { "show", "Pokazuje rejestr(y)", ALLOW_ANY_ARGS, cmd_show },
    { "print", "print bla bla bla", ALLOW_ANY_ARGS, cmd_print },
    { "help", "Help", 0, cmd_help },
    { "?", "Help", 0, cmd_help },
    { "quit" , "Wychodzi z programu", 0, cmd_quit },
    { "exit" , "Wychodzi z programu", 0, cmd_quit },
    { NULL, NULL, 0, NULL },
};

struct s_cmd *lookup_cmd(const char *s)
{
    struct s_cmd *l = &cmds[0];

    while(l != NULL && l->name != NULL) {
        if(!strcmp(s, l->name)) {
            return l;
        }

        l++; 
    }
    return NULL;
}

/* Przetwarzanie inputu od usera , wykonwywanie komendy */
void parse_cmd(char *buf)
{
    char *s, *ptr;
    char *av[80];
    int x, ac = 0;
    struct s_cmd *c;

    s = ptr = buf;

    while(ptr != NULL && *ptr != '\n' && *ptr != '\r' && *ptr != '\0') {
        if(*ptr == ' ') {
            *ptr = '\0';
            av[ac++] = strdup(s);
            //printf("dodano<%s>\n", s);
            s = ptr + 1;
        }
        ptr++;
    }

    // ostatni elem
    if(s != NULL) {
        av[ac++] = strdup(s);
    }

    av[ac] = NULL;

#define FOR_EACH(blah) for(x = 0; av[x] != NULL; x++) { \
        blah; \
    }

    //FOR_EACH(printf("x=%d %s\n", x, av[x]))

    if(*av[0] == '@') {
        /* Obsluga Labeli -- czyli nic nie robimy :P */

    } else {
        c = lookup_cmd(av[0]);
        if(c == NULL) {
            printf("%% Nie ma takiej komendy <%s>\n", av[0]);
            if(workmode == M_SCRIPT) 
                exit(1);
            return;
        }

        if( c->min_args != ALLOW_ANY_ARGS && c->min_args < ac-1 ) {
            printf("%% %s: Za malo argumentow\n", av[0]);
            if(workmode == M_SCRIPT)
                exit(1);
            return;
        }

        c->f(ac-1, av+1);

        if(workmode == M_CLI)
            printf("\n");
    }

    FOR_EACH(free(av[x]))
}

int main(int ac, char **av)
{
    printf("\n\n31337-x86-Little-Endian-wielki-ROTFL by vnull, b00ting...\n\n");
    setbuf(stdout, NULL);
    input = stdin;
    
    if(ac >= 2) {
        char *fname = av[1];
        printf("Opening %s\n", fname);
        input = fopen(fname, "r");
        if(input == NULL) {
            perror("fopen");
            exit(-1);
        }
        workmode = M_SCRIPT;
    }

    init_regs();

    while(work && !feof(input)) {
        char in[80];

        memset(in, 0, sizeof(in));

        if(workmode == M_CLI) {
            printf("mega-super-hiper-x86> ");
        }

        fgets(in, sizeof(in)-1, input);
        // ostatni znak \13
        in[strlen(in)-1] = '\0';
        parse_cmd(in);
    }

    destroy_regs();
    fclose(input);
    
    return 0;
}

