//
// ui-dumb.c
//
//
//
//-UserX 2002/03/19

#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>

#include "ui/ui-dumb.h"
#include "base/mem.h"
#include "base/str.h"
#include "base/strarray.h"
#include "base/strio.h"
#include "base/logger.h"
#include "base/getch.h"
#include "base/time.h"
#include "crypt/random.h"
#include "base/dblock.h"

#include "file/file.h"

/*
Provides a UI for dumb terminals. 
@author userx
@name ui-dumb
*/

//#define BLANKUIDUMBWINDOW {NULL, NULL, 
UIDumbWindow blankuidumbwindow = {
	{
		NULL, 
		NULL, 
		(UIFuncUIWindowInit)uidwInit, 
		(UIFuncUIWindowFree)uidwFree, 
		(UIFuncUIWindowRun)uidwRun, 
		(UIFuncUIControlSetState)uidcSetState, 
		(UIFuncUIControlSetValue)uidcSetValue,
		(UIFuncUIEntryAdd)uidwAddControl
	},
	'a',
	0
};


UIWindow *uidumbMake(void) {
	UIDumbWindow *uidw;
	uidw = memCopy(&blankuidumbwindow, sizeof(UIDumbWindow), "UIDumbWindow", NULL);
	return (UIWindow *)uidw;
}

void uidwInit(UIDumbWindow *uidw) {
	//nothing to do
}

void uidwFree(UIDumbWindow *uidw) {
	//nothing to do
}

void uidwRun(UIDumbWindow *uidw) {
	UIDumbControl *uidc;
	int i;
	if(uidw == NULL) {
		return;
	}
	if(uidw->uiw.uica->size <= 0) {
		return;
	}

	for(i = 0; i < uidw->uiw.uica->size; i++) {
		uidc = (UIDumbControl *)(uidw->uiw.uica->data[i]);
		if(uidc->uic.entry.notifyfunc != NULL) {
			uidc->uic.entry.notifyfunc(uidw, uidc, UINOTIFY_INIT);
		}
	}
	
	while(1) {
		uidwDraw(uidw);
		if(uidwDoInput(uidw) == 0) {
			break;
		}
	}
}

void uidwAddControl(UIDumbWindow *uidw, UIEntry *uie) {
	UIDumbControl *uidc;
	uidc = memAlloc(sizeof(UIDumbControl), "UIDumbControl", NULL);
	uieCopyAt(&uidc->uic.entry, uie);

	switch(uie->type) {
	case UITYPE_BUTTON:
	case UITYPE_BUTTONOK:
	case UITYPE_BUTTONCANCEL:
	case UITYPE_EDIT:
	case UITYPE_EDIT_LONG:
	case UITYPE_CHECK:
	case UITYPE_RADIO:
		if(uidw->lastletter == 0) {
			uidw->lastletter = uidw->firstletter;
		} else {
			uidw->lastletter++;
		}
		uidc->menuletter = uidw->lastletter;
		break;
	case UITYPE_COLUMN_START_2:
	case UITYPE_COLUMN_STOP:
	case UITYPE_RADIOSTART:
	case UITYPE_RADIOSTOP:
	case UITYPE_TEXT:
	case UITYPE_BOXSTART:
	case UITYPE_BOXSTOP:
	case UITYPE_NONE:
		uidc->menuletter = 0;
		break;
	default:
		LOGERROR(stringJoinMany("uidwAddControl unhandle control type:", intToString(uidc->uic.entry.type), NULL));
	}
	uicaAdd(uidw->uiw.uica, (UIControl *) uidc);
}

void uidcSetState(UIDumbWindow *uidw, int index, int state) {
	uidw->uiw.uica->data[index]->state = state;
}

void uidcSetValue(UIDumbWindow *uidw, int index) {
	//nothing to do
}

