// *********************************************************************
//    Copyright (c) 2009  Jean-Francois Garnier
//    Copyright (c) 2018  Christoph Giesselink
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// *********************************************************************

// *********************************************************************
// HP41Hpil.cpp : implementation file
// *********************************************************************

#include"StdAfx.h"
#include"V41.h"
#include"TcpIp.h"

const WORD Hpil::RFC = 0x500;               // RFC frame

Hpil::Hpil(HP41 *pHP41,flag fEnableAutoIDY,flag fEnableRFC)
  : m_pHP41(pHP41)
  , m_fEnableAutoIDY(fEnableAutoIDY)
  , m_fEnableRFC(fEnableRFC)
  , m_hEventIDY(NULL)
  , m_hThreadIDY(NULL)
  , m_eMode(eNone)
  , m_dwCpuCycles(0)
  , m_wLastFrame(0)
  , m_wLastCmd(0)
  , m_bLoopClosed(true)                     // interface loop is closed
{
  InitializeCriticalSection(&m_csLockIDY);

  // HPIL registers
  memset(HPIL_REG,0,sizeof(HPIL_REG));
}

Hpil::~Hpil()
{
  DeleteCriticalSection(&m_csLockIDY);
}

/****************************/
// check for frame in transfer with 10s timeout (10*5780)
/****************************/
bool Hpil::IsFrameInTransfer(DWORD dwIncr)
{
  bool bInTransfer = m_eMode != eNone;
  if (bInTransfer)
  {
    m_dwCpuCycles += dwIncr;
    if (m_dwCpuCycles >= 10*5780)           // check for timeout
    {
      bInTransfer = false;
      m_eMode = eNone;
    }
  }
  return bInTransfer;
}

/****************************/
// worker thread for AUTO IDY frames
/****************************/
UINT __stdcall Hpil::AutoIdyWorkerThread(LPVOID pParam)
{
  Hpil *p = reinterpret_cast<Hpil *>(pParam);

  // light sleep, CA, AUTO IDY -> 10ms Timer, IDY frame
  // 0            0   0           0           0
  // 0            0   1           0           0
  // 0            1   0           0           0
  // 0            1   1           0           0
  // 1            0   0           0           0
  // 1            0   1           1           0
  // 1            1   0           0           0
  // 1            1   1           1           1

  ASSERT(p != NULL);

  while (WaitForSingleObject(p->m_hEventIDY,10) == WAIT_TIMEOUT)
  {
    if (   p->m_fEnableAutoIDY              // AUTO IDY feature enabled
        && p->m_pHP41->IsSleeping() == HP41::eLightSleep // light sleep
        && (p->HPIL_REG[0] & 0x40) != 0     // check CA (controller active)
       )
    {
      p->SendFrame(0x6C0);                  // send IDY C0 frame
    }
  }
  return 0;
}

/****************************/
// create AUTO IDY handler thread
/****************************/
bool Hpil::CreateAutoIdyThread()
{
  DWORD dwThreadAutoIdyId;

  if ((HPIL_REG[3] & 0x40) != 0)            // AUTO IDY on
  {
    ASSERT(m_hThreadIDY == NULL);
    VERIFY(m_hEventIDY = CreateEvent(NULL,FALSE,FALSE,NULL));

    // create AUTO IDY handler thread
    m_hThreadIDY = CreateThread(NULL,
                                0,
                                (LPTHREAD_START_ROUTINE)&AutoIdyWorkerThread,
                                this,
                                0,
                                &dwThreadAutoIdyId);
  }
  return m_hThreadIDY != NULL;
}

/****************************/
// destroy AUTO IDY handler thread
/****************************/
void Hpil::DestroyAutoIdyThread()
{
  if (m_hThreadIDY != NULL)                 // AUTO IDY thread running
  {
    SetEvent(m_hEventIDY);
    WaitForSingleObject(m_hThreadIDY,INFINITE);
    CloseHandle(m_hThreadIDY);
    m_hThreadIDY = NULL;
  }
  if (m_hEventIDY)                          // delete event
  {
    CloseHandle(m_hEventIDY);
    m_hEventIDY= NULL;
  }
}

