/*
Copyright (C) 1996-1997 Id Software, Inc.

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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
// sys_win.h

#include "quakedef.h"
#include "winquake.h"
#include "resource.h"
#include "errno.h"
#include "fcntl.h"
#include <limits.h>

#define MINIMUM_WIN_MEMORY  0x0c00000
#define MAXIMUM_WIN_MEMORY  0x1000000

#define PAUSE_SLEEP   50        // sleep time on pause or minimization
#define NOT_FOCUS_SLEEP 20        // sleep time when not focus

int   starttime;
qboolean ActiveApp, Minimized;
qboolean  WinNT;

HWND  hwnd_dialog;    // startup dialog box

static double   pfreq;
static double   curtime = 0.0;
static double   lastcurtime = 0.0;
static int      lowshift;
static HANDLE   hinput, houtput;

HANDLE    qwclsemaphore;

static HANDLE tevent;

void Sys_InitFloatTime (void);

void MaskExceptions (void);
void Sys_PopFPCW (void);
void Sys_PushFPCW_SetHigh (void);

void Sys_DebugLog(char *file, char *fmt, ...)
{
    va_list argptr; 
    static char data[1024];
    int fd;
    
    va_start(argptr, fmt);
    vsprintf(data, fmt, argptr);
    va_end(argptr);
    fd = open(file, O_WRONLY | O_CREAT | O_APPEND, 0666);
    write(fd, data, strlen(data));
    close(fd);
};

/*
===============================================================================

FILE IO

===============================================================================
*/

/*
================
filelength
================
*/
int filelength (FILE *f)
{
  int   pos;
  int   end;

  pos = ftell (f);
  fseek (f, 0, SEEK_END);
  end = ftell (f);
  fseek (f, pos, SEEK_SET);

  return end;
}


int Sys_FileTime (char *path)
{
  FILE  *f;
  int   t, retval;

  t = VID_ForceUnlockedAndReturnState ();
  
  f = fopen(path, "rb");

  if (f)
  {
    fclose(f);
    retval = 1;
  }
  else
  {
    retval = -1;
  }
  
  VID_ForceLockState (t);
  return retval;
}

void Sys_mkdir (char *path)
{
  _mkdir (path);
}


/*
===============================================================================

SYSTEM IO

===============================================================================
*/

/*
================
Sys_MakeCodeWriteable
================
*/
void Sys_MakeCodeWriteable (unsigned long startaddr, unsigned long length)
{
  DWORD  flOldProtect;

//@@@ copy on write or just read-write?
  if (!VirtualProtect((LPVOID)startaddr, length, PAGE_READWRITE, &flOldProtect))
      Sys_Error("Protection change failed\n");
}


/*
================
Sys_Init
================
*/
void Sys_Init (void)
{
  LARGE_INTEGER PerformanceFreq;
  unsigned int  lowpart, highpart;
  OSVERSIONINFO vinfo;

#ifndef SERVERONLY
  // allocate a named semaphore on the client so the
  // front end can tell if it is alive

  // mutex will fail if semephore allready exists
    qwclsemaphore = CreateMutex(
        NULL,         /* Security attributes */
        0,            /* owner       */
        "qwcl"); /* Semaphore name      */
  if (!qwclsemaphore)
    Sys_Error ("QWCL is already running on this system");
  CloseHandle (qwclsemaphore);

    qwclsemaphore = CreateSemaphore(
        NULL,         /* Security attributes */
        0,            /* Initial count       */
        1,            /* Maximum count       */
        "qwcl"); /* Semaphore name      */
#endif

  MaskExceptions ();
  Sys_SetFPCW ();

#if 0
  if (!QueryPerformanceFrequency (&PerformanceFreq))
    Sys_Error ("No hardware timer available");

// get 32 out of the 64 time bits such that we have around
// 1 microsecond resolution
  lowpart = (unsigned int)PerformanceFreq.LowPart;
  highpart = (unsigned int)PerformanceFreq.HighPart;
  lowshift = 0;

  while (highpart || (lowpart > 2000000.0))
  {
    lowshift++;
    lowpart >>= 1;
    lowpart |= (highpart & 1) << 31;
    highpart >>= 1;
  }

  pfreq = 1.0 / (double)lowpart;

  Sys_InitFloatTime ();
#endif

  // make sure the timer is high precision, otherwise
  // NT gets 18ms resolution
  timeBeginPeriod( 1 );

  vinfo.dwOSVersionInfoSize = sizeof(vinfo);

  if (!GetVersionEx (&vinfo))
    Sys_Error ("Couldn't get OS info");

  if ((vinfo.dwMajorVersion < 4) ||
    (vinfo.dwPlatformId == VER_PLATFORM_WIN32s))
  {
    Sys_Error ("QuakeWorld requires at least Win95 or NT 4.0");
  }
  
  if (vinfo.dwPlatformId == VER_PLATFORM_WIN32_NT)
    WinNT = true;
  else
    WinNT = false;
}


