/*
 * ASCD : copyright (c) Aley Keprt 1998-2012
 * SimCoupe : Copyright (c) Allan Skillman 1996
 *
 * File processor.c : main z80 loop and interrupt routines
 *
    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 Z80CPU

#include "z80.h"


//CPU timing constants
enum {
  TSTATES_PER_LINE_SAM = 384,
  TSTATES_PER_LINE_ZX = (224+20),  //+20 pro Shock Megademo
  TSTATES_PER_LINE_128 = (228+16), //+16 pro Shock Megademo
  TSTATES_PER_LINE_ZX81 = 208,
};


#define parity(a) (partable[a])

static const BYTE partable[256]={
  4, 0, 0, 4, 0, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 4,
  0, 4, 4, 0, 4, 0, 0, 4, 4, 0, 0, 4, 0, 4, 4, 0,
  0, 4, 4, 0, 4, 0, 0, 4, 4, 0, 0, 4, 0, 4, 4, 0,
  4, 0, 0, 4, 0, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 4,
  0, 4, 4, 0, 4, 0, 0, 4, 4, 0, 0, 4, 0, 4, 4, 0,
  4, 0, 0, 4, 0, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 4,
  4, 0, 0, 4, 0, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 4,
  0, 4, 4, 0, 4, 0, 0, 4, 4, 0, 0, 4, 0, 4, 4, 0,
  0, 4, 4, 0, 4, 0, 0, 4, 4, 0, 0, 4, 0, 4, 4, 0,
  4, 0, 0, 4, 0, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 4,
  4, 0, 0, 4, 0, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 4,
  0, 4, 4, 0, 4, 0, 0, 4, 4, 0, 0, 4, 0, 4, 4, 0,
  4, 0, 0, 4, 0, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 4,
  0, 4, 4, 0, 4, 0, 0, 4, 4, 0, 0, 4, 0, 4, 4, 0,
  0, 4, 4, 0, 4, 0, 0, 4, 4, 0, 0, 4, 0, 4, 4, 0,
  4, 0, 0, 4, 0, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 4,
};


namespace {

  //Z80 CPU registers
  WORD pc, ix, iy, sp;

  BYTE a, b, c, f, d, e, h, l;
  #define af ((a<<8)|f)
  #define bc ((b<<8)|c)
  #define de ((d<<8)|e)
  #define hl ((h<<8)|l)

  BYTE i, r;
  
  BYTE a1, f1, b1, c1, d1, e1, h1, l1;
  #define bc_prime ((b1<<8)|c1)
  #define af_prime ((a1<<8)|f1)
  #define de_prime ((d1<<8)|e1)
  #define hl_prime ((h1<<8)|l1)
/*
  static union { struct { BYTE f,a; }; WORD af; };
  static union { struct { BYTE c,b; }; WORD bc; };
  static union { struct { BYTE e,d; }; WORD de; };
  static union { struct { BYTE l,h; }; WORD hl; };
*/
  bool iff1, iff2;
  BYTE im;

  int LineFreq;              //number of tstates per line

  int IntActiveTime;         //time (in tstates) an int is active (Port 249 != 0xff)

  const int MouseStrobeTime  //time (in tstates) from a mouse strobe to a counter reset
    = 384 * 150 / 64;  //384 je Tstates per line, LineFreq is 64microsec (PAL), mouse strobe is 200 us (tak to chtěl Velesoft)

  int LineCycleCounter;      //counter used to count the cycles per line

  int IntActiveCounter;      //counter used for interrupt active timing
  int MouseActiveCounter;    //counter used for mouse active timing

  int tstates = 0;
  BYTE radjust = 0;

  EM zxmode;
};

//-----------------------------------------------------------------------

