// $Id: afarutil.c,v 1.5 2012/05/08 18:24:00 lpoulsen Exp $
//
// Copyright (c) 2008 by Afar Communications Inc, Santa Barbara, CA 93117, USA
//
// $Log: afarutil.c,v $
// Revision 1.5  2012/05/08 18:24:00  lpoulsen
// Compile all modules with -pthread on Linux
// Clean up all compiler warnings
// Count packets in OUT OF MEMORY message
//
// Revision 1.4  2012/05/03 19:15:16  lpoulsen
// Correct minor compiler warnings and header file dependencies.
//
// Revision 1.3  2011/08/25 22:40:43  lpoulsen
// Clean builds on Windows and Linux
//
// Revision 1.2  2011/08/25 22:32:26  lpoulsen
// Updates to allow Linux build
//
// Revision 1.1  2011/07/08 21:50:30  lpoulsen
// Eliminate (almost all) use of debug_level, use debug_flags instead.
//
// Revision 1.12  2010/11/20 01:49:13  lpoulsen
// Implement more F-keys
//
// Revision 1.11  2009/08/06 17:02:33  lpoulsen
// NRS Toolkit 1.15
//
// Revision 1.10  2009/07/08 00:12:51  lpoulsen
// Get more current numbers for the "now" display.
// Force 0.0 loss after 100 non-loss cycles (will never decay completely
//   by organic scale-down cycle-by-cycle)
//
// Revision 1.9  2009/07/07 22:12:28  lpoulsen
// Matching updated manual.
//
// Revision 1.8  2009/06/20 01:49:38  lpoulsen
// Echo "\r\n" for a '\r' on Win98 in afar_console_gets()
//
// Revision 1.7  2008/12/06 01:48:45  lpoulsen
// Fix some Win98 compatibility issues.
//
// Revision 1.6  2008/11/26 02:55:28  lpoulsen
// Latest bench test updates
//
// Revision 1.5  2008/11/20 18:54:22  lpoulsen
// Bugfixes developed during bench testing.
//
// Revision 1.4  2008/10/14 21:47:03  lpoulsen
// Poll a bit faster
//
// Revision 1.3  2008/03/08 01:49:06  lpoulsen
// Divided NRSTRAIN program out from NRSPOLL.
//
// Revision 1.2  2008/03/06 19:23:30  lpoulsen
// Allow time_t argument to afar_time()
//
// Revision 1.1  2008/02/21 18:29:41  lpoulsen
// Utility routines:
// - date formatting
//
//
#include <stdlib.h>
#include <time.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include "afartype.h"
#include "afarutil.h"
#include "dbflags.h"

#ifndef LINUX
#include <windows.h>

extern int errno;
       OSVERSIONINFOEX afar_osinfo;
#endif

time_t     afar_time_val;
struct tm *afar_tm_p;

#if 0
char *
afar_date(const time_t t)
{
    static char afar_date_str[12];

    afar_time_val = (t ? t : time(0));
    afar_tm_p     = localtime(&afar_time_val);

    sprintf(afar_date_str, "%4d-%02d-%02d",
            afar_tm_p->tm_year+1900, afar_tm_p->tm_mon+1, afar_tm_p->tm_mday);
    return afar_date_str;
}

char *
afar_time(const time_t t)
{
    static char afar_time_str[12];

    if (debug_flags & DBFLG_DATE_TIME) printf("afar_time(%d)\n", (int)t);

    afar_time_val = (t ? t : time(0));
    afar_tm_p     = localtime(&afar_time_val);
    if (afar_tm_p == NULL) {
        printf("afar_time() failed localtime()\n");
        printf("   sizeof(time_t) = %d %d\n",
               sizeof(time_t), sizeof(afar_time_val));
        printf("   failed to convert %d (0x%08X)\n",
               (int)afar_time_val, (unsigned int)afar_time_val);
        printf("   time(0) = %d (0x%08X)\n",
               (int)time(0), (unsigned int)time(0));
        exit(-1);
    }
    if (debug_flags & DBFLG_DATE_TIME)
        printf("afar_time(%d) localtime() done\n", (int)t);

    sprintf(afar_time_str, "%02d:%02d:%02d",
            afar_tm_p->tm_hour, afar_tm_p->tm_min, afar_tm_p->tm_sec);
    if (debug_flags & DBFLG_DATE_TIME) printf("afar_time() = %s\n", afar_time_str);
    return afar_time_str;
}
#endif

