/*
  0702941432 <- last update (yymmddhh'')
     _____________________________________________________________
    /                                                             \ 
    | Module:       expmisc.cpp - explib's miscellaneous functions
    \_____________________________________________________________/

    Description:    this contains classes & functions not large enough
                    to create own .h and .cpp files for it.

    Refers to:      explib.h
    Used by:        everywhere

    Contents:
        see expmisc.h

    Local Terminology:

        Abbrev. Meaning
        ------- ------------------------
        ...     ...


    Developers: ID: Name:
    =========== --- -----
                JTH Juergen Thumm
  _____________________________________/VERSION HISTORY\____________________
 /Date_ Version Who What____________________________________________________\ 
 yymmdd ------- --- ---------------------------------------------------------
 930823 0.500   JTH created
 940201             renamed file to 'misc.cpp'
 940207             FineTimer; remain() run low bug fixed
 940213 0.6         StringLib (not yet tested!!), HexDump;
 940908         JTH port to Windows 3.1; see .h
 941203             FastTrig.
 941209             FineTimer moved into this file.
 950311             FineTimer: run-time switch if to use hardware access.
                    this switch works only if compiled with ALLOW_HW.
 950312             FineTimer::open()/close(). opens implicitely in ctr.
 950422             reworked altrand().
*/

#   include <math.h>

#   include "expdef.h"

#   ifdef   WIN16
#   include <dos.h>
#   include <mmsystem.h>
#   endif

#   include "explib.h"
#   include "misc.h"

//  ModInit(misc_o);

long filesize(const char *filename)
{_
 FILE *f=fopen(filename,"r");
 long size;

    if(!f)  return 0;

    fseek(f, 0, SEEK_END);

    if((size = ftell(f)) < 0)   size = 0;   // error case

    fclose(f);

    return size;
}

uchar *loadfile(const char* filename, ulong* retsize, char* mode)
{
 FILE  *f;
 long   s;
 uchar HUGE *m;

    if(!(s=filesize(filename))) return 0;

    if(retsize) *retsize = s;

#ifdef  WIN16
    if(!(m=(uchar HUGE *)farcalloc(s+2, 1)))    return 0;
#else
    if(!(m=(uchar HUGE *)calloc(s+2, 1)))       return 0;
#endif

    if(!(f=fopen(filename, mode)))
    {
#ifdef  WIN16
        farfree(m);
#else
        free(m);
#endif
        return 0;
    }

#ifdef  WIN16
    farfread(m, 0x1, s, f);
#else
    fread(m, 0x1, s, f);    
#endif

    fclose(f);

    return  (uchar *)m;
}

long farfread(void HUGE *dest, long bsize, long nblocks, FILE *f)
{
 long nread, ntotal=0, remain = bsize * nblocks;
 uchar HUGE *m = (uchar HUGE *)dest;
#define TMPSIZE 8192
 static uchar tmpbuf[TMPSIZE+2];

    while(remain > 0)
    {
        nread   = fread(tmpbuf, 0x1, min(remain,TMPSIZE), f);

        if(nread <= 0)  break;  // error, eof

#ifdef  WIN16
        _fmemcpy((void far *)(m + ntotal), tmpbuf, nread);
#else
        memcpy(      (void *)(m + ntotal), tmpbuf, nread);
#endif

        remain  -= nread;

        ntotal  += nread;
    }

    return  ntotal;
}

//  ================ String Library Functions ================
//|| StringLib 

uchar StringLib::buf[STRLIB_STRMAX+2];

char* StringLib::center(char* dest, char* src, size_t destsize)
{_
 size_t len = min(strlen(src), destsize);

    strncpy(&dest[(destsize-len)/2], src, len);

    return dest;
}

char* StringLib::tolower(char *str)
{_  while(*str) {*str = ::tolower(*str); str++;} return str;    }

char* StringLib::toupper(char *str)
{_  while(*str) {*str = ::toupper(*str); str++;} return str;    }

int StringLib::strnicmp(char *s1, char *s2, size_t cnt)
{_
 char c1,c2;

    while(cnt--)
    {
        c1  = ::toupper(*s1++); // use ctype.h's function
        c2  = ::toupper(*s2++);

        if(c1-c2)   return  c1 - c2;
    }

    return 0;   // strings equal
}