void uidwDraw(UIDumbWindow *uidw) {
	int i;
	char *s1;
	char *s2;

	UIDumbControl *uidc;
	for(i = 0; i < uidw->uiw.uica->size; i++) {
		uidc = (UIDumbControl *) uidw->uiw.uica->data[i];
		s1 = stringMake(3);
		if((uidc->uic.state & UISTATE_DISABLED) == 0) {
			//enabled
			s1[0] = '<';
			s1[1] = toupper(uidc->menuletter);
			s1[2] = '>';
		} else {
			//disabled
			s1[0] = ' ';
			s1[1] = tolower(uidc->menuletter);
			s1[2] = ' ';
		}
		s1[3] = 0;
		switch(uidc->uic.entry.type) {
		case UITYPE_BUTTON:
		case UITYPE_BUTTONOK:
		case UITYPE_BUTTONCANCEL:
			s2 = stringAppend("...", uidc->uic.entry.caption);
			break;
		case UITYPE_EDIT:
		case UITYPE_EDIT_LONG:
			s2 = stringCopyMany(
					"===", 
					uidc->uic.entry.caption,
					":",
					uidc->uic.stringvalue,
				NULL);
			break;
		case UITYPE_CHECK:
			s2 = stringAppend("[ ]", uidc->uic.entry.caption);
			if(uidc->uic.value != 0) {
				s2[1] = 'X';
			}
			break;
		case UITYPE_RADIO:
			s2 = stringAppend("( )", uidc->uic.entry.caption);
			if(uidc->uic.value != 0) {
				s2[1] = '*';
			}
			break;
		case UITYPE_COLUMN_START_2:
		case UITYPE_COLUMN_STOP:
		case UITYPE_RADIOSTART:
		case UITYPE_RADIOSTOP:
			stringFree(s1);
			continue;
		case UITYPE_TEXT:
		case UITYPE_BOXSTART:
			stringFree(s1);
			s1 = "--";
			s2 = stringCopy(uidc->uic.entry.caption);
			break;
		case UITYPE_BOXSTOP:
			stringFree(s1);
			s1 = "--";
			s2 = "----------------------------------------";
			break;
			//continue;
		default:
			LOGERROR(stringJoinMany("uidwDraw unhandle control type:", intToString(uidc->uic.entry.type), NULL));
			stringFree(s1);
			continue;
		}
		printf("%s%s\n", s1, s2);
		stringFree(s1);
		stringFree(s2);
	}
}

int uidwDoInput(UIDumbWindow *uidw) {
	UIDumbControl *uidc;
	char c;
	char *s;
	int i;
	int j;
	printf("choose:>");
	while(1) {
		//c = tolower(getchar());
		//c = scanf("%c\n", &c);
		//c = tolower(c);
		s = stringReadLine(fhstdin);
		c = tolower(s[0]);
		stringFree(s);
		if(c < uidw->firstletter || c > uidw->lastletter) {
			//continue;
			return 1;
		}
		//printf("%c\n", c);
		printf("\n");
		for(i = 0; i < uidw->uiw.uica->size; i++) {
			uidc = (UIDumbControl*)(uidw->uiw.uica->data[i]);
			if(uidc->menuletter == c && (uidc->uic.state & UISTATE_DISABLED) == 0) {
				switch(uidc->uic.entry.type) {
				case UITYPE_BUTTON:
				case UITYPE_BUTTONOK:
				case UITYPE_BUTTONCANCEL:
					if(uidc->uic.entry.notifyfunc != NULL) {
						if(uidc->uic.entry.notifyfunc(uidw, uidc, UINOTIFY_ACTIVATED) == UINOTIFYRETURN_EXIT) {
							return 0;
						}
					}
					break;
				case UITYPE_EDIT:
				case UITYPE_EDIT_LONG:
					printf("%s:", uidc->uic.entry.caption);
					uidc->uic.oldstringvalue = uidc->uic.stringvalue;
					s = stringMake(2048);
					//j = scanf("%s", s); //todo: replace this with somthing that can't be overflowed.
					s = stringReadLineOrEOF(fhstdin);

					//if(j == 1) {					
						uidc->uic.stringvalue = stringTrim(s, "\r\n");
						if(uidc->uic.entry.notifyfunc != NULL) {
							if(uidc->uic.entry.notifyfunc(uidw, uidc, UINOTIFY_CHANGE) == UINOTIFYRETURN_OK) {
								stringFree(uidc->uic.oldstringvalue);
							} else {
								stringFree(uidc->uic.stringvalue);
								uidc->uic.stringvalue = uidc->uic.oldstringvalue;
							}
						}
					//}
					uidc->uic.oldstringvalue = NULL;
					break;
				case UITYPE_CHECK:
					uidc->uic.oldvalue = uidc->uic.value;
					uidc->uic.value = (uidc->uic.value != 0) ? 0 : 1;
					if(uidc->uic.entry.notifyfunc != NULL) {
						if(uidc->uic.entry.notifyfunc(uidw, uidc, UINOTIFY_CHANGE) == UINOTIFYRETURN_OK) {
							//do nothing
						} else {
							uidc->uic.value = uidc->uic.oldvalue;
						}
					}
					break;
				case UITYPE_RADIO:
					j = uicaClearRadioGroup(uidw->uiw.uica, i);
					uidc->uic.value = 1;
					if(uidc->uic.entry.notifyfunc != NULL) {
						if(uidc->uic.entry.notifyfunc(uidw, uidc, UINOTIFY_CHANGE) == UINOTIFYRETURN_OK) {
							//do nothing
						} else {
							uicaClearRadioGroup(uidw->uiw.uica, j);
							uidw->uiw.uica->data[j]->value = 1;
							uidc->uic.value = 0;
						}
					}
					break;
				default:
					LOGERROR(stringJoinMany("uidwDoInput unhandle control type:", intToString(uidc->uic.entry.type), NULL));
				}
				return 1;
			}
		}
		//continue;
		return 1;
	}
	return 1;
}


