// *********************************************************************
//    Copyright (c) 2001 Zharkoi Oleg
//    Revised 2004 Warren Furlow
//    Revised 2009 Reinhard Breuer - corrected SHIFT ON problem with flags
//
// 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.
// *********************************************************************

// *********************************************************************
// HP41Timer.cpp
//   Executes the instructions for the timer chip
// Ref: Zenrom Pg. 126, HEPAX VOL II Pg. 139-141, 1LF6 Timer Chip Detailed Description
//      1LF6 timer Chip Detailed Description
// *********************************************************************

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

// TimerSelA ==1 if TIMER=A, ==0 if TIMER=B
// Clock A is usually current time and B is usually stopwatch time (good until Dec 20, 2330)
// Alarm A is usually time of next alarm or cleared. B usually has a constant of 09999999999000h which is checked for timer integrity
// Scratch A is used to hold the last time when corrected.  B bit 5 is set for 24 hour, bit 6 is set for display of both time and date.
// Interval A,B - 56 bits total but only 20 are used: [4-0] sss.hh where sss = seconds, hh = hundredths
// Timer status - 13 bits
//   Bit  Meaning
//   0    ALMA  - Set on valid compare of Alarm A with Clock A
//   1    DTZA  - Set on overflow of clock A (or decrement of 10's complement)
//   2    ALMB  - Set on valid compare of Alarm B with Clock B
//   3    DTZB  - Set on overflow of Clock B
//   4    DTZIT - Set by terminal count state of Interval Timer (it has counted a whole interval)
//   5    PUS   - Power Up Status - set when power first applied or falls below a certain minimum
//   6    CKAEN - Enable Clock A incrementers
//   7    CKBEN - Enable Clock B incrementers
//   8    ALAEN - Enables comparator logic between Alarm A and Clock A - Set if Alarm A is enabled.  Since time alarms are usually enabled, this flag is usually set
//   9    ALBEN - Enables comparator logic between Alarm B and Clock B - Set if Alarm B is enabled.  Always clear since stopwatch alarms are not possible.  Timer alarms occur as a result of bit 3 set.
//  10    ITEN  - Enables Interval Timer incrementing and comparator logic (interval timer is running)
//  11    TESTA - Enables Test A mode
//  12    TESTB - Enables Test B mode
//
// This code does not do any accuracy factor corrections

/****************************/
void HP41::InitTimer()
  {
  if (fTimer)
    return;
  fTimer=TRUE;
  fTimerEnable=TRUE;                                          // enable timer increment
  ResetTimer();
  timeGetDevCaps(&tc,sizeof(tc));                             // get timer resolution
  bAccurateTimer = (timeBeginPeriod(tc.wPeriodMin) == TIMERR_NOERROR);
  TimerEvent=timeSetEvent(10,0,TimerProc,reinterpret_cast<DWORD_PTR>(this),TIME_PERIODIC|TIME_CALLBACK_FUNCTION);
  }


/****************************/
void HP41::ResetTimer()
  {
  if (!fTimer)
    return;
  TimerSelA=1;
  memset(CLK_A,0,sizeof(TMR_S)+((byte *)&TMR_S-(byte *)CLK_A));

  FI_REG_Timer = 0;                                           // FI flags timer

  // set to current time
  ConvertToReg14(ALM_B,9999999999000);                        // anti-corruption constant
  TMR_S[1]&=0x04;                                             // bit 6 - Clock A incrementing
  COleDateTime CurrentTime=COleDateTime::GetCurrentTime();    // preset the clock with current time
  UINT64 ClockA=(UINT64)((CurrentTime.m_dt-2.0)*8640000.0);   // this always overrides any user set value
  ConvertToReg14(CLK_A,ClockA);
  }


/****************************/
void HP41::DeInitTimer()
  {
  if (!fTimer)
    return;
  fTimer=FALSE;
  fTimerEnable=FALSE;                                         // disable timer increment
  if(TimerEvent)
    timeKillEvent(TimerEvent);
  TimerEvent=NULL;
  if (bAccurateTimer)                                         // "Accurate timer" running
    {
    timeEndPeriod(tc.wPeriodMin);                             // finish service
    }
  }


