/*  Part of SWI-Prolog

    Author:        Jan Wielemaker
    E-mail:        J.Wielemaker@vu.nl
    WWW:           http://www.swi-prolog.org
    Copyright (c)  1999-2011, University of Amsterdam
    All rights reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions
    are met:

    1. Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer.

    2. Redistributions in binary form must reproduce the above copyright
       notice, this list of conditions and the following disclaimer in
       the documentation and/or other materials provided with the
       distribution.

    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    POSSIBILITY OF SUCH DAMAGE.
*/

#include <windows.h>
#include <tchar.h>
#define _MAKE_DLL 1
#undef _export
#include "console.h"
#include "console_i.h"
#include "common.h"
#include <memory.h>
#include <string.h>
#include <ctype.h>

#ifndef EOF
#define EOF -1
#endif

typedef void (*function)(Line ln, int chr);	/* edit-function */

static function dispatch_table[256];	/* general dispatch-table */
static function dispatch_meta[256];	/* ESC-char dispatch */
static RlcCompleteFunc _rlc_complete_function = rlc_complete_file_function;

static void	init_line_package(RlcData b);
static void	bind_actions(void);

#ifndef min
#define min(a, b) ((a) < (b) ? (a) : (b))
#define max(a, b) ((a) > (b) ? (a) : (b))
#endif

#ifndef TRUE
#define TRUE 1
#define FALSE 0
#endif

#ifndef EOS
#define EOS 0
#endif

#ifndef ESC
#define ESC 27
#endif

#define COMPLETE_NEWLINE 1
#define COMPLETE_EOF	 2

#define ctrl(c)	((c) - '@')
#define META_OFFSET 128
#define meta(c) ((c) + META_OFFSET)

		 /*******************************
		 *	       BUFFER		*
		 *******************************/

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
make_room(Line, int room)
	Make n-characters space after the point.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

static void
make_room(Line ln, size_t room)
{ while ( ln->size + room + 1 > ln->allocated )
  { if ( !ln->data )
    { ln->data = rlc_malloc(256 * sizeof(TCHAR));
      ln->allocated = 256;
    } else
    { ln->allocated *= 2;
      ln->data = rlc_realloc(ln->data, ln->allocated * sizeof(TCHAR));
    }
  }

  memmove(&ln->data[ln->point + room], &ln->data[ln->point],
	  (ln->size - ln->point)*sizeof(TCHAR));
  ln->size += room;
  if ( room > 0 )
    ln->change_start = min(ln->change_start, ln->point);
}


static void
set_line(Line ln, const TCHAR *s)
{ size_t len = _tcslen(s);

  ln->size = ln->point = 0;
  make_room(ln, len);
  _tcsncpy(ln->data, s, len);
}


static void
terminate(Line ln)
{ if ( !ln->data )
  { ln->data = rlc_malloc(sizeof(TCHAR));
    ln->allocated = 1;
  }
  ln->data[ln->size] = EOS;
}


static void
delete(Line ln, size_t from, size_t len)
{ if ( from < 0 || from > ln->size || len < 0 || from + len > ln->size )
    return;

  _tcsncpy(&ln->data[from], &ln->data[from+len], ln->size - (from+len));
  ln->size -= len;
}


		 /*******************************
		 *	     POSITIONING	*
		 *******************************/

static size_t
back_word(Line ln, size_t from)
{ from = min(from, ln->size);
  from = max(0, from);

  if ( ln->data )
  { while(!rlc_is_word_char(ln->data[from-1]) && from > 0 )
      from--;
    while(rlc_is_word_char(ln->data[from-1]) && from > 0 )
      from--;
  }

  return from;
}

static size_t
forw_word(Line ln, size_t from)
{ from = min(from, ln->size);
  from = max(0, from);

  if ( ln->data )
  { while(!rlc_is_word_char(ln->data[from]) && from < ln->size )
      from++;
    while(rlc_is_word_char(ln->data[from]) && from < ln->size )
      from++;
  }

  return from;
}

		 /*******************************
		 *       EDITING FUNCTIONS	*
		 *******************************/

static __inline void
changed(Line ln, size_t from)
{ ln->change_start = min(ln->change_start, from);
}


static void
insert_self(Line ln, int chr)
{ make_room(ln, 1);
  ln->data[ln->point++] = chr;
}


static void
backward_delete_character(Line ln, int chr)
{ if ( ln->point > 0 )
  { memmove(&ln->data[ln->point-1], &ln->data[ln->point],
	    (ln->size - ln->point)*sizeof(TCHAR));
    ln->size--;
    ln->point--;
  }

  changed(ln, ln->point);
}


