//define headers.
#include <stdio.h>
#include <stdlib.h>

//declare variable for the unsigned.
unsigned char data_mem[10000];
//all the variables needed fro the signed.
signed int _memory_register[16],_opcode_1,_opcode_2,_output;
long control = 0;
//unsigned char variable declaration.
unsigned char statement_ins[4];
signed long _off_set_mem,address;
//check variable for the method execute.
int check_if_execute;
//generate method for the file source data generation into the variable.
void _generate(FILE *src){fread(data_mem, 10000, 1, src);}

//assing value to the c
int _method_ex_sign(int c, int n)
{c = c | (0xffffffff << n);return c;}


void _method_fetch() {
    statement_ins[0] = data_mem[control];
    //switch case for the statement instruction to move along
    switch(statement_ins[0] >> 4)
    {
        //for the first case if it falls under less than 9 and equals to 0
        case 0 ... 9:
        case 14:
        case 15:
        //do the following if follows
            statement_ins[1] = data_mem[control + 1];
            break;
        case 10 ... 13:
        //do the following if follows
            statement_ins[1] = data_mem[control + 1];
            statement_ins[2] = data_mem[control + 2];
            statement_ins[3] = data_mem[control + 3];
            break;
        //else the case will be that the opcode is invalid.
        default: printf("Error: Opcode Invalid");
    }
}

void _method_dispatch(){
    //switch case for the statement instruction to move along
    switch(statement_ins[0] >> 4){
        case 0 ... 6:
        //for the first case if it falls under less than 6 and equals to 0
            _opcode_1 = _memory_register[(statement_ins[0] & 0x0f)];
            _opcode_2 = _memory_register[(statement_ins[1] >> 4)];
            break;
        case 7:
        //if 7 is the statement instruciton value
            printf("%x\n", _opcode_2 = statement_ins[1] >> 5);
            _opcode_1 = _memory_register[(statement_ins[0] & 0x0f)];
            _opcode_2 = statement_ins[1] >> 5;
            _off_set_mem = statement_ins[1] & 0x1f;
            break;
        case 8:
            _opcode_1 = ((statement_ins[0] & 0x0f) << 8) | statement_ins[1];
            break;
        case 9:
            _opcode_1 = _memory_register[(statement_ins[0] & 0x0f)];
            _opcode_2 = statement_ins[1];
            break;
        case 10 ... 11:
        //between the following value we will register the offset and do the calculation.
            _opcode_1 = _memory_register[(statement_ins[0] & 0x0f)];
            _opcode_2 = _memory_register[(statement_ins[1] >> 4)];
            _off_set_mem = ((statement_ins[1] & 0x0f) << 16) + (statement_ins[2] << 8) +
                        statement_ins[3];
            break;
            //once the opration is run we should break the process else it will go into the next
            //switch statement.
            //not like the one we were doing in the methodfetch.
        case 12:
            _off_set_mem = ((statement_ins[0] & 0x0f)<<24) + (statement_ins[1] << 16) +
            (statement_ins[2] << 8) + statement_ins[3];
            
            break;
        case 13:
        //assing the offset value from the instruction.
            _opcode_1 = _memory_register[(statement_ins[0] & 0x0f)];
            _opcode_2 = statement_ins[1];
            _off_set_mem = (statement_ins[2] << 8) | statement_ins[3];
            break;
        case 14 ... 15:
            _opcode_1 = (statement_ins[0] & 0x0f);
            _opcode_2 = (statement_ins[1] >> 4);
            _off_set_mem = statement_ins[1] & 0x0f;
            break;
    }
}

