////////////////////////////////////////////////////////////////////////////////
// Loopback.cpp -- this file is part of the Emulator Developers Kit
// available at http://ourworld.compuserve.com/homepages/pc64/develop.htm
//
// Implements a loopback connector for the userport which allows the
// Test Suite to perform additional tests of the CIA6526:
//
//   PA2 (M) <--  PC (8)
//   PB1 (D) <--> PB2 (E)
//   PB0 (C)  --> FLAG (B)
//   PB3 (F) <--> CNT1 (4) <--> CNT2 (6)
//   PB4 (H) <--> SP1 (5) <--> SP2 (7)
//   PB6 (K)  --> RESET (3) with 15 nF against GND (low for 6 cycles min)
//
// In Visual C++ "Tools / Options / Directories", add
//   Include files                = C:\PC64Win\EDK\Include
//
// In Visual C++ "Build / Settings / Debug", set
//   Executable for debug session = ..\..\PC64Win.exe
//   Working Directory            =
//   Program arguments            = -extension Loopback.dll Loopback.p00
//
// Note that bidirectional user defined connections between port pins and/or
// lines *MUST* use OnEveryChange() and InputWithoutThis()! The functions
// OnHigh/Low(), IsInputHigh/Low(), OnPortChange() and GetInput() will only
// work with either unidirectional user defined connections or the
// bidirectional ConnectTo() function which is provided by the EDK.

#include <EDK.h>


////////////////////////////////////////////////////////////////////////////////
// define the Loopback class

class Loopback : public Object {
public:

  // interfaces
  Port PA;
  Port PB;
  Line PC;
  Line Flag;
  Line CNT;
  Line SP;
  Line Reset;
  Timer ResetEnd;

  // initialisation
  virtual void DoInit() {

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

    // initialize components
    PA.Init("PA", this);
    PB.Init("PB", this);
    PB.SetOnEveryChange((pfn)OnEveryPBChange);
    PC.Init("PC", this);
    PC.SetOnHigh((pfn)OnPCHigh);
    PC.SetOnLow((pfn)OnPCLow);
    Flag.Init("Flag", this);
    CNT.Init("CNT", this);
    CNT.SetOnEveryChange((pfn)OnEveryCNTChange);
    SP.Init("SP", this);
    SP.SetOnEveryChange((pfn)OnEverySPChange);
    Reset.Init("Reset", this);
    ResetEnd.Init("ResetEnd", this);
    ResetEnd.SetOnFire((pfn)OnResetEnd);
  }

private:

  // output from PB to lines
  void OnEveryPBChange() {
    byte bIn = PB.GetInputWithoutThis();

    // PB1 (D) <--> PB2 (E)
    byte bOut = 0;
    if ((bIn & 0x02) != 0) {
      bOut |= 0x04;
    }
    if ((bIn & 0x04) != 0) {
      bOut |= 0x02;
    }
    PB.SetOutput(PB.GetOutput() & ~0x06 | bOut);

    // PB0 (C)  --> FLAG (B)
    Flag.SetOutput((bIn & 0x01) != 0);

    // PB3 (F)  --> CNT1 (4) <--> CNT2 (6)
    CNT.SetOutput((bIn & 0x08) != 0);

    // PB4 (H)  --> SP1 (5) <--> SP2 (7)
    SP.SetOutput((bIn & 0x10) != 0);

    // PB6 (K)  --> RESET (3)
    if ((bIn & 0x40) == 0) {
      //if (Reset.IsOutputHigh()) {
        Reset.SetOutputLow();
        ResetEnd.StartCounter(6);
      //}
    }
  }

  // set RESET high again after 6 cycles
  void OnResetEnd() {
    Reset.SetOutputHigh();
  }

  // PB3 (F) <--  CNT1 (4) <--> CNT2 (6)
  void OnEveryCNTChange() {
    if (CNT.IsInputWithoutThisHigh()) {
      PB.SetOutput(PB.GetOutput() | 0x08);
    } else {
      PB.SetOutput(PB.GetOutput() & ~0x08);
    }
  }

  // PB4 (H) <--  SP1 (5) <--> SP2 (7)
  void OnEverySPChange() {
    if (SP.IsInputWithoutThisHigh()) {
      PB.SetOutput(PB.GetOutput() | 0x10);
    } else {
      PB.SetOutput(PB.GetOutput() & ~0x10);
    }
  }

  // PA2 (M) <--  PC (8)
  void OnPCHigh() {
    PA.SetOutput(0xFF);
  }
  void OnPCLow() {
    PA.SetOutput((byte)~0x04);
  }
};


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

RegisterPersistentClass(Loopback);


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

static void CreateLoopbackIfC64(Object* pObject) {

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

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

  // connect the Loopback lines to the C64 Userport
  p->PA.ConnectTo(*(Port*)pC64->GetChild("CIA2.PA"));
  p->PB.ConnectTo(*(Port*)pC64->GetChild("CIA2.PB"));
  p->PC.ConnectTo(*(Line*)pC64->GetChild("CIA2.PC"));
  p->Flag.ConnectTo(*(Line*)pC64->GetChild("CIA2.Flag"));
  p->CNT.ConnectTo(*(Line*)pC64->GetChild("CIA1.CNT"));
  p->CNT.ConnectTo(*(Line*)pC64->GetChild("CIA2.CNT"));
  //p->SP.ConnectTo(*(Line*)pC64->GetChild("CIA1.SP"));
  //p->SP.ConnectTo(*(Line*)pC64->GetChild("CIA2.SP"));
  p->Reset.ConnectTo(*(Line*)pC64->GetChild("Reset"));
  p->ResetEnd.SetClock(*pC64->GetClock());

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

  // don't delete the Loopback object now
  p.release();
}


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

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