/* 
 * ASCD : copyright (c) Aley Keprt 1998-2012
 * SimCoupe : Copyright (c) Allan Skillman 1996
 *
 * Emulations of the ED operations of the Z80 CPU instruction set - part of xz80.
 * Copyright (C) 1994 Ian Collier.

   This file is part of Z80 CPU emulation library used in ASCD emulator.

   This library is free software: you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public License
   along with Foobar.  If not, see <http://www.gnu.org/licenses/>.
 */


#define edinstr(opcode,cycles) case opcode: { tstates+=cycles

#define input(var) { \
  var=in_byte(bc); \
  PORT_ACCESS(c); \
  f=(f&1)|(var&0xa8)|((!var)<<6)|parity(var); \
}

#define sbchl(x) { \
  WORD z=(x);\
  unsigned long t=(hl-z-cy)&0x1ffff;\
  f=((t>>8)&0xa8)|(t>>16)|2|\
    (((hl&0xfff)<(z&0xfff)+cy)<<4)|\
    (((hl^z)&(hl^t)&0x8000)>>13)|\
    ((!(t&0xffff))<<6)|2;\
  l=t;\
  h=t>>8;\
}

#define adchl(x) { \
  WORD z=(x);\
  unsigned long t=hl+z+cy;\
  f = ((t>>8)&0xa8) | (t>>16) | (((hl&0xfff)+(z&0xfff)+cy>0xfff)<<4) | (((~hl^z)&(hl^t)&0x8000)>>13) | ((!(t&0xffff))<<6);\
  l = t;\
  h = t>>8;\
}

#define neg ( a = -a, f = (a&0xa8) | ((!a)<<6) | (((a&15)>0)<<4) | ((a==128)<<2) | 2 | (a>0) )