/****************************/
// executes intelligent HPIL instructions
/****************************/
void Hpil::exec_perph_hpil()
{
  /* HPIL */
  if ((m_pHP41->Tyte1&0x002) == 0x000)      // CH= n
  {
    hpil_wr(m_pHP41->control_perph,m_pHP41->Tyte1>>2);
  }
  else if ((m_pHP41->Tyte1&0x03E) == 0x03A) // RDPTRN n
  {
    // the upper 4 bits with the register select are ignored,
    // the hardware use the register no. from the SELPF command
    const int n=hpil_rd(m_pHP41->control_perph);
    memset(&m_pHP41->C_REG[2],0,12);
    m_pHP41->C_REG[0]=n&15;
    m_pHP41->C_REG[1]=n>>4;
  }
  else if ((m_pHP41->Tyte1&0x03F) == 0x003) // ?PFSET n
  {
    // used as RTNCPU after pheripheral read
  }
}

/****************************/
// update HP41 FI register with HPIL flags
/****************************/
void Hpil::update_flags()
{
  EnterCriticalSection(&m_pHP41->csFI);
  {
    m_pHP41->FI_REG&=~(0x1f << 6);          // mask HPIL flags 10 - 6

    // mask interrupts with R1W FLGENB bit (2-complement)
    const byte ctrlReg = HPIL_REG[1] & -(HPIL_REG[8] & 0x1);

    // if hpil flag enabled, update FI
    // flags register 1 :   4:ifcr, 3:srqr, 2:frav, 1:frns, 0:orav
    m_pHP41->FI_REG |= (ctrlReg & 1)  << (10 - 0);
    m_pHP41->FI_REG |= (ctrlReg & 2)  << ( 9 - 1);
    m_pHP41->FI_REG |= (ctrlReg & 4)  << ( 8 - 2);
    m_pHP41->FI_REG |= (ctrlReg & 8)  << ( 7 - 3);
    m_pHP41->FI_REG |= (ctrlReg & 16) << ( 6 - 4);
  }
  LeaveCriticalSection(&m_pHP41->csFI);
}

/****************************/
// send frame to data bus
/****************************/
void Hpil::SendFrame(WORD wFrame)
{
  TRACE(_T("Frame send: %03X\n"),wFrame);
  m_pHP41->TraceHpilFrame(_T("send"),wFrame);

  EnterCriticalSection(&m_csLockIDY);
  {
    bool bNoDevice = TRUE;                  // frame not send

    // Power-On initialization sequence is IDY 23 IFC IDY 00 AAU AAD 01 ...
    // the sequence after interactive commands (like GTO ..) is IDY 00 AAU AAD 01 ...,
    // so IDY 23 IFC is unique at power on with a 82160A 1H module
    //
    // AUTO IDY generates IDY C0 frames, but "The 8 data bits of the automatically
    // generated IDY are undefined, so it should not be used for a parallel poll."

    // send frame to external loop when external loop is available
    // and the frame is IDY 23 or the loop is closed
    if (   m_pHP41->m_pTcpIp != NULL
        && (wFrame == 0x623 || m_bLoopClosed))
    {
      if (m_eMode == eNone)                 // calculator origin
      {
        m_eMode = eController;
        m_dwCpuCycles = 0;
      }
      else if (m_eMode == eDevice)          // loop origin
      {
        m_eMode = eNone;
        // TRACE(_T("End of Device: %u\n"),m_dwCpuCycles);
      }

      m_bLoopClosed = false;

      if (wFrame != RFC)                    // not a RFC frame
      {
        m_wLastFrame = wFrame;              // last send frame
        HPIL_REG[1] &= 0xf8;                // FRAV=FRNS=ORAV=0
        update_flags();
      }

      bNoDevice = m_pHP41->m_pTcpIp->TcpSendFrame(wFrame);
    }

    if (bNoDevice)                          // no next device
    {
      // simulate closed loop on no device
      m_eMode = eNone;                      // not in transfer any more
      HPIL_REG[1] = (HPIL_REG[1] & 0x18)    // preserve IFCR, SRQR
                  | (HPIL_REG[8] & 0xe0)    // C13, C12, C11, FRNS=0
                  | 0x05;                   // FRAV=ORAV=1, ack frame to stop repeating
      HPIL_REG[2] = (byte) wFrame;          // copy frame data
      update_flags();
    }
  }
  LeaveCriticalSection(&m_csLockIDY);
};

