#include <stdio.h>
#include <bios.h>
#include <unistd.h>
#include <signal.h>
#include <assert.h>
#include "kdefines.h"
#include "shifts.h"
#include "myscan.h"
#include "tcodes.h"
#include "keys.h"
#include "keytabs.h"
#include "keyname.h"
#include "handle.h"

#if 0
#define DEBUG(x...) \
	do{ \
		fprintf(stderr, "<D> "); \
		fprintf(stderr, x); \
		fprintf(stderr, "\n"); \
	}while(0)
#else
#define DEBUG(x...) /* nothing */
#endif

static t_key_layout *k = NULL; /* active keyboard layout */
#define KEY_BUFSZ 2 /* buffer size */
static t_key buffer[KEY_BUFSZ]; /* key buffer */
static int buf_fill = 0; /* is buffer filled? */
static t_ascii deadkey = 0; /* No deadkey means 0 */

/*
* Short note about code effectivity:
* - I know it takes some instructions to call function, but macros doesn't
*   work well (no type checking, awful debugging) and (ansi-) C doesn't have
*    inline ... so if you are interested in speeding up this code, rewrite
*    those simple funcs as macros ... (it's simple job, isn't it?).
*    But: "You've been warned!"
*/

/* Is key buffer empty? */
static t_bool key_buffer_isempty(void){
	return !buf_fill;
}

/* Add value to buffer (if possible) */
static void key_add_buffer(t_ascii t, t_flags f){
	if(buf_fill == KEY_BUFSZ)
		assert(! "Tried add to full key_buffer");
	/* Any idea how can I do following code better way? */
	buffer[buf_fill] = t |
		(MATCH_ALL(f, MASK_CTRL) ? KEYMASK_CTRL : KEYMASK_NONE) |
		(MATCH_ALL(f, MASK_ALT) ? KEYMASK_ALT : KEYMASK_NONE) |
		(MATCH_ALL(f, MASK_RSHIFT) ? KEYMASK_RSHIFT : KEYMASK_NONE) |
		(MATCH_ALL(f, MASK_LSHIFT) ? KEYMASK_LSHIFT : KEYMASK_NONE) |
		(MATCH_ALL(f, MASK_LALT) ? KEYMASK_LALT : KEYMASK_NONE) |
		(MATCH_ALL(f, MASK_RALT) ? KEYMASK_RALT : KEYMASK_NONE) |
		(MATCH_ALL(f, MASK_LCTRL) ? KEYMASK_LCTRL : KEYMASK_NONE) |
		(MATCH_ALL(f, MASK_RCTRL) ? KEYMASK_RCTRL : KEYMASK_NONE) |
		(MATCH_ALL(f, MASK_SCROLL_LOCK) ? KEYMASK_SCROLL : KEYMASK_NONE) |
		(MATCH_ALL(f, MASK_NUM_LOCK) ? KEYMASK_NUM : KEYMASK_NONE) |
		(MATCH_ALL(f, MASK_CAPS_LOCK) ? KEYMASK_CAPS : KEYMASK_NONE) |
		(MATCH_ALL(f, MASK_INSERT) ? KEYMASK_INSERT : KEYMASK_NONE) |
		(MATCH_ALL(f, MASK_SYSRQ_PRESS) ? KEYMASK_SYSRQ : KEYMASK_NONE) |
		(MATCH_ALL(f, MASK_SCROLL_PRESS) ? KEYMASK_SCROLLP : KEYMASK_NONE) |
		(MATCH_ALL(f, MASK_NUM_PRESS) ? KEYMASK_NUMP : KEYMASK_NONE) |
		(MATCH_ALL(f, MASK_CAPS_PRESS) ? KEYMASK_CAPSP : KEYMASK_NONE) |
		KEYMASK_NONE;
	buf_fill++;
}

/* Delete and return value from buffer */
static t_key key_buffer_pop(void){
	if(key_buffer_isempty())
		assert(! "Can't pop empty buffer!");
	return (buffer[--buf_fill] & KEYMASK_BASIC);
}

/* Delete and return value from buffer */
static t_key key_buffer_pop_raw(void){
	if(key_buffer_isempty())
		assert(! "Can't pop empty buffer!");
	return buffer[--buf_fill];
}

