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

#define Class CVC1541
extern Class MSVC4Bug;

byte* CVC1541::apbROM[4];
int CVC1541::iROMRef;


DeviceType CVC1541::GetType() {
  return eVC1541;
}


CString CVC1541::GetDisk() {
  return FDC.Disk;
}


void CVC1541::SetDisk(const CString& NewDisk) {

  FDC.SetDisk(NewDisk);

  // set WPS line low for 1/2 s, then set WPS line to -fReadOnly
  __asm {
    push ThisReg
    mov ThisReg,this
    SetLow(mvar(WPSOut), 1)
    pop ThisReg
  }
  DiskChange.StartCounter(985248/2);
}


void CVC1541::OnDiskChange() {

  if (!FDC.fReadOnly) {
    __asm {
      push ThisReg
      mov ThisReg,this
      SetHigh(mvar(WPSOut), 1)
      pop ThisReg
    }
  }

}


////////////////////////////////////////
// don't format disk at C8C6

proc(OnEE24)
  mov AL,0x4C
  ret
endp

proc(OnEE25)
  mov AL,0x46
  ret
endp

proc(OnEE26)
  mov AL,0xEE
  ret
endp

void CVC1541::InitFormat() {
  pCPU->SetTrap(apbROM[2] + 0x0E24, pCPU->AllocCodeTrap(this, make_bpfn(fn(OnEE24))));
  pCPU->SetTrap(apbROM[2] + 0x0E25, pCPU->AllocCodeTrap(this, make_bpfn(fn(OnEE25))));
  pCPU->SetTrap(apbROM[2] + 0x0E26, pCPU->AllocCodeTrap(this, make_bpfn(fn(OnEE26))));
}


////////////////////////////////////////
// CPU idle at $EBFF, busy at IRQ/Reset

proc(OnIdle)
  push EBX
  push ThisReg
  mov ThisReg,ECX

  SetLow(mvar(Idle), 1)

  mov AL,0x58

  pop ThisReg
  pop EBX
  ret
endp


proc(OnIRQIdleLow)
  push EBX
  push ThisReg
  mov ThisReg,ECX

  SetHigh(mvar(Idle), 1)

  pop ThisReg
  pop EBX
  ret
endp


proc(OnResetIdleHigh)
  push EBX
  push ThisReg
  mov ThisReg,ECX

  SetHigh(mvar(Idle), 1)

  pop ThisReg
  pop EBX
  ret
endp


proc(OnResetIdleLow)
  push EBX
  push ThisReg
  mov ThisReg,ECX

  SetHigh(mvar(Idle), 1)

  pop ThisReg
  pop EBX
  ret
endp


/*
  CLine Idle;           // halts CPU at $EBFF
  CLine IRQIdle;
  CLine ResetIdle;
*/

void CVC1541::InitIdle() {

  IRQIdle.Init("IRQIdle", this);
  IRQIdle.SetOnLow(make_pfn(fn(OnIRQIdleLow)));
  IRQIdle.ConnectTo(IRQ);

  ResetIdle.Init("ResetIdle", this);
  ResetIdle.SetOnHigh(make_pfn(fn(OnResetIdleHigh)));
  ResetIdle.SetOnLow(make_pfn(fn(OnResetIdleLow)));
  ResetIdle.ConnectTo(Reset);

  pCPU->SetTrap(apbROM[2] + 0x0BFF, pCPU->AllocCodeTrap(this, make_bpfn(fn(OnIdle))));
  Idle.Init("Idle", this);
  Idle.ConnectTo(pCPU->RDY);

}


////////////////////////////////////////
// IEC input

proc(OnATNHigh)
  push ThisReg
  mov ThisReg,ECX

  mov AL,mvar(bIECIn)
  and AL,01111111b
  mov mvar(bIECIn),AL
  SetPort(mvar(IECOut), 1)

  SetLow(mvar(NotATN), 1)

  pop ThisReg
  ret
endp


proc(OnATNLow)
  push ThisReg
  mov ThisReg,ECX

  mov AL,mvar(bIECIn)
  or AL,10000000b
  mov mvar(bIECIn),AL
  SetPort(mvar(IECOut), 1)
 
  SetHigh(mvar(NotATN), 1)

  pop ThisReg
  ret
endp


proc(OnClockHigh)
  push ThisReg
  mov ThisReg,ECX

  mov AL,mvar(bIECIn)
  and AL,11111011b
  mov mvar(bIECIn),AL
  SetPort(mvar(IECOut), 1)

  pop ThisReg
  ret
endp


proc(OnClockLow)
  push ThisReg
  mov ThisReg,ECX

  mov AL,mvar(bIECIn)
  or AL,00000100b
  mov mvar(bIECIn),AL
  SetPort(mvar(IECOut), 1)

  pop ThisReg
  ret
