program GUI;

uses crt, gLib2D, sdl_addon, sdl, sdl_ttf, sysutils, dateutils, chatterbot;

// -------------- Stack stuff

const
	STACK_SIZE = 8;

type
	stack_text = record
		text : Array[1..STACK_SIZE] of string;
		top_ptr : integer;
	end;
	
	inputChar = record
		input : string;
		caps : boolean;
		ready : boolean;
	end;

function initStack() : stack_text;
begin
	initStack.top_ptr := 0;
end;

function isEmpty(txt : stack_text) : boolean;
begin
	isEmpty := txt.top_ptr = 0;
end;

function isFull(txt : stack_text) : boolean;
begin
	isFull := txt.top_ptr = STACK_SIZE;
end;

function head(txt : stack_text) : string;
begin
	head := txt.text[txt.top_ptr];
end;

function pop(var txt : stack_text) : string;
begin
	if isEmpty(txt) then pop := ''
	else
	begin
		pop := txt.text[txt.top_ptr];
		txt.top_ptr := txt.top_ptr-1;
	end;
end;

procedure push(input : string ; var txt : stack_text);
var
	tmp : stack_text;
begin
	if not isFull(txt) then
	begin
		txt.top_ptr := txt.top_ptr + 1;
		txt.text[txt.top_ptr] := input;
	end
	else
	begin
		tmp := initStack();
		repeat
			push(pop(txt),tmp)
		until tmp.top_ptr = STACK_SIZE-1;
		txt := initStack();
		repeat
			push(pop(tmp),txt)
		until txt.top_ptr = STACK_SIZE-1;
		push(input,txt);
	end;
end;

// -------------- Background

procedure blitBG(bgImg : gImage);
begin
	gBeginRects(bgImg);
		gSetAlpha(255);
		gSetCoordMode(g_up_left);
		gSetCoord(0,0);
		gAdd;
	gEnd;	
end;

// -------------- Input

procedure blitInput(inputImg : gImage ; empty, bottom : boolean);
begin
	gBeginRects(inputImg);
		if empty then gSetAlpha(100) else gSetAlpha(255);
		gSetCoordMode(g_up_left);
		if bottom then gSetCoord(75,499) else gSetCoord(75,450);
		gSetColor(white);
		gAdd;
	gEnd;
end;

procedure showInput(var input : stack_text ; font : PTTF_font ; var line_full : boolean ; bottom : boolean);
var
	buffer : gImage;
begin
	if isEmpty(input) then
	begin
		buffer := gTextLoad('< Tapez votre message >',font);
		blitInput(buffer,true,false);
		gTexFree(buffer);
	end
	else
	begin
		buffer := gTextLoad(pop(input),font);
		blitInput(buffer,false,bottom and not isEmpty(input));
		gTexFree(buffer);
		if (buffer^.w > 420) and bottom then
			line_full := true
		else if bottom then
			line_full := false;
		if bottom and not isEmpty(input) then showInput(input,font,line_full,false);
	end;
end;

// -------------- Chatlog

procedure blitChatlog(chatlogImg : gImage ; y : integer);
begin
	gBeginRects(chatlogImg);
	        gSetAlpha(255);
	        gSetCoordMode(g_up_left);
	        gSetCoord(75,y);
	        gSetColor(white);
	        gAdd;
	gEnd;
end;

procedure showChatlog(var chatlog : stack_text ; font : PTTF_font ; y : integer);
var
	buffer : gImage;
begin
	if not isEmpty(chatlog) and (y > 0) then
	begin
		buffer := gTextLoad(pop(chatlog),font);
		blitChatlog(buffer,y);
		gTexFree(buffer);
		showChatlog(chatlog,font,y-49);
	end;
end;

// -------------- Other blitting

procedure wholeBlit(bg : gImage ; input, chatlog : stack_text ; font : PTTF_font ; var line_full : boolean);
var
	tmpChat, tmpInput : stack_text;
begin
	blitBG(bg);
	tmpChat := chatlog;
	showChatlog(tmpChat,font,334);
	tmpInput := input;
	showInput(tmpInput,font,line_full,true);
	gFlip;
end;

// -------------- Keyboard/Updates

function partition(txt : string ; font : PTTF_font) : ptr_string;
// Adaptation du texte (saut de ligne) pour le bot
var
	line : string;
	res : ptr_string;
	i, l : integer;
	tmp : gImage;
begin
	line := '';
	res := nil;
	l := length(txt);
	for i := 1 to l do
	begin
		line := line+txt[i];
		tmp := gTextLoad(line,font);
		if tmp^.w > 420 then // saut de ligne
		begin
			res := queuePtr(line,res);
			line := '';
		end;
		gTexFree(tmp);
	end;
	if line <> '' then res := queuePtr(line,res);
	partition := res;