#if 0
char *
afar_date_64(const time64_t t)
{
    static char afar_date_str[12];
    SYSTEMTIME  afar_syst;

    if (t) {
        FileTimeToSystemTime((void *)&t, &afar_syst);
    } else {
        GetLocalTime(&afar_syst);
    }

    sprintf(afar_date_str, "%4d-%02d-%02d",
            afar_syst.wYear, afar_syst.wMonth, afar_syst.wDay);
    return afar_date_str;
}

char *
afar_time_64(const time64_t t)
{
    static char afar_time_str[12];
    SYSTEMTIME  afar_syst;

    if (t) {
        FileTimeToSystemTime((void *)&t, &afar_syst);
    } else {
        GetLocalTime(&afar_syst);
    }

    sprintf(afar_time_str, "%02d:%02d:%02d",
            afar_syst.wHour, afar_syst.wMinute, afar_syst.wSecond);
    return afar_time_str;
}
#endif

#define MAX_SMOOTH 64
#define round(x) ((x) >= 0 ? (long)((x)+0.5) : (long)((x)-0.5))

static struct {
    float counter;
    int   decay;
    int   factor;
    int   div;
    int   n;
} smooth_data[MAX_SMOOTH] = {{0},{0}};

void
afar_smooth_setup(int index, int decay, int factor, int div, int initial)
{
    if (index >= MAX_SMOOTH) {
        printf("*error* smoother index %d is too large - max %d allowed\n",
                index, MAX_SMOOTH);
        return;
    }
    smooth_data[index].decay = decay;
    smooth_data[index].factor = factor;
    smooth_data[index].div = div;
    smooth_data[index].counter = 0;
    smooth_data[index].n       = 0;
}

void
afar_smooth_count(int index, int value)
{
    if (index >= MAX_SMOOTH) return;
    if (smooth_data[index].n < smooth_data[index].decay) {
        smooth_data[index].counter += smooth_data[index].factor * value
                                      / (float) smooth_data[index].decay;
        smooth_data[index].n++;
    } else {
        smooth_data[index].counter = (( smooth_data[index].counter
                                  * (smooth_data[index].decay - 1))
                                + (float) smooth_data[index].factor * value )
                                / (float) smooth_data[index].decay;
    }
}

void
afar_smooth_set(int index, int value)
{
    if (index >= MAX_SMOOTH) return;
    smooth_data[index].counter = value * smooth_data[index].div
                                / (float) smooth_data[index].decay;
    smooth_data[index].n = 1;
}

int
afar_smooth_read(int index)
{
    if (index >= MAX_SMOOTH) return 0;
    if (smooth_data[index].n == 0) return 0;
    if (smooth_data[index].n < smooth_data[index].decay) {
        return round(smooth_data[index].counter 
                     / (smooth_data[index].n * smooth_data[index].div)
                     * smooth_data[index].decay );
    }
    return round(smooth_data[index].counter / smooth_data[index].div);
}

// afar_idiv() - a rounding integer division
int
afar_idiv(int a, int b)
{
    if (b == 0) {
        printf("afar_idiv %d %d - bad arguments\n", a, b);
        return 0;
    }
    if (b > 0)
        return (a + (b/2)) / b;
    else
        return (a - (b/2)) / b;
}

#if 0
// Deprecated - we should use typedef uint8 ipAddr[4];
// so use format_ip_addr(ipAddr *ipadr) instead of this one
char *
afar_fmt_ipadr(uint32 ipadr)
{
    static char afar_ipadr_buf[16];

    sprintf(afar_ipadr_buf, "%u.%u.%u.%u",
                        (ipadr & 0xFF000000) >> 24,
                        (ipadr & 0x00FF0000) >> 16,
                        (ipadr & 0x0000FF00) >> 8,
                        (ipadr & 0x000000FF)      );
    return afar_ipadr_buf;
}
#endif