/****************************/
void HP41::TimerUpdateFI()
  {
  if (!fTimer)
    return;
  if (perph_selected == 0xfb)                                 // timer selected
    {
    EnterCriticalSection(&csFI);
      {
      FI_REG |= FI_REG_Timer;                                 // activate timer FI flags
      }
    LeaveCriticalSection(&csFI);
    }
  }


/****************************/
// check if register contain zero
bool HP41::IsRegZero(
  byte *REG)
  {
  bool bZero=true;

  // register buffer not aligned, so byte access
  for (int i=0;bZero && i<14;i++)
    {
    bZero=(REG[i]==0);
    }
  return bZero;
  }


/****************************/
// increment BCD timer register
void HP41::IncRegTimer(
  byte *REG)
  {
  byte c=1;         // inc register
  for (int i=0;i<14;i++)
    {
    byte d=REG[i]+c;
    c=(d>=10)?1:0;  // update carry
    REG[i]=d%10;
    }
  }


/****************************/
// increment all Time module timers
void HP41::IncAllTimers()
  {
  if (fTimer)                     // time module equipped
    {
    int fAlert=0;
    EnterCriticalSection(&csTimer);
    // Clock A
    if (TMR_S[1]&0x04)            // bit 6 - Clock A enabled
      {
      IncRegTimer(CLK_A);
      if (IsRegZero(CLK_A))
        {
        TMR_S[0]|=0x02;           // set bit 1 - overflow in clock A
        EnterCriticalSection(&csFI);
          {
          FI_REG|=0x2000;         // set flag 13 - general service request flag
          }
        LeaveCriticalSection(&csFI);
        FI_REG_Timer|=0x1000;     // set flag 12 - timer request
        Wakeup();
        }
      // if bit 8 set - enable ClockA & AlarmA comparator
      if (TMR_S[2]&0x01 && (memcmp(CLK_A,ALM_A,14) == 0))
        {
        TMR_S[0]|=0x01;           // set bit 0 - valid compare
        EnterCriticalSection(&csFI);
          {
          FI_REG|=0x2000;         // set flag 13 - general service request flag
          }
        LeaveCriticalSection(&csFI);
        FI_REG_Timer|=0x1000;     // set flag 12
        Wakeup();
        fAlert=1;
        }
      }
    // Clock B
    if (TMR_S[1]&0x08)            // bit 7 - Clock B enabled
      {
      IncRegTimer(CLK_B);
      if (IsRegZero(CLK_B))
        {
        TMR_S[0]|=0x08;           // set bit 3 - overflow in clock B
        EnterCriticalSection(&csFI);
          {
          FI_REG|=0x2000;         // set flag 13 - general service request flag
          }
        LeaveCriticalSection(&csFI);
        FI_REG_Timer|=0x1000;     // set flag 12
        Wakeup();
        }
      // if bit 9 set - enable ClockB & AlarmB comparator
      if (TMR_S[2]&0x02 && (memcmp(CLK_B,ALM_B,14) == 0))
        {
        TMR_S[0]|=0x04;           // set bit 2 - valid compare
        EnterCriticalSection(&csFI);
          {
          FI_REG|=0x2000;         // set flag 13 - general service request flag
          }
        LeaveCriticalSection(&csFI);
        FI_REG_Timer|=0x1000;     // set flag 12
        Wakeup();
        fAlert=1;
        }
      }
    // Interval Timer
    if (TMR_S[2]&0x04)            // bit 10 - interval timer enabled
      {
      IncRegTimer(INTV_CNT);
      if ((memcmp(INTV_CNT,INTV_TV,5) == 0))
        {
        memset(INTV_CNT,0,5);     // reset interval timer to zero
        TMR_S[1]|=0x01;           // set bit 4 - DTZIT - Decrement Through Zero Interval timer
        EnterCriticalSection(&csFI);
          {
          FI_REG|=0x2000;         // set flag 13 - general service request flag
          }
        LeaveCriticalSection(&csFI);
        FI_REG_Timer|=0x1000;     // set flag 12
        Wakeup();
        }
      }
    LeaveCriticalSection(&csTimer);
    // alert for an alarm
    if (fAlert)
      {
      if (theApp.m_pMainWnd->IsIconic())
        theApp.m_pMainWnd->ShowWindow(SW_RESTORE);
      theApp.m_pMainWnd->SetForegroundWindow();
      if (SoundMode==eSoundNone)
        for (int i=1;i<5;i++)
          MessageBeep(0xFFFFFFFF);
      }
    }
  }