void _method_execute()
{
    //switch case for the statement instruction to move along
    switch(statement_ins[0] >> 4){
        //if the case is 0
        case 0:
            check_if_execute = 1;
            control += 2;
            break;
            //break the process here itself

            //if the case is 1
        case 1:
            _output = _opcode_1 + _opcode_2;
            control += 2;
            break;
            //break if the process here itslef
        case 2:
            _output = _opcode_1 & _opcode_2;
            control += 2;
            break;
        case 3:
            _output = _opcode_1/_opcode_2;
            control += 2;
            break;
        case 4:
            _output = _opcode_1 * _opcode_2;
            control += 2;
            break;
        case 5:
            _output = _opcode_1 - _opcode_2;
            control += 2;
            break;
        case 6:
        //will update the value for the control for all thecases.

            _output = _opcode_1 | _opcode_2;
            control +=2;
            break;
        case 7:
            if(_opcode_2 == 1)
                _output = _opcode_1 >> _off_set_mem;
            else if(_opcode_2 == 0)
                _output = _opcode_1 << _off_set_mem;
            control += 2;
            break;
        case 8:
        //special case for the swtich case value 8
            switch(_opcode_1){
                case 0:
                {
                    // have to recheck the vlaue for the opcode 1
                    for(int i = 0; i < 16; i++)
                        printf("Function : _memory_register %02d: 0x%08x\n", i, _memory_register[i]);
                    printf("\n");
                    break;
                }
                case 1:
                    for(int i = 0; i < 500; i+=8)
                        printf("%04d: %02x%02x %02x%02x %02x%02x %02x%02x\n", i, data_mem[i], data_mem[i+1] , data_mem[i+2], data_mem[i+3],data_mem[i+4], data_mem[i+5], data_mem[i+6], data_mem[i+7]);
                    break;
            }
            control += 2;
            break;
        case 9:
        //follows the cases for the 9 and further.
            if(_opcode_2 >> 7 == 1)
            {
                _output = _opcode_1 + _method_ex_sign(_opcode_2, 8) ;
            }
            else
                _output = _opcode_1 + _opcode_2;
            control += 2;
            break;
            //else case if the case false it down.
        case 10:
            if(_opcode_1 == _opcode_2)
            {
                if(_off_set_mem >> 19 == 1)
                    control += _method_ex_sign(_off_set_mem, 20);
                else
                    control += _off_set_mem;
            }
            else
                control += 4;
            break;
        case 11:
            if(_opcode_1 < _opcode_2)
            {
                if(_off_set_mem >> 19 == 1)
                    control += _method_ex_sign(_off_set_mem, 20);
                else
                    control += _off_set_mem;
            }
            else
                control +=4;
            break;
        case 12:
            control = _off_set_mem;
            break;
        case 13:
        //else case if the case false it down.
            if(data_mem[_opcode_1 + _opcode_2] != 0)
                {
                    _memory_register[(statement_ins[0] & 0x0f)] = data_mem[_opcode_1 + _opcode_2];
                    control -= _off_set_mem;
                }
            else
                control += 4;
            break;
            //else case if the case false it down.
        case 14:
            control += 2;
            break;
            //else case if the case false it down.
        case 15:
            _output = _memory_register[_opcode_1];
            control += 2;
            break;
            
    }
    
}

void _method_store()
{
    //switch for the cases to move along
    // will store the value 
    switch(statement_ins[0] >> 4){
        case 1 ... 6:
            _memory_register[statement_ins[1] & 0x0f] = _output;
            break;
        case 7:
        case 9:
        //assing the value for the output into the memory register.
            _memory_register[(statement_ins[0] & 0x0f) ] = _output;
            break;
        case 14:
            if(_off_set_mem >> 3 == 1)
            {
                //check the avlue for the offset if the value falls under here.
                address = _memory_register[_opcode_2] - (-_off_set_mem & 0x0f);
                _memory_register[_opcode_1]  = (data_mem[address + 3] << 24) | (data_mem[address + 2] << 16) |
                (data_mem[address + 1] << 8) | data_mem[address];
            }
            else
            {
                //else case if the case false it down.
                address = _memory_register[_opcode_2] + _off_set_mem;
                _memory_register[_opcode_1] = (data_mem[address + 3] <<24) | (data_mem[address + 2] << 16 ) |
                (data_mem[address + 1] << 8) | data_mem[address];
            }
            break;
            
        case 15:
            if(_off_set_mem >> 3 == 1)
            {//else case if the case false it down.
                _off_set_mem = _memory_register[_opcode_2] - (-_off_set_mem & 0x0f);
                data_mem[_off_set_mem + 3] = _output >> 24;
                data_mem[_off_set_mem + 2] = (_output >> 16) & 0xff;
                data_mem[_off_set_mem + 1] = (_output >> 8) & 0xff;
                data_mem[_off_set_mem] = (_output) & 0xff;
            }
            else
            {//else case if the case false it down.
                _off_set_mem = _memory_register[_opcode_2] + _off_set_mem;
                data_mem[_off_set_mem + 3] = _output >> 24;
                data_mem[_off_set_mem + 2] = (_output >> 16) & 0xff;
                data_mem[_off_set_mem + 1] = (_output >> 8) & 0xff;
                data_mem[_off_set_mem] = (_output) & 0xff;
            }
            break;
    }
}

//main method for the intial point.

int main(int argc, char **argv) {
    int i = 0;
    FILE *src = fopen(argv[1],"r");
    
    for(i = 0; i < 1000; i++)
        data_mem[i] = 0;
    //generate the src from the file opened.
    _generate(src);
    i = 1;
    
    //validation on the value for the integer.size.
    if(sizeof(int) != 4)
    {
        printf("Error: Please enter 4 or the value is not 4\n");
        exit(0);
    }
    
    while(check_if_execute == 0)
        {
            _method_fetch();
            _method_dispatch();
            _method_execute();
            _method_store();
            _output = 0;
            _off_set_mem = 0;
        }
     //close the file after the use.   
    fclose(src);
    return 0;
}