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

#include"cpu.h"


struct decode decode_list[NUM_INST] = {{"INVALID",INVALID},
                                       {"HALT",HALT},
                                       {"READ",READ},
                                       {"WRITE",WRITE},
                                       {"LOAD",LOAD},
                                       {"STORE",STORE},
                                       {"ADD",ADD},
                                       {"SUB",SUB},
                                       {"MUL",MUL},
                                       {"DIV",DIV},
                                       {"MOD",MOD},
                                       {"BRANCH",BRANCH},
                                       {"BRANCHNEG",BRANCHNEG},
                                       {"BRANCHZERO",BRANCHZERO},
                                      };



void init(cpu_t *cpu){
    cpu->pc = 0;
    cpu->acc = 0;
    clearMem(&(cpu->mem));
}

word_t getOpcodeCPU(const cpu_t cpu){
    return getWord(cpu.mem,cpu.pc);
}

void splitOpcode(word_t opcode, word_t *operation, word_t *operand){

    *operation = opcode >> 16;
    *operand = opcode & 0x00FF;

}

word_t buildOpcode(word_t operation, word_t operand){

    return operation<<16 | operand;

}

word_t decodeOperation(struct decode decode_list[NUM_INST], char *operation_str){
int i = 0;

    for (i = 0;i<NUM_INST;i++){
        if (strcmp(decode_list[i].inst_name,operation_str) == 0){
            return decode_list[i].opcode;
        }
    }

    return INVALID;

}

word_t decodeOperand(char *operand_str){
int i = 0, j=0;
int digit;
word_t operand = 0;
int pow = 1;
int base = 10;

    for (i = 0;operand_str[i]!='\0';i++){
            if (operand_str[i]=='x'){
                j = 2;
                base = 16;
                break;
            }
    }

    for (i = j;operand_str[i]!='\0';i++){
        digit = (int)(operand_str[i]-48);
        operand = operand*pow+digit;
        pow *= base;
    }

    return operand;

}

void run(cpu_t *cpu){
    word_t opcode;
    word_t operation;
    word_t operand;
    word_t temp;

    int running = 1;

    while (running){

        opcode = getOpcodeCPU(*cpu);

        splitOpcode(opcode, &operation, &operand);

        switch(operation){
            case READ:
                scanf("%d",&(cpu->acc));
            break;

            case WRITE:
                printf("%d\n",cpu->acc);
            break;

            case LOAD:
                cpu->acc = getWord(cpu->mem,operand);
            break;

            case STORE:
                setWord(&(cpu->mem),operand,cpu->acc);
            break;

            case ADD:
                temp = getWord(cpu->mem,operand);
                cpu->acc += temp;
            break;

            case SUB:
                temp = getWord(cpu->mem,operand);
                cpu->acc -= temp;
            break;

            case MUL:
                temp = getWord(cpu->mem,operand);
                cpu->acc *= temp;
            break;

            case DIV:
                temp = getWord(cpu->mem,operand);
                cpu->acc /= temp;
            break;

            case MOD:
                temp = getWord(cpu->mem,operand);
                cpu->acc %= temp;
            break;

            case BRANCH:
                cpu->pc = getWord(cpu->mem,operand);
            break;

            case BRANCHNEG:
                if (cpu->acc<0)
                    cpu->pc = getWord(cpu->mem,operand);
            break;

            case BRANCHZERO:
                if (cpu->acc == 0)
                    cpu->pc = getWord(cpu->mem,operand);
            break;

            case HALT:
                running = 0;
            break;

            default:
            break;
        }

        if (opcode!= BRANCH && opcode!= BRANCHNEG && opcode!=BRANCHZERO && opcode!= HALT)
            cpu->pc++;

    }
}

int consumeSpaces(char *str, int i){

    // Descartar espaços em branco
    // for(i=0;line[i]==' ';i++);
    while (str[i]==' '){
        i++;
    }

    return i;
}

int getSubstring(char *src_str, int i, char *dest_str, char terminator){
int j = 0;

    while (src_str[i]!=terminator && src_str[i]!='\0' && src_str[i]!='\n'){
        dest_str[j] = src_str[i];
        i++;
        j++;
    }
    dest_str[j] = '\0';

    return i;
}

void splitCommand(char * line,int * argc, char argv[][80]){
    int i = 0;

    *argc = 0;

    while (line[i]!='\0'){
        i = consumeSpaces(line,i);
        i = getSubstring(line,i,argv[*argc],' ');
        (*argc)++;
    }

}

void exec(cpu_t *cpu){
char line[800];
int argc;
char argv[10][80];
char * command;

    printf("Tiny CPU 2016 - v1.0\n");

    while(1){
        printf(">");
        gets(line);
        splitCommand(line,&argc,argv);
        command = argv[0];

        if (strcmp(command,"dump")==0){
            dumpCPU(*cpu);
        }
        else if (strcmp(command,"exit")==0){
            exitCPU(*cpu);
            printf("Shutting down\n");
            break;
        }
       else if (strcmp(command,"asm")==0){
            assembleProgram(argv[1],argv[2]);
        }
        else if (strcmp(command,"run")==0){
            loadProgram(cpu,argv[1]);
            run(cpu);
            printf("\n");
        }
        else if (strcmp(command,"help")==0){
            printf("Tiny cpu commands:\n");
            printf("dump - print dump of cpu including memory content\n");
            printf("exit - terminate CPU execution\n");
            printf("asm source_file obj_file - convert mneumonic instructions in byte codes \n");
            printf("run obj_file - loads byte code program into cpu memory\n");
            printf("help - prints tiny commands");
            printf("\n");
        }
        else{
            printf("Invalid command\n");
        }
    }
}


void dumpCPU(const cpu_t cpu){
    printf("CPU:\n");
    printf("pc = %08x\n",cpu.pc);
    printf("acc = %08x\n",cpu.acc);
    dumpMem(cpu.mem);

}


void parseInstructionLine(char *line, char *operation_str, char *operand_str ){

int pos;


    pos = consumeSpaces(line,0);

    pos = getSubstring(line,pos,operation_str,' ');

    pos = consumeSpaces(line,pos);

    pos = getSubstring(line,pos,operand_str,'\0');

}

void exitCPU(const cpu_t cpu){
    exit(0);
};


void assembleProgram(char *src_file_name, char * obj_file_name){
    FILE * sf, *of;

    char line[80];

    char operation_str[10];
    char operand_str[10];

    word_t operand;
    word_t operation;
    word_t opcode;

    sf = fopen(src_file_name,"r");
    of = fopen(obj_file_name,"wb");

    if (sf != NULL){


        while (fgets(line,80,sf)!=NULL){

            parseInstructionLine(line, operation_str, operand_str);

            operation = decodeOperation(decode_list,operation_str);

            if (operation != INVALID){
                operand = decodeOperand(operand_str);
            }
            else{
                operation = 0x0;
                operand = decodeOperand(operation_str);

            }

            opcode = buildOpcode(operation,operand);

            fwrite(&opcode,sizeof(word_t),1,of);
        }
    }

    printf("Compile succeded...\n");

    fclose(sf);
    fclose(of);
}


void loadProgram(cpu_t *cpu,char * file_name){
    FILE * of;
    word_t address;
    word_t opcode;
    int  num_bytes;


    of = fopen(file_name,"rb");
    address = 0;
    if (of !=NULL){

        do{
            num_bytes = fread(&opcode,sizeof(word_t),1,of);


            if (num_bytes>0){
                setWord(&(cpu->mem),address,opcode);
                address++;
            }

        }while (num_bytes!=0);
    }

    printf("Program loaded in CPU memory\n");
}