int StringLib::stricmp(char* s1, char* s2)
{_
 char c1,c2;

    while(*s1 && *s2)
    {
        c1  = ::toupper(*s1++);
        c2  = ::toupper(*s2++);

        if(c1-c2)   return  c1 - c2;
    }

    return *s1 - *s2;
}

char* StringLib::catf(char *str, char *format, ...)
{_
 va_list ap;

  va_start(ap, format);
   vsprintf(buf, format, ap);
  va_end(ap);

   return strcat(str, buf);
}

char* StringLib::printf(char *format, ...)
{_
 va_list ap;

  va_start(ap, format);

   securebuf(); // prepare for writing

   vsprintf(buf, format, ap);

   checkbuf();  // issues SysErr on buffer overflow

  va_end(ap);

   return buf;
}

// ===== writing a hex dump from some memory block to a file =====
//|| HexDump   

void HexDump(char *strptr, size_t strsize, FILE *dest)
{_
 char tbuf[100];
 int  ind = 3;      /* indent */
 int  outlen, tint, tint2, outl2;
 struct { char *data; size_t size; } buf;
 unsigned char *bp,uchr;

  buf.data  = strptr;
  buf.size  = strsize;

  while(buf.size > 0)
  {
    /* Binary logging: dump upto 16 bytes per line as HEX */

    bp      = buf.data;
    outlen  = min(16, (int)buf.size);

    /*                      1         2         3         4          */
    /*            01234567890123456789012345678901234567890123456789 */
    strcpy(tbuf, "   >                                                "
                 "                   " );
    outl2   = outlen;
    for(tint=1+ind, tint2=51+ind; outl2; outl2--, tint += 3, tint2++)
    {
        uchr    =   *bp++;

        sprintf(tbuf + tint, "%02X ", uchr);
        if(!isprint(uchr))  uchr = 0x1; /* nonprintable char */
        tbuf[tint2] = uchr;
    }
    tbuf[tint-1]    = '<';
    tbuf[tint  ]    = ' ';

    fprintf(dest, "%s\n", tbuf);

    buf.data    += outlen;
    buf.size    -= outlen;
  }
}

static unsigned long ar_seed = 0;

void saltrand(int xseed)
{
    ar_seed  = (unsigned long)xseed;
}

int altrand()
{
    ar_seed += 0x36742C31UL;

    if(ar_seed < (unsigned long)LONG_MAX)
    {
        ar_seed = (ar_seed << 1);
    }
    else
        ar_seed = (ar_seed << 1) | 0x1;

    ar_seed +=  (       (ar_seed >> 24) & 0xFFUL
                    +   (ar_seed >> 16) & 0xFFUL
                    +   (ar_seed >>  8) & 0xFFUL
                    +   (ar_seed      ) & 0xFFUL    );

    ar_seed ^= 0xC5C5C5C5UL;

    return  ar_seed % RAND_MAX;
}

//  ------------ keyboard class ------------

uchar KeyBoard::states[256];

ulong KeyBoard::instcnt = 0;

KeyBoard::KeyBoard()
{
    if(instcnt++ == 0)

        memset(states, 0, sizeof(states));
}

KeyBoard::~KeyBoard()
{
    if(instcnt)

        instcnt--;
}

uchar KeyBoard::stateof(int key)
{
    //  get hardware status of key 'key'

    uchar   nowstate;

#ifdef  AMIGA
    volatile uchar* keyport = (uchar*)0xbfec01;

    //  translate KEY_ to Amiga ScanCode:

    static uchar KeyToAScan[] =
    
        {
            KEY_ESCAPE, 116,
            KEY_LEFT,    96,
            KEY_RIGHT,   98,

            KEY_SPACE,  126,

            ',',  52,
            '.',  48,

            'P', 204,
            'X', 154,

            0x00, 0x00  // END OF TABLE
        };

    uchar   *ttp = KeyToAScan;

    while(*ttp)
        {
            if(*ttp == (uchar)key)
                {   key = *++ttp;   break;  }
            ttp += 2;
        }

    nowstate    = (*keyport == ((uchar)key | 0x1)) ? KEYSTATE_DOWN : 0;
#endif

#ifdef  WIN16
    ushort systate = (ushort)GetAsyncKeyState(key);

    nowstate    = (systate & 0x8000) ? KEYSTATE_DOWN : 0;
#endif

    //  update states table and check first if state
    //  has changed

    uchar   old = states[key & 0xFF];

    states[key & 0xFF]  = nowstate;

    if(nowstate && !old)  nowstate |= KEYSTATE_PRESSED;

    return  nowstate;
}