end;

function answer(var chatlog : stack_text ; dico : word_array ; db : node ; font : PTTF_font) : integer;
var
	getInput : string; // Stockage de l'entrée utilisateur
	getOutput, tmp : ptr_string; // Stockage de la réponse bot
	copyChat : stack_text; // Copie du chatlog
begin
//	sleep(1000); // 1 seconde d'attente, histoire que ça paraisse plus "humain"
	copyChat := chatlog;
	getInput := '';
	
	repeat
		getInput := pop(copyChat) + getInput;
	until head(copyChat) = 'Utilisateur dit :';
	
	push('EISTI-Bot dit : ',chatlog);
	getOutput := partition(chatMain(getInput,dico,db),font);
	repeat
		push(getOutput^.txt,chatlog);
		tmp := getOutput^.next;
		dispose(getOutput);
		getOutput := tmp;
	until getOutput = nil;

	answer := 0;
end;
	

function send(var chatlog, input : stack_text) : integer;
var
	buffer : stack_text;
begin
	buffer := initStack();
	repeat
		push(pop(input),buffer)
	until isEmpty(input);
	push('Utilisateur dit :',chatlog);
	repeat
		push(pop(buffer),chatlog)
	until isEmpty(buffer);
	send := 3;
end;

procedure appendInput(
	var input : stack_text;
	txt : string;
	var line_full : boolean);
var
	buffer : string;
	l : integer;
begin
	buffer := pop(input);
	l := length(buffer);
	if (txt = '\0') then
	// Si backspace (et si possible sur une ligne)
	begin
		if (ord(buffer[l]) > 128) then delete(buffer,l-1,2)
		else delete(buffer,l,1);
		if buffer <> '' then push(buffer,input);
	end
	else if line_full then
	// Sinon, si ligne remplie
	begin
		line_full := false;
		push(buffer,input);
		push(txt,input);
	end
	else // Sinon, ajout en milieu de ligne
		push(buffer+txt,input);
end;
				
function processInput(key : integer ; inputTmp : inputChar) : inputChar;
// Pour un clavier AZERTY
var
	res : inputChar;
begin
	res.input := '';
	if inputTmp.caps then
	begin
		case key of
		SDLK_exclaim : res.input := inputTmp.input+'§';
		SDLK_quotedbl : res.input := inputTmp.input+'3';
		SDLK_dollar : res.input := inputTmp.input+'£';
		SDLK_ampersand : res.input := inputTmp.input+'1';
		SDLK_quote : res.input := inputTmp.input+'4';
		SDLK_leftparen : res.input := inputTmp.input+'5';
		SDLK_rightparen : res.input := inputTmp.input+'°';
		SDLK_asterisk : res.input := inputTmp.input+'µ';
		SDLK_comma : res.input := inputTmp.input+'?';
		SDLK_minus : res.input := inputTmp.input+'6';
		SDLK_colon : res.input := inputTmp.input+'/';
		SDLK_semicolon : res.input := inputTmp.input+'.';
		SDLK_less : res.input := inputTmp.input+'>';
		SDLK_equals : res.input := inputTmp.input+'+';
		SDLK_caret : res.input := inputTmp.input+'"';
		SDLK_underscore : res.input := inputTmp.input+'8';
		224 : res.input := inputTmp.input+'0';
		231 : res.input := inputTmp.input+'9';
		232 : res.input := inputTmp.input+'7';
		233 : res.input := inputTmp.input+'2';
		249 : res.input := inputTmp.input+'%';
		else if (key >= 97) and (key <= 122) then res.input := inputTmp.input+chr(key-32);
		end;
	end
	else
	begin
		case key of
		224 : res.input := inputTmp.input+'à';
		231 : res.input := inputTmp.input+'ç';
		232 : res.input := inputTmp.input+'è';
		233 : res.input := inputTmp.input+'é';
		249 : res.input := inputTmp.input+'ù';
		else res.input := inputTmp.input+chr(key);
		end;
	end;
	if key = 8 then res.input := '\0';
	res.ready := not ((res.input = '') or (res.input = '^') or (res.input = '"'));
	res.caps := inputTmp.caps;
	
	if res.ready then
	begin
		if res.input = '^ ' then res.input := '^' else
		if res.input = '" ' then res.input := '"' else
		if res.input = '^a' then res.input := 'â' else
		if res.input = '^e' then res.input := 'ê' else
		if res.input = '^i' then res.input := 'î' else
		if res.input = '^o' then res.input := 'ô' else
		if res.input = '^u' then res.input := 'û' else
		if res.input = '"a' then res.input := 'ä' else
		if res.input = '"e' then res.input := 'ë' else
		if res.input = '"i' then res.input := 'ï' else
		if res.input = '"o' then res.input := 'ö' else
		if res.input = '"u' then res.input := 'ü' else
		if res.input = '^A' then res.input := 'Â' else
		if res.input = '^E' then res.input := 'Ê' else
		if res.input = '^I' then res.input := 'Î' else
		if res.input = '^O' then res.input := 'Ô' else
		if res.input = '^U' then res.input := 'Û' else
		if res.input = '"A' then res.input := 'Ä' else
		if res.input = '"E' then res.input := 'Ë' else
		if res.input = '"I' then res.input := 'Ï' else
		if res.input = '"O' then res.input := 'Ö' else
		if res.input = '"U' then res.input := 'Ü';
	end;
		
	processInput := res;
