/*
   GSPLUS - Advanced Apple IIGS Emulator Environment
   Based on the KEGS emulator written by Kent Dickey
   See COPYRIGHT.txt for Copyright information
   See LICENSE.txt for license (GPL v2)
 */

#define WIN32_LEAN_AND_MEAN     /* Tell windows we want less header gunk */
#define STRICT                  /* Tell Windows we want compile type checks */

#include <windows.h>
#include <Shlwapi.h>

#include "winresource.h"
#include "defc.h"
#include "protos_windriver.h"

#include <io.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

extern void gsportinit(HWND _hwnd);
extern void gsportshut();
extern HWND g_hwnd_main;

extern char *g_status_ptrs[MAX_STATUS_LINES];
extern int g_win_status_debug;
extern int g_win_status_debug_request;
extern int g_win_fullscreen_state;

int win_nonblock_read_stdin(int fd, char *bufptr, int len)     {
  DWORD charsRead = 0;
  ReadConsole(GetStdHandle(STD_INPUT_HANDLE), bufptr, len, &charsRead, NULL);

  if (charsRead == 0)
  {
    errno = EAGAIN;
    return -1;
  }
  else
  {
    DWORD charsWritten = 0;
    WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), bufptr, charsRead, &charsWritten, NULL);
    return charsRead;
  }
}

void get_cwd(LPTSTR buffer, int size) {
  HMODULE hSelf;
  hSelf = GetModuleHandle(NULL);
  GetModuleFileName(hSelf,buffer,size);
  PathRemoveFileSpec(buffer);
  //printf("Local directory: [%s]\n",buffer);
}

void x_dialog_create_gsport_conf(const char *str)      {
  // Just write the config file already...
  config_write_config_gsplus_file();
}

int x_show_alert(int is_fatal, const char *str) {

  if (str && *str) {
    adb_all_keys_up();
    MessageBox(NULL, str, "GS+", is_fatal ? MB_ICONERROR : MB_ICONWARNING);
  }
  return 0;
}

void get_default_window_size(LPSIZE size) {
  // Calculate the window client dimensions.
  RECT rect;
  rect.left = 0;
  rect.top = 0;
  rect.bottom = X_A2_WINDOW_HEIGHT;
  if (g_win_status_debug)
    rect.bottom += (MAX_STATUS_LINES * 16);
  rect.right = X_A2_WINDOW_WIDTH;

  // Calculate the window rectangle, which is the client area plus non-client area (e.g. frame and caption).
  AdjustWindowRect(&rect, WS_TILED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX, FALSE);

  // Return the window size.
  size->cx = rect.right - rect.left;
  size->cy = rect.bottom - rect.top;
}

void x_toggle_status_lines() {
  SIZE size;

  if (!g_win_fullscreen_state)
  {
    g_win_status_debug = !g_win_status_debug;
    g_win_status_debug_request = g_win_status_debug;

    get_default_window_size(&size);
    SetWindowPos(g_hwnd_main, NULL, 0, 0, size.cx, size.cy, SWP_NOMOVE | SWP_NOZORDER);
    x_redraw_status_lines();
  }
}

void x_show_console(int show) {
  HWND hWnd = GetConsoleWindow();
  if (hWnd)
    ShowWindow(hWnd, show ? SW_SHOW : SW_HIDE);

  if (g_hwnd_main)
    SetFocus(g_hwnd_main);
}

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) {
  return main(0,0);
}

/* cygwin may have old headers ... */
#ifndef ENABLE_VIRTUAL_TERMINAL_INPUT
#define ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200
#endif

#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
#endif

#ifndef DISABLE_NEWLINE_AUTO_RETURN
#define DISABLE_NEWLINE_AUTO_RETURN 0x0008
#endif


#ifdef __CYGWIN__

static ssize_t handle_read(void *r, void *cookie, char *buffer, size_t size) {

  BOOL ok;
  DWORD n;
  ok = ReadConsole((HANDLE)cookie, buffer, size, &n, NULL);
  if (!ok) { errno = EIO; return -1; }
  return n;
}