endp


proc(OnDataHigh)
  push ThisReg
  mov ThisReg,ECX

  mov AL,mvar(bIECIn)
  and AL,11111110b
  mov mvar(bIECIn),AL
  SetPort(mvar(IECOut), 1)

  pop ThisReg
  ret
endp


proc(OnDataLow)
  push ThisReg
  mov ThisReg,ECX

  mov AL,mvar(bIECIn)
  or AL,00000001b
  mov mvar(bIECIn),AL
  SetPort(mvar(IECOut), 1)

  pop ThisReg
  ret
endp


////////////////////////////////////////
// IEC output

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

  // Clock (inverted)
  test AH,00001000b
  je NoClock
  push EAX
  test AL,00001000b
  jne ClockLow
  SetHigh([ThisReg]CIECDevice.Clock, 1)
  jmp ClockCont
ClockLow:
  SetLow([ThisReg]CIECDevice.Clock, 1)
ClockCont:
  pop EAX
NoClock:

  // Data = ~(Data out bit 1) & ~(-ATN in bit 7 ^ ATN A bit 4)
  test AH,10010010b
  je NoData
  push EAX
  test AL,00000010b
  jne DataLow
  and AL,10010000b
  je DataHigh
  cmp AL,10010000b
  jne DataLow
DataHigh:
  SetHigh([ThisReg]CIECDevice.Data, 2)
  jmp DataCont
DataLow:
  SetLow([ThisReg]CIECDevice.Data, 2)
DataCont:
  pop EAX
NoData:

  pop ThisReg
  pop EBX
  }
}


////////////////////////////////////////
// FDC control

void CVC1541::OnControlChange(byte bState, byte bChanges) {
  __asm {
  push ThisReg
  mov ThisReg,this
  mov AL,bState
  mov AH,bChanges

  // motor
  test AH,00000100b
  je NoMotor
  push EAX
  test AL,00000100b
  je MotorOff
  SetHigh(mvar(Motor), 1)
  jmp MotorCont
MotorOff:
  SetLow(mvar(Motor), 1)
MotorCont:
  pop EAX
NoMotor:

  // drive LED
  test AH,00001000b
  je NoLED
  push EAX
  test AL,00001000b
  je LEDOff
  SetHigh(mvar(LED), 2)
  jmp LEDCont
LEDOff:
  SetLow(mvar(LED), 2)
LEDCont:
  pop EAX
NoLED:

  // speed control
  test AH,01100000b
  je NoSpeed
  push EAX
  shr EAX,4
  and EAX,00000110b
  neg EAX
  add EAX,32
  Send(mvar(Clocks), 1)
  pop EAX
NoSpeed:

  pop ThisReg
  }
}


////////////////////////////////////////
// FDC input Sync

proc(OnSyncHigh)
  push ThisReg
  mov ThisReg,ECX

  mov AL,mvar(bControlIn)
  or AL,10000000b
  mov mvar(bControlIn),AL

  SetPort(mvar(Control), 1)

  pop ThisReg
  ret
endp


proc(OnSyncLow)
  push ThisReg
  mov ThisReg,ECX

  mov AL,mvar(bControlIn)
  and AL,01111111b
  mov mvar(bControlIn),AL

  SetPort(mvar(Control), 1)

  pop ThisReg
  ret
endp


////////////////////////////////////////
// FDC input WPS

proc(OnWPSHigh)
  push ThisReg
  mov ThisReg,ECX

  mov AL,mvar(bControlIn)
  or AL,00010000b
  mov mvar(bControlIn),AL

  SetPort(mvar(Control), 1)

  pop ThisReg
  ret
endp


proc(OnWPSLow)
  push ThisReg
  mov ThisReg,ECX

  mov AL,mvar(bControlIn)
  and AL,11101111b
  mov mvar(bControlIn),AL

  SetPort(mvar(Control), 1)

  pop ThisReg
  ret
endp


////////////////////////////////////////////////////////////////////////////////
// fast loading file into buffer

int CVC1541::Load(CString NewName, byte* pbBuffer, int iSize) {

  byte abTrack[21 * 256];
  try {

    CBMFileName Name;
    Name.Parse(NewName, 0, 16);
    if (Name.fDirectory) {
      return 0;
    }

    // search the directory for the file name
    ReadTrack(18, abTrack);
    int iSector = 1;
    do {
      byte* pbEntry = abTrack + iSector * 256 + 2;
      for (int i = 0; i < 8; i++) {
        if (pbEntry[0] != 0) {
          byte* pbEnd = pbEntry + 3 + 16;
          while (pbEnd > pbEntry + 3 && pbEnd[-1] == 160) {
            pbEnd--;
          }
          if (Name.Match((char*)pbEntry + 3, (char*)pbEnd)) {
            if (Name.cType != '?' && Name.cType != "DSPUR           "[pbEntry[0] & 15]) {
              error("64,FILE TYPE MISMATCH,00,00");
            }
            return Load(abTrack, pbEntry[1], pbEntry[2], pbBuffer, iSize);
          }
        }
        pbEntry += 32;
      }
      if (abTrack[iSector * 256] != 18) {
        break;
      }
      abTrack[iSector * 256] = 0; // avoid endless loop
      iSector = abTrack[iSector * 256 + 1];
    } while (iSector < 19);
  } catch (...) {
  }
  return 0;
}