static void
delete_character(Line ln, int chr)
{ if ( ln->point < ln->size )
  { ln->point++;
    backward_delete_character(ln, chr);
  }
}


static void
backward_character(Line ln, int chr)
{ if ( ln->point > 0 )
    ln->point--;
}


static void
forward_character(Line ln, int chr)
{ if ( ln->point < ln->size )
    ln->point++;
}


static void
backward_word(Line ln, int chr)
{ ln->point = back_word(ln, ln->point);
}


static void
forward_word(Line ln, int chr)
{ ln->point = forw_word(ln, ln->point);
}


static void
backward_delete_word(Line ln, int chr)
{ size_t from = back_word(ln, ln->point);

  memmove(&ln->data[from], &ln->data[ln->point],
	  (ln->size - ln->point)*sizeof(TCHAR));
  ln->size -= ln->point - from;
  ln->point = from;
  changed(ln, from);
}


static void
forward_delete_word(Line ln, int chr)
{ size_t to = forw_word(ln, ln->point);

  memmove(&ln->data[ln->point], &ln->data[to], (ln->size - to)*sizeof(TCHAR));
  ln->size -= to - ln->point;
  changed(ln, ln->point);
}


static void
transpose_chars(Line ln, int chr)
{ if ( ln->point > 0 && ln->point < ln->size )
  { int c0 = ln->data[ln->point-1];
    ln->data[ln->point-1] = ln->data[ln->point];
    ln->data[ln->point] = c0;
    changed(ln, ln->point-1);
  }
}


static void
start_of_line(Line ln, int chr)
{ ln->point = 0;
}


static void
end_of_line(Line ln, int chr)
{ ln->point = ln->size;
}


static void
kill_line(Line ln, int chr)
{ ln->size = ln->point;
  changed(ln, ln->size);
}


static void
empty_line(Line ln, int chr)
{ ln->size = ln->point = 0;
  changed(ln, 0);
}


static void
enter(Line ln, int chr)
{ ln->point = ln->size;
#ifdef DOS_CRNL
  make_room(ln, 2);
  ln->data[ln->point++] = '\r';
  ln->data[ln->point++] = '\n';
#else
  make_room(ln, 1);
  ln->data[ln->point++] = '\n';
#endif
  terminate(ln);
  ln->complete = COMPLETE_NEWLINE;
}


static void
eof(Line ln, int chr)
{ ln->point = ln->size;
  terminate(ln);
  ln->complete = COMPLETE_EOF;
}


static void
delete_character_or_eof(Line ln, int chr)
{ if ( ln->size == 0 )
  { ln->point = ln->size;
    terminate(ln);
    ln->complete = COMPLETE_EOF;
  } else
    delete_character(ln, chr);
}


static void
undefined(Line ln, int chr)
{
}


static void
interrupt(Line ln, int chr)
{ raise(SIGINT);
}

		 /*******************************
		 *		HISTORY		*
		 *******************************/

static void
add_history(rlc_console c, const TCHAR *data)
{ const TCHAR *s = data;

  while(*s && *s <= ' ')
    s++;

  if ( *s )
    rlc_add_history(c, s);
}


static void
backward_history(Line ln, int chr)
{ const TCHAR *h;

  if ( rlc_at_head_history(ln->console) && ln->size > 0 )
  { terminate(ln);
    add_history(ln->console, ln->data);
  }

  if ( (h = rlc_bwd_history(ln->console)) )
  { set_line(ln, h);
    ln->point = ln->size;
  }
}


static void
forward_history(Line ln, int chr)
{ if ( !rlc_at_head_history(ln->console) )
  { const TCHAR *h = rlc_fwd_history(ln->console);

    if ( h )
    { set_line(ln, h);
      ln->point = ln->size;
    }
  } else
    empty_line(ln, chr);
}

		 /*******************************
		 *	      COMPLETE		*
		 *******************************/

RlcCompleteFunc
rlc_complete_hook(RlcCompleteFunc new)
{ RlcCompleteFunc old = _rlc_complete_function;

  _rlc_complete_function = new;

  return old;
}


static int
common(const TCHAR *s1, const TCHAR *s2, int insensitive)
{ int n = 0;

  if ( !insensitive )
  { while(*s1 && *s1 == *s2)
    { s1++, s2++;
      n++;
    }
    return n;
  } else
  { while(*s1)
    { if ( _totlower(*s1) == _totlower(*s2) )
      { s1++, s2++;
	n++;
      } else
	break;
    }
    return n;
  }
}


