/*
*
*     SYS.C.SYS - system-wide general functions header
*
*         21-12-91 INH  Original
*         14-02-92       Event logic changed
*         13-04-92       Timer added
*         14-04-92       Rewritten with R12 values
*         24-11-93       Messages look-up
*         16-05-95       Add time to trace function
* 2.15  1997.10.14 RJW   Callback fns ARMcoded -> SYSs.s
*       1998.09    MB    Added tracker option and compressedtrace feature
* 2.33  1998.09.03 W     Added DebugT option to SYStrace.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>

/*#include "wimp.h"*/
/*#include "werr.h"*/
/*#include "msgs.h"*/
/*#include "os.h"*/

#include "kernel.h"
#include "swis.h"
#include "wimplib.h"

#include "sys.h.stdtypes"
#include "sys.h.sys"
#include "sys.h.config"
#include "sys.h.hrdstate"
#include "sys.h.FEstate"
#include "sys.h.syss"
#include "sys.h.cpu"
#include "sys.h.devices"
#include "module.h.pcsupport"
#include "module.h.pcdevhelp"

#define new(Type)   (Type *)malloc(sizeof(Type))

#define DEBUG 0
#define ARMCALLBACKS 1  /* callback fns done in assembler */

/* Globals *********************** */

struct HardwareState *SYS_State_Ptr;
struct FrontEndState *SYS_FEState_Ptr;
int TraceDestination = TraceFile;

bool                  SYS_ErrorPending;

BYTE SYS_TempBuf [SYS_BufSize];


/* Trace and debug routines ******************************* */

static char stringbuf[256];


/* **************** */

static FILE *serial_fh=NULL;
static int  trackhandle=0;

#define Tracker_Open   0xCF000
#define Tracker_Close  0xCF001
#define Tracker_SetPos 0xCF002
#define Tracker_WriteS 0xCF003
#define Tracker_CLS    0xCF004
#define Tracker_Simple 0xCF005

static char tracker_title[]="!PC\0";

static void SYS__CloseTracker(void)
{
  _kernel_swi_regs R;

  R.r[0] = trackhandle;
  _kernel_swi(Tracker_Close, &R, &R);
}

static char last_traced[256]="\0";
static int  times_traced=0;
static int  debugT=0;

void SYS_trace   ( char *s, ... )
{
  _kernel_swi_regs R;
  va_list x;
  char    str[256];

  if ( TraceDestination == TraceOff ) return;
  va_start (x, s);

  vsprintf(stringbuf, s, x);

  if (strcmp(stringbuf, last_traced) == 0) {
    times_traced++; return;
  }

  //if (strcmp(stringbuf, "DebugT:" ) == 0)  /*this is DebugT info*/
   // debugT = 1;
  //if (strcmp(last_traced, "DebugT:") == 0)  /*2nd or later debugT chars */
    //debugT = 2;

  switch (TraceDestination)
  {
    case TraceFile    :
      if (times_traced) printf(" (x%d)\n", times_traced+1);
      //if (debugT == 1)
      printf("%d: %s\n", SYS_GetTime(), stringbuf);
      break;
    case TraceSerial  :
      if (serial_fh == NULL)
      {
        serial_fh = fopen("serial:", "wb");
        setvbuf(serial_fh, NULL, _IONBF, 0);
      }
      if (times_traced) fprintf(serial_fh, " (x%d)\n", times_traced+1);
      fprintf(serial_fh, "%d: %s\n", SYS_GetTime(), stringbuf);
      break;
    case TraceTracker :
      if (trackhandle == 0)
      {
        R.r[0] = (int) tracker_title;
        R.r[1] = 80;
        R.r[2] = 10000;
        R.r[3] = 1;
        _kernel_swi(Tracker_Open, &R, &R);
        trackhandle = R.r[0];
        atexit(SYS__CloseTracker);
      }
      else
        R.r[0] = trackhandle;
      R.r[1] = (int) str;
      if (times_traced)
      {
        sprintf(str, " (x%d)\n", times_traced+1);
        _kernel_swi(Tracker_WriteS, &R, &R);
      }
      sprintf(str, "%d: %s\n", SYS_GetTime(), stringbuf);
      _kernel_swi(Tracker_WriteS, &R, &R);
      break;
  }
  if (strcmp(stringbuf, last_traced) != 0)
    strcpy(last_traced, stringbuf),
    times_traced = 0;
}

/* *************** */

static char *quickhex = "0123456789ABCDEF";