/* Is there a key waiting in _real_ key buffer? */
static t_bool key_real_keyhit(){
	return bioskey(0x11);
}

/* Get _real_ flags from keyboard */
static t_flags key_real_flags(void){
	return bioskey(0x12);
}

/* Return _real_ keypress from keyboard, but don't remove it! */
static t_complete key_real_showkey(void){
	return bioskey(0x11);
}

/* Return _real_ keypress from keyboard and remove it! */
static t_complete key_real_readkey(void){
	return bioskey(0x10);
}

/* complete to scan conversion */
static t_scan key_complete2scan(t_complete c){
	return (t_scan) (c >> 8);
}

/* complete to ascii conversion */
static t_ascii key_complete2ascii(t_complete c){
	return (t_ascii) (c & 0xff);
}

/* is shift key pressed ? (resolves shift from caps + shift) */
static t_bool key_isshift(t_flags f){
	t_bool res = 0;
	if(MATCH_ANY(f, MASK_CAPS_LOCK|MASK_CAPS_PRESS))
		res = !res;
	if(MATCH_ANY(f, MASK_RSHIFT|MASK_LSHIFT))
		res = !res;
	return res;
}

/* look up if it's dead key */
static t_ascii key_lookup_dead(t_tcode t, t_flags f){
	if(key_isshift(f)){/* if shifted */
		if(k->shift_deadkey){
			return k->shift_deadkey[t];
		}
	}else{/* not shifted */
		if(k->plain_deadkey){
			return k->plain_deadkey[t];
		}
	}
	return 0; /* dflt action */
}

/* compose key */
static t_ascii key_compose(t_tcode t, t_bool s){
	t_compose_table *tmp = NULL;
	t_bool matchtbl = 0;
	t_composition *codes = NULL;

	if(k->compose){/* compose table not null ? */
		tmp = k->compose;
		while(tmp->dead){ /* search dead key */
			if(tmp->dead == deadkey){ /* match */
				matchtbl = 1;
				break;
			}
			tmp++;
		}
		if(!matchtbl) /* tbl not found */
			return 0;
		codes = tmp->codes;
		while(codes->shift || codes->tcode || codes->ascii){ /* search code */
			if(s == codes->shift && t == codes->tcode){ /* match */
				DEBUG("Compose: match: %d", codes->ascii);
				return (codes->ascii);
			}
			codes++;
		}
		return 0; /* not found */
	}else{
		return 0; /* no compose table */
	}
}