static ssize_t handle_write(void *r, void *cookie, const char *buffer, size_t size) {

  static char *crbuffer = NULL;
  static int crbuffer_size = 0;

  BOOL ok;
  DWORD n;
  int crcount = 0;
  int i, j;

  for (i = 0; i < size; ++i) {
    if (buffer[i] == '\n') ++crcount;
  }

  if (crcount) {
    if (crbuffer_size < crcount + size) {
      crbuffer = realloc(crbuffer, size + crcount);
      if (!crbuffer) return -1;
      crbuffer_size = crcount + size;
    }

    for (i = 0, j = 0; i < size; ++i) {
      char c = buffer[i];
      if (c == '\n') crbuffer[j++] = '\r';
      crbuffer[j++] = c;
    }
  }


  ok = WriteConsole((HANDLE)cookie, crcount ? crbuffer : buffer, size + crcount, &n, NULL);
  if (!ok) { errno = EIO; return -1; }
  return size;
}

#endif

static void set_file_handle(FILE *fp, HANDLE h) {

  #ifdef __CYGWIN__
  fp->_file = -1;
  fp->_cookie = (void *)h;
  fp->_read = handle_read;
  fp->_write = handle_write;
  fp->_seek = NULL;
  fp->_close = NULL;
  #else
  int fd = _open_osfhandle((intptr_t)h, _O_TEXT);
  fp->_file = fd;
  #endif
}

int g_win32_cygwin;
void win_init_console(void) {
  /*
    powershell/cmd
    fd 0/1/2 closed
    GetStdHandle return 0
    stdin = -2, stdout = -2, stderr = -2

    msys/cygwin
    fd 0/1/2 open
    GetStdHandle return value (type = 3/pipe)
    stdin = 0, stdout = 1, stderr = 2
  */

  //struct stat st;
  //int ok;
  int fd;
  HANDLE h;
  DWORD mode;

#if 0
  FILE *dbg = fopen("debug.txt", "a+");
  h = GetStdHandle(STD_INPUT_HANDLE);
  fprintf(dbg, "STD_INPUT_HANDLE: %p\n", h);
  fprintf(dbg, "GetFileType: %08x\n", GetFileType(h));
  fprintf(dbg, "%d %d %d\n", stdin->_file, stdout->_file, stderr->_file);
  fclose(dbg);
#endif

  g_win32_cygwin = 0;

  setvbuf(stdout, NULL, _IOLBF, BUFSIZ);
  setvbuf(stderr, NULL, _IOLBF, BUFSIZ);

#if 0
  if (fstat(0, &st) == 0) {
    g_win32_cygwin = 1;
    return;
  }
#endif

  SetStdHandle(STD_INPUT_HANDLE, 0);
  SetStdHandle(STD_OUTPUT_HANDLE, 0);
  SetStdHandle(STD_ERROR_HANDLE, 0);
  #if 0
  stdin->_file = 0;
  stdout->_file = 1;
  stderr->_file = 2;
  #endif


  AllocConsole();
  SetConsoleTitle("GS+");


  h = GetStdHandle(STD_INPUT_HANDLE);
  if (h != INVALID_HANDLE_VALUE) {

    mode = 0;
    GetConsoleMode(h, &mode);
    mode |= ENABLE_VIRTUAL_TERMINAL_INPUT;
    SetConsoleMode(h, mode);

    set_file_handle(stdin, h);
  }

  h = GetStdHandle(STD_OUTPUT_HANDLE);
  if (h != INVALID_HANDLE_VALUE) {

    mode = 0;
    GetConsoleMode(h, &mode);
    mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN;
    SetConsoleMode(h, mode);

    //SetConsoleTextAttribute(h, BACKGROUND_BLUE | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);
    set_file_handle(stdout, h);
  }


  h = GetStdHandle(STD_ERROR_HANDLE);
  if (h != INVALID_HANDLE_VALUE) {

    mode = 0;
    GetConsoleMode(h, &mode);
    mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN;
    SetConsoleMode(h, mode);

    set_file_handle(stderr, h);
  }


#if 0
  dbg = fopen("debug.txt", "a+");
  h = GetStdHandle(STD_INPUT_HANDLE);
  fprintf(dbg, "STD_INPUT_HANDLE: %p\n", h);
  fprintf(dbg, "GetFileType: %08x\n", GetFileType(h));
  fprintf(dbg, "%d %d %d\n", stdin->_file, stdout->_file, stderr->_file);
  fclose(dbg);
#endif
}


static void exit_sleep(void) {
  /* todo -- "press return to continue" */
  if (!g_win32_cygwin)
    sleep(10);
}

int main(int argc, char **argv) {
  // Hide the console initially to reduce window flashing.  We'll show the console later if needed.

  //atexit(exit_sleep);
  win_init_console();

  //x_show_console(0);


  // Register the window class.
  WNDCLASS wndclass;
  SIZE size;
  RECT rect;
  HHOOK hook;

  wndclass.style = 0;
  wndclass.lpfnWndProc = (WNDPROC)win_event_handler;
  wndclass.cbClsExtra = 0;
  wndclass.cbWndExtra = 0;
  wndclass.hInstance = GetModuleHandle(NULL);
  wndclass.hIcon = LoadIcon(wndclass.hInstance, MAKEINTRESOURCE(IDC_GSPORT32));
  wndclass.hCursor = LoadCursor((HINSTANCE) NULL, IDC_ARROW);
  wndclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
  wndclass.lpszMenuName = NULL;
  wndclass.lpszClassName = "gsport";

  if(!RegisterClass(&wndclass)) {
    printf("Registering window failed\n");
    exit(1);
  }

  // Create the window.
  get_default_window_size(&size);

  HWND hwnd = CreateWindowEx(WS_EX_ACCEPTFILES, "gsport", "GSplus - Apple //gs Emulator",
                             WS_TILED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
                             CW_USEDEFAULT, CW_USEDEFAULT,
                             size.cx, size.cy,
                             NULL, NULL, GetModuleHandle(NULL), NULL);


#if 0
  // Enable non-blocking, character-at-a-time console I/O.
  // win_nonblock_read_stdin() expects this behavior.
  DWORD mode;
  GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &mode);
  mode &= ~ENABLE_LINE_INPUT;
  SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), mode);
#endif

  hook = SetWindowsHookEx(WH_KEYBOARD_LL, win_ll_keyboard, NULL, 0);

  hook = SetWindowsHookEx(WH_KEYBOARD_LL, win_ll_keyboard, NULL, 0);

  gsportinit(hwnd);
  int ret =  gsplusmain(argc, argv);

  UnhookWindowsHookEx(hook);
  UnregisterClass(wndclass.lpszClassName,GetModuleHandle(NULL));


  gsportshut();
  return ret;
}

void x_check_input_events() {
  MSG msg;

  while(PeekMessage(&msg, g_hwnd_main, 0, 0, PM_NOREMOVE)) {
    if(GetMessage(&msg, g_hwnd_main, 0, 0) > 0) {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    } else {
      printf("GetMessage returned <= 0\n");
      my_exit(2);
    }
  }
}

void x_redraw_status_lines()      {
  COLORREF oldtextcolor, oldbkcolor;
  char    *buf;
  int line;
  int len;
  int height;
  int margin;

  height = 16;
  margin = 0;
  if (g_win_status_debug)
  {
    HDC localdc = GetDC(g_hwnd_main);
    oldtextcolor = SetTextColor(localdc, RGB(255, 255, 255));
    oldbkcolor = SetBkColor(localdc, RGB(0, 0, 0));
    for(line = 0; line < MAX_STATUS_LINES; line++) {
      buf = g_status_ptrs[line];
      if(buf != 0) {
        len = strlen(buf);
        TextOut(localdc, 10, X_A2_WINDOW_HEIGHT +
                height*line + margin, buf, len);
      }
    }
    SetTextColor(localdc, oldtextcolor);
    SetBkColor(localdc, oldbkcolor);
    ReleaseDC(g_hwnd_main,localdc);
  }
}

int x_calc_ratio(float ratiox,float ratioy)     {
  return 0;       // not stretched
}