void SYS_tracedata ( BYTE *s, int len )
{
  int i, d;

  if ( !CFG.Trace ) return;

  while (len > 0)
  {
    memset ( stringbuf, ' ', 64 );
    for ( i=0; i<16; i++ )
    {
      d = *s++;
      stringbuf[i*3] = quickhex[d>>4];
      stringbuf[i*3+1] = quickhex[d&15];
      stringbuf[48+i] = (d < 32 || d > 127) ? '.' : d;
      if ( --len <= 0 )
        break;
    }

    stringbuf[64] = 0;
    SYS_trace(stringbuf);
    /*printf("Tracedata[%s]\n", stringbuf);*/
  }

}

#if 0 /* moved into PCMEM module - not yet needed elsewhere */
/* Do OSCLI command */

void OScommand ( char * command )
{
  _kernel_swi_regs R;
  _kernel_oserror *err;

    R.r[0] = (int) command ;
    err = _kernel_swi ( OS_CLI, &R, &R );
    if ( err != NULL )
    {
     SYS_trace("Error giving command:%s:%s", command, err->errmess );
    }
}
#endif

/* **************** */


void SYS_error   ( bool fatal, char *s, ... )
{
  _kernel_oserror er;
  char *msg;
  va_list x;

  va_start (x, s);

  /* The 'tidy up' bit is done here so that trace msgs from shutdown & other
      event routines don't trample on stringbuf.
  */

  if ( SYS_FEState.FullFErunning )
  {
     /*SYS_callEvent (SYS_StopFE);
     SYS_FEState.FullFErunning = false;*/
     /*SYS_FEState.SuspendRequest = true;
     wimp_setmode(SYS_FEState.WimpMode.ModeNum);*/
     /*SYS_callEvent (SYS_StartWinFE);
     SYS_FEState.WinFErunning = true;*/
  }

  if ( fatal )
  {
    if ( SYS_FEState.HasInputFocus )
    {
       SYS_callEvent (SYS_LoseFocus);
       SYS_FEState.HasInputFocus = false;
    }

    if ( SYS_FEState.FullFErunning)
    {
       SYS_callEvent (SYS_StopFE);
       SYS_FEState.FullFErunning = false;
       wimp_set_mode(SYS_FEState.WimpMode.ModeNum);
    }

    if ( SYS_FEState.WinFErunning)
    {
       SYS_callEvent (SYS_StopWinFE);
       SYS_FEState.WinFErunning = false;
    }

    SYS_callEvent (SYS_Shutdown);
  }

  /* Lookup message and exit */

  msg = msgs_lookup(s);
  if ( msg==NULL || msg[0]==0 )
    sprintf(stringbuf, "Can't lookup error '%s'", s );
  else
    vsprintf(stringbuf, msg, x);

  strcpy(er.errmess, stringbuf);

  if ( fatal )
  {
    SYS_trace("Fatal: %s\n", stringbuf );
    wimp_report_error(&er, 0, "PC");
    exit(1);
  }

  SYS_trace("Warning: %s\n", stringbuf);
  wimp_report_error(&er, 0, "PC");
}

/* Miscellaneous routines ******************************** */

static int start_time=-1;

int SYS_GetTime()
{
  if ( start_time == -1) start_time = *((int*)0x10C);
  return * ((int *)0x10C)-start_time;
}

bool SYS_IntAvailable ( int level )
{
  if ( level< 0 || level >= 32 )
    return false;

  return (SYS_State.IntsAvailable & (1 << level) );
}

/* Routines for use by devices ******************************** */

void SYS_registerIO   ( int first, int last,
      RdFnPtr Rd8, RdFnPtr Rd16, WrFnPtr Wr8, WrFnPtr Wr16, int R12val )
{
  int i, firstslot, lastslot;

  firstslot = first >> 2; /* Assume I/O slots at 4-byte intervals */
  lastslot  = last  >> 2;

  if ( firstslot>lastslot || firstslot<0 || lastslot>=SYS_nIOSlots )
  {
    SYS_error(true, "ieparms2", "SYS_registerIO", first, last);
    return;
  }

  for (i=firstslot; i<=lastslot; i++)
  {
    SYS_State.IOhandlers[i].Read8   = Rd8;
    SYS_State.IOhandlers[i].Read16  = Rd16;
    SYS_State.IOhandlers[i].Write8  = Wr8;
    SYS_State.IOhandlers[i].Write16 = Wr16;
    SYS_State.IOhandlers[i].R12value= R12val;

  }
  #if DEBUG
    SYS_trace ("SYS_registerIO from %x to %x",(firstslot<<2),(lastslot<<2));
  #endif

}

/* *************** */