void Sys_Error (char *error, ...)
{
  va_list   argptr;
  char    text[1024], text2[1024];
  DWORD   dummy;

  Host_Shutdown ();

  va_start (argptr, error);
  vsprintf (text, error, argptr);
  va_end (argptr);

  MessageBox(NULL, text, "Error", 0 /* MB_OK */ );

#ifndef SERVERONLY
  CloseHandle (qwclsemaphore);
#endif

  exit (1);
}

void Sys_Printf (char *fmt, ...)
{
  va_list   argptr;
  char    text[1024];
  DWORD   dummy;
  
  va_start (argptr,fmt);
  vprintf (fmt, argptr);
  va_end (argptr);
}

void Sys_Quit (void)
{
  VID_ForceUnlockedAndReturnState ();

  Host_Shutdown();
#ifndef SERVERONLY
  if (tevent)
    CloseHandle (tevent);

  if (qwclsemaphore)
    CloseHandle (qwclsemaphore);
#endif

  exit (0);
}


#if 0
/*
================
Sys_DoubleTime
================
*/
double Sys_DoubleTime (void)
{
  static int      sametimecount;
  static unsigned int oldtime;
  static int      first = 1;
  LARGE_INTEGER   PerformanceCount;
  unsigned int    temp, t2;
  double        time;

  Sys_PushFPCW_SetHigh ();

  QueryPerformanceCounter (&PerformanceCount);

  temp = ((unsigned int)PerformanceCount.LowPart >> lowshift) |
       ((unsigned int)PerformanceCount.HighPart << (32 - lowshift));

  if (first)
  {
    oldtime = temp;
    first = 0;
  }
  else
  {
  // check for turnover or backward time
    if ((temp <= oldtime) && ((oldtime - temp) < 0x10000000))
    {
      oldtime = temp; // so we can't get stuck
    }
    else
    {
      t2 = temp - oldtime;

      time = (double)t2 * pfreq;
      oldtime = temp;

      curtime += time;

      if (curtime == lastcurtime)
      {
        sametimecount++;

        if (sametimecount > 100000)
        {
          curtime += 1.0;
          sametimecount = 0;
        }
      }
      else
      {
        sametimecount = 0;
      }

      lastcurtime = curtime;
    }
  }

  Sys_PopFPCW ();

    return curtime;
}

/*
================
Sys_InitFloatTime
================
*/
void Sys_InitFloatTime (void)
{
  int   j;

  Sys_DoubleTime ();

  j = COM_CheckParm("-starttime");

  if (j)
  {
    curtime = (double) (Q_atof(com_argv[j+1]));
  }
  else
  {
    curtime = 0.0;
  }

  lastcurtime = curtime;
}

#endif

double Sys_DoubleTime (void)
{
  static DWORD starttime;
  static qboolean first = true;
  DWORD now;
  double t;

  now = timeGetTime();

  if (first) {
    first = false;
    starttime = now;
    return 0.0;
  }
  
  if (now < starttime) // wrapped?
    return (now / 1000.0) + (LONG_MAX - starttime / 1000.0);

  if (now - starttime == 0)
    return 0.0;

  return (now - starttime) / 1000.0;
}