int uidDialog(char *title, char *text, int type, int defaultchoice) {
	StringArrayHandle *sa;
	char *s;
	int i;
	//char c;
	if(type && UIDIALOG_ALLTYPE == 0) {
		return 0;
	}
	sa = saMake();

	for(i = 1; i < UIDIALOG_ALLTYPE; i <<= 1) {
		switch(i & type) {
		case UIDIALOG_YES:
			s = "Yes";
			break;
		case UIDIALOG_NO:
			s = "No";
			break;
		case UIDIALOG_OK:
			s = "Ok";
			break;
		case UIDIALOG_CANCEL:
			s = "Cancel";
			break;
		default:
			s = NULL;
		}
		if(s != NULL) {
			if((i & type) == defaultchoice) {
				s = stringToUpper(s);
			}
			saAppend(sa, s);
		}
	}
	s = saImplode(sa, "/");
	saFree(sa);
	printf("%s (%s):", text, s);
	stringFree(s);
	while(1) {
		//i = tolower(getchar());
		//c = scanf("%c\n", &c);
		//c = tolower(c);
		s = stringReadLineOrEOF(fhstdin);
		i = tolower(s[0]);
		stringFree(s);
		switch(i){
		case 'y':
			i = UIDIALOG_YES;
			break;
		case 'n':
			i = UIDIALOG_NO;
			break;
		case 'o':
			i = UIDIALOG_OK;
			break;
		case 'c':
			i = UIDIALOG_CANCEL;
			break;
		case '\n':
		case '\r':
		case '\x1a':
			i = defaultchoice;
			break;
		default:
			i = 0;
		}
		i &= type;
		if(i != 0) {
			printf("\n");
			return i;
		}
	}
}

int uidGetEntropy(char *title, char *text, int bits) {
#define MATCHLENGTH 8 /*probably excessive*/
        DataBlock* db = dblockMake(MATCHLENGTH * 2, MATCHLENGTH * 2, "KeyEntropy buffer");
        uint64 clocknow = 0, clockprev;
        int discard;
        int i;
	int bitsgot = 0;
	int escapecount = 0;
	int c;

	printf("\n%s\n", text);
	printf(_("Press random keys...\n"));

	while (bitsgot < bits) {
		c = getch();
		if(c == (EOF)) {
			break;
		}
		discard = 0;
		memmove(db->data, db->data + 1, db->size - 1);
		db->data[db->size - 1] = c;
		clockprev = clocknow;
		clocknow = timeGetTicks();
		if((clocknow - clockprev) <= 10000) {
			/*probably part of a multi character key press*/
			discard = 1;
		}

		for(i = 1; i <= MATCHLENGTH; i++) {
			if(memcmp(db->data + db->size - i,
				  db->data + db->size - i - i, i) == 0) {
				/*matched immediately preceding key sequences*/
				discard = 1;
				break;
			}
		}
		printf("%c", discard ? '.' : '*');
		randomAddEntropyClock(discard ? 0 : 1);
		randomAddEntropy(c, discard ? 0 : 1);
		if(!discard) bitsgot += 2;
	}
	
	printf("\a");
	printf(_("\nEnough entropy gathered!\n"));
	printf(_("Press enter twice to continue.\n"));
	while(escapecount < 2) {
		c = getch();
		switch(c) {
		case '\r':
		case '\n':
			escapecount++;
			break;
		case EOF:
			return bitsgot;
			break;
		default:
			escapecount = 0;
		}
	}

	return bitsgot;
}

