#include <iostream>
#include <iomanip>
#include <string>

// Opcodes

#define add    0x01
#define sub    0x29
#define hlt    0xf4
#define jnz    0x75
#define MRmov  0x8b
#define RMmov  0x89

// extrahiere die enzelne Felder eines Befehls
// anhand von Bitmasken.

#define EXTEND8(x) ((signed int)(signed char)(x))
#define RD(I)      (I&0x07)
#define RS(I)      ((I&0x38)>>3)
#define mod(I)     ((I&0xc0)>>6)

// Die Namen der Register
const char *Regs[8]=
  { "eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi" };

class cpu_Y86t
{
public:
  // constructor: alles auf 0
  cpu_Y86t():IP(0) {
    for(unsigned i=0; i<100; i++) MEM[i]=0;
    for(unsigned j=0; j<8; j++) R[j]=0;
    ZF=false;
  }

  unsigned IP;	          // Der Instruction Pointer
  unsigned char MEM[100]; // Hauptspeicher: 100 Adressen
  unsigned R[8];          // Die Werte der 8 Register
  bool ZF;                // Flags

  void show(); // Zustand anzeigen
  void step(); // eine Instruktion ausfuehren
  void run();  // Programm ausfuehren
};

void cpu_Y86t::show() {
  std::cout << "IP=" << std::hex << IP << std::endl;

  for(unsigned i=0; i<8; i++)
    std::cout << Regs[i] << "=" << std::hex << std::setw(9) << R[i] << " ";

  std::cout << "ZF=" << ZF;
  std::cout << std::endl;

  std::cout << "MEM: ";

  for(unsigned i=0; i<32; i++)
  {
    std::cout << std::hex << std::setw(3) << (unsigned)MEM[i];
    if((i&0xf)==0xf) std::cout << std::endl << "     ";
  }

  std::cout << std::endl;
}

void cpu_Y86t::step() {
  unsigned ea;
  unsigned I0=MEM[IP];
  unsigned I1=MEM[IP+1];
  unsigned I2=MEM[IP+2];
  IP+=1;
  
  switch(I0) {
  case MRmov:
    if(mod(I1)==1) // move from memory to register
    {
      IP+=2;
      std::cout << "MRmov " << Regs[RS(I1)] << ", [ESI+0x" << EXTEND8(I2) << "]" << std::endl;
      ea=R[6]+EXTEND8(I2);
      R[RS(I1)]=MEM[ea]|(MEM[ea+1]>>8)|(MEM[ea+2]>>16)|(MEM[ea+3]>>24);
    }
    else
      std::cout << "Modus nicht unterstuetzt" << std::endl;
    break;

  case RMmov:
    if(mod(I1)==1) // move from register to memory
    {
      IP+=2;
      ea=R[6]+EXTEND8(I2);
      std::cout << "RMmov [ESI+0x" << EXTEND8(I2) << "], " << Regs[RS(I1)] << std::endl;
      MEM[ea+0]=(R[RS(I1)]&0xff);
      MEM[ea+1]=(R[RS(I1)]&0xff00)>>8;
      MEM[ea+2]=(R[RS(I1)]&0xff0000)>>16;
      MEM[ea+3]=(R[RS(I1)]&0xff000000)>>24;
    }
    else if(mod(I1)==3) // move from register to register
    {
      IP+=1;
      std::cout << "RRmov " << Regs[RD(I1)] << ", " << Regs[RS(I1)] << std::endl;
      R[RD(I1)]=R[RS(I1)];
    }
    else
      std::cout << "Modus nicht unterstuetzt" << std::endl;
    break;

  case add:
    IP+=1;
    std::cout << "add " << Regs[RD(I1)] << ", " << Regs[RS(I1)] << std::endl;
    R[RD(I1)]+=R[RS(I1)];
    ZF=(R[RD(I1)]==0); 
    break;
  
  case sub:
    IP+=1;
    std::cout << "sub " << Regs[RD(I1)] << ", " << Regs[RS(I1)] << std::endl;
    R[RD(I1)]-=R[RS(I1)];
    ZF=(R[RD(I1)]==0); 
    break;
  
  case jnz: // jump if not zero
    IP+=1;
    std::cout << "jnz 0x" << IP+EXTEND8(I1) << std::endl;
    if(!ZF) IP+=EXTEND8(I1);
    break;
    
  case hlt: // halt
    std::cout << "hlt" << std::endl;
    exit(0);
    
  default:
    std::cout << "Unbekannte Instruktion"
              << std::endl;
  }
}

void cpu_Y86t::run() {
  while(true) {
    show();

    std::string s;
    if(!std::getline(std::cin, s)) break;
    if(s=="q") break;
    
    step();
  }
}

unsigned char program[]=
  "\x29\xF6"          // 00000000 sub esi,esi
  "\x29\xC0"          // 00000002 sub eax,eax
  "\x29\xDB"          // 00000004 sub ebx,ebx
  "\x8B\x56\x17"      // 00000006 mov edx,[esi+0x17]
  "\x01\xD0"          // 00000009 add eax,edx
  "\x01\xC3"          // 0000000B add ebx,eax
  "\x89\xC1"          // 0000000D mov ecx,eax
  "\x8B\x56\x1B"      // 0000000F mov edx,[esi+0x1b]
  "\x29\xD1"          // 00000012 sub ecx,edx
  "\x75\xF0"          // 00000014 jnz 0x6
  "\xF4"              // 00000016 hlt
  "\x01\x00\x00\x00"  // 00000017 dd 1
  "\x0a\x00\x00\x00"; // 0000001B dd 10

int main() {
  cpu_Y86t cpu_Y86;

  for(unsigned i=0; i<sizeof(program); i++)
    cpu_Y86.MEM[i]=program[i];

  cpu_Y86.run();
}