{
  BYTE op=read_byte(pc);
  pc++;
  radjust++;

  switch(op){

edinstr(0x40,8+4);
  input(b);
endinstr;

edinstr(0x41,8+4);
  PORT_ACCESS(c); out_byte(bc,b);
endinstr;

edinstr(0x42,12+3);
  sbchl(bc);
endinstr;

edinstr(0x43,20);
  {WORD addr=read_word(pc);
   pc+=2;
   write_word(addr,bc);
  }
endinstr;

edinstr(0x44,4);
  neg;
endinstr;

edinstr(0x45,8);
  iff1=iff2;
  ret;
endinstr;

edinstr(0x46,4);
  im=0;
endinstr;

edinstr(0x47,8);
  i=a;
endinstr;

edinstr(0x48,8+4);
  input(c);
endinstr;

edinstr(0x49,8+4);
  PORT_ACCESS(c); out_byte(bc,c);
endinstr;

edinstr(0x4a,12+3);
  adchl(bc);
endinstr;

edinstr(0x4b,20);
  {WORD addr=read_word(pc);
   pc+=2;
   c=read_byte(addr);
   b=read_byte(addr+1);
  }
endinstr;

edinstr(0x4c,4);
  neg;
endinstr;

edinstr(0x4d,8);
  ret;
endinstr;

edinstr(0x4e,4);
  im=0;
endinstr;

edinstr(0x4f,8);
  r=a;
  radjust=r;
endinstr;

edinstr(0x50,8+4);
  input(d);
endinstr;

edinstr(0x51,8+4);
  PORT_ACCESS(c); out_byte(bc,d);
endinstr;

edinstr(0x52,12+3);
  sbchl(de);
endinstr;

edinstr(0x53,20);
  {WORD addr=read_word(pc);
   pc+=2;
   write_word(addr,de);
  }
endinstr;

edinstr(0x54,4);
  neg;
endinstr;

edinstr(0x55,8);
  ret;
endinstr;

edinstr(0x56,4);
  im=1;
endinstr;

edinstr(0x57,8);
  a=i;
  f=(f&1)|(a&0xa8)|((!a)<<6)|(iff2<<2);
endinstr;

edinstr(0x58,8+4);
  input(e);
endinstr;

edinstr(0x59,8+4);
  PORT_ACCESS(c); out_byte(bc,e);
endinstr;

edinstr(0x5a,12+3);
  adchl(de);
endinstr;

edinstr(0x5b,20);
  {WORD addr=read_word(pc);
   pc+=2;
   e=read_byte(addr);
   d=read_byte(addr+1);
  }
endinstr;

edinstr(0x5c,4);
  neg;
endinstr;

edinstr(0x5d,8);
  ret;
endinstr;

edinstr(0x5e,4);
  im=2;
endinstr;

edinstr(0x5f,8);
  r=(r&0x80)|(radjust&0x7f);
  a=r;
  f=(f&1)|(a&0xa8)|((!a)<<6)|(iff2<<2);
endinstr;

edinstr(0x60,8+4);
  input(h);
endinstr;

edinstr(0x61,8+4);
  PORT_ACCESS(c); out_byte(bc,h);
endinstr;

edinstr(0x62,12+3);
  sbchl(hl);
endinstr;

edinstr(0x63,16);
  {WORD addr=read_word(pc);
   pc+=2;
   write_word(addr,hl);
  }
endinstr;

edinstr(0x64,4);
  neg;
endinstr;

edinstr(0x65,8);
  ret;
endinstr;

edinstr(0x66,4);
  im=0;
endinstr;

edinstr(0x67,16);
  {BYTE t=read_byte(hl);
   BYTE u=(a<<4)|(t>>4);
   a=(a&0xf0)|(t&0x0f);
   write_byte(hl,u);
   f=(f&1)|(a&0xa8)|((!a)<<6)|parity(a);
  }
endinstr;

edinstr(0x68,8+4);
  input(l);
endinstr;

edinstr(0x69,8+4);
  PORT_ACCESS(c); out_byte(bc,l);
endinstr;

edinstr(0x6a,12+3);
  adchl(hl);
endinstr;

edinstr(0x6b,16);
  {WORD addr=read_word(pc);
   pc+=2;
   l=read_byte(addr);
   h=read_byte(addr+1);
  }
endinstr;

edinstr(0x6c,4);
  neg;
endinstr;

edinstr(0x6d,8);
  ret;
endinstr;

edinstr(0x6e,4);
  im=0; //undocumented
endinstr;

edinstr(0x6f,8);
  {BYTE t=read_byte(hl);
   BYTE u=(a&0x0f)|(t<<4);
   a=(a&0xf0)|(t>>4);
   write_byte(hl,u);
   f=(f&1)|(a&0xa8)|((!a)<<6)|parity(a);
  }
endinstr;

edinstr(0x70,8+4);
  {BYTE x;input(x);} 
endinstr;

edinstr(0x71,8+4);
  PORT_ACCESS(c); out_byte(bc,0);
endinstr;

edinstr(0x72,12+3);
  sbchl(sp);
endinstr;

edinstr(0x73,20);
  {WORD addr=read_word(pc);
   pc+=2;
   write_word(addr,sp);
  }
endinstr;

edinstr(0x74,4);
  neg;
endinstr;

edinstr(0x75,8);
  ret;
endinstr;

edinstr(0x76,4);
  im=1;
endinstr;

edinstr(0x78,8+4);
  input(a);
endinstr;

edinstr(0x79,8+4);
  PORT_ACCESS(c); out_byte(bc,a);
endinstr;

edinstr(0x7a,12+3);
  adchl(sp);
endinstr;

edinstr(0x7b,20);
  {WORD addr=read_word(pc);
   pc+=2;
   sp=read_word(addr);
  }
endinstr;

edinstr(0x7c,4);
  neg;
endinstr;

edinstr(0x7d,8);
  ret;
endinstr;

edinstr(0x7e,4);
  im=2;
endinstr;

edinstr(0xa0,16);
  {BYTE x=read_byte(hl);
   write_byte(de,x);
   if(!++l)h++;
   if(!++e)d++;
   if(!c--)b--;
   f=(f&0xc1)|(x&0x28)|(((b|c)>0)<<2);
  }
endinstr;

edinstr(0xa1,12);
  {BYTE carry=cy;
   cpa(read_byte(hl));
   if(!++l)h++;
   if(!c--)b--;
   f=(f&0xfa)|carry|(((b|c)>0)<<2);
  }
endinstr;

edinstr(0xa2,12+4);
  {BYTE t=in_byte(bc);
   PORT_ACCESS(c);
   write_byte(hl,t);
   if(!++l)h++;
   b--;
   f=(b&0xa8)|((b>0)<<6)|2|((parity(b)^c)&4); 
/*   f|=(0x02|((b==0)<<6)); */
  }
endinstr;

//I can't determine the correct flags outcome for the block OUT instructions.  Spec says that the carry
//flag is left unchanged and N is set to 1, but that doesn't seem to be the case...
edinstr(0xa3,12+4);
  {BYTE x=read_byte(hl);
   b--;
   PORT_ACCESS(c); out_byte(bc,x);
   if(!++l)h++;
   f=(f&1)|0x12|(b&0xa8)|((b==0)<<6); 
/*   f|=(0x02|((b==0)<<6)); */
  }
endinstr;

edinstr(0xa8,16);
  {BYTE x=read_byte(hl);
   write_byte(de,x);
   if(!l--)h--;
   if(!e--)d--;
   if(!c--)b--;
   f=(f&0xc1)|(x&0x28)|(((b|c)>0)<<2);
  }
endinstr;

edinstr(0xa9,12);
  {BYTE carry=cy;
   cpa(read_byte(hl));
   if(!l--)h--;
   if(!c--)b--;
   f=(f&0xfa)|carry|(((b|c)>0)<<2);
  }
endinstr;

edinstr(0xaa,12+4);
  {BYTE t=in_byte(bc);
   PORT_ACCESS(c);
   write_byte(hl,t);
   if(!l--)h--;
   b--;
   f=(b&0xa8)|((b>0)<<6)|2|((parity(b)^c^4)&4);
/*   f|=(0x02|((b==0)<<6)); */

  }
endinstr;

edinstr(0xab,12+4);
  {BYTE x=read_byte(hl);
   b--;
   PORT_ACCESS(c); out_byte(bc,x);
   if(!l--)h--;
   f=(f&1)|0x12|(b&0xa8)|((b==0)<<6); 
/*   f|=(0x02|((b==0)<<6)); */

  }
endinstr;

 //Note: the Z80 implements "*R" as "*" followed by JR -2. No reason to change this...

edinstr(0xb0,16);
  {BYTE x=read_byte(hl);
   write_byte(de,x);
   if(!++l)h++;
   if(!++e)d++;
   if(!c--)b--;
   f=(f&0xc1)|(x&0x28)|(((b|c)>0)<<2);
   if(b|c)pc-=2,tstates+=4;
  }
endinstr;

edinstr(0xb1,12);
  {BYTE carry=cy;
   cpa(read_byte(hl));
   if(!++l)h++;
   if(!c--)b--;
   f=(f&0xfa)|carry|(((b|c)>0)<<2);
   if((f&0x44)==4)pc-=2,tstates+=4;
  }
endinstr;

edinstr(0xb2,12+4);
  {BYTE t=in_byte(bc);
   PORT_ACCESS(c);
   write_byte(hl,t);
   if(!++l)h++;
   b--;
   f=(b&0xa8)|((b>0)<<6)|2|((parity(b)^c)&4); 
   if(b)pc-=2,tstates+=4;
  }
endinstr;

edinstr(0xb3,12+4);
  {BYTE x=read_byte(hl);
   b--;
   PORT_ACCESS(c); out_byte(bc,x);
   if(!++l)h++;
   f=(f&1)|0x12|(b&0xa8)|((b==0)<<6);
   if(b)pc-=2,tstates+=4;
  }
endinstr;

edinstr(0xb8,16);
  {BYTE x=read_byte(hl);
   write_byte(de,x);
   if(!l--)h--;
   if(!e--)d--;
   if(!c--)b--;
   f=(f&0xc1)|(x&0x28)|(((b|c)>0)<<2);
   if(b|c)pc-=2,tstates+=4;
  }
endinstr;

edinstr(0xb9,12);
  {BYTE carry=cy;
   cpa(read_byte(hl));
   if(!l--)h--;
   if(!c--)b--;
   f=(f&0xfa)|carry|(((b|c)>0)<<2);
   if((f&0x44)==4)pc-=2,tstates+=4;
  }
endinstr;

edinstr(0xba,12+4);
  {BYTE t=in_byte(bc);
   PORT_ACCESS(c);
   write_byte(hl,t);
   if(!l--)h--;
   b--;
   f=(b&0xa8)|((b>0)<<6)|2|((parity(b)^c^4)&4);
   if(b)pc-=2,tstates+=4;
  }
endinstr;

edinstr(0xbb,12+4); //otdr
  {BYTE x=read_byte(hl);
   b--;
   PORT_ACCESS(c); out_byte(bc,x);
   if(!l--)h--;
   f=(f&1)|0x12|(b&0xa8)|((b==0)<<6);
   if(b)pc-=2,tstates+=4;
  }
endinstr;

edinstr(0xfe,4);
  if(EDFEtrap) EDFEtrap();
endinstr;

default: 
  tstates+=4;
  break;
}
}