//Set all CPU registers
void Z80SetRegs(const Z80_Regs *rr) {
  a=rr->a;
  f=rr->f;
  b=rr->b;
  c=rr->c;
  d=rr->d;
  e=rr->e;
  h=rr->h;
  l=rr->l;
  a1=rr->a1;
  f1=rr->f1;
  b1=rr->b1;
  c1=rr->c1;
  d1=rr->d1;
  e1=rr->e1;
  h1=rr->h1;
  l1=rr->l1;
  ix=rr->ix;
  iy=rr->iy;
  pc=rr->pc;
  sp=rr->sp;
  i=rr->i;
  r=rr->r;

  //bits01:IM, bit2=IFF1, bit3=IFF2
  im = rr->im&3;
  iff1 = rr->iff&1;
  iff2 = rr->iff>>1&1;

  LineCycleCounter = rr->LineCycleCounter;
  IntActiveCounter = rr->IntActiveCounter;
  MouseActiveCounter = rr->MouseActiveCounter;
}

//-----------------------------------------------------------------------

//Get all CPU registers
void Z80GetRegs(Z80_Regs *rr) {
  rr->a=a;
  rr->f=f;
  rr->b=b;
  rr->c=c;
  rr->d=d;
  rr->e=e;
  rr->h=h;
  rr->l=l;
  rr->r=r;
  rr->a1=a1;
  rr->f1=f1;
  rr->b1=b1;
  rr->c1=c1;
  rr->d1=d1;
  rr->e1=e1;
  rr->h1=h1;
  rr->l1=l1;
  rr->ix=ix;
  rr->iy=iy;
  rr->pc=pc;
  rr->sp=sp;
  rr->i=i;
  rr->r=r;

  rr->im = im&3;
  rr->iff = (iff1!=0) | (iff2!=0)<<1;

  rr->LineCycleCounter = LineCycleCounter;
  rr->IntActiveCounter = IntActiveCounter;
  rr->MouseActiveCounter = MouseActiveCounter;
  rr->lineno = 0; //linky pocitame jinde
}

//-----------------------------------------------------------------------

BYTE Z80GetI(void) {
  return i;
}

//-----------------------------------------------------------------------

//Resetuje vsechny CPU registry a porty a nastavi rychlost CPU dle modelu pocitace
void Z80Reset(EM zxmode) {
  a=f=b=c=d=e=h=l=a1=f1=b1=c1=d1=e1=h1=l1=i=r=im=0;
  iff1=iff2=false;
  ix=iy=sp=pc=0;
//  radjust=0;
  LineCycleCounter = 0;
  IntActiveCounter = 0;
  MouseActiveCounter = 0;
  ::zxmode = zxmode;

  //linková frekvence PAL je 64 us (312 linek, 50 snímků za sekundu --> 15600 linek za sekundu = 64 us na linku)
  //přerušení mají být aktivní 100 us (ale Velesoft chtěl 200 us pro myš)

  //Define the number of tstates per line
  switch(zxmode) {
  case EM_Sam: 
    LineFreq = TSTATES_PER_LINE_SAM;
    IntActiveTime = 120; //128; //snizeno ze 128 na 120 kvuli Shadebobs Show
    break;
  case EM_48k:
    LineFreq = TSTATES_PER_LINE_ZX;
    IntActiveTime = 44; //správně má být 32, ale některé instrukce časujeme pomaleji, takže dávám "pro jistotu" 33% rezervu
    break;
  case EM_128k:
    LineFreq = TSTATES_PER_LINE_128;
    IntActiveTime = 44; //správně má být 32, ale některé instrukce časujeme pomaleji, takže dávám "pro jistotu" 33% rezervu
    break;
  case EM_ZX80:
  case EM_ZX81:
    LineFreq = TSTATES_PER_LINE_ZX81;
    IntActiveTime = 32;
    break;
  }
}

//-----------------------------------------------------------------------

//Z80 CPU emulation of a single PAL scanline
//do_interrupts=1 to let the emulator process maskable interrupts before the first instruction
void Z80RunLine(bool do_interrupts) {

  bool DelayedEI = false; //flag and counter to carry out a delayed interrupt
  register BYTE ixoriy = 0, new_ixoriy = 0;
  radjust = r;

  while(true) {

    ixoriy = new_ixoriy;
    new_ixoriy = 0;

    DelayedEI = false;
    register BYTE op = read_byte(pc);
    pc++;
    radjust++;

    switch(op) {
#include "z80ops.h"
    }
    
    LineCycleCounter += tstates;

    if(new_ixoriy) continue;

//do_interrupts:

    //if we have strobed the mouse, we will need to do a reset after 100us
    if(MouseStrobed && (MouseActiveCounter+=tstates) >= MouseStrobeTime) {
      MouseActiveCounter = 0;
      MouseStrobed = false;
    }

    //call the interrupt if required
    if(status_reg != 0xff) {

      //reset interrupt flag after IntActiveTime tstates
      IntActiveCounter += tstates;
      if(IntActiveCounter >= IntActiveTime) {
        IntActiveCounter-=IntActiveTime;
        status_reg=0xff;
      }

      //maskable interrupts
      if(status_reg!=0xff && iff1 && !DelayedEI) {
        if(op==0x76) pc++; //HALT
        iff1 = iff2 = 0;

        if(IntActiveCounter>100) {
          iff1=0;
        }

        switch(im) {
        case 0:
        case 1:
          //there is little to distinguish between these cases
          LineCycleCounter += 15; //puvodne bylo 8
          push2(pc);
          pc=0x38;
          break;
          
        case 2:
          LineCycleCounter += 22; //puvodne bylo 8
          WORD addr=read_word((i<<8)|0xff);
          push2(pc);
          pc=addr;
          break;
        }
      }
    }

    //testujeme, zda uz jsme na konci linky
    if(LineCycleCounter >= LineFreq) {

      LineCycleCounter -= LineFreq;

      //musime aktualizovat registr r, aby se mohl pripadne ulozit do snapshotu i do QuickSave
      r = (r & 0x80) | (radjust & 0x7f);

      break;
    }
  }
}


//Z80 CPU emulation of a single PAL scanline - ZX81
void Z80RunZX81() {

  bool DelayedEI = false; //flag and counter to carry out a delayed interrupt
  register BYTE ixoriy = 0, new_ixoriy = 0;
  radjust = r;

  while(true) {

    ixoriy = new_ixoriy;
    new_ixoriy = 0;

    DelayedEI = false;
    register BYTE op = read_byte(pc);
    
    if(pc>=0x8000) {

      //zobrazovaci data
      if((op&0x40)==0) {
        //op necha zobrazit - nutno dodelat
        //vsechny hodnoty s bit6=0 zmeni na nulu
        op = 0;
      }
    }
    pc++;
    radjust++;

    switch(op) {
#include "z80ops.h"
    }
    
    LineCycleCounter += tstates;

    if(new_ixoriy) continue;

    //maskable interrupt je aktivni kdyz R bit6=1
    if(radjust & 0x40) {

      //maskable interrupts
      if(iff1 && !DelayedEI) {
        iff1 = iff2 = 0;

        switch(im) {
        case 0:
        case 1:
          LineCycleCounter += 12;
          push2(pc);
          pc=0x38;
          break;
          
        case 2:
          LineCycleCounter += 18;
          WORD addr=read_word((i<<8)|0xff);
          push2(pc);
          pc=addr;
          break;
        }
      }
    }

    //testujeme, zda uz jsme na konci linky
    if(LineCycleCounter >= LineFreq) {

      LineCycleCounter -= LineFreq;

      //musime aktualizovat registr r, aby se mohl pripadne ulozit do snapshotu i do QuickSave
      r = (r & 0x80) | (radjust & 0x7f);

      break;
    }
  }
}


void Z80DoNMI() {
  if(read_byte(pc)==0x76) pc++;
  iff2 = iff1;
  iff1 = false;
  //The Z80 performs a machine fetch cycle for 5 Tstates but ignores the result.
  //It takes a further 10 Tstates to jump to the NMI routine at 0x66.
  LineCycleCounter+=16;
  push2(pc);
  pc=0x66;
}


//resetuje citac doby aktivity preruseni
void Z80ResetIntActiveCounter(void) { IntActiveCounter = 0; }

//vraci aktualni hodnotu LineCycleCounter
int Z80GetLineCycleCounter(void) { return LineCycleCounter; }