char *
fmt_uint_sep(uint32 ival)
{
    static char fmtbuf[4][16];
    static int  ii = 0;
    int         vlen;
    int         j = 0, k= 0;

    ii = (ii + 2) & 0x03;

    sprintf(fmtbuf[ii], "%u", (unsigned int)ival);
    vlen = strlen(fmtbuf[ii]);
    switch (vlen)
    {
    case 10:
        fmtbuf[ii+1][j++] = fmtbuf[ii][k++];
        fmtbuf[ii+1][j++] = ',';
    case 9:
        fmtbuf[ii+1][j++] = fmtbuf[ii][k++];
    case 8:
        fmtbuf[ii+1][j++] = fmtbuf[ii][k++];
    case 7:
        fmtbuf[ii+1][j++] = fmtbuf[ii][k++];
        fmtbuf[ii+1][j++] = ',';
    case 6:
        fmtbuf[ii+1][j++] = fmtbuf[ii][k++];
    case 5:
        fmtbuf[ii+1][j++] = fmtbuf[ii][k++];
    case 4:
        fmtbuf[ii+1][j++] = fmtbuf[ii][k++];
        fmtbuf[ii+1][j++] = ',';
    case 3:
        fmtbuf[ii+1][j++] = fmtbuf[ii][k++];
    case 2:
        fmtbuf[ii+1][j++] = fmtbuf[ii][k++];
    case 1:
    case 0:
    default:
        fmtbuf[ii+1][j++] = fmtbuf[ii][k++];
        fmtbuf[ii+1][j] = 0;
    }
    return fmtbuf[ii+1];
}

// format a percentage:
//  0.00 to 5.00 show as "2.1"
//  above 5.00   show as " 25"
//
char *
fmt_uint_tenths(uint32 ival)
{
    static char fmtbuf[4][4];
    static int  ii = 0;

    ii = (ii + 1) & 0x03;

    if (ival < 50) {
        fmtbuf[ii][0] = (char) ('0' + (ival / 10));
        fmtbuf[ii][1] = '.';
        fmtbuf[ii][2] = (char) ('0' + (ival % 10));
        fmtbuf[ii][3] = 0;
    }
    else {
        sprintf(fmtbuf[ii], "%3d", afar_idiv(ival, 10));
    }
    return fmtbuf[ii];
}

#if defined(_MSC_VER) || defined(_MSC_EXTENSIONS)
  #define DELTA_EPOCH_IN_MICROSECS  11644473600000000Ui64
#else
  #define DELTA_EPOCH_IN_MICROSECS  11644473600000000ULL
#endif
 
#ifdef WINDOWS
int gettimeofday(struct timeval *tv, struct timezone *tz)
{
  FILETIME ft;
  unsigned __int64 tmpres = 0;
  static int tzflag;
 
  if (NULL != tv)
  {
    GetSystemTimeAsFileTime(&ft);
 
    tmpres |= ft.dwHighDateTime;
    tmpres <<= 32;
    tmpres |= ft.dwLowDateTime;
 
    /*converting file time to unix epoch*/
    tmpres /= 10;  /*convert into microseconds*/
    tmpres -= DELTA_EPOCH_IN_MICROSECS; 
    tv->tv_sec = (long)(tmpres / 1000000UL);
    tv->tv_usec = (long)(tmpres % 1000000UL);
  }
 
  if (NULL != tz)
  {
    if (!tzflag)
    {
      _tzset();
      tzflag++;
    }
    tz->tz_minuteswest = _timezone / 60;
    tz->tz_dsttime = _daylight;
  }
  return 0;
}

#ifdef USE_MODERN_KBHIT

// This version does not work on Win98
//  - it does not consume the character !!!

#define COOKED (ENABLE_ECHO_INPUT \
               |ENABLE_LINE_INPUT \
               |ENABLE_PROCESSED_INPUT)
int
afar_kbhit(void)
{
    HANDLE       hConsoleInput = GetStdHandle(STD_INPUT_HANDLE);
    INPUT_RECORD ConsoleBuffer;
    TCHAR        ConsoleTchar;
    int          n;

    SetConsoleMode(hConsoleInput, 0);    // RAW input mode

retry:
    PeekConsoleInput(hConsoleInput, &ConsoleBuffer, 1, &n);

    if (n == 0) {
        SetConsoleMode(hConsoleInput, COOKED);    // undo RAW input mode
        return 0;
    }
    if ((ConsoleBuffer.EventType != KEY_EVENT)
     || (ConsoleBuffer.Event.KeyEvent.bKeyDown == FALSE))
    {
        ReadConsoleInput(hConsoleInput, &ConsoleBuffer, 1, &n);
        if (n != 1) {
            printf("+++ ERROR: afar_kbhit()\n");
            exit(EXIT_FAILURE);
        }
        goto retry;
    }
    ReadConsoleInput(hConsoleInput, &ConsoleBuffer, 1, &n);
    if (n != 1) {
            printf("+++ ERROR: afar_kbhit()\n");
            exit(EXIT_FAILURE);
    }
    SetConsoleMode(hConsoleInput, COOKED);    // normal input mode

    if (ConsoleBuffer.Event.KeyEvent.wVirtualKeyCode == VK_BACK)
        return KEY_BACK;
    if (ConsoleBuffer.Event.KeyEvent.wVirtualKeyCode == VK_DELETE)
        return KEY_DELETE;
    if (ConsoleBuffer.Event.KeyEvent.wVirtualKeyCode == VK_DOWN)
        return KEY_DOWN;

    if (ConsoleBuffer.Event.KeyEvent.wVirtualKeyCode == VK_END)
        return KEY_END;
    if (ConsoleBuffer.Event.KeyEvent.wVirtualKeyCode == VK_HOME)
        return KEY_HOME;
    if (ConsoleBuffer.Event.KeyEvent.wVirtualKeyCode == VK_LEFT)
        return KEY_LEFT;

    if (ConsoleBuffer.Event.KeyEvent.wVirtualKeyCode == VK_NEXT)
        return KEY_NEXT;
    if (ConsoleBuffer.Event.KeyEvent.wVirtualKeyCode == VK_PRIOR)
        return KEY_PRIOR;
    if (ConsoleBuffer.Event.KeyEvent.wVirtualKeyCode == VK_RETURN)
        return KEY_RETURN;

    if (ConsoleBuffer.Event.KeyEvent.wVirtualKeyCode == VK_TAB)
        return KEY_TAB;
    if (ConsoleBuffer.Event.KeyEvent.wVirtualKeyCode == VK_UP)
        return KEY_UP;

    if (ConsoleBuffer.Event.KeyEvent.wVirtualKeyCode == VK_F1)
        return KEY_F1;
    if (ConsoleBuffer.Event.KeyEvent.wVirtualKeyCode == VK_F2)
        return KEY_F2;
    if (ConsoleBuffer.Event.KeyEvent.wVirtualKeyCode == VK_F3)
        return KEY_F3;
    if (ConsoleBuffer.Event.KeyEvent.wVirtualKeyCode == VK_F4)
        return KEY_F4;
    if (ConsoleBuffer.Event.KeyEvent.wVirtualKeyCode == VK_F5)
        return KEY_F5;

    return ConsoleBuffer.Event.KeyEvent.uChar.AsciiChar;
}
#else
        // This version works on Win98
int
afar_kbhit(void)
{
    int c;

    c = _kbhit();
    if (c == 0)
        return 0;

    c = _getch();
    if (c == 0xE0) // FUNCTION KEY
    {
        c = _getch();

        if (c == 0x47) // HOME function
            return KEY_HOME;

        if (c == 0x48) // up arrow
            return KEY_UP;

        if (c == 0x49) // PGUP function
            return KEY_PRIOR;

        if (c == 0x4B) // left arrow
            return KEY_LEFT;

        if (c == 0x4F) // END function
            return KEY_END;

        if (c == 0x50) // down arrow
            return KEY_DOWN;

        if (c == 0x51) // PGDN function
            return KEY_NEXT;

        if (c == 0x53) // DELete
            return KEY_DELETE;
    } else if (c == 0) {
        c = _getch();

        if (c == 0x3B)	// F1
            return KEY_F1;

        if (c == 0x3C)	// F2
            return KEY_F2;

        if (c == 0x3D)	// F3
            return KEY_F3;

        if (c == 0x3E)	// F4
            return KEY_F4;

        if (c == 0x3F)	// F5
            return KEY_F5;
       return KEY_UNKNOWN;
    } else {
      if (c == VK_BACK)
        return KEY_BACK;
    
      if (c == VK_RETURN)
        return KEY_RETURN;
    
      if (c == VK_TAB)
        return KEY_TAB;
    }
    return c;
}
#endif