/****************************/
// called every .01 sec (10 millisec)
/****************************/
void CALLBACK HP41::TimerProc(
  UINT uId,         // handle of window for timer messages
  UINT uMsg,        // WM_TIMER message
  DWORD_PTR dwUser, // user argument
  DWORD_PTR dw1,    // reserved
  DWORD_PTR dw2)    // reserved
  {
  HP41 *p = reinterpret_cast<HP41 *>(dwUser);

  if (p->fTimerEnable)                // timer increment enabled
    {
    p->IncAllTimers();                // increment all timers
    p->LastTimerUpdate=timeGetTime(); // the actual time stamp
    }
  }


/****************************/
// calculate no. of CPU instructions since last timer update
uint HP41::GetInstrTimerUpdate() const
{
  // elapsed time in ms
  uint elaspedTime=timeGetTime() - LastTimerUpdate;
  if (elaspedTime>10) elaspedTime=10;
  return INSTR_TIMERINC * elaspedTime / 1000;
}


/****************************/
// Converts UINT64 to Reg14 (BCD)
void HP41::ConvertToReg14(
  byte *DEST_REG,
  UINT64 Src)
  {
  EnterCriticalSection(&csTimer);
  for (int i=0;i<14;i++)
    {
    DEST_REG[i]=(byte)(Src%10);
    Src/=10;
    }
  LeaveCriticalSection(&csTimer);
  }


/****************************/
// Converts Reg14 (BCD) to UINT64
void HP41::ConvertToUINT64(
  UINT64 *Dest,
  byte *SRC_REG)
  {
  UINT64 temp=0;
  EnterCriticalSection(&csTimer);
  for (int i=13;i>=0;i--)
    {
    temp*=10;
    temp+=SRC_REG[i]%10;
    }
  *Dest=temp;
  LeaveCriticalSection(&csTimer);
  }


