////////////////////////////////////////////////////////////////////////////////
// Userport.cpp -- this file is part of the Emulator Developers Kit
// available at http://ourworld.compuserve.com/homepages/pc64/develop.htm
//
// Redirects userport at $DD01 to LPT (PRINT PEEK(56577) and POKE 56577,X).
//
// In Visual C++ "Tools / Options / Directories", add
//   Include files                = C:\PC64Win\EDK\Include\Win32
//
// In Visual C++ "Build / Settings / Debug", set
//   Executable for debug session = ..\PC64Win.exe
//   Working Directory            = ..
//   Program arguments            = -extension "Userport.dll"

#include <EDK.h>


////////////////////////////////////////////////////////////////////////////////
// define the Userport class

class Userport : public Object {

  // private member variables
  int iLPTPort;
  CPU6510C64* pCPU;
  byte* pbIO;
  byte bOldTrapDD01;
  byte bNewTrapDD01;
  byte bOldTrapDD03;
  byte bNewTrapDD03;
  byte bPR;
  byte bDDR;
  byte abFill[2];

  // reset the LPT port
  Line Reset;
  void OnReset();
  void SetOutput();

  // port access functions
  byte ReadPR();
  void WritePR(byte bValue);
  byte ReadDDR();
  void WriteDDR(byte bValue);

protected:

  // initialisation
  void DoInit();

public:

  // constructor
  Userport() {
    iLPTPort = 0;
    pCPU = NULL;
    pbIO = NULL;
    bOldTrapDD01 = 0;
    bNewTrapDD01 = 0;
    bOldTrapDD03 = 0;
    bNewTrapDD03 = 0;
    bPR = 0;
    bDDR = 0;
  }

  // destructor
  virtual ~Userport() {
    if (bNewTrapDD01 != 0) {
      pCPU->SetTrap(pbIO + 0x0D01, bOldTrapDD01);
      pCPU->FreeTrap(bNewTrapDD01);
      bNewTrapDD01 = 0;
    }
    if (bNewTrapDD03 != 0) {
      pCPU->SetTrap(pbIO + 0x0D03, bOldTrapDD03);
      pCPU->FreeTrap(bNewTrapDD03);
      bNewTrapDD03 = 0;
    }
  }

  // connect Userport to C64
  void ConnectToC64(C64* pC64);
};


////////////////////////////////////////////////////////////////////////////////
// register class for persistence

RegisterPersistentClass(Userport);


////////////////////////////////////////////////////////////////////////////////
// initialisation

void Userport::DoInit() {

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

  // get LPT port address
  int iLPT = gconf.GetInt("Extensions\\Userport", "LPT (1-4)", 1);
  iLPTPort = GetLPTPort(iLPT);
  if (iLPTPort == 0) {
    error("BIOS Data Area says that there is no LPT%d in this computer. Please switch to another LPT in the Control Center.", iLPT);
  }
  trace("$DD01 has been mapped to LPT%d at port address %X", iLPT, iLPTPort);

  // initialize components
  Reset.Init("Reset", this);
  Reset.SetOnLow((pfn)OnReset);

  // reset the LPT port
  OnReset();
}


////////////////////////////////////////////////////////////////////////////////
// reset the LPT port

void Userport::OnReset() {
  bPR = 0;
  bDDR = 0;
  SetOutput();
}

void Userport::SetOutput() {

  // do not set inputs low because they may be bidirectional
  outp(iLPTPort + 0, bPR | ~bDDR);

  // open drain control lines must be high in order to be used as inputs
  outp(iLPTPort + 2, 0x04);
}


////////////////////////////////////////////////////////////////////////////////
// port access functions

byte Userport::ReadPR() {

  // read the LPT
  int iData = inp2(iLPTPort + 0);
  int iStatus = inp2(iLPTPort + 1);
  int iControl = inp2(iLPTPort + 2);

  // separate the lines
  flag fError = (iStatus & 0x08) != 0;
  flag fSelect = (iStatus & 0x10) != 0;
  flag fPE = (iStatus & 0x20) != 0;
  flag fAck = (iStatus & 0x40) != 0;
  flag fBusy = (iStatus & 0x80) == 0;
  flag fAutoFD = (iControl & 0x02) == 0;
  flag fReset = (iControl & 0x04) != 0;
  flag fSelIn = (iControl & 0x08) == 0;

  // read only the inputs and return latch for the outputs
  return (((fError << 0) | (fAck << 1) | (fBusy << 2) | (fPE << 3) | (fSelect << 4) | (fAutoFD << 5) | (fReset << 6) | (fSelIn << 7)) & ~bDDR) | (bDDR & iData);
}

void Userport::WritePR(byte bValue) {
  bPR = bValue;
  SetOutput();
}

byte Userport::ReadDDR() {
  return bDDR;
}

void Userport::WriteDDR(byte bValue) {
  bDDR = bValue;
  SetOutput();
}


////////////////////////////////////////////////////////////////////////////////
// connect Userport to C64

void Userport::ConnectToC64(C64* pC64) {

  // connect Reset line
  Reset.ConnectTo(*(Line*)pC64->GetChild("Reset"));

  // hook reads and writes to $DD01
  pCPU = (CPU6510C64*)pC64->GetChild("CPU");
  pbIO = pC64->GetIO();
  bOldTrapDD01 = pCPU->GetTrap(pbIO + 0x0D01);
  bNewTrapDD01 = pCPU->AllocIOTrap(this, (bpfn)ReadPR, (pfnb)WritePR);
  pCPU->SetTrap(pbIO + 0x0D01, bNewTrapDD01);
  bOldTrapDD03 = pCPU->GetTrap(pbIO + 0x0D03);
  bNewTrapDD03 = pCPU->AllocIOTrap(this, (bpfn)ReadDDR, (pfnb)WriteDDR);
  pCPU->SetTrap(pbIO + 0x0D03, bNewTrapDD03);
}


////////////////////////////////////////////////////////////////////////////////
// create a new Userport object and connect it to the C64

static void CreateUserportIfC64(Object* pObject) {

  // check for C64 object
  if (typeid(*pObject) != typeid(C64)) {
    return;
  }
  C64* pC64 = (C64*)pObject;

  // create a new Userport object and make it a child of the C64
  auto_ptr<Userport> p = new Userport;
  p->Init("Userport", pC64);

  // map the register at address $DD01
  p->ConnectToC64(pC64);

  // delete the Userport object when the C64 is deleted
  p->SetAutoDelete();

  // don't let auto_ptr delete the Userport object now
  p.release();
}


////////////////////////////////////////////////////////////////////////////////
// dll entry

BOOL WINAPI DllMain(HINSTANCE, DWORD dwReason, LPVOID) {
  try {
    switch (dwReason) {
    case DLL_PROCESS_ATTACH:
      ForAllObjectsCall(CreateUserportIfC64);
      AddAfterInitHook(CreateUserportIfC64);
      break;
    case DLL_PROCESS_DETACH:
      RemoveAfterInitHook(CreateUserportIfC64);
      DeleteAllObjectsOfType(typeid(Userport));
      break;
    }
    return TRUE;
  } catch (...) {
    report();
    return FALSE;
  }
}