char *
afar_console_gets(char *buffer, const int buflen, int *terminator)
{
    if (afar_osinfo.dwPlatformId == 0)
    {
        afar_osinfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
        GetVersionEx((void *)&afar_osinfo);
    }
    if (afar_osinfo.dwPlatformId == 2) // WinNT family
    {
        char *cp;
        int l;

        cp = fgets(buffer, buflen, stdin);
        if (terminator) {
            if (cp == NULL) {
                *terminator = -1;
                return NULL;
            }
            l = strlen(buffer);
            if (l = 0) {
                *terminator = 0;
                return cp;
            }
            if (buffer[l - 1] < ' ') {
                *terminator = buffer[l - 1];
                buffer[strlen(buffer) - 1] = 0;
            }
            else
                *terminator = 0;
        }
        return cp;
    }
    else                   // assume 1 = "old" Windows
    {
        int i;

        if (terminator) *terminator = 0;

        for (i = 0; i < buflen; i++)
        {
            int c = _getch();
            if (c <= 0) {
                buffer[i] = 0;
                if (terminator) *terminator = -1;
                return buffer;
            }
            else if ((c >= ' ') && (c <= '~')) {
                _putch(c);
                buffer[i] = c;
                continue;
            }
            else if ((c == KEY_BACK) || (c == '\b')) {
                if (i == 0)
                    continue;
                i = i - 2;
                _putch(KEY_BACK);
                _putch(' ');
                _putch(KEY_BACK);
            }
            else {
                _putch(c);
                if (c == '\r')
                   _putch('\n');
                if (terminator) {
                    *terminator = c;
                    buffer[i] = 0;
                    return buffer;
                }
                buffer[i] = c;
                if (i < (buflen - 1))
                    buffer[i + 1] = 0;
                return buffer;
            }
        }
        return buffer;
    }
}
#endif

// -------------------------------------------------------------
//      Return a pointer to first occurrence within string
//      s1 of string s2, or return zero if s1 does not
//      contain any instance of s2.  If *s2 == 0, return s1.
// -------------------------------------------------------------

char *
strstr_nocase(const char *s1, const char *s2)
{
    register size_t len2;

    if (!(len2 = strlen(s2)))
        return (char *) s1;

    for (; *s1; ++s1)
    {
        if (strncmp_nocase(s1, s2, len2) == 0)
            return (char *) s1;
    }
    return 0;
}

// -------------------------------------------------------------
//       Compare strings (at most n bytes):
//            s1>s2: >0  s1==s2: 0  s1<s2: <0
// -------------------------------------------------------------

int
strncmp_nocase(const char *s1, const char *s2, const size_t nn)
{
    register char c1, c2;
    int n = nn;

    while (--n >= 0)
    {
	c1 = *s1++;
	if (c1 == 0)
	    return 0;
	if (isupper(c1))
	    c1 = tolower(c1);

	c2 = *s2++;
	if (c2 == 0)
	    return 0;
	if (isupper(c2))
	    c2 = tolower(c2);

	if (c1 != c2)
	    break;
    }
    return (n < 0 ? 0 : c1 - c2);
}

// --------------------------------------------------------------
//      Copy a string to a new heap buffer
// --------------------------------------------------------------

char *
strdup(const char *s)
{
    int   len = strlen(s);
    char *cp  = malloc(len+1);
    strncpy(cp, s, len+1);
    return cp;
}

// --------------------------------------------------------------
//      Open a file with a default extension
// --------------------------------------------------------------

FILE *
afar_fopen_ext(const char *fname, const char *mode, const char *ext)
{
    char *cp;
    FILE *fp;
    char *pp = NULL;

    // if this is reading, and there is a file with the given
    //   name (with or without a .xyz extension), let's use it

    if (mode[0] == 'r')
    {
        fp = fopen(fname, mode);
        if ((fp) || (errno != ENOENT))
            return fp;
    }

    cp = malloc(strlen(fname)+strlen(ext)+3);
    strcpy(cp, fname);

    // if there is a period less than 5 bytes from the end, then
    //   the user gave a file extension, and we assume he meant it

    pp = strrchr(cp, '.');
    if (pp && (strlen(pp) < 6))
    {
        fp = fopen(fname, mode);
        free(cp);
        return fp;
    }

    // if none of those special cases, add the extension
    if (ext[0] != '.')
        strcat(cp, ".");
    strcat(cp, ext);
    fp = fopen(cp, mode);
    if (debug_flags & DBFLG_DATE_TIME)
        printf("fopen(%s,%s) returned %08X errno=%d\n", cp, mode,
               (unsigned int)fp, errno);
    free(cp);
    return fp;
}

// End