int CVC1541::Load(byte* pbTrack, int iTrack, int iSector, byte* pbBuffer, int iSize) {

  int iData = 0;
  for (;;) {
    int iSectors = ReadTrack(iTrack, pbTrack);
    for (;;) {
      if (iSector > iSectors) {
        error("67,ILLEGAL TRACK OR SECTOR,%02d,%02d", iTrack, iSector);
      }

      // copy data bytes
      byte* pbSector = pbTrack + iSector * 256;
      int iBytes = 254;
      if (pbSector[0] == 0) {
        iBytes = pbSector[1] - 1;
      }
      if (iData + iBytes > iSize) {
        return 0;
      }
      memcpy(pbBuffer + iData, pbSector + 2, iBytes);
      iData += iBytes;

      // next sector
      iSector = pbSector[1];
      if (pbSector[0] != iTrack) {
        iTrack = pbSector[0];
        if (iTrack == 0) { // last sector
          return iData;
        }
        break;
      }
    }
  }
  return 0;
}


int CVC1541::ReadTrack(int iTrack, byte* pbBuffer) {

  if (iTrack < 1 || iTrack > 35) {
    error("67,ILLEGAL TRACK OR SECTOR,%02d,00", iTrack);
  }

  // calculate position and size
  int iOffset;
  int iSectors;
  if (iTrack < 18) {
    iOffset = (iTrack - 1) * 21;
    iSectors = 21;
  } else if (iTrack < 25) {
    iOffset = (18 - 1) * 21 + (iTrack - 18) * 19;
    iSectors = 19;
  } else if (iTrack < 31) {
    iOffset = (18 - 1) * 21 + (25 - 18) * 19 + (iTrack - 25) * 18;
    iSectors = 18;
  } else {
    iOffset = (18 - 1) * 21 + (25 - 18) * 19 + (31 - 25) * 18 + (iTrack - 31) * 17;
    iSectors = 17;
  }

  // read the track
  SetFilePointer(FDC.hFile, iOffset * 256, NULL, FILE_BEGIN);
  ReadFile(FDC.hFile, pbBuffer, iSectors * 256);
  return iSectors;
}


////////////////////////////////////////////////////////////////////////////////
// constructor

/*
  byte* pbRAM;          // 2 kb at $x000-$x7FF
  byte* pbIO;           // 4 kb at $1000-$1FFF
  byte* pbDummy;        // 4 kb for dummy reads and writes
  static byte* apbROM[4];// 16 kb Original.154 at $C000-$FFFF
  static int iROMRef;   // number of 1541 floppies with Original.154

  CCPU65xx CPU;         // 6502
  CVIA6522 VIA1;        // $1800 serial bus
  CVIA6522 VIA2;        // $1C00 drive control
  CFDC325572 FDC;       // floppy disk controller
  CLine IRQ;

  CLine NotATN;
  CPort IECOut;
  byte bIECIn;          // IEC address bits in PB 5-6
  byte abFiller[3];

  CPort Control;        // $1C00 drive control
  CLine Motor;          // PB2 drive motor
  CLine LED;            // PB3 drive LED
  CLine WPS;            // PB4 write protect switch
  CSendMsg Clocks;      // PB5/PB6 speed control
  CLine Sync;           // PB7
  CLine OE;
  byte bControlIn;
  byte abControlFill[3];
*/