static void
complete(Line ln, int chr)
{ if ( _rlc_complete_function )
  { rlc_complete_data dbuf;
    RlcCompleteData data = &dbuf;

    memset(data, 0, sizeof(dbuf));
    data->line      = ln;
    data->call_type = COMPLETE_INIT;

    if ( (*_rlc_complete_function)(data) )
    { TCHAR match[COMPLETE_MAX_WORD_LEN];
      int nmatches = 1;
      size_t ncommon = _tcslen(data->candidate);
      size_t patlen = ln->point - data->replace_from;

      _tcscpy(match, data->candidate);

      data->call_type = COMPLETE_ENUMERATE;
      while( (*data->function)(data) )
      { ncommon = common(match, data->candidate, data->case_insensitive);
	match[ncommon] = EOS;
	nmatches++;
      }
      data->call_type = COMPLETE_CLOSE;
      (*data->function)(data);

      delete(ln, data->replace_from, patlen);
      ln->point = data->replace_from;
      make_room(ln, ncommon);
      _tcsncpy(&ln->data[data->replace_from], match, ncommon);
      ln->point += ncommon;
      if ( nmatches == 1 && data->quote )
	insert_self(ln, data->quote);
    }
  }
}

#define MAX_LIST_COMPLETIONS 256

static void
list_completions(Line ln, int chr)
{ if ( _rlc_complete_function )
  { rlc_complete_data dbuf;
    RlcCompleteData data = &dbuf;

    memset(data, 0, sizeof(dbuf));
    data->line      = ln;
    data->call_type = COMPLETE_INIT;

    if ( (*_rlc_complete_function)(data) )
    { TCHAR *buf[COMPLETE_MAX_MATCHES];
      int n, nmatches = 0;
      size_t len = _tcslen(data->candidate) + 1;
      size_t longest = len;
      size_t cols;

      buf[nmatches] = rlc_malloc(len*sizeof(TCHAR));
      _tcsncpy(buf[nmatches], data->candidate, len);
      nmatches++;

      data->call_type = COMPLETE_ENUMERATE;
      while( (*data->function)(data) )
      { len = _tcslen(data->candidate) + 1;
	buf[nmatches] = rlc_malloc(len*sizeof(TCHAR));
	_tcsncpy(buf[nmatches], data->candidate, len);
	nmatches++;
	longest = max(longest, len);

	if ( nmatches > COMPLETE_MAX_MATCHES )
	{ TCHAR *msg = _T("\r\n! Too many matches\r\n");

	  while(*msg)
	    rlc_putchar(ln->console, *msg++);
	  ln->reprompt = TRUE;
	  data->call_type = COMPLETE_CLOSE;
	  (*data->function)(data);
	  return;
	}
      }
      data->call_type = COMPLETE_CLOSE;
      (*data->function)(data);

      cols = ScreenCols(ln->console) / longest;
      rlc_putchar(ln->console, '\r');
      rlc_putchar(ln->console, '\n');

      for(n=0; n<nmatches; )
      { TCHAR *s = buf[n];
	len = 0;

	while(*s)
	{ len++;
	  rlc_putchar(ln->console, *s++);
	}

	rlc_free(buf[n++]);

	if ( n % cols == 0 )
	{ rlc_putchar(ln->console, '\r');
	  rlc_putchar(ln->console, '\n');
	} else
	{ while( len++ < longest )
	  rlc_putchar(ln->console, ' ');
	}
      }
      if ( nmatches % cols != 0 )
      { rlc_putchar(ln->console, '\r');
	rlc_putchar(ln->console, '\n');
      }

      ln->reprompt = TRUE;
    }
  }
}


		 /*******************************
		 *	      REPAINT		*
		 *******************************/

static void
output(rlc_console b, TCHAR *s, size_t len)
{ while(len-- > 0)
  { if ( *s == '\n' )
      rlc_putchar(b, '\r');
    rlc_putchar(b, *s++);
  }
}


static void
update_display(Line ln)
{ if ( ln->reprompt )
  { const TCHAR *prompt = rlc_prompt(ln->console, NULL);
    const TCHAR *s = prompt;

    rlc_putchar(ln->console, '\r');
    while(*s)
      rlc_putchar(ln->console, *s++);

    rlc_get_mark(ln->console, &ln->origin);

    ln->change_start = 0;
    ln->reprompt = FALSE;
  }

  rlc_goto_mark(ln->console, &ln->origin, ln->data, ln->change_start);
  output(ln->console,
	 &ln->data[ln->change_start], ln->size - ln->change_start);
  rlc_erase_from_caret(ln->console);
  rlc_goto_mark(ln->console, &ln->origin, ln->data, ln->point);
  rlc_update(ln->console);

  ln->change_start = ln->size;
}

		 /*******************************
		 *	     TOPLEVEL		*
		 *******************************/