/****************************/
void HP41::TimerWrite()
  {
  switch (Modifier)
    {
    case 0:              // WTIME
      {
      EnterCriticalSection(&csTimer);
      if (TimerSelA)
        {
        memcpy(CLK_A,C_REG,14);
        }
      else
        {
        memcpy(CLK_B,C_REG,14);
        }
      LeaveCriticalSection(&csTimer);
      break;
      }
    case 1:              // WTIME-
      {
      EnterCriticalSection(&csTimer);
      if (TimerSelA)
        {
        memcpy(CLK_A,C_REG,14);
        }
      else
        {
        memcpy(CLK_B,C_REG,14);
        }
      LeaveCriticalSection(&csTimer);
      break;
      }
    case 2:              // WALM
      {
      if (TimerSelA)
        {
        memcpy(ALM_A,C_REG,14);
        }
      else
        {
        memcpy(ALM_B,C_REG,14);
        }
      break;
      }
    case 3:              // WSTS
      {
      if (TimerSelA)     // first 6 status bits may only be cleared
        {
        TMR_S[0]&=C_REG[0];
        TMR_S[1]&=(0x0C|(C_REG[1]&0x03));
        //RB++ check for bits 0 to 5 to be zero and clear flag 12 & 13
        // all flags should be buffered for each peripheral and ORed at the beginning of each instruction.
        // flag 12 should only indicate 1, if flag 13 is set AND Perph Addr=FB
        // see: 1LF6 detailed description -> pg. 6 -> 2.7 FLAG and System Interrupt
        if ((TMR_S[0] | (TMR_S[1] & 0x03)) == 0)
          {
          EnterCriticalSection(&csFI);
            {
            FI_REG &= 0xcfff;  // clear flag 12, 13
            }
          LeaveCriticalSection(&csFI);
          FI_REG_Timer = 0;    // clear flag 12
          }
        //RB--
        }
      else               // write 13 bit accuracy factor (not used in emulation)
        {
        ACC_F[3]=C_REG[4]&0x1;
        ACC_F[2]=C_REG[3];
        ACC_F[1]=C_REG[2];
        ACC_F[0]=C_REG[1];
        }
      break;
      }
    case 4:              // WSCR
      {
      if (TimerSelA)
        memcpy(SCR_A,C_REG,14);
      else
        memcpy(SCR_B,C_REG,14);
      break;
      }
    case 5:              // WINTST - set and start interval time
      {
      memcpy(INTV_TV,C_REG,5);  // set terminal count value
      memset(INTV_CNT,0,5);
      TMR_S[2]|=0x04;    // set bit 10- ITEN - Interval Timer Enable
      break;
      }
    case 7:              // STPINT - stop interval timer
      {
      TMR_S[2]&=0x0B;    // clear bit 10
      break;
      }
    case 8:              // WKUPOFF - clear test mode
      {
      if (TimerSelA)
        TMR_S[2]&=0x07;  // clear bit 11 - Test A mode
      else
        TMR_S[3]&=0x0E;  // clear bit 12 - Test B mode
      break;
      }
    case 9:              // WKUPON - set test mode
      {
      if (TimerSelA)
        TMR_S[2]|=0x08;  // set bit 11
      else
        TMR_S[3]|=0x01;  // set bit 12
      break;
      }
    case 0xA:            // ALMOFF
      {
      if (TimerSelA)
        TMR_S[2]&=0x0E;  // clear bit 8
      else
        TMR_S[2]&=0x0D;  // clear bit 9
      break;
      }
    case 0xB:            // ALMON
      {
      if (TimerSelA)
        TMR_S[2]|=0x01;  // set bit 8
      else
        TMR_S[2]|=0x02;  // set bit 9
      break;
      }
    case 0xC:            // STOPC
      {
      if (TimerSelA)     // should never stop Clock A or time will get messed up!
        TMR_S[1]&=0x0B;  // clear bit 6 - Clock A incrementer
      else
        TMR_S[1]&=0x07;  // clear bit 7 - Clock B incrementer
      break;
      }
    case 0xD:            // STARTC
      {
      if (TimerSelA)
        TMR_S[1]|=0x04;  // set bit 6
      else
        TMR_S[1]|=0x08;  // set bit 7
      break;
      }
    case 0xE:            // TIMER=B
      {
      TimerSelA=0;
      break;
      }
    case 0xF:            // TIMER=A
      {
      TimerSelA=1;
      break;
      }
    }
  }


/****************************/
void HP41::TimerRead()
  {
  switch (Modifier)
    {
    case 0:             // RTIME
      {
      EnterCriticalSection(&csTimer);
      if (TimerSelA)
        {
        memcpy(C_REG,CLK_A,14);
        }
      else
        {
        memcpy(C_REG,CLK_B,14);
        }
      LeaveCriticalSection(&csTimer);
      break;
      }
    case 1:             // RTIMEST
      {
      EnterCriticalSection(&csTimer);
      if (TimerSelA)
        {
        memcpy(C_REG,CLK_A,14);
        }
      else
        {
        memcpy(C_REG,CLK_B,14);
        }
      LeaveCriticalSection(&csTimer);
      break;
      }
    case 2:             // RALM
      {
      if (TimerSelA)
        memcpy(C_REG,ALM_A,14);
      else
        memcpy(C_REG,ALM_B,14);
      break;
      }
    case 3:             // RSTS
      {
      if (TimerSelA)
        memcpy(C_REG,TMR_S,14);
      else
        {
        memset(C_REG,0,14);
        for (int i=0;i<4;i++)
          C_REG[i+1]=ACC_F[i];
        }
      break;
      }
    case 4:             // RSCR
      {
      if (TimerSelA)
        memcpy(C_REG,SCR_A,14);
      else
        memcpy(C_REG,SCR_B,14);
      break;
      }
    case 5:             // RINT
      {
      memcpy(C_REG,INTV_TV,5);
      memset(&C_REG[5],0,9);
      break;
      }
    default:
      {
      break;
      }
    }
  }