/****************************/
// receive frame and updates
// status and control registers
/****************************/
void Hpil:: RecvFrame(WORD wFrame)
{
  TRACE(_T("Frame recv: %03X\n"),wFrame);
  m_pHP41->TraceHpilFrame(_T("recv"),wFrame);

  m_bLoopClosed = true;

  // CMD frame and CA (controller active) and adding RFC frame enabled
  if ((wFrame & 0x700) == 0x400 && (HPIL_REG[0] & 0x40) != 0 && m_fEnableRFC)
  {
    m_wLastCmd = wFrame;                    // remember last CMD frame
    SendFrame(RFC);                         // send the RFC frame
    return;
  }

  EnterCriticalSection(&m_csLockIDY);
  {
    if (m_eMode == eNone)                   // loop origin
    {
      m_eMode = eDevice;
      m_dwCpuCycles = 0;
    }
    else if (m_eMode == eController)        // calculator origin
    {
      m_eMode = eNone;
      // TRACE(_T("End of Controller: %u\n"),m_dwCpuCycles);
    }

    // CA (controller active) and RFC frame
    if ((HPIL_REG[0] & 0x40) != 0 && wFrame == RFC)
    {
      wFrame = m_wLastCmd;                  // use the last CMD frame as answer
    }

    // adjust flags according to received frame and state:
    if ((wFrame&0x400)==0)                  // DOE : 0xx
    {
      if (HPIL_REG[0]&0x10)                 // LA
      {
        HPIL_REG[1] |= 5;                   // FRAV=ORAV=1
      }
      else if (HPIL_REG[0]&0x20)            // TA
      {
        if ((m_wLastFrame&0xff)!=(wFrame&0xff))
          HPIL_REG[1] |= 3;                 // FRNS=ORAV=1
        else
          HPIL_REG[1] |= 1;                 // ORAV=1
      }
      else
      {
        SendFrame(wFrame);                  // case LA=TA=0, retransmit DOE frames!
      }
      if ((wFrame&0x100)!=0)
        HPIL_REG[1] |= 8;                   // SRQR=1
      else
        HPIL_REG[1] &= ~8;                  // SRQR=0
    }

    else if ((wFrame&0x200)!=0)             // IDY : 11x
    {
      if ((HPIL_REG[0]&0x30)==0x30)         // TA=LA=1 (scope mode)
        HPIL_REG[1] |= 5;                   // FRAV=ORAV=1
      else
        HPIL_REG[1] |= 1;                   // ORAV=1
      if ((wFrame&0x100)!=0)
        HPIL_REG[1] |= 8;                   // SRQR=1
      else
        HPIL_REG[1] &= ~8;                  // SRQR=0
    }

    else if ((wFrame&0x100)==0)             // CMD : 100
    {
      if ((HPIL_REG[0]&0x30)==0x30)         // TA=LA=1 (scope mode)
        HPIL_REG[1] |= 5;                   // FRAV=ORAV=1
      else if (m_wLastFrame==wFrame)
        HPIL_REG[1] |= 1;                   // ORAV=1
      else
        HPIL_REG[1] |= 3;                   // FRNS=ORAV=1
      if (wFrame==0x490)                    // IFC
        HPIL_REG[1] |= 16;                  // IFCR=1
    }

    else                                    // RDY : 101
    {
      if ((HPIL_REG[0]&0x30)==0x30)         // TA=LA=1 (scope mode)
        HPIL_REG[1] |= 5;                   // FRAV=ORAV=1
      else if ((wFrame&0xc0)==0x40)         // ARG
        HPIL_REG[1] |= 5;                   // FRAV=ORAV=1
      else
      {
        if (m_wLastFrame==wFrame)
          HPIL_REG[1] |= 1;                 // ORAV=1
        else
          HPIL_REG[1] |= 3;                 // FRNS=ORAV=1
      }
    }

    if ((HPIL_REG[1] & 6) != 0)             // if FRNS or FRAV, returned frame in R1R & R2R
    {
      HPIL_REG[2] = (byte) wFrame;          // returned frame
      HPIL_REG[1] &= 0x1f;
      HPIL_REG[1] |= (wFrame & 0x700)>>3;   // save received bits 8-10 to R1R
    }
    update_flags();

    // check for AUTO IDY mode wakeup conditions
    if (   m_pHP41->IsSleeping() == HP41::eLightSleep // light sleep
        && ((HPIL_REG[3] & 0x40) != 0)                // AUTO IDY on
        && (
            (   ((HPIL_REG[0] & 0x40) != 0)           // CA active
             && ((HPIL_REG[1] & 0x08) != 0)           // SRQR=1
            )
            ||
            (   ((HPIL_REG[0] & 0x40) == 0)           // CA inactive
             && ((HPIL_REG[1] & 0x16) != 0)           // IFCR or FRAV or FRNS = true
            )
           )
       )
    {
     m_pHP41->Wakeup();                               // CPU wakeup
    }
  }
  LeaveCriticalSection(&m_csLockIDY);
};