void SYS_registerMem  ( int first, int last,
      RdFnPtr Rd8, RdFnPtr Rd16, WrFnPtr Wr8, WrFnPtr Wr16, int R12val )
{
  int i, firstslot, lastslot;

  firstslot = (first-SYS_MemBase) >> 14; /* Assumes 16Kbyte memory chunks */
  lastslot  = (last -SYS_MemBase) >> 14;

  if ( firstslot>lastslot || firstslot<0 || lastslot>=SYS_nMemSlots )
  {
    SYS_error(true, "ieparms2", "SYS_registerMem", first, last);
    return;
  }

  for (i=firstslot; i<=lastslot; i++)
  {
    SYS_State.Memhandlers[i].Read8   = Rd8;
    SYS_State.Memhandlers[i].Read16  = Rd16;
    SYS_State.Memhandlers[i].Write8  = Wr8;
    SYS_State.Memhandlers[i].Write16 = Wr16;
    SYS_State.Memhandlers[i].R12value= R12val;
  }

}

/* **************** */

void SYS_removeIO     ( int first, int last)
{
  SYS_registerIO (first, last,
         SYS_State.NullHandlers.Read8,
         SYS_State.NullHandlers.Read16,
         SYS_State.NullHandlers.Write8,
         SYS_State.NullHandlers.Write16, 0 );


}

/* **************** */

void SYS_removeMem    ( int first, int last)
{
  SYS_registerMem (first, last,
         SYS_State.NullHandlers.Read8,
         SYS_State.NullHandlers.Read16,
         SYS_State.NullHandlers.Write8,
         SYS_State.NullHandlers.Write16, 0 );

}

/* **************** */

/* Each new event is added to the end of the current list of event
   handlers. Therefore, they will be called in the order of
   installation of the device modules
*/

void SYS_registerEvent ( int evtno,  EvtFnPtr newfn, int R12val )
{
  struct CallList *newnode, *p;

  newnode = new( struct CallList );
  if ( newnode == NULL )
    SYS_error( true, "ienomem" );

  newnode->fn.Evt = newfn;
  newnode->R12value = R12val;
  newnode->next = NULL;

  p = SYS_State.eventlist[evtno];
  if ( p == NULL )
     SYS_State.eventlist[evtno] = newnode;
  else
  {
     while ( p->next != NULL ) p = p->next;

     p->next = newnode;
  }
}

/* **************** */

void SYS_registerHPC ( int HPC_id,  HPCFnPtr newfn, int flg, int R12val )
{
  HPC_handler *newnode;
  int listno;

  newnode = new( HPC_handler );
  if ( newnode == NULL )
    SYS_error( true, "ienomem" );

  newnode->DispatchFn = newfn;
  newnode->ServiceID  = HPC_id;
  newnode->flags      = flg;
  newnode->requesting = false;
  newnode->R12_val    = R12val;

  listno = ServiceIDHashFn(HPC_id);

  newnode->next     = SYS_State.HPCHandlerLists[listno];
  SYS_State.HPCHandlerLists[listno] = newnode;
}

/* ******************** */

void SYS_registerCfg ( CfgFnPtr newfn, int R12val )
{
  struct CallList *newnode;

  newnode = new( struct CallList );
  if ( newnode == NULL )
    SYS_error( true, "ienomem" );
  newnode->fn.Cfg   = newfn;
  newnode->next     = SYS_State.Config_list;
  newnode->R12value = R12val;

  SYS_State.Config_list = newnode;
}

/* ----------------------- */

#if ARMCALLBACKS
/* this function in SYSs.s */
#else
void SYS_SetCallback ( Callback *pCB, OneIntFnPtr fnptr, int param )
{
  pCB->fn     = fnptr;
  pCB->R0val  = param;
  pCB->R12val = 0;
  SYS_trace("SYS_SetCallBack %08x r0=%08x", fnptr, param);
  _swi( PCSupport_SetCallback, _IN(0), pCB );
}
#endif

/* ***************************************************** */

void  SYS_Interrupt (int intno)
{
  SYS_State.CPU_Interrupt (intno);
}

/* **************** */

void  SYS_Activity ( int count )
{
  if ( count > SYS_FEState.Activity )
    SYS_FEState.Activity = count;
}

/* Routines for use by CPU module and front end ************************* */

void SYS_callEvent ( int evtno )
{
  struct CallList *p;

  for ( p = SYS_State.eventlist[evtno]; p != NULL; p = p->next )
  {
    if ( p->R12value == 0 )
    {
      p->fn.Evt();
    }
    else
    {
      _kernel_swi_regs r;
      SYSs_CallViaR12 ( &r, (int) p->fn.Evt, p->R12value );
    }
  }
}

/* **************** */

bool SYS_callCfg  ( char *text )
{
  struct CallList *p;
  bool res = false;

  for ( p = SYS_State.Config_list; p != NULL; p = p->next )
  {
    if ( p->R12value == 0 )
    {
      res = p->fn.Cfg(text);
    }
    else
    {
      _kernel_swi_regs r;
      r.r[0] = (int) text;
      res = SYSs_CallViaR12 ( &r, (int) p->fn.Cfg, p->R12value );
    }

    if ( res )
      return true;
  }

  return false;
}