CVC1541::CVC1541(int iDevice, CIECMaster* pIECMaster) {

  Object::Init("VC1541", NULL);
  extern CC64* gpC64;

  pCPU = new CPU6502;
  pCPU->Init("CPU", this);
  VIA1.Init("VIA1", this);
  VIA2.Init("VIA2", this);

  // allocate memory
  pbRAM = pCPU->AllocMem(4096);
  pbIO = pCPU->AllocMem(4096);
  pbDummy = pCPU->AllocMem(4096);
  if (iROMRef == 0) {
//C64 Kernal - Original R3.rom
//C64 Basic - Original.rom
//C64 Charset - Original.rom
//VC1541 - Original.rom
    HANDLE hFile = OpenFile(GetProgramDirectory("Original.154"));
    for (int i = 0; i < 4; i++) {
      ASSERT(apbROM[i] == NULL);
      apbROM[i] = pCPU->AllocMem(4096);
      ReadFile(hFile, apbROM[i], 4096);
    }
    CloseHandle(hFile);
  }
  iROMRef++;

  // map RAM and ROM
  pCPU->MapMem(0, pbRAM, pbRAM);
  pCPU->MapMem(1, pbIO, pbIO);
  for (int i = 2; i < 12; i++) {
    pCPU->MapMem(i, pbDummy, pbDummy);
  }
  for (i = 12; i < 16; i++) {
    pCPU->MapMem(i, apbROM[i - 12], pbDummy);
  }

  // map IO chips
  pCPU->MapChip(&VIA1, pbIO + 0x0800, 16);
  pCPU->MapChip(&VIA2, pbIO + 0x0C00, 16);

  // internal connections
  Reset.Init("Reset", this);
  Reset.ConnectTo(pCPU->Reset);
  Connect(VIA1.Reset, Reset);
  Connect(VIA2.Reset, Reset);
  IRQ.Init("IRQ", this);
  IRQ.ConnectTo(pCPU->IRQ);
  Connect(VIA1.Int, IRQ);
  Connect(VIA2.Int, IRQ);
  InitIdle();
  InitFormat();

  // connect VIA2 ports to FDC
  Connect(VIA2.PA, FDC.Data);
  Connect(FDC.Step, VIA2.PB);
  Motor.Init("Motor", this);
  Connect(FDC.MTR, Motor);
  Clocks.Init("Clocks", this);
  Connect(FDC.Clocks, Clocks);
  Control.Init("Control", this);
  Control.SetOnChange((pfnbb)OnControlChange);
  Connect(Control, VIA2.PB);

  // FDC control lines
  OE.Init("OE", this);
  Connect(VIA2.CB2, OE);
  Connect(FDC.OE, OE);
  Connect(VIA2.CA2, FDC.SOE);
  Connect(VIA2.CA1, FDC.ByteReady);
  Connect(pCPU->SO, FDC.ByteReady);

  // FDC input lines SYNC and WPS
  bControlIn = 0xFF;
  Sync.Init("Sync", this);
  Sync.SetOnHigh(make_pfn(fn(OnSyncHigh)));
  Sync.SetOnLow(make_pfn(fn(OnSyncLow)));
  Connect(Sync, FDC.Sync);
  WPS.Init("WPS", this);
  WPS.SetOnHigh(make_pfn(fn(OnWPSHigh)));
  WPS.SetOnLow(make_pfn(fn(OnWPSLow)));
  WPSOut.Init("WPSOut", this);
  Connect(WPS, WPSOut);
  LED.Init("LED", this);
  DiskChange.Init("DiskChange", this);
  DiskChange.SetOnFire((pfn)OnDiskChange);

  // connect IO chips to IEC bus
  ATN.Init("ATN", this);
  ATN.SetOnHigh(make_pfn(fn(OnATNHigh)));
  ATN.SetOnLow(make_pfn(fn(OnATNLow)));
  NotATN.Init("NotATN", this);
  Clock.Init("Clock", this);
  Clock.SetOnHigh(make_pfn(fn(OnClockHigh)));
  Clock.SetOnLow(make_pfn(fn(OnClockLow)));
  Data.Init("Data", this);
  Data.SetOnHigh(make_pfn(fn(OnDataHigh)));
  Data.SetOnLow(make_pfn(fn(OnDataLow)));
  IECOut.Init("IECOut", this);
  IECOut.SetOnChange((pfnbb)OnIECChange);
  Connect(IECOut, VIA1.PB);
  bIECIn = (byte)(0x1A | ((iDevice - 8) << 5));
  __asm {
    push ThisReg
    mov ThisReg,this
    mov AL,mvar(bIECIn)
    SetPort(mvar(IECOut), 1)
    SetLow(mvar(NotATN), 1);
    pop ThisReg
  }
  Connect(VIA1.CA1, NotATN);

  // connect IEC bus to C64
  Connect(Reset, pIECMaster->Reset);
  Connect(ATN, pIECMaster->ATN);
  Connect(Clock, pIECMaster->Clock);
  Connect(Data, pIECMaster->Data);

}


////////////////////////////////////////////////////////////////////////////////
// destructor

CVC1541::~CVC1541() {
  if (pCPU != NULL) {
    iROMRef--;
    if (iROMRef == 0) {
      for (int i = 0; i < 4; i++) {
        pCPU->FreeMem(apbROM[i], 4096);
        apbROM[i] = NULL;
      }
    }
    delete pCPU;
    pCPU = NULL;
  }
}