end;

function onKeypress( // Quand on appuie sur une touche...
	var input, chatlog : stack_text;
	var inputTmp : inputChar;
	var line_full : boolean) : integer;
var
	key : integer;
begin
	key := sdl_get_keypressed;
	case key of
		SDLK_capslock, SDLK_rshift, SDLK_lshift :
		begin
			inputTmp.caps := not inputTmp.caps;
			onKeyPress := 0;
		end;
		SDLK_return : if not isEmpty(input) then onKeyPress := 2;
		SDLK_escape : onKeyPress := 4;
	else
		begin
			inputTmp := processInput(key,inputTmp);
			if inputTmp.ready then
			begin
				if not isFull(input) then
					appendInput(input,inputTmp.input,line_full);
				inputTmp.ready := false;
			end;
			onKeyPress := 0;
		end;
	end;
end;

// -------------- Main procedures


procedure main(dico : word_array ; db : node);
// Procédure principale du GUI
var
	chatlog : stack_text; // Messages envoyés/reçus
	input : stack_text; // Message potentiel à envoyer
	event : integer; // Détermine un événement
	// 0 = Etat initial, 1 = Insertion char, 2 = Envoi du message utilisateur, 3 = Retour du bot, 4 = Quitter
	line_full : boolean; // true si ligne pleine, false sinon
	font : PTTF_font; // Police
	bg : gImage;
	inputTmp : inputChar; // chaine temporaire à ajouter à input
	buffer : string; // Copie de la chaîne actuellement envoyée
	keypress : boolean; // Touche enfoncée ou non
	timestart : tDateTime; // Moment où une touche est enfoncée
	
begin
	gClear(white);
	
	inputTmp.input := '';
	inputTmp.caps := false;
	inputTmp.ready := false;
	keypress := false;
	timestart := NOW;
	buffer := '';

	font := TTF_OpenFont('font.ttf', 30); // Police
	line_full := false; // Champ texte vide
	event := 0; // Etat initial
        
	bg := gTexLoad('background.jpg'); // Initialisation background
        
	chatlog := initStack(); // Init chatlog
	push('EISTI-Bot dit :',chatlog);
	push('Salutations, jeune ami(e) !',chatlog);
	
	input := initStack(); // Init input
	
	while true do
	begin
		gClear(white);
		wholeBlit(bg,input,chatlog,font,line_full);
		case event of
			2 : 	begin
					event := send(chatlog,input);
					line_full := false;
				end;
			3 : 	begin
					event := answer(chatlog,dico,db,font);
				end;
			4 :	exit;
		end;
		
		if not isEmpty(input) and (buffer <> '') and keypress and (milliSecondsBetween(timestart,NOW) > 500) then // Répéter lettre
		begin
			if not (isFull(input) and line_full) or (buffer = '\0') then
				appendInput(input,buffer,line_full);
		end;
		
		while sdl_update = 1 do
		begin
			if (sdl_get_keypressed > 0) then
			begin
				timestart := NOW;
				keypress := true;
				event := onKeyPress(input,chatlog,inputTmp,line_full);
				buffer := inputTmp.input;
				if (inputTmp.input <> '^') and (inputTmp.input <> '"') then inputTmp.input := '';
			end;
			if (sdl_key_release > 0) then
			begin
				keypress := false;
				if (sdl_key_release = SDLK_lshift) or (sdl_key_release = SDLK_rshift) then
				inputTmp.caps := not inputTmp.caps;
			end;
			if sdl_do_quit then
				exit;
		end;
	end;
        
	gTexFree(bg);
end;

var
	dico : word_array;
	db : node;

begin
	randomize;
	dico := loadDB();
	db := buildDB();
	main(dico,db);
	kill(db);
end.