//|| FastTrig
ulong  FastTrig::instcnt  = 0;
intf  *FastTrig::tsin     = 0;
intf  *FastTrig::ttan     = 0;
ushort FastTrig::idegmax  = 0;
ushort FastTrig::idegmask = 0;

bool FastTrig::AllocTabs()
{
    return(     (tsin = new intf[idegmax])
            &&  (ttan = new intf[idegmax])  );
}

void FastTrig::FreeTabs()
{
    if(tsin)    {   delete [] tsin;  tsin  = 0; }
    if(ttan)    {   delete [] ttan;  ttan  = 0; }
}

FastTrig::FastTrig(ushort ndeg)
{
    if(++instcnt == 1)  // 1st instance
    {
        idegmax     = ndeg;
        idegmask    = ndeg - 1;

        if(!AllocTabs())    return;
    }
    else
    if(idegmax != ndeg)
    {
        // a non-first instance CAN'T get another precision!

        FreeTabs();

        return; 
    }

    for(ushort x=0; x<idegmax; x++)
    {
        tsin[x] = (intf)
                  (
                       ::sin(
                            (double)x * 2.0 * 3.141592654 // angle * 2 PI
                          / (double)idegmax               // : max. angle
                         )
                          
                    * (double)(1L << INTF_SCALEB)   // scaled for intf
                  );

        ttan[x] = (intf)
                  (
                       ::tan(
                            (double)x * 2.0 * 3.141592654 // angle * 2 PI
                          / (double)idegmax               // : max. angle
                         )
                          
                    * (double)(1L << INTF_SCALEB)   // scaled for intf
                  );
    }
}

FastTrig::~FastTrig()
{
    if(--instcnt <= 0)
    {
        instcnt = 0;

        FreeTabs();
    }
}

//  --------------------- TIMER ----------------------

#ifndef NO_FINETIMER

//|| FineTimCode
void FineTimer::delay(long msec)
{
    // 'cause implementation of FineTimer supports just upto very few msec
    // per period, we use this rather clumsy loop for larger periods:

    for(long i=0; i<msec; i++)
    {
        beginperiod(1);
        waitforperiodend();
        endperiod();
    }
}

#ifdef  AMIGA

void FineTimer::beginperiod(long msec)
{
    if(msec > 22)
    {
        SysErr(2901941804); msec=22;
    }

 // What now follows is 100.00% hardware dependent and
 // runs only on Commodore AMIGA systems (and even there isn't
 // very conform with system guidelines)
 // But what else may we do to measure milliseconds?

 volatile uchar *xBFD400=(uchar*)0xBFD400;
 volatile uchar *xBFD500=(uchar*)0xBFD500;
 volatile uchar *xBFDE00=(uchar*)0xBFDE00;

 // set the 16 Bit timer division value:
 uword TIMVAL = (uword)714 * (uword)msec;   // MAX. VALUE FOR msec: 22
 // ^714 kHz divided by...
 //  714 = 1000 (=0,0010 sec.)
 // 1428 =  500 (=0,0020 sec.)
 // 1785 =  400 (=0,0025 sec.)
 const uchar TIMHISTART = (TIMVAL>>8);
 const uchar TIMLOSTART = (TIMVAL-(TIMHISTART<<8));

    *xBFDE00 = (uchar)0;    // stop CIA B, Timer A
    *xBFD400 = TIMLOSTART;  // load countdown value
    *xBFD500 = TIMHISTART;  // into latch (FIRST Lo, THEN Hi!)

    // program & start timer:
    *xBFDE00 = (uchar)0x1F;
    // ^source=systemclock/10,
    //  runmode=OneShot, 3.PBon=TRUE
}

void FineTimer::waitforperiodend()
{
 volatile uchar *xBFD100=(uchar*)0xBFD100;

    // wait for timer underflow:
    while(*xBFD100 & (uchar)(1<<6));    // test CIA-B, parallel port B
}

