////////////////////////////////////////////////////////////////////////////////
// JoystickLPT.cpp -- this file is part of the Personal C64 Emulator
// available at http://ourworld.compuserve.com/homepages/pc64/develop.htm
//
// Reads C64 joysticks on LPT1 to LPT4

#include <EDK.h>


// timer requires a class
class JoystickLPT : public Object {

  int iLPT;         // LPT1 to LPT4
  int iPort;        // port address from BIOS Data Area
  flag fPC64Cable;  // 0 = Alternative joystick adaptor, 1 = on PC64 cable
  int iRefresh;     // system clocks between two reads
  Timer Read;       // next read
  void OnRead();

public:

  // constructor
  JoystickLPT() {
    iLPT = 0;
    iPort = 0;
    fPC64Cable = false;
    iRefresh = 0;
  }

public:

  // interface reflects directions and button
  Port State;

  // initialisation
  void Init(int iNewLPT, Clock* pNewClock);

};


// initialisation
void JoystickLPT::Init(int iNewLPT, Clock* pNewClock) {

  // check parameter
  assert(iNewLPT >= 1 && iNewLPT <= 4);
  iLPT = iNewLPT;

  // read the port address from BIOS
  iPort = GetLPTPort(iLPT);
  if (iPort == 0) {
    error("Port address of LPT%d is NULL", iLPT);
  }

  // read settings
  char ac[80];
  wsprintf(ac, "LPT%d on PC64 Cable (1 or 0)", iLPT);
  fPC64Cable = gconf.GetInt("Joystick", ac, 0);
  wsprintf(ac, "LPT%d Refresh (10..500 ms)", iLPT);
  iRefresh = pNewClock->ClocksFromSeconds(gconf.GetInt("Joystick", ac, 50) * 1e-3);

  // initialize subcomponents
  State.Init("State", this);
  Read.Init("Read", this);
  Read.SetClock(*pNewClock);
  Read.SetOnFire((pfn)OnRead);
  Read.StartCounter(iRefresh);

}


// read the joystick
void JoystickLPT::OnRead() {

  int i;
  if (fPC64Cable) {

    // clear up and down
    outp(iPort + 2, 0x00);

    // read fire
    outp(iPort + 0, 0x30);
    i = (inp2(iPort + 2) & 1) << 4;

    // read right
    outp(iPort + 0, 0x50);
    i |= (inp2(iPort + 2) & 1) << 3;

    // read left
    outp(iPort + 0, 0x60);
    i |= (inp2(iPort + 2) & 1) << 2;

    // clear left
    outp(iPort + 0, 0x70);

    // read down
    outp(iPort + 2, 0x08);
    i |= (inp2(iPort + 2) & 1) << 1;

    // read up
    outp(iPort + 2, 0x02);
    i |= (inp2(iPort + 2) & 1) << 0;

    // clear up
    outp(iPort + 2, 0x00);

    // invert all switches
    i ^= 0xFF;

  } else {

    // pull up resistors
    outp(iPort, 0xF1);

    // read the position
    i = inp2(iPort + 1) >> 3;

    // invert only BUSY, the other lines are low active
    i ^= 0xF0;

  }

  // return the result to the emulator
  State.SetOutput(i);

  // call me back sometimes
  Read.StartCounter(iRefresh);

}


// pointers to joysticks for LPT1 to LPT4
static JoystickLPT* gapJoystick[4];


// create and connect the LPT joysticks
void InitJoysticksLPT(Port& Primary, Port& Secondary, Clock* pNewClock) {

  // LPT1 to LPT4
  char ac[80];
  for (int i = 0; i < 4; i++) {

    // check if joystick is enabled
    wsprintf(ac, "Use C64 Joystick on LPT%d (1 or 0)", i + 1);
    if (gconf.GetInt("Joystick", ac, 0) != 0) {

      // create the joystick
      gapJoystick[i] = new JoystickLPT;
      gapJoystick[i]->Init(i + 1, pNewClock);

      // connect it to the primary or secondary port
      wsprintf(ac, "LPT%d Same C64 Port as Numpad Keys (1 or 0)", i + 1);
      flag fSamePort = gconf.GetInt("Joystick", ac, i & 1);
      gapJoystick[i]->State.ConnectTo(fSamePort ? Primary : Secondary);

    }
  }
}


// destroy the LPT joysticks
void ExitJoysticksLPT() {
  for (int i = 0; i < 4; i++) {
    if (gapJoystick[i] != NULL) {
      delete gapJoystick[i];
      gapJoystick[i] = NULL;
    }
  }
}