/* **************** */

void SYS_DispatchHPC ( BYTE *buf )
{
  HPC_handler *p;
  int id;
  id =  buf[0] + (buf[1]<<8);

  for ( p = SYS_State.HPCHandlerLists[ServiceIDHashFn(id)];
        p != NULL;
        p = p->next )
  {
    if ( p->ServiceID == id )  /* Found it */
    {
      if ( p->R12_val == 0 )
        p->DispatchFn ( buf );
      else
      {
        _kernel_swi_regs r;
        r.r[0] = (int) buf;
        SYSs_CallViaR12 ( &r, (int) p->DispatchFn, p->R12_val );
      }

      return;
    }
  }

  SYS_trace("Unknown HPC Dispatch:");
  SYS_tracedata(buf, 16 );
  buf[0] = buf[1] = buf[2] = buf[3] = 0xFF;
}

/* **************** */

/* SYS_DoCallbacks ()

   Will process the list of all callback routines which
   have been registered with the 386PC_SetCallback SWI.
   This will traditionally be called as soon as the CPU
   emulation has stopped for any reason.
*/

#if ARMCALLBACKS
/* this function in SYSs.s */
#else
void SYS_DoCallbacks ( void )
{
  Callback *pCB;

  while ( pCB = (Callback*)_swi ( PCSupport_GetCallback, 0 ) )
  {
    if ( pCB->R12val == 0 )
      pCB->fn ( pCB->R0val );
    else
    {
      _kernel_swi_regs r;
      r.r[0] = pCB->R0val;
      SYSs_CallViaR12 ( &r, (int) pCB->fn, pCB->R12val );
    }
  }
}
#endif
/* --------------------- */

static bool ModuleShutdown(void)
{
  _kernel_swi_regs R;
  /* Tell support module we're going */
  R.r[0] = 0;
  R.r[1] = 0;
  _kernel_swi ( PCDevHelp_Init, &R, &R );

  /* Release card */
  R.r[0] = 0;
  _kernel_swi ( PCSupport_LockCard, &R, &R );

  return false;
}

/* Init routine *************************************** */

bool SYS_Init (void)
{
  _kernel_swi_regs R;
  float version ;

  /* Get details from PCSupport modules */

  if ( _kernel_swi ( PCSupport_ModuleInfo, &R, &R ) != NULL )
  {
    printf("PCSupport module is not loaded!\n");
    return false;
  }

  /* Returns R0 = module version * 100, R1 -> static data area,
       R2 = size of static data area */

  if ( R.r[0] != 223)
  {
    version =  (float)R.r[0]/100;
    printf("PCSupport module is the wrong version!\nFound version: %f.2, should be 2.23\n",version);
    return false;
  }

  if ( R.r[2] < (sizeof(struct HardwareState)+sizeof(struct FrontEndState)) )
  {
    printf("Eek! Harvey's got some structure sizes wrong!\n");
    return false;
  }

  SYS_State_Ptr   = (struct HardwareState *) R.r[1];
  SYS_FEState_Ptr = (struct FrontEndState *)
                           (R.r[1] + sizeof(struct HardwareState));

  /* Try to lock card */
  R.r[0] = 1;
  if ( _kernel_swi ( PCSupport_LockCard, &R, &R ) != NULL || R.r[0] != 0 )
  {
    printf("PC Card is already in use\n");
    return false;
  }

  /* Zero out structures */
  memset ( (BYTE *) SYS_State_Ptr, 0, sizeof(struct HardwareState) );
  memset ( (BYTE *) SYS_FEState_Ptr, 0, sizeof(struct FrontEndState) );

  SYS_State.current_ver = HRDSTATE_CURR_VER;
  SYS_State.min_compat_ver = HRDSTATE_MIN_VER;
  SYS_FEState.current_ver = FESTATE_CURR_VER;
  SYS_FEState.min_compat_ver = FESTATE_MIN_VER;

  R.r[0] = (int) SYS_State_Ptr;
  R.r[1] = (int) SYS_FEState_Ptr;
  _kernel_swi ( PCDevHelp_Init, &R, &R );

  /* Assume trace is on until we've finished processing Config */

  CFG.Trace = true;

  SYS_registerEvent ( SYS_Shutdown, ModuleShutdown, 0 );

  return true;
}

/* **************** */


extern void log_callback(int r0, int r1, int r2, int r3)
{
  NotUsed(r2);

  SYS_trace("cb @ %08x r0=%08x r12=%08x", r1, r0, r3);
}

extern void about_to_call_swi(void)
{
  SYS_trace("about to call SWI (last cb successful");
}