intf FineTimer::remain()
{
 volatile uchar *xBFD100=(uchar*)0xBFD100;
 volatile uchar *xBFD400=(uchar*)0xBFD400;
 volatile uchar *xBFD500=(uchar*)0xBFD500;
 uchar lo,hi1,hi2;
 uword bustick;

    // if timer has already run low, reading the counter
    // would return the max value. So check this:

    ifn(*xBFD100 & (uchar)(1<<6))   // test CIA-B, parallel port B
        return convfi(0);   // ran low: return 0

    // read remaining timer standing:

    do
    {
        hi1 = *xBFD500;
        lo  = *xBFD400;
        hi2 = *xBFD500;
    }
    while(hi2 != hi1);  // as long as hi underflowed while reading lo

    bustick = (((uword)hi1 << 8) | lo);

    return convfi((int)(bustick)) / 714;    // return as intfloat in msec's
}

#endif  // AMIGA timer class code

#ifdef  WIN16

#   define  ALLOW_HW    // allow usage of hardware timer
#   define  DELAY   for(x=0;x<10;x++)

static uword inst_cnt = 0;  // instance counter

FineTimer::FineTimer(ushort bUseHW)
{
    open(bUseHW);   // open as hard- or software timer
    inst_cnt++;
}

void FineTimer::open(ushort bUseHW)
{
 usehw  = bUseHW;

#ifdef ALLOW_HW
 if(usehw)
 {
    volatile ulong x;

    // initially setup value from which hardware timer
    // starts countdown loop:

    outportb(0x43, 0x34);   DELAY;

    // divide 1.19 MHz system clock by 32768,
    // i.e. timer will run down 27.536 milliseconds
    // in every period

    outportb(0x40, 0x00);   DELAY;  // low byte
    outportb(0x40, 0x00);   DELAY;  // high byte
 }
#endif  
}

FineTimer::~FineTimer()
{
    close();
    --inst_cnt;
}

void FineTimer::close(void)
{
#ifdef ALLOW_HW
 if(usehw)
 {
    volatile ulong x;

    outportb(0x43, 0x34);   DELAY;

    outportb(0x40, 0);      DELAY;
    outportb(0x40, 0);      DELAY;
 }
#endif
}

void FineTimer::beginperiod(long msec)
{
#ifdef ALLOW_HW
 if(usehw)
 {
    volatile ulong x;
    uchar uc;

    if(msec > 20)
    {
        perr("timer period too large, using 20 msec");
        msec = 20;
    }

    outportb(0x43, 0x00);   DELAY;
    uc  = inportb(0x40);    DELAY;  // low byte
    uc  = inportb(0x40);    DELAY;  // high byte

    tstart  = uc;   // remember initial timer state
    tlen    = (uword)msec << 2;     // msec's * 4 !
 }
 else
 {
#endif
    ustart  = timeGetTime();
    ulen    = msec;
#ifdef ALLOW_HW
 }
#endif
}

void FineTimer::waitforperiodend()
{
    // wait 'til countdown reaches zero

    while(remain());
}

intf FineTimer::remain()
{
#ifdef ALLOW_HW
 if(usehw)
 {
    volatile ulong x;
    uchar   uc;
    uword   diff;

    outportb(0x43, 0x00);   DELAY;
    uc  = inportb(0x40);    DELAY;
    uc  = inportb(0x40);    DELAY;  // high byte

    //  calc difference to tstart:

    if(uc <= tstart)

        diff    = tstart - uc;

    else

        diff    = tstart + (255 - uc);

    //  'diff' represents now milliseconds * 4

    //  'tlen' was also set in msec * 4

    //  required distance reached?

    if(diff >= tlen)

        return  convfi(0);  // over it: return 0

    else

        return  convfi(tlen - diff) >> 2;   // return rest
 }
 else
 {
#endif
    ulong   diff = timeGetTime() - ustart;

    if(diff >= ulen)    return  convfi(0);
    else                return  convfi(ulen - diff);
#ifdef ALLOW_HW
 }
#endif
}

#endif  // WIN16 timer class code

void FineTimer::endperiod()
{
}

#endif