TCHAR *
read_line(rlc_console b)
{ line ln;

  init_line_package(b);

  memset(&ln, 0, sizeof(line));
  ln.console = b;
  rlc_get_mark(b, &ln.origin);

  while(!ln.complete)
  { int c;
    rlc_mark m0, m1;
    function func;

    rlc_get_mark(b, &m0);
    if ( (c = getch(b)) == IMODE_SWITCH_CHAR )
      return RL_CANCELED_CHARP;

    if ( c == EOF )
    { eof(&ln, c);
      update_display(&ln);
      break;
    } else if ( c == ESC )
    { if ( (c = getch(b)) == IMODE_SWITCH_CHAR )
	return RL_CANCELED_CHARP;
      if ( c > 256 )
	func = undefined;
      else
	func = dispatch_meta[c&0xff];
    } else
    { if ( c >= 256 )
	func = insert_self;
      else
	func = dispatch_table[c&0xff];
    }

    rlc_get_mark(b, &m1);

    (*func)(&ln, c);
    if ( m0.mark_x != m1.mark_x || m0.mark_y != m1.mark_y )
      ln.reprompt = TRUE;
    update_display(&ln);
  }
  rlc_clearprompt(b);

  add_history(b, ln.data);

  return ln.data;
}


		 /*******************************
		 *	     DISPATCH		*
		 *******************************/

static void
init_dispatch_table()
{ static int done;

  if ( !done )
  { int n;

    for(n=0; n<32; n++)
      dispatch_table[n] = undefined;
    for(n=32; n<256; n++)
      dispatch_table[n] = insert_self;
    for(n=0; n<256; n++)
      dispatch_meta[n] = undefined;

    bind_actions();

    done = TRUE;
  }
}


static void
init_line_package(RlcData b)
{ init_dispatch_table();
  rlc_init_history(b, 50);
}

		 /*******************************
		 *	       BIND		*
		 *******************************/

typedef struct _action
{ char		*name;
  function	 function;
  unsigned char  keys[4];
} action, *Action;

#define ACTION(n, f, k) { n, f, k }

static action actions[] = {
  ACTION("insert_self",		      insert_self,		 ""),
  ACTION("backward_delete_character", backward_delete_character, "\b"),
  ACTION("complete",		      complete,			 "\t"),
  ACTION("enter",		      enter, 			 "\r\n"),
  ACTION("start_of_line",	      start_of_line,		 {ctrl('A')}),
  ACTION("backward_character",	      backward_character,        {ctrl('B')}),
  ACTION("interrupt",		      interrupt,		 {ctrl('C')}),
  ACTION("end_of_line",		      end_of_line,		 {ctrl('E')}),
  ACTION("forward_character",	      forward_character,	 {ctrl('F')}),
  ACTION("transpose_chars",	      transpose_chars,		 {ctrl('T')}),
  ACTION("kill_line",		      kill_line,		 {ctrl('K')}),
  ACTION("backward_history",	      backward_history,		 {ctrl('P')}),
  ACTION("forward_history",	      forward_history,		 {ctrl('N')}),
  ACTION("empty_line",		      empty_line,		 {ctrl('U')}),
  ACTION("eof",			      eof,			 {ctrl('Z')}),

  ACTION("delete_character_or_eof",   delete_character_or_eof,	 {ctrl('D')}),
  ACTION("delete_character",	      delete_character,		 {127}),
  { "forward_word",		forward_word,  {meta(ctrl('F')), meta('f')}},
  { "backward_word",		backward_word, {meta(ctrl('B')), meta('b')}},
  { "forward_delete_word",	forward_delete_word, {meta(127), meta('d')}},
  ACTION("list_completions",	      list_completions,     {meta('?')}),
  ACTION("backward_delete_word",      backward_delete_word, {meta('\b')}),

  ACTION(NULL,			      NULL,			 "")
};

int
rlc_bind(int chr, const char *fname)
{ if ( chr >= 0 && chr <= 256 )
  { Action a = actions;

    for( ; a->name; a++ )
    { if ( strcmp(a->name, fname) == 0 )
      { if ( chr > META_OFFSET )
	  dispatch_meta[chr-META_OFFSET] = a->function;
	else
	  dispatch_table[chr] = a->function;

	return TRUE;
      }
    }
  }

  return FALSE;
}

static void
bind_actions()
{ Action a = actions;

  for( ; a->name; a++ )
  { unsigned char *k = a->keys;

    for( ; *k; k++ )
    { int chr = *k & 0xff;

      if ( chr > META_OFFSET )
	dispatch_meta[chr-META_OFFSET] = a->function;
      else
	dispatch_table[chr] = a->function;
    }
  }
}