char *Sys_ConsoleInput (void)
{
  static char text[256];
  static int    len;
  INPUT_RECORD  recs[1024];
  int   count;
  int   i, dummy;
  int   ch, numread, numevents;
  HANDLE  th;
  char  *clipText, *textCopied;

  for ( ;; )
  {
    if (!GetNumberOfConsoleInputEvents (hinput, &numevents))
      Sys_Error ("Error getting # of console events");

    if (numevents <= 0)
      break;

    if (!ReadConsoleInput(hinput, recs, 1, &numread))
      Sys_Error ("Error reading console input");

    if (numread != 1)
      Sys_Error ("Couldn't read console input");

    if (recs[0].EventType == KEY_EVENT)
    {
      if (!recs[0].Event.KeyEvent.bKeyDown)
      {
        ch = recs[0].Event.KeyEvent.uChar.AsciiChar;

        switch (ch)
        {
          case '\r':
            WriteFile(houtput, "\r\n", 2, &dummy, NULL);  

            if (len)
            {
              text[len] = 0;
              len = 0;
              return text;
            }
            break;

          case '\b':
            WriteFile(houtput, "\b \b", 3, &dummy, NULL); 
            if (len)
            {
              len--;
              putch('\b');
            }
            break;

          default:
            Con_Printf("Stupid: %d\n", recs[0].Event.KeyEvent.dwControlKeyState);
            if (((ch=='V' || ch=='v') && (recs[0].Event.KeyEvent.dwControlKeyState & 
              (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED))) || ((recs[0].Event.KeyEvent.dwControlKeyState 
              & SHIFT_PRESSED) && (recs[0].Event.KeyEvent.wVirtualKeyCode
              ==VK_INSERT))) {
              if (OpenClipboard(NULL)) {
                th = GetClipboardData(CF_TEXT);
                if (th) {
                  clipText = GlobalLock(th);
                  if (clipText) {
                    textCopied = malloc(GlobalSize(th)+1);
                    strcpy(textCopied, clipText);
/* Substitutes a NULL for every token */strtok(textCopied, "\n\r\b");
                    i = strlen(textCopied);
                    if (i+len>=256)
                      i=256-len;
                    if (i>0) {
                      textCopied[i]=0;
                      text[len]=0;
                      strcat(text, textCopied);
                      len+=dummy;
                      WriteFile(houtput, textCopied, i, &dummy, NULL);
                    }
                    free(textCopied);
                  }
                  GlobalUnlock(th);
                }
                CloseClipboard();
              }
            } else if (ch >= ' ')
            {
              WriteFile(houtput, &ch, 1, &dummy, NULL); 
              text[len] = ch;
              len = (len + 1) & 0xff;
            }

            break;

        }
      }
    }
  }

  return NULL;
}

void Sys_Sleep (void)
{
}


void Sys_SendKeyEvents (void)
{
    MSG        msg;

  while (PeekMessage (&msg, NULL, 0, 0, PM_NOREMOVE))
  {
  // we always update if there are any event, even if we're paused
    scr_skipupdate = 0;

    if (!GetMessage (&msg, NULL, 0, 0))
      Sys_Quit ();
        TranslateMessage (&msg);
        DispatchMessage (&msg);
  }
}



/*
==============================================================================

 WINDOWS CRAP

==============================================================================
*/

/*
==================
WinMain
==================
*/
void SleepUntilInput (int time)
{

  MsgWaitForMultipleObjects(1, &tevent, FALSE, time, QS_ALLINPUT);
}



/*
==================
WinMain
==================
*/
HINSTANCE global_hInstance;
int     global_nCmdShow;
char    *argv[MAX_NUM_ARGVS];
static char *empty_string = "";
HWND    hwnd_dialog;


