/*  $Id$

    Part of SWI-Prolog

    Author:        Jan Wielemaker and Matt Lilley
    E-mail:        J.Wielemaker@cs.vu.nl
    WWW:           http://www.swi-prolog.org
    Copyright (C): 2010, VU University, Amsterdam
    Copyright (C): 2009, SCIENTIFIC SOFTWARE AND SYSTEMS LIMITED

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library 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
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#define WINDOWS_LEAN_AND_MEAN 1
#if (_MSC_VER >= 1300)
#include <winsock2.h>			/* Needed on VC8 */
#include <windows.h>
#else
#include <windows.h>			/* Needed for MSVC 5&6 */
#include <winsock2.h>
#endif
#include "pl-incl.h"

#define ANSI_MAGIC		(0x734ab9de)
#define ANSI_BUFFER_SIZE	(256)
#define ANSI_MAX_ARGC		(10)

typedef enum
{ CMD_INITIAL = 0,
  CMD_ESC,
  CMD_ANSI
} astate;


typedef struct
{ int magic;
  HANDLE hConsole;
  IOSTREAM *pStream;
  void *saved_handle;

  wchar_t buffer[ANSI_BUFFER_SIZE];
  size_t buffered;
  int argv[ANSI_MAX_ARGC];
  int argc;
  int argstat;
  astate cmdstat;			/* State for sequence processing */
  WORD def_attr;			/* Default attributes */
} ansi_stream;


static IOFUNCTIONS con_functions;
static IOFUNCTIONS *saved_functions;

static void
Message(const char *fm, ...)
{ char buf[1024];
  va_list(args);

  return;

  va_start(args, fm);
  vsprintf(buf, fm, args);
  MessageBox(NULL, buf, "SWI-Prolog", MB_OK|MB_TASKMODAL);
  va_end(args);
}


static int
flush_ansi(ansi_stream *as)
{ size_t written = 0;

  while ( written < as->buffered )
  { BOOL rc;
    DWORD done;

    rc = WriteConsoleW(as->hConsole,
		       &as->buffer[written],
		       (DWORD)(as->buffered-written),
		       &done,
		       NULL);

    if ( rc )
    { written += done;
    } else
    { as->buffered = 0;
      return -1;
    }
  }

  as->buffered = 0;
  return 0;
}


static int
send_ansi(ansi_stream *as, int chr)
{ as->buffer[as->buffered++] = chr;

  if ( as->buffered == ANSI_BUFFER_SIZE ||
       (as->pStream->flags & SIO_NBUF) ||
       (chr == '\n' && (as->pStream->flags & SIO_LBUF)) )
    return flush_ansi(as);

  return 0;
}

#define FG_MASK (FOREGROUND_RED|FOREGROUND_BLUE|FOREGROUND_GREEN)
#define BG_MASK (BACKGROUND_RED|BACKGROUND_BLUE|BACKGROUND_GREEN)

static void
set_ansi_attributes(ansi_stream *as)
{ CONSOLE_SCREEN_BUFFER_INFO info;

  if ( GetConsoleScreenBufferInfo(as->hConsole, &info) )
  { int i;
    WORD attr = info.wAttributes;

    for(i=0; i < as->argc; i++)
    { switch( as->argv[i] )
      { case 0:
	  attr = as->def_attr;
	  break;
	case 1:
	  attr |= FOREGROUND_INTENSITY;
	  break;
	case 22:
	  attr &= ~FOREGROUND_INTENSITY;
	  break;
	default:
	  if ( as->argv[i] >= 30 && as->argv[i] <= 39 )
	  { int fg = as->argv[i] - 30;

	    attr &= ~FG_MASK;

	    if ( fg == 9 )		/* default */
	    { attr |= (as->def_attr & FG_MASK);
	    } else
	    { if ( fg % 2 == 1 )
		attr |= FOREGROUND_RED;
	      if ( fg >= 4 )
		attr |= FOREGROUND_BLUE;
	      if ( (fg == 2) || (fg == 3) || (fg == 6) || (fg == 7) )
		attr |= FOREGROUND_GREEN;
	    }
	  } else if ( as->argv[i] >= 40 && as->argv[i] <= 49 )
	  { int bg = as->argv[i] - 40;

	    attr &= ~BG_MASK;
	    if ( bg == 9 )		/* default */
	    { attr |= (as->def_attr & BG_MASK);
	    } else
	    { if ( bg % 2 == 1 )
		attr |= BACKGROUND_RED;
	      if ( bg >= 4 )
		attr |= BACKGROUND_BLUE;
	      if ( (bg == 2) || (bg == 3) || (bg == 6) || (bg == 7) )
		attr |= BACKGROUND_GREEN;
	    }
	  }
      }
    }

    if ( attr != info.wAttributes )
    { flush_ansi(as);
      SetConsoleTextAttribute(as->hConsole, attr);
    }
  }
}