/****************************/
// write to a HPIL chip register (1LB6 simulation)
/****************************/
void Hpil::hpil_wr(int reg, int n)
{
  n &= 255;

  switch (reg)
  {
    case 0: // status reg.
      if (n&1)
      {
        // MCL
        HPIL_REG[0] |= 0x80;                // SC=1
        HPIL_REG[1] &= 0xe1;                // IFCR=SRQR=FRNS=FRAV=0
        HPIL_REG[1] |= 1;                   // ORAV=1
        HPIL_REG[8] &= ~1;                  // FLGENB=0
        update_flags();

        m_eMode = eNone;                    // not in transfer any more
      }
      if (n&2)
      {
        // CLIFCR
        HPIL_REG[1] &= 0xef;                // IFCR=0
        update_flags();
      }
      HPIL_REG[0]=n&0xf9;                   // SLRDY & CLIFCR are self clearing
      break;

    case 1: // control reg.
      HPIL_REG[8] = n;                      // write to R1W
      update_flags();
      break;

    case 2: // data reg.
      // build frame
      n |= (HPIL_REG[8]&0xe0)<<3;           // get bits 8-10 from R1W
      SendFrame(n);                         // send frame
      break;

    case 3: // parallel poll reg.
      if ((HPIL_REG[reg] ^ n) & 0x40)       // AUTO IDY changed
      {
        TRACE(_T("AUTO IDY %s\n"),((n & 0x40) != 0) ? _T("enabled") : _T("disabled"));

        HPIL_REG[reg] = n;
        if ((n & 0x40) != 0)                // AUTO IDY bit set
        {
          CreateAutoIdyThread();            // start AUTO IDY generator
        }
        else                                // bit cleared
        {
          DestroyAutoIdyThread();           // stop AUTO IDY generator
        }
      }
      HPIL_REG[reg] = n;
      break;

    default:
      HPIL_REG[reg] = n;
  }
}

/****************************/
// read from a HPIL chip register (1LB6 simulation)
/****************************/
int Hpil::hpil_rd(int reg)
{
  int n;

  switch (reg)
  {
    case 2:
      n=HPIL_REG[1] & 6;                    // FRAV & FRNS
      HPIL_REG[1] &= 0xf9;                  // FRAV=FRNS=0
      if (n)
      {
        HPIL_REG[8] &= ~0xe0;               // clear CO3-CO1
        HPIL_REG[8] |= HPIL_REG[1] & 0xe0;  // copy CO3-CO1 from R1R to R1W
      }
      update_flags();
    break;
  }
  return HPIL_REG[reg];
}