/* Fills buffer with a key (if any avail) */
static void key_fill_buffer(void){
	t_flags f = MASK_NONE;
	t_complete c = 0;
	t_tcode t = MYSCAN_NONE;
	t_ascii a = 0;
	t_ascii ndead = 0; /* possible new dead key? */

	while(key_buffer_isempty() && key_real_keyhit()){
		t = MYSCAN_NONE;
		/* read keys until we will get not null tcode */
		while(key_real_keyhit() && t == MYSCAN_NONE){
			f = key_real_flags();
			c = key_real_showkey();
			t = scan_to_tcode(key_complete2scan(c), key_complete2ascii(c), f);
			if(t == MYSCAN_NONE) /* null key, read it out */
				key_real_readkey();
		}
		if(t == MYSCAN_NONE) /* no keys in buf and there were no valid */
			break;
		if(t == MYSCAN_PLAIN){ /* key entered via Alt + numbers */
			key_add_buffer(key_complete2ascii(c), f); /* add to buffer */
			key_real_readkey(); /* read out of buffer */
			continue; /* next pass ... de facto 'return' */
		}
		if(MATCH_NONE(f, MASK_CTRL|MASK_ALT)){ /* no Ctrl, Alt */
			if((ndead = key_lookup_dead(t, f))){ /* dead key */
				if(deadkey){ /* old dead key exists */
					if(ndead == deadkey){ /* equals */
						/* we're ignoring this dead ... */
						key_real_readkey(); /* read out of buffer */
					}else{ /* neq */
						key_add_buffer(deadkey, MASK_NONE); /* add to buffer */
						deadkey = ndead; /* replace with new one */
						key_real_readkey(); /* read out of buffer */
					}
				}else{ /* no old dead key */
					deadkey = ndead; /* add as new dead */
					key_real_readkey(); /* read out of buffer */
				}
			}else{ /* not dead key */
				if(deadkey){ /* old dead key exists */
					a = key_compose(t, key_isshift(f));
					if(!a){ /* was not composed :( */
						key_add_buffer(deadkey, 0); /* add key to buffer */
					}else{
						key_add_buffer(a, f); /* add key to buffer */
						key_real_readkey(); /* read out of buffer */
					}
					deadkey = 0; /* clear dead key */
				}else{ /* no old dead key */
					if(key_isshift(f)){ /* shift'd */
						assert(k->shift != NULL);
						a = k->shift[t];
					}else{ /* plain */
						assert(k->plain != NULL);
						a = k->plain[t];
					}
					key_add_buffer(a, f); /* add key to buffer */
					key_real_readkey(); /* read out of buffer */
				}
			}
		}else{ /* Ctrl, Alt or both pressed */
			if(deadkey){ /* old dead exists */
				key_add_buffer(deadkey, MASK_NONE); /* add to buffer */
				deadkey = 0; /* clear deadkey */
			}else{ /* no old dead key */
				/* rewrite t in tables ctrl, alt, ctrl_alt */
				if(MATCH_ALL(f, MASK_CTRL|MASK_ALT)){ /* ctrl + alt */
					assert(k->ctrl_alt != NULL);
					a = k->ctrl_alt[t];
				}else{ /* either ctrl or alt */
					if(MATCH_ALL(f, MASK_CTRL)){ /* ctrl */
						assert(k->ctrl != NULL);
						a = k->ctrl[t];
					}else{ /* alt? */
						if(MATCH_ALL(f, MASK_ALT)){ /* alt */
							assert(k->alt != NULL);
							a = k->alt[t];
						}else{ /* nothing - impossible! */
							assert(! "Problem resolving key - got no flag :(");
						}
					}
				}
				key_add_buffer(a, f); /* add key to buffer */
				key_real_readkey(); /* read out of buffer */
			}
		}
	}
}

/* Key functions initialization */
t_bool key_init(void){
	/* FIXME: keymap verification? */
	signal(SIGINT, SIG_IGN);
	k = &english_layout;
	return 1;
}

/* Key functions shutdown */
t_bool key_shut(void){
	k = NULL;
	signal(SIGINT, SIG_DFL);
	return 1;
}

/* Keyboard layout switch */
t_bool key_switch(char *name){
	if(!strcmp(name, "pc2") || !strcmp(name, "PC2") || !strcmp(name, "czech")){
		k = &pc2_layout;
		return 1;
	}
	if(!strcmp(name, "kam") || !strcmp(name, "KAM")){
		k = &kam_layout;
		return 1;
	}
	if(!strcmp(name, "en") || !strcmp(name, "EN") || !strcmp(name, "english")
			|| !strcmp(name, "rom")){
		k = &english_layout;
		return 1;
	}
	return 0;
}

/* Is there a key in buffer? */
t_bool key_pressed(){
	key_fill_buffer(); /* fill the buffer - alias check for keys */
	return !key_buffer_isempty(); /* is there any key? */
}

/* Read a key - something like 'getxkey' but with key-mapping */
t_key key_read(){
	while(!key_pressed()){ usleep(5); } /* wait for key, if necessary */
	key_fill_buffer(); /* it's not need ... but we must be sure :) */
	return key_buffer_pop(); /* return the key */
}

/* 'raw' access to keys - returns all flags + ascii */
t_raw key_raw(){
	while(!key_pressed()){ usleep(5); } /* wait for key, if necessary */
	key_fill_buffer(); /* it's not need ... but we must be sure :) */
	return key_buffer_pop_raw(); /* return the key - raw */
}

/* key to name - only for 'special' keys (code > 256) */
char *key_to_name(t_key k){
	if((k & KEYMASK_BITS) > 255){
		if((k & KEYMASK_BITS) > MAX_KEYNAME){
			return "out_of_range";
		}else{
			return keyname_table[(k & KEYMASK_BITS) - 256];
		}
	}else{
		return "error";
	}
}

/* Is this key printable? */
t_bool key_isprintable(t_ascii c){
	if(c < 32 || c > 255) return 0;
	return 1;
}

/* vim: set ts=4 cindent shiftwidth=4 tw=0 : */