int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    MSG       msg;
  quakeparms_t  parms;
  double      time, oldtime, newtime;
  MEMORYSTATUS  lpBuffer;
  static  char  cwd[1024];
  int       t;
  RECT      rect;

    /* previous instances do not exist in Win32 */
    if (hPrevInstance)
        return 0;

  global_hInstance = hInstance;
  global_nCmdShow = nCmdShow;

  lpBuffer.dwLength = sizeof(MEMORYSTATUS);
  GlobalMemoryStatus (&lpBuffer);

  if (!GetCurrentDirectory (sizeof(cwd), cwd))
    Sys_Error ("Couldn't determine current directory");

  if (cwd[Q_strlen(cwd)-1] == '/')
    cwd[Q_strlen(cwd)-1] = 0;

  parms.basedir = cwd;
  parms.cachedir = NULL;

  parms.argc = 1;
  argv[0] = empty_string;

  while (*lpCmdLine && (parms.argc < MAX_NUM_ARGVS))
  {
    while (*lpCmdLine && ((*lpCmdLine <= 32) || (*lpCmdLine > 126)))
      lpCmdLine++;

    if (*lpCmdLine)
    {
      argv[parms.argc] = lpCmdLine;
      parms.argc++;

      while (*lpCmdLine && ((*lpCmdLine > 32) && (*lpCmdLine <= 126)))
        lpCmdLine++;

      if (*lpCmdLine)
      {
        *lpCmdLine = 0;
        lpCmdLine++;
      }
      
    }
  }

  parms.argv = argv;

  COM_InitArgv (parms.argc, parms.argv);

  parms.argc = com_argc;
  parms.argv = com_argv;

  hwnd_dialog = CreateDialog(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, NULL);

  if (hwnd_dialog)
  {
    if (GetWindowRect (hwnd_dialog, &rect))
    {
      if (rect.left > (rect.top * 2))
      {
        SetWindowPos (hwnd_dialog, 0,
          (rect.left / 2) - ((rect.right - rect.left) / 2),
          rect.top, 0, 0,
          SWP_NOZORDER | SWP_NOSIZE);
      }
    }

    ShowWindow (hwnd_dialog, SW_SHOWDEFAULT);
    UpdateWindow (hwnd_dialog);
    SetForegroundWindow (hwnd_dialog);
  }

// take the greater of all the available memory or half the total memory,
// but at least 8 Mb and no more than 16 Mb, unless they explicitly
// request otherwise
  parms.memsize = lpBuffer.dwAvailPhys;

  if (parms.memsize < MINIMUM_WIN_MEMORY)
    parms.memsize = MINIMUM_WIN_MEMORY;

  if (parms.memsize < (lpBuffer.dwTotalPhys >> 1))
    parms.memsize = lpBuffer.dwTotalPhys >> 1;

  if (parms.memsize > MAXIMUM_WIN_MEMORY)
    parms.memsize = MAXIMUM_WIN_MEMORY;

  if (COM_CheckParm ("-heapsize"))
  {
    t = COM_CheckParm("-heapsize") + 1;

    if (t < com_argc)
      parms.memsize = Q_atoi (com_argv[t]) * 1024;
  }

  parms.membase = malloc (parms.memsize);

  if (!parms.membase)
    Sys_Error ("Not enough memory free; check disk space\n");

  tevent = CreateEvent(NULL, FALSE, FALSE, NULL);

  if (!tevent)
    Sys_Error ("Couldn't create event");

  Sys_Init ();

// because sound is off until we become active
  S_BlockSound ();

  Sys_Printf ("Host_Init\n");
  Host_Init (&parms);

  oldtime = Sys_DoubleTime ();

    /* main window message loop */
  while (1)
  {
  // yield the CPU for a little while when paused, minimized, or not the focus
    if ((cl.paused && (!ActiveApp && !DDActive)) || Minimized || block_drawing)
    {
      SleepUntilInput (PAUSE_SLEEP);
      scr_skipupdate = 1;   // no point in bothering to draw
    }
    else if (!ActiveApp && !DDActive)
    {
      SleepUntilInput (NOT_FOCUS_SLEEP);
    }

    newtime = Sys_DoubleTime ();
    time = newtime - oldtime;
    Host_Frame (time);
    oldtime = newtime;
  }

    /* return success of application */
    return TRUE;
}

