#include "vcpu.h"
#include "mem.h"
#include "screen.h"
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

// Current VCPU state
VCPU_State State;

void VCPUInit(){
  State.PC=ADDR_ROM;
  State.S=0;
  srand(time(NULL));
}

void VCPUFetch(){
  unsigned char byte[2];
  MemRead(byte,2,State.PC); // Little indian to -1 no +1
  State.opcode=byte[0];
  State.opcode=State.opcode<<8;
  State.opcode=State.opcode | byte[1];
  State.PC+=2;
}

void VCPUDecode(){
  State.X=(State.opcode>>8) & 0xF;
  State.Y=(State.opcode>>4) & 0xF;
  State.N=State.opcode & 0xF;

  State.NN=State.Y;
  State.NN=State.NN<<4;
  State.NN=State.NN | State.N;

  State.NNN=State.opcode&0x0FFF;
}

void VCPUExecute(){
  //  VCPUDump();
  switch(State.opcode >> 12){
  case 0x0: // Clear screen or return from subroutine
    if(State.N == 0){ // Clear screen
      ScreenClear();
    }
    else { // Return from subroutine
      State.PC=State.stack[State.S];
      State.S--;
    }
    break
      ;;
  case 0x1: // Jump
    State.PC=State.NNN;
    break
      ;;
  case 0x2: // Call
    State.S++;
    State.stack[State.S]=State.PC;
    State.PC=State.NNN;
    break
      ;;
  case 0x3: // SE: VX, byte
    if(State.V[State.X]==State.NN)
      State.PC+=2;
    break;
    ;;
  case 0x4: // SNE: VX, byte
    if(State.V[State.X]!=State.NN)
      State.PC+=2;
    break;
    ;;
  case 0x5: // SE: VX, VY
    if(State.V[State.X]==State.V[State.Y])
      State.PC+=2;
    break;
    ;;
  case 0x6:
    State.V[State.X]=State.NN;
    break
      ;;
  case 0x7:
    State.V[State.X]+=State.NN;
    break
      ;;
  case 0x8: // Register operations
    switch(State.N){
    case 0x1: // VX = VX OR VY
      State.V[State.X]=State.V[State.X] | State.V[State.Y];
      break;
      ;;
    case 0x2: // VX = VX AND VY
      State.V[State.X]=State.V[State.X] & State.V[State.Y];
      break;
      ;;
    case 0x3: // VX = VX XOR VY
      State.V[State.X]=State.V[State.X] ^ State.V[State.Y];
      break;
      ;;
    case 0x4: // VX = VX + VY
      if(State.V[State.X] + State.V[State.Y] > 255)
        State.V[REG_FLAG]=1;
      else
        State.V[REG_FLAG]=0;
      State.V[State.X]=(State.V[State.X] + State.V[State.Y]) & 0xFF;
      break;
      ;;
    case 0x5: // VX = VX - VY
      if(State.V[State.X] > State.V[State.Y])
        State.V[REG_FLAG]=1;
      else
        State.V[REG_FLAG]=0;
      State.V[State.X]=State.V[State.X] - State.V[State.Y];
      break;
      ;;
    case 0x6: // VX = VX SHR 1
      if(State.V[State.X] & 0x1 == 1)
        State.V[REG_FLAG]=1;
      else
        State.V[REG_FLAG]=0;
      State.V[State.X]=State.V[State.X] >> 1;
      break;
      ;;
    case 0x7: // VX = VY - VX
      if(State.V[State.X] < State.V[State.Y])
        State.V[REG_FLAG]=1;
      else
        State.V[REG_FLAG]=0;
      State.V[State.X]=State.V[State.Y] - State.V[State.X];
      break;
      ;;
    case 0xE: // VX = VX SHL 1
      if(State.V[State.X] >> 15 == 1)
        State.V[REG_FLAG]=1;
      else
        State.V[REG_FLAG]=0;
      State.V[State.X]=State.V[State.X] << 1;
      break;
      ;;
    }    
    break
      ;;
  case 0x9: // SNE: VX, VY
    if(State.V[State.X]!=State.V[State.Y])
      State.PC+=2;
    break;
    ;;
  case 0xA:
    State.I=State.NNN;
    break
      ;;
  case 0xB:
    State.PC=State.V[0]+State.NNN;
    break
      ;;
  case 0xC:
    unsigned short n = rand() % 255 + 1;
    State.V[State.X]=n & State.NN;
    break
      ;;    
  case 0xD: // Draw a sprite
    int X=State.V[State.X]%63;
    int Y=State.V[State.Y]%31;
    State.V[REG_FLAG]=0; // Set flag to 0
    int width,height;
    ScreenWH(&width,&height);
    for(char row=0;row<State.N;row++){
      // Stop if row out of screen
      if(Y+row>=height)
        break;
      char sprite;
      MemRead(&sprite,1,State.I+row); // Load sprite
      // Draw sprite
      for(int shift=0;shift<8;shift++){
        // Stop if column is out of screen
        if(X+shift >= width)
          break;
        if(ScreenPixelApply(X+shift,Y+row,(sprite>>(7-shift))&0x1))
          State.V[REG_FLAG]=1;
      }
    }
    break;
    ;;
  case 0xE:
    // TODO
    break;;
  case 0xF:
    switch(State.NN){
    case 0x07: // Get timer
      State.V[State.X]=State.DT;
      break;
      ;;
    case 0x0A:
      // TODO
      break;
      ;;
    case 0x15: // Set timer
      State.DT=State.V[State.X];
      break;
      ;;
    case 0x18: // Set sound timer
      State.ST=State.V[State.X];
      break;
      ;;
    case 0x1E: // I = I + VX
      State.I=State.I+State.V[State.X];
      break;
      ;;
    case 0x29:
      // TODO
      break;
      ;;
    case 0x33:
      // TODO
      break;
      ;;
    case 0x55:
      MemCopy(State.V,0xF,State.I);
      break;
      ;;
    case 0x65:
      MemRead(State.V,0xF,State.I);
      break;
      ;;
    }
    break;
      ;;
  }
}

void VCPUDump(){
  printf("opcode: 0x%04x\n",State.opcode&0xFFFF);
  printf("X: 0x%01x\n",State.X);
  printf("Y: 0x%01x\n",State.Y);
  printf("N: 0x%01x\n",State.N);
  printf("NN: 0x%02x\n",State.NN);
  printf("NNN: 0x%03x\n",State.NNN);
}