#include <EDK.h>
#include "General.h"
#include "CC64.h"
#include "CVC1541.h"
#include "CIECDisasm.h"

#define Class CC64
extern Class MSVC4Bug;

#pragma optimize("", off)

Clock* gpClock;

CC64::CC64() {
  pVIC = NULL;
  pCPU = NULL;
  pCIA1 = NULL;
  pCIA2 = NULL;
  pSID = NULL;
}

// initialisation
void CC64::DoInit() {

  // initialize base class
  Object::DoInit();

  Keyboard.Init("Keyboard", this);
  pVIC = new CVIC656x;
  gpClock = (class Clock*)pVIC; // VIC is Clock for speed
  gpClock->SetFrequency(985248);
  pVIC->Init("VIC", this);
  //pVIC->SetClock(*gpClock);
  pCPU = new CPU6510C64;
  pCPU->Init("CPU", this);
  pCPU->SetClock(*gpClock);
  pCIA1 = new CIA6526;
  pCIA1->Init("CIA1", this);
  pCIA1->SetClock(*gpClock);
  pCIA2 = new CIA6526;
  pCIA2->Init("CIA2", this);
  pCIA2->SetClock(*gpClock);
  pSID = new SID6581;
  pSID->Init("SID", this);
  pSID->SetClock(*gpClock);

  Display.pKeyboard = &Keyboard;

  pbRAM = pCPU->AllocMem(65536);
  pbKernal = pCPU->AllocMem(8192);
  pbBasic = pCPU->AllocMem(8192);
  pbCharset = pCPU->AllocMem(4096);
  pbIO = pCPU->AllocMem(4096);

  InFile ROM;
  win(SetCurrentDirectory(GetProgramDirectory()));
  ROM.SetName("Original.64k");
  ROM.Read(pbKernal, 8192);
  ROM.SetName("Original.64b");
  ROM.Read(pbBasic, 8192);
  ROM.SetName("Original.64c");
  ROM.Read(pbCharset, 4096);

  InitMemMapping();
  MemMapping.Init("MemMapping", this);
  MemMapping.SetOnChange((pfnbb)OnMemMappingChange);
  MemMapping.ConnectTo(pCPU->P);

  pVIC->pDisplay = &Display;
  pVIC->pbBitmap = NULL;
  pVIC->pbLine = NULL;
  pVIC->pbLineShift = NULL;

  pCPU->MapChip(pCPU, pbRAM, 2);
  pCPU->MapChip(pVIC, pbIO, 0x0400);
  pCPU->MapChip(pSID, pbIO + 0x0400, 0x0400);
  byte bTrap = pCPU->AllocIOTrap(this, (bpfn)ReadColor, (pfnb)WriteColor);
  for (int i = 0x0800; i < 0x0C00; i++) {
    pbIO[i] = 0x10;
    pCPU->SetTrap(pbIO + i, bTrap);
  }
  pCPU->MapChip(pCIA1, pbIO + 0x0C00, 0x0100);
  pCPU->MapChip(pCIA2, pbIO + 0x0D00, 0x0100);
  InitCopyright();

  CC64* pC64 = this;
  extern void CVIC656xSetVideoRAMAndCharROM();
  extern void CVIC656xSetColorRAM();
  __asm {
    push ThisReg
    mov ThisReg,pC64
    mov EAX,mvar(pbIO)
    add EAX,0x0800
    push EAX
    mov EAX,mvar(pbRAM)
    mov EBX,mvar(pbCharset)
    mov ThisReg,mvar(pVIC)
    call CVIC656xSetVideoRAMAndCharROM
    pop EAX
    call CVIC656xSetColorRAM
    pop ThisReg
  }

  Reset.Init("Reset", this);
  NMI.Init("NMI", this);
  IRQ.Init("IRQ", this);

  Keyboard.Reset.ConnectTo(Reset);
  pCPU->Reset.ConnectTo(Reset);
  pCIA1->Reset.ConnectTo(Reset);
  pCIA2->Reset.ConnectTo(Reset);
  pSID->Reset.ConnectTo(Reset);

  Keyboard.Restore.ConnectTo(NMI);
  pCIA2->Int.ConnectTo(NMI);
  pCPU->NMI.ConnectTo(NMI);

  pCIA1->Int.ConnectTo(IRQ);
  pVIC->Int.ConnectTo(IRQ);
  pCPU->IRQ.ConnectTo(IRQ);

  pCPU->RDY.ConnectTo(pVIC->BA);

  MainsFreq.Init("MainsFreq", this);
  MainsFreq.SetClock(*gpClock);
  MainsFreq.SetOnFire((pfn)OnMainsFreq);
  MainsFreq.StartCounter(985248 / 100);
  Mains.Init("Mains", this);
  Mains.ConnectTo(pCIA1->TOD);
  Mains.ConnectTo(pCIA2->TOD);

  Display.pKeyboard = &Keyboard;
  Keyboard.KeyRow.ConnectTo(pCIA1->PA);
  Keyboard.KeyCol.ConnectTo(pCIA1->PB);

  Joystick.Init("Joystick", this);
  Joystick.SetOnChange((pfnbb)OnJoystickChange);
  Joystick.ConnectTo(Keyboard.Joystick);
  SecondJoystick.Init("SecondJoystick", this);
  SecondJoystick.SetOnChange((pfnbb)OnSecondJoystickChange);
  SecondJoystick.ConnectTo(Keyboard.SecondJoystick);
  Joy1.Init("Joy1", this);
  Joy2.Init("Joy2", this);
  Joy1.ConnectTo(pCIA1->PA);
  Joy2.ConnectTo(pCIA1->PB);
  bSwapJoysticks = TRUE;

  extern flag IsValidLicense();
  if (IsValidLicense()) {
    extern void InitJoysticks(Port& Primary, Port& Secondary, ::Clock* pNewClock);
    InitJoysticks(Keyboard.Joystick, Keyboard.SecondJoystick, gpClock);
    extern void InitJoysticksLPT(Port& Primary, Port& Secondary, ::Clock* pNewClock);
    InitJoysticksLPT(Keyboard.Joystick, Keyboard.SecondJoystick, gpClock);
  }

  VICBank.Init("VICBank", this);
  VICBank.SetOnChange((pfnbb)OnVICBankChange);
  VICBank.ConnectTo(pCIA2->PA);

  InitLP();
  LPIn.ConnectTo(pCIA1->PB);
  LPOut.ConnectTo(pVIC->LP);

  IECMaster.pCPU = pCPU;
  IECMaster.pbKernal = pbKernal;
  extern void CIECMasterListen();
  extern void CIECMasterListenCh();
  extern void CIECMasterIECOut();
  extern void CIECMasterUnlisten();
  extern void CIECMasterTalk();
  extern void CIECMasterTalkCh();
  extern void CIECMasterIECIn();
  extern void CIECMasterUntalk();
  extern void CIECMasterLoad();
  pCPU->SetTrap(pbKernal + 0x0D09, pCPU->AllocCodeTrap(&IECMaster, make_bpfn(CIECMasterTalk)));
  pCPU->SetTrap(pbKernal + 0x0D0C, pCPU->AllocCodeTrap(&IECMaster, make_bpfn(CIECMasterListen)));
  pCPU->SetTrap(pbKernal + 0x0DB9, pCPU->AllocCodeTrap(&IECMaster, make_bpfn(CIECMasterListenCh)));
  pCPU->SetTrap(pbKernal + 0x0DC7, pCPU->AllocCodeTrap(&IECMaster, make_bpfn(CIECMasterTalkCh)));
  pCPU->SetTrap(pbKernal + 0x0DDD, pCPU->AllocCodeTrap(&IECMaster, make_bpfn(CIECMasterIECOut)));
  pCPU->SetTrap(pbKernal + 0x0DEF, pCPU->AllocCodeTrap(&IECMaster, make_bpfn(CIECMasterUntalk)));
  pCPU->SetTrap(pbKernal + 0x0DFE, pCPU->AllocCodeTrap(&IECMaster, make_bpfn(CIECMasterUnlisten)));
  pCPU->SetTrap(pbKernal + 0x0E13, pCPU->AllocCodeTrap(&IECMaster, make_bpfn(CIECMasterIECIn)));
  pCPU->SetTrap(pbKernal + 0x14A7, pCPU->AllocCodeTrap(&IECMaster, make_bpfn(CIECMasterLoad)));

  InitIEC();

  //IECMaster.apDevice[8] = new CVC1541(8, &IECMaster, pEventRoot);
  //new CCPU65xxDisasm(&((CVC1541*)IECMaster.apDevice[8])->CPU);
  //new CIECDisasm(&IECMaster);
}

CC64::~CC64() {

  delete IECMaster.apDevice[8];

  pCPU->FreeMem(pbIO, 4096);
  pCPU->FreeMem(pbCharset, 4096);
  pCPU->FreeMem(pbBasic, 8192);
  pCPU->FreeMem(pbKernal, 8192);
  pCPU->FreeMem(pbRAM, 65536);

  extern void ExitSound();
  ExitSound();
  extern void ExitJoysticks();
  ExitJoysticks();
  extern void ExitJoysticksLPT();
  ExitJoysticksLPT();

  delete pSID;
  pSID = NULL;
  delete pCIA2;
  pCIA2 = NULL;
  delete pCIA1;
  pCIA1 = NULL;
  delete pCPU;
  pCPU = NULL;

  delete pVIC;
  pVIC = NULL;
  gpClock = NULL;

}

#pragma optimize("", on)


byte CC64::ReadColor() {
  int i = pCPU->GetRWAddress();
  assert(i >= 0xD800 && i < 0xDC00);
  return (byte)(pbIO[i - 0xD000] | 0xF0);
}

void CC64::WriteColor(byte bValue) {
  int i = pCPU->GetRWAddress();
  assert(i >= 0xD800 && i < 0xDC00);
  pbIO[i - 0xD000] = (byte)(bValue & 0x0F | 0x10);
}


extern void CVIC656xSetVideoRAMAndCharROM();


void CC64::OnVICBankChange(byte bState, byte bChanges) {
  __asm {
  push EBX
  push ThisReg
  mov ThisReg,this
  mov AL,bState
  mov AH,bChanges

  test AH,00000011b
  je SameBank

  push EAX

  not EAX
  and EAX,11b
  xor EBX,EBX
  test AL,01b
  jne NoChar
  mov EBX,mvar(pbCharset)
NoChar:

  shl EAX,14
  add EAX,mvar(pbRAM)
  push ThisReg
  mov ThisReg,mvar(pVIC)
  call CVIC656xSetVideoRAMAndCharROM
  pop ThisReg

  pop EAX

SameBank:
  pop ThisReg
  pop EBX
  }
}


#undef SetHigh
#undef SetLow

void CC64::OnMainsFreq() {
  if (Mains.IsOutputHigh()) {
    Mains.SetOutputLow();
  } else {
    Mains.SetOutputHigh();
  }
  MainsFreq.StartCounter(985248 / 100);
}


proc(Joystick)
  // pass buttons 2-4 to keyboard
  or AL,11100000b
  // avoid illegal combinations
  test AL,00000011b
  jne NoUpAndDown
  or AL,00000011b
NoUpAndDown:
  test AL,00001100b
  jne NoLeftAndRight
  or AL,00001100b
NoLeftAndRight:
  // add auto fire

  // pass settings to CIA
  and BL,BL
  je SetJoy2
  SetPort(mvar(Joy1), 1)
  ret
align 4
SetJoy2:
  SetPort(mvar(Joy2), 2)
  ret
endp

void CC64::OnJoystickChange(byte bState, byte bChanges) {
  __asm {
  push EBX
  push ThisReg
  mov ThisReg,this
  mov AL,bState
  mov AH,bChanges

  push EAX
  mov BL,mvar(bSwapJoysticks)
  call fn(Joystick)
  pop EAX

  pop ThisReg
  pop EBX
  }
}


void CC64::OnSecondJoystickChange(byte bState, byte bChanges) {
  __asm {
  push EBX
  push ThisReg
  mov ThisReg,this
  mov AL,bState
  mov AH,bChanges

  push EAX
  mov BL,mvar(bSwapJoysticks)
  xor BL,1
  call fn(Joystick)
  pop EAX

  pop ThisReg
  pop EBX
  }
}


void CKeyboardSwapJoysticks() {
  extern CC64* gpC64;
  gpC64->bSwapJoysticks ^= 1;
}




#ifdef ExportEDK
  #undef global
  #define global __declspec(dllexport)
#endif

// for the EDK

global byte* CC64::GetBasic() {
  return pbBasic;
};

global byte* CC64::GetKernal() {
  return pbKernal;
}

global byte* CC64::GetCharset() {
  return pbCharset;
}

global byte* CC64::GetIO() {
  return pbIO;
}

global byte* CC64::GetRAM() {
  return pbRAM;
}

global Clock* CC64::GetClock() {
  return (class Clock*)pVIC;
}

#undef global
#define global