static void
rlc_need_arg(ansi_stream *as, int arg, int def)
{ if ( as->argc < arg )
  { as->argv[arg-1] = def;
    as->argc = arg;
  }
}


static int
put_ansi(ansi_stream *as, int chr)
{ switch(as->cmdstat)
  { case CMD_INITIAL:
      switch(chr)
      {
#if 0
	case '\b':
	  CMD(rlc_caret_backward(b, 1));
	  break;
        case Control('G'):
	  MessageBeep(MB_ICONEXCLAMATION);
	  break;
	case '\r':
	  CMD(rlc_cariage_return(b));
	  break;
	case '\n':
	  CMD(rlc_caret_down(b, 1));
	  break;
	case '\t':
	  CMD(rlc_tab(b));
	  break;
#endif
	case 27:			/* ESC */
	  as->cmdstat = CMD_ESC;
	  break;
	default:
	  return send_ansi(as, chr);
      }
      break;
    case CMD_ESC:
      switch(chr)
      { case '[':
	  as->cmdstat = CMD_ANSI;
	  as->argc    = 0;
	  as->argstat = 0;		/* no arg */
	  break;
	default:
	  as->cmdstat = CMD_INITIAL;
	  break;
      }
      break;
    case CMD_ANSI:			/* ESC [ */
      if ( chr >= '0' && chr <= '9' )
      { if ( !as->argstat )
	{ as->argv[as->argc] = (chr - '0');
	  as->argstat = 1;		/* positive */
	} else
	{ as->argv[as->argc] = as->argv[as->argc] * 10 + (chr - '0');
	}

	break;
      }
      if ( !as->argstat && chr == '-' )
      { as->argstat = -1;		/* negative */
	break;
      }
      if ( as->argstat )
      { as->argv[as->argc] *= as->argstat;
	if ( as->argc < (ANSI_MAX_ARGC-1) )
	  as->argc++;			/* silently discard more of them */
	as->argstat = 0;
      }
      switch(chr)
      { case ';':
	  return 0;			/* wait for more args */
#if 0
	case 'H':
	case 'f':
	  rlc_need_arg(as, 1, 0);
	  rlc_need_arg(as, 2, 0);
	  CMD(rlc_set_caret(as, as->argv[0], as->argv[1]));
	  break;
	case 'A':
	  rlc_need_arg(as, 1, 1);
	  CMD(rlc_caret_up(as, as->argv[0]));
	  break;
	case 'B':
	  rlc_need_arg(as, 1, 1);
	  CMD(rlc_caret_down(as, as->argv[0]));
	  break;
	case 'C':
	  rlc_need_arg(as, 1, 1);
	  CMD(rlc_caret_forward(as, as->argv[0]));
	  break;
	case 'D':
	  rlc_need_arg(as, 1, 1);
	  CMD(rlc_caret_backward(as, as->argv[0]));
	  break;
	case 's':
	  CMD(rlc_save_caret_position(as));
	  break;
	case 'u':
	  CMD(rlc_restore_caret_position(as));
	  break;
	case 'J':
	  if ( as->argv[0] == 2 )
	    CMD(rlc_erase_display(as));
	  break;
	case 'K':
	  CMD(rlc_erase_line(as));
	  break;
#endif
	case 'm':
	  rlc_need_arg(as, 1, 0);
	  set_ansi_attributes(as);
      }
      as->cmdstat = CMD_INITIAL;
  }

  return 0;
}

