/*
 * ========================================================================
 * Copyright 2006-2007 University of Washington
 * Copyright 2013-2022 Eduardo Chappa
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * ========================================================================
 */

#include <system.h>
#include <general.h>

#include "../estruct.h"
#include "../mode.h"
#include "../pico.h"
#include "../edef.h"
#include "../efunc.h"
#include "../keydefs.h"

#include "tty.h"
#include "getkey.h"
#include "read.h"
#include "mouse.h"

#ifdef _WINDOWS
#include "mswin.h"
static int	MapMSKEYtoPK(int c);
#endif /* _WINDOWS */


/* internal declarations */
static int timeo = 0;

/* these next two are declared in pith/conf.h */
int
set_input_timeout(int t)
{
    int oldtimeo = timeo;

    timeo = t;
    return(oldtimeo);
}

int
get_input_timeout(void)
{
    return(timeo);
}


#ifndef _WINDOWS


/* internal prototypes */
void		bail(void);
int		ReadyForKey(int);



void
bail(void)
{
    sleep(30);				/* see if os receives SIGHUP */
    kill(getpid(), SIGHUP);			/* eof or bad error */
}


#if	TYPEAH
/* 
 * typahead - Check to see if any characters are already in the
 *	      keyboard buffer
 */
int
typahead(void)
{
    int x;	/* holds # of pending chars */

    return((ioctl(0,FIONREAD,&x) < 0) ? 0 : x);
}
#endif /* TYPEAH */



/*
 * ReadyForKey - return true if there's no timeout or we're told input
 *		 is available...
 */
int
ReadyForKey(int timeout)
{
    switch(input_ready(timeout)){
      case READY_TO_READ:
	return(1);
	break;

      case NO_OP_COMMAND:
      case NO_OP_IDLE:
      case READ_INTR:
	return(0);

      case BAIL_OUT:
      case PANIC_NOW:
	emlwwrite(_("Problem reading from keyboard!"), NULL);
	kill(getpid(), SIGHUP);	/* Bomb out (saving our work)! */
	/* no return */
    }

    /* can't happen */
    return(0);
}



/*
 * GetKey - Read in a key.
 * Do the standard keyboard preprocessing. Convert the keys to the internal
 * character set.  Resolves escape sequences and returns no-op if global
 * timeout value exceeded.
 */
UCS
GetKey(void)
{
    UCS ch, status, cc;

    if(sendnow){
	ch = Pmaster && Pmaster->auto_cmds && *Pmaster->auto_cmds 
		? *Pmaster->auto_cmds++ : NODATA;

	if (ch >= 0x00 && ch <= 0x1F)
	      ch = CTRL | (ch+'@');

        return(ch);
    }

    if(!ReadyForKey(FUDGE-5))
      return(NODATA);

    switch(status = kbseq(simple_ttgetc, NULL, bail, input_cs, &ch)){
      case 0: 	/* regular character */
	break;

      case KEY_DOUBLE_ESC:
	/*
	 * Special hack to get around comm devices eating control characters.
	 */
	if(!ReadyForKey(5))
	  return(BADESC);		/* user typed ESC ESC, then stopped */
	else
	    switch(status = kbseq(simple_ttgetc, NULL, bail, input_cs, &ch)){
	      case  KEY_UP		:
	      case  KEY_DOWN		:
	      case  KEY_RIGHT		:
	      case  KEY_LEFT		:
	      case  KEY_PGUP		:
	      case  KEY_PGDN		:
	      case  KEY_HOME		:
	      case  KEY_END		:
	      case  KEY_DEL		:
	      case F1  :
	      case F2  :
	      case F3  :
	      case F4  :
	      case F5  :
	      case F6  :
	      case F7  :
	      case F8  :
	      case F9  :
	      case F10 :
	      case F11 :
	      case F12 :
		return(CTRL | status);
		break;

	      case 0: 	/* regular character */
		break;

	      default:				/* punt the whole thing	*/
		(*term.t_beep)();
		return(BADESC);
		break;
	    }

	ch &= 0x7f;
	if(isdigit((unsigned char)ch)){
	    int n = 0, i = ch - '0';

	    if(i < 0 || i > 2)
	      return(BADESC);		/* bogus literal char value */

	    while(n++ < 2){
		if(!ReadyForKey(5)
		   || (!isdigit((unsigned char) (ch =
				   (*term.t_getchar)(NODATA, NULL, bail)))
		       || (n == 1 && i == 2 && ch > '5')
		       || (n == 2 && i == 25 && ch > '5'))){
		    return(BADESC);
		}

		i = (i * 10) + (ch - '0');
	    }

	    ch = i;
	}
	else{
	    if(islower((unsigned char)ch))	/* canonicalize if alpha */
	      ch = toupper((unsigned char)ch);

	    return((isalpha((unsigned char)ch) || ch == '@'
		    || (ch >= '[' && ch <= '_'))
		    ? (CTRL | ch) : ((ch == ' ') ? (CTRL | '@') : ch));
	}

	break;

#ifdef MOUSE
      case KEY_XTERM_MOUSE:
	{
	    /*
	     * Special hack to get mouse events from an xterm.
	     * Get the details, then pass it past the keymenu event
	     * handler, and then to the installed handler if there
	     * is one...
	     */
	    static int    down = 0;
	    int           x, y, button;
	    unsigned long cmd;

	    button = (*term.t_getchar)(NODATA, NULL, bail) & 0x03;

	    x = (*term.t_getchar)(NODATA, NULL, bail) - '!';
	    y = (*term.t_getchar)(NODATA, NULL, bail) - '!';

	    if(button == 0){
		down = 1;
		if(checkmouse(&cmd, 1, x, y))
		  return((UCS) cmd);
	    }
	    else if(down && button == 3){
		down = 0;
		if(checkmouse(&cmd, 0, x, y))
		  return((UCS) cmd);
	    }

	    return(NODATA);
	}

	break;
#endif /* MOUSE */

      case  KEY_UP		:
      case  KEY_DOWN		:
      case  KEY_RIGHT		:
      case  KEY_LEFT		:
      case  KEY_PGUP		:
      case  KEY_PGDN		:
      case  KEY_HOME		:
      case  KEY_END		:
      case  KEY_DEL		:
      case F1  :
      case F2  :
      case F3  :
      case F4  :
      case F5  :
      case F6  :
      case F7  :
      case F8  :
      case F9  :
      case F10 :
      case F11 :
      case F12 :
	return(status);

      case  CTRL_KEY_UP		:
	return(CTRL | KEY_UP);
      case  CTRL_KEY_DOWN	:
	return(CTRL | KEY_DOWN);
      case  CTRL_KEY_RIGHT	:
	return(CTRL | KEY_RIGHT);
      case  CTRL_KEY_LEFT	:
	return(CTRL | KEY_LEFT);

      case KEY_SWALLOW_Z:
	status = BADESC;
      case KEY_SWAL_UP:
      case KEY_SWAL_DOWN:
      case KEY_SWAL_LEFT:
      case KEY_SWAL_RIGHT:
	do
	  if(!ReadyForKey(2)){
	      status = BADESC;
	      break;
	  }
	while(!strchr("~qz", (*term.t_getchar)(NODATA, NULL, bail)));

	return((status == BADESC)
		? status
		: status - (KEY_SWAL_UP - KEY_UP));
	break;

      case KEY_KERMIT:
	do{
	    cc = ch;
	    if(!ReadyForKey(2))
	      return(BADESC);
	    else
	      ch = (*term.t_getchar)(NODATA, NULL, bail) & 0x7f;
	}while(cc != '\033' && ch != '\\');

	ch = NODATA;
	break;

      case BADESC:
	(*term.t_beep)();
	return(status);

      default:				/* punt the whole thing	*/
	(*term.t_beep)();
	break;
    }

    if (ch >= 0x00 && ch <= 0x1F)       /* C0 control -> C-     */
      ch = CTRL | (ch+'@');

    return(ch);
}


/* 
 * kbseq - looks at an escape sequence coming from the keyboard and 
 *         compares it to a trie of known keyboard escape sequences, and
 *         returns the function bound to the escape sequence.
 * 
 *     Args: getcfunc     -- Function to get a single character from stdin,
 *                           called with the next two arguments to this
 *                           function as its arguments.
 *           recorder     -- If non-NULL, function used to record keystroke.
 *           bail_handler -- Function used to bail out on read error.
 *           c            -- Pointer to returned character.
 *
 *  Returns: BADESC
 *           The escaped function.
 *           0 if a regular char with char stuffed in location c.
 */
UCS
kbseq(int (*getcfunc)(int (*recorder)(int ), void (*bail_handler)(void )),
      int (*recorder)(int),
      void (*bail_handler)(void),
      void *data,
      UCS *ch)
{
    unsigned char c;
    int      first = 1;
    KBESC_T *current;

    current = kbesc;
    if(current == NULL)				/* bag it */
      return(BADESC);

    while(1){
	c = (*getcfunc)(recorder, bail_handler);

	while(current->value != c){
	    if(current->left == NULL){		/* NO MATCH */
		if(first){
		    unsigned long octets_so_far, remaining_octets;
		    unsigned char *inputp;
		    UCS ucs;
		    unsigned char inputbuf[20];

		    /*
		     * Regular character.
		     * Read enough bytes to make up a character and convert it to UCS-4.
		     */
		    memset(inputbuf, 0, sizeof(inputbuf));
		    inputbuf[0] = c;
		    octets_so_far = 1;
		    for(;;){
			remaining_octets = octets_so_far;
			inputp = inputbuf;
			ucs = mbtow(data, &inputp, &remaining_octets);
			switch(ucs){
			  case CCONV_BADCHAR:
			    /*
			     * Not really a BADESC but that ought to
			     * be sufficient. We can add another type if
			     * we need to.
			     */
			    return(BADESC);
			  
			  case CCONV_NEEDMORE:
			    if(octets_so_far >= sizeof(inputbuf))
			      return(BADESC);

			    c = (*getcfunc)(recorder, bail_handler);
			    inputbuf[octets_so_far++] = c;
			    break;

			  default:
			    /* got a good UCS-4 character */
			    *ch = ucs;
			    return(0);
			}
		    }

		    /* NOTREACHED */
		    return(0);
		}
		else
		  return(BADESC);
	    }
	    current = current->left;
	}

	if(current->down == NULL)		/* match!!!*/
	  return(current->func);
	else
	  current = current->down;

	first = 0;
    }
}


#define	newnode()	(KBESC_T *)malloc(sizeof(KBESC_T))

/*
 * kpinsert - insert a keystroke escape sequence into the global search
 *	      structure.
 */
void
kpinsert(char *kstr, int kval, int termcap_wins)
{
    register	char	*buf;
    register	KBESC_T *temp;
    register	KBESC_T *trail;

    if(kstr == NULL)
      return;

    /*
     * Don't allow escape sequences that don't start with ESC unless
     * termcap_wins.  This is to protect against mistakes in termcap files.
     */
    if(!termcap_wins && *kstr != '\033')
      return;

    temp = trail = kbesc;
    buf = kstr;

    for(;;){
	if(temp == NULL){
	    temp = newnode();
	    temp->value = *buf;
	    temp->func = 0;
	    temp->left = NULL;
	    temp->down = NULL;
	    if(kbesc == NULL)
	      kbesc = temp;
	    else
	      trail->down = temp;
	}
	else{				/* first entry */
	    while((temp != NULL) && (temp->value != *buf)){
		trail = temp;
		temp = temp->left;
	    }

	    if(temp == NULL){   /* add new val */
		temp = newnode();
		temp->value = *buf;
		temp->func = 0;
		temp->left = NULL;
		temp->down = NULL;
		trail->left = temp;
	    }
	}

	if(*(++buf) == '\0')
	  break;
	else{
	    /*
	     * Ignore attempt to overwrite shorter existing escape sequence.
	     * That means that sequences with higher priority should be
	     * set up first, so if we want termcap sequences to override
	     * hardwired sequences, put the kpinsert calls for the
	     * termcap sequences first.  (That's what you get if you define
	     * TERMCAP_WINS.)
	     */
	    if(temp->func != 0)
	      return;

	    trail = temp;
	    temp = temp->down;
	}
    }
    
    /*
     * Ignore attempt to overwrite longer sequences we are a prefix
     * of (down != NULL) and exact same sequence (func != 0).
     */
    if(temp != NULL && temp->down == NULL && temp->func == 0)
      temp->func = kval;
}



/*
 * kbdestroy() - kills the key pad function key search tree
 *		 and frees all lines associated with it
 *
 *               Should be called with arg kbesc, the top of the tree.
 */
void
kbdestroy(KBESC_T *kb)
{
    if(kb){
	kbdestroy(kb->left);
	kbdestroy(kb->down);
	free((char *)kb);
	kb = NULL;
    }
}

#else /* _WINDOWS */

/*
 * Read in a key.
 * Do the standard keyboard preprocessing. Convert the keys to the internal
 * character set.  Resolves escape sequences and returns no-op if global
 * timeout value exceeded.
 */
UCS
GetKey(void)
{
    UCS			ch = 0;
    long		timein;
    

    ch = NODATA;
    timein = time(0L);

    /*
     * Main character processing loop.
     */
    while(!mswin_charavail()) {

#ifdef MOUSE
	/* Check Mouse.  If we get a mouse event, convert to char
	 * event and return that. */
	if (checkmouse (&ch,0,0,0)) {
	    curwp->w_flag |= WFHARD;
	    return (ch);
	}
#endif /* MOUSE */


	/* Check Timeout. */
	if(time(0L) >= timein+(FUDGE-10)) 
	    return(NODATA);
    }

    
    return (mswin_getc_fast());
}

#endif /* _WINDOWS */