static ssize_t
write_ansi(void *handle, char *buffer, size_t size)
{ ansi_stream *as = handle;
  size_t  n = size/sizeof(wchar_t);
  const wchar_t *s = (const wchar_t*)buffer;
  const wchar_t *e = &s[n];

  Message("Writing %d characters", n);

  for( ; s<e; s++)
  { if ( put_ansi(as, *s) != 0 )
      return -1;			/* error */
  }

  Message("Wrote %d characters", n);
  return n * sizeof(wchar_t);
}


static int
close_ansi(void *handle)
{ ansi_stream *as = handle;

  if ( as->magic == ANSI_MAGIC )
  { as->pStream->functions = saved_functions;
    as->pStream->handle    = as->saved_handle;

    PL_free(as);
    return 0;
  }

  return -1;
}


static int
control_ansi(void *handle, int op, void *data)
{ ansi_stream *as = handle;

  switch( op )
  { case SIO_FLUSHOUTPUT:
      return flush_ansi(as);
    case SIO_SETENCODING:
      return -1;			/* We cannot change the encoding! */
    case SIO_LASTERROR:
      return 0;				/* TBD */
    default:
      return -1;
  }
}


		 /*******************************
		 *	USER WIN32 CONSOLE	*
		 *******************************/

static ssize_t
Sread_win32_console(void *handle, char *buffer, size_t size)
{ GET_LD
  ansi_stream *as = handle;
  BOOL rc;
  DWORD done;
  DWORD mode;
  int isRaw = FALSE;

  if ( Suser_input &&
       Suser_input->handle == handle &&
       PL_ttymode(Suser_input) == PL_RAWTTY )
  { if ( GetConsoleMode(as->hConsole, &mode) &&
	 SetConsoleMode(as->hConsole,
			mode & ~(ENABLE_LINE_INPUT|ENABLE_ECHO_INPUT)) )
      isRaw = TRUE;
  }

  if ( !PL_wait_for_console_input(as->hConsole) )
    goto error;

  rc = ReadConsoleW(as->hConsole,
		    buffer,
		    (DWORD)(size / sizeof(wchar_t)),
		    &done,
		    NULL);

  if ( rc )
  { if ( isRaw )
      SetConsoleMode(as->hConsole, mode);
    return done * sizeof(wchar_t);
  }

error:
  if ( isRaw )
    SetConsoleMode(as->hConsole, mode);

  return -1;
}


static int
wrap_console(HANDLE h, IOSTREAM *s, IOFUNCTIONS *funcs)
{ ansi_stream *as;

  as = PL_malloc(sizeof(*as));
  memset(as, 0, sizeof(*as));

  as->hConsole     = h;
  as->pStream      = s;
  as->saved_handle = s->handle;

  s->handle    = as;
  s->encoding  = ENC_WCHAR;
  s->functions = funcs;

  return TRUE;
}


static void
init_output(void *handle, CONSOLE_SCREEN_BUFFER_INFO *info)
{ ansi_stream *as = handle;

  as->def_attr = info->wAttributes;
}


int
PL_w32_wrap_ansi_console(void)
{ HANDLE hIn    = GetStdHandle(STD_INPUT_HANDLE);
  HANDLE hOut   = GetStdHandle(STD_OUTPUT_HANDLE);
  HANDLE hError = GetStdHandle(STD_ERROR_HANDLE);
  CONSOLE_SCREEN_BUFFER_INFO info;

  if ( hIn    == INVALID_HANDLE_VALUE ||
       hOut   == INVALID_HANDLE_VALUE ||
       hError == INVALID_HANDLE_VALUE ||
       !GetConsoleScreenBufferInfo(hOut, &info) )
    return FALSE;

  saved_functions       = Sinput->functions;
  con_functions	        = *Sinput->functions;
  con_functions.read    = Sread_win32_console;
  con_functions.write   = write_ansi;
  con_functions.close   = close_ansi;
  con_functions.control = control_ansi;
  con_functions.seek    = NULL;

  wrap_console(hIn,    Sinput,  &con_functions);
  wrap_console(hOut,   Soutput, &con_functions);
  wrap_console(hError, Serror,  &con_functions);

  init_output(Soutput->handle, &info);
  init_output(Serror->handle, &info);

  PL_set_prolog_flag("tty_control", PL_BOOL, TRUE);
  return TRUE;
}