"use strict";

var DEFINE_DIALOGUE_STATES = {
	OPENING : 0,
	READY : 1,
	TEXT : 2,
	PAUSE : 3,
	WAIT : 4,
	CHOOSE : 5,
	CLOSING : 6
};

/*
dialogue script options:

{command:"buffer", text:"new text"}
{command:"clear"}
{command:"pause", frames:frames}
{command:"wait"} // for Controlled advance
{command:"chooseH", choices:{"optiontext":newindex, "optiontext":newindex, ...}}
{command:"chooseV", choices:{"optiontext":newindex, "optiontext":newindex, ...}}
{command:"goto", newindex:newindex}
{command:"return", retval:retval}

*/
/*
a controller for text dialogue

dialogueData: 
{ 
	x: pixelx,
	y: pixely,
	width: tileswide, // == pixelswide/8
	height: tileshigh, // ==pixelshigh/8
	pauses: bool, // does this dialogue halt gameobject updates?
	blocks: bool, // does this dialogue prevent gameplay input?
	title: string, // label to put on the upper-left of the window, if any
	script: [{...},...] // an array of options/commands which will be evaluated in sequence
}
controls: the Controls object for advancing this dialogue
callback: a function(index,retval) to be called when entering a script entry, and when the dialogue exits
	index: index (into script) of the command being started; -1 when dialogue exits
	retval: return value of the dialogue/choice; null if not returning
auto: should this dialogue auto-advance without player input?
*/
var Dialogue = function(dialogueData,controls,callback,auto){
	//var windowImg = SDS_Load_Image("art/sheets/interface/font.png");
	//this.tileSheet = new TileSheet(3, 3, 4, 4, windowImg);

	this.x = dialogueData.x;
	this.y = dialogueData.y;
	this.width = dialogueData.width;
	this.height = dialogueData.height;

	this.pauses = dialogueData.pauses;
	this.blocks = dialogueData.blocks;

	this.title = dialogueData.title ? " "+dialogueData.title+" " : null;

	this.frameCanvas = document.createElement('canvas');
	this.frameCanvas.width = (this.width+2)*8;
	this.frameCanvas.height = (this.height+2)*8;
	this.frameCanvas.getContext('2d').clearRect(0,0,this.frameCanvas.width,this.frameCanvas.height);

	this.textCanvas = document.createElement('canvas');
	this.textCanvas.width = this.width*8;
	this.textCanvas.height = this.height*8;
	this.textCanvas.getContext('2d').clearRect(0,0,this.textCanvas.width,this.textCanvas.height);

	this.colorCanvas = document.createElement('canvas');
	this.colorCanvas.width = this.width*8;
	this.colorCanvas.height = this.height*8;
	this.colorCanvas.getContext('2d').clearRect(0,0,this.colorCanvas.width,this.colorCanvas.height);
	//this.colorCanvas.getContext('2d').fillStyle = "#fff";
	//this.colorCanvas.getContext('2d').fillRect(0,0,this.colorCanvas.width,this.colorCanvas.height);

	this.titleColorCanvas = document.createElement('canvas');
	this.titleColorCanvas.width = this.width*8;
	this.titleColorCanvas.height = 8;
	this.titleColorCanvas.getContext('2d').clearRect(0,0,this.colorCanvas.width,this.colorCanvas.height);


	this.script = dialogueData.script;
	this.scriptIndex=-1; // will become 0 when READY runs

	this.controls = controls;

	this.callback = callback;

	this.auto = auto ? auto : false;
	
	this.textBuffer = "";// remaining to draw
	this.textRows = [""]; // text in each row of the displayed dialog
	this.charx = 0;
	//this.chary = 0;

	this.state = DEFINE_DIALOGUE_STATES.OPENING;
	this.sfx = new Chiptune("FX_dlg_open",SDS.sfxchip);
	this.sfx.play(false);

	this.frameTimer=0;

	this.choices = []; // [{start:{col:col, row:row}, end:{col:col, row:row}}, ...]
	this.choiceIndex=0;
	this.retval = -1;

	this._heap = {};
};
	Dialogue.prototype.windowImg = SDS_Load_Image("art/sheets/interface/font.png");
	Dialogue.prototype.tileSheet = new TileSheet(3, 3, 4, 4, Dialogue.prototype.windowImg);

	Dialogue.prototype._redrawText = function(){
		this._heap.textRow = Math.max(0,this.textRows.length-this.height);

		this.textCanvas.getContext('2d').clearRect(0,0,this.textCanvas.width,this.textCanvas.height);

		for (this._heap.row=0; this._heap.row<this.height; this._heap.row++){
			for (this._heap.col=0; this._heap.col<this.width; this._heap.col++){
				if (this._heap.row+this._heap.textRow<this.textRows.length && this._heap.col<this.textRows[this._heap.row+this._heap.textRow].length){
					this._drawCharacter(this.textRows[this._heap.row+this._heap.textRow].charCodeAt(this._heap.col),this._heap.col,this._heap.row);
				}
				else{
					this._drawCharacter(" ".charCodeAt(0),this._heap.col,this._heap.row);
				}
			}
		}
	};
	Dialogue.prototype._drawCharacter = function(charcode,col,row){
		this.textCanvas.getContext('2d').clearRect(col<<3,row<<3,8,8);
		this.tileSheet.drawIntoCanvas(this.textCanvas,charcode,col<<3,row<<3);
	};

	Dialogue.prototype.Update = function(){
		this._heap.row=0;
		this._heap.col=0;
		this._heap.tile=0;
		this._heap.newLine="";
		this._heap.choice="";

		switch(this.state){
			// state OPENING: do the animation, and prep canvasses when ready
			case DEFINE_DIALOGUE_STATES.OPENING:
				this.frameTimer++;
				if (this.frameTimer>=8){
					for (this._heap.row=0; this._heap.row<this.height+2; this._heap.row++){
						for (this._heap.col=0; this._heap.col<this.width+2; this._heap.col++){
							this._heap.tile=0;
							this._heap.prefill = null;
							if (this._heap.row===0){
								if (this._heap.col===0){
									this._heap.tile=8;
								}
								else if (this._heap.col===this.width+1){
									this._heap.tile=10;
								}
								else{
									this._heap.tile=9;
								}
								if (this.title){
									if (this._heap.col===1){
										this._heap.tile=24;
									}
									else if (this._heap.col>0 && this._heap.col-2<this.title.length){
										this._heap.tile = this.title.charCodeAt(this._heap.col-2);
										this._heap.prefill = "#ed0";
									}
									else if (this._heap.col===this.title.length+2){
										this._heap.tile=25;
									}
								}
							}
							else if (this._heap.row===this.height+1){
								if (this._heap.col===0){
									this._heap.tile=14;
								}
								else if (this._heap.col===this.width+1){
									this._heap.tile=12;
								}
								else{
									this._heap.tile=13;
								}
							}
							else{
								if (this._heap.col===0){
									this._heap.tile=15;
								}
								else if (this._heap.col===this.width+1){
									this._heap.tile=11;
								}
								else{
									this._heap.tile=0;
								}
							}

							if (this._heap.prefill){
								this.titleColorCanvas.getContext('2d').fillStyle = this._heap.prefill;
								this.titleColorCanvas.getContext('2d').fillRect((this._heap.col-1)<<3,0,8,8);
							}

							this.tileSheet.drawIntoCanvas(this.frameCanvas,this._heap.tile,this._heap.col<<3,this._heap.row<<3);
						}
					}

					this.colorCanvas.getContext('2d').fillStyle = "#fff";
					this.colorCanvas.getContext('2d').fillRect(0,0,this.colorCanvas.width,this.colorCanvas.height);

					this._redrawText();
					
					this.state=DEFINE_DIALOGUE_STATES.READY;
				}
				else{
					this.colorCanvas.getContext('2d').clearRect(0,0,this.colorCanvas.width,this.colorCanvas.height);
					for (this._heap.row=0; this._heap.row<this.height; this._heap.row++){
						for (this._heap.col=0; this._heap.col<this.width; this._heap.col++){
							this.tileSheet.drawIntoCanvas(this.colorCanvas,this.frameTimer,this._heap.col<<3,this._heap.row<<3);
						}
					}
				}
			break;
			// state READY: reset the frametimer and load the next command/state
			case DEFINE_DIALOGUE_STATES.READY:
				this.frameTimer=0;

				this.scriptIndex++;
				if (this.callback) this.callback(this.scriptIndex,null);

				if (this.scriptIndex>=this.script.length){
					this.state=DEFINE_DIALOGUE_STATES.CLOSING;
					this.sfx = new Chiptune("FX_dlg_close",SDS.sfxchip);
					this.sfx.play(false);
				}
				else{
					switch(this.script[this.scriptIndex].command){
						case "buffer":
							this.textBuffer+=this.script[this.scriptIndex].text;
							this.state=DEFINE_DIALOGUE_STATES.TEXT;
						break;
						case "clear":
							this.textRows = [""];
							this.textBuffer = ""; // just in case
							this.charx=0;
							this._redrawText();
							this.state=DEFINE_DIALOGUE_STATES.READY; // to make it explicit
						break;
						case "pause":
							this.state=DEFINE_DIALOGUE_STATES.PAUSE;
						break;
						case "wait":
							this.state=DEFINE_DIALOGUE_STATES.WAIT;
						break;
						case "chooseH":
							this._heap.newLine = "";
							this.choices = [];
							for (this._heap.choice in this.script[this.scriptIndex].choices){
								this.choices.push({
									start:{col:this._heap.newLine.length,row:Math.min(this.textRows.length,this.height-1)}, 
									newIndex:this.script[this.scriptIndex].choices[this._heap.choice]
								});
								this._heap.newLine = this._heap.newLine+" "+this._heap.choice+" ";
								this.choices[this.choices.length-1].end = {col:this._heap.newLine.length-1,row:Math.min(this.textRows.length,this.height-1)};
							}
							this.textRows.push(this._heap.newLine);
							this.choiceIndex = 0;
							this.charx = 0;
							this._redrawText();
							this.state=DEFINE_DIALOGUE_STATES.CHOOSE;
						break;
						case "chooseV":
							this.choices = [];
							for (this._heap.choice in this.script[this.scriptIndex].choices){
								this.choices.push({
									start:{col:0}, 
									end:{col:this._heap.choice.length+1},
									newIndex:this.script[this.scriptIndex].choices[this._heap.choice]
								});
								this.textRows.push(" "+this._heap.choice+" ");
							}
							for (this._heap.row=0; this._heap.row<this.choices.length; this._heap.row++){
								if (this.textRows.length>=this.height){
									this.choices[this._heap.row].start.row = this.height-this.choices.length+this._heap.row;
									this.choices[this._heap.row].end.row = this.height-this.choices.length+this._heap.row;
								}
								else{
									this.choices[this._heap.row].start.row = this.textRows.length-this.choices.length+this._heap.row;
									this.choices[this._heap.row].end.row = this.textRows.length-this.choices.length+this._heap.row;
								}
							}
							this.choiceIndex = 0;
							this.charx = 0;
							this._redrawText();
							this.state=DEFINE_DIALOGUE_STATES.CHOOSE;
						break;
						case "goto":
							this.scriptIndex = this.script[this.scriptIndex].newindex-1;
							this.state=DEFINE_DIALOGUE_STATES.READY; // to make it explicit
						break;
						case "return":
							this.retval = this.script[this.scriptIndex].retval;
							this.state=DEFINE_DIALOGUE_STATES.CLOSING;
							this.sfx = new Chiptune("FX_dlg_close",SDS.sfxchip);
							this.sfx.play(false);
						break;
					}
					// and by default we stay in READY (junk command)
				}

			break;
			// state TEXT: add characters to the display from the buffer; manage output position
			case DEFINE_DIALOGUE_STATES.TEXT:
				if (GLOBAL_SPEEDRUN_EXTREME){
					this.state=DEFINE_DIALOGUE_STATES.READY;
					break;
				}

				this._heap.charsLeft=3;
				do {
					if (this.textRows.length==0) this.textRows=[""]; // for safety
					this._heap.nextChar = this.textBuffer.substr(0,1);
					this.textBuffer = this.textBuffer.substr(1);
					// add characters one at a time
					this._heap.nextSpace = this.textBuffer.indexOf(" ");
					this._heap.nextNewline = this.textBuffer.indexOf("\n");
					if (this._heap.nextSpace>=0 && this._heap.nextNewline>=0) this._heap.nextBreak = Math.min(this._heap.nextSpace,this._heap.nextNewline);
					else this._heap.nextBreak = Math.max(this._heap.nextSpace,this._heap.nextNewline);
					if (this._heap.nextBreak===-1) this._heap.nextBreak = this.textBuffer.length;
					// add a newline if
					// (a) the next character is a literal newline or
					// (b) the next character is whitespace/breakable (currently just " ") and we'll hit the edge before the next break
					if (this._heap.nextChar==="\n" || (this._heap.nextChar===" " && this.charx+this._heap.nextBreak>=this.width)){
						this.textRows.push("");
						this._redrawText();
						this.charx=0;
					}
					else{
						this.textRows[this.textRows.length-1]+=this._heap.nextChar;
						this._drawCharacter(this._heap.nextChar.charCodeAt(0),this.charx,Math.min(this.textRows.length-1,this.height-1))
						this.charx++;
					}
					this._heap.charsLeft--;

					if (this.textBuffer.length==0) this.state=DEFINE_DIALOGUE_STATES.READY;
				}
				while (this.state===DEFINE_DIALOGUE_STATES.TEXT && this._heap.charsLeft>0 && !this.auto && this.controls.held.A);

			break;
			// state PAUSE: do nothing until frametimer says so
			case DEFINE_DIALOGUE_STATES.PAUSE:
				if (GLOBAL_SPEEDRUN_EXTREME){
					this.state=DEFINE_DIALOGUE_STATES.READY;
					break;
				}

				this.frameTimer++;
				if (this.frameTimer>=this.script[this.scriptIndex].frames) this.state=DEFINE_DIALOGUE_STATES.READY;
			break;
			// state WAIT: await player/control OK
			case DEFINE_DIALOGUE_STATES.WAIT:
				if (GLOBAL_SPEEDRUN_EXTREME){
					this.state=DEFINE_DIALOGUE_STATES.READY;
					break;
				}
				
				if (this.auto || this.controls.pressed.A || this.controls.pressed.B) this.state=DEFINE_DIALOGUE_STATES.READY;
				else{
					this.frameTimer++;
					this._drawCharacter((this.frameTimer&8) ? 255 : " ".charCodeAt(0),this.width-1,this.height-1);
				}
			break;
			// state CHOOSE: track controls input to select option; update aesthetics and await OK
			case DEFINE_DIALOGUE_STATES.CHOOSE:
				this._heap.newchoice=false;
				if (this.frameTimer===0){
					this._heap.newchoice=true;
					this.frameTimer++;
				}
				if (this.controls.pressed.right || this.controls.pressed.down){
					this.choiceIndex=(this.choiceIndex+1)%this.choices.length;
					this._heap.newchoice=true;
					this.sfx = new Chiptune("FX_pick",SDS.sfxchip);
					this.sfx.play(false);
				}
				if (this.controls.pressed.left || this.controls.pressed.up){
					this.choiceIndex--;
					if (this.choiceIndex<0) this.choiceIndex = this.choices.length-1;
					this._heap.newchoice=true;
					this.sfx = new Chiptune("FX_pick",SDS.sfxchip);
					this.sfx.play(false);
				}

				for (this._heap.i=0; this._heap.newchoice && this._heap.i<this.choices.length; this._heap.i++){
					this._drawCharacter(
						(this._heap.i===this.choiceIndex) ? 171 : " ".charCodeAt(0),
						this.choices[this._heap.i].start.col,
						this.choices[this._heap.i].start.row
					);
					this._drawCharacter(
						(this._heap.i===this.choiceIndex) ? 187 : " ".charCodeAt(0),
						this.choices[this._heap.i].end.col,
						this.choices[this._heap.i].end.row
					);
				}

				if (this.auto || this.controls.pressed.A || this.controls.pressed.B){
					this.scriptIndex = this.choices[this.choiceIndex].newIndex-1; // -1 because READY will ++ it
					
					this.sfx = new Chiptune("FX_confirm",SDS.sfxchip);
					this.sfx.play(false);

					this.textRows.push("");
					this._redrawText();
					this.state=DEFINE_DIALOGUE_STATES.READY;
				}

			break;
			// state CLOSING: run the animation; callback and return 1 when done
			case DEFINE_DIALOGUE_STATES.CLOSING:
				if (this.frameTimer===0){
					this.frameCanvas.getContext('2d').clearRect(0,0,this.frameCanvas.width,this.frameCanvas.height);
					this.titleColorCanvas.getContext('2d').clearRect(0,0,this.colorCanvas.width,this.colorCanvas.height);
					this.textCanvas.getContext('2d').clearRect(0,0,this.textCanvas.width,this.textCanvas.height);
				}
				if (this.frameTimer<8){
					this.colorCanvas.getContext('2d').clearRect(0,0,this.colorCanvas.width,this.colorCanvas.height);
					for (this._heap.row=0; this._heap.row<this.height; this._heap.row++){
						for (this._heap.col=0; this._heap.col<this.width; this._heap.col++){
							this.tileSheet.drawIntoCanvas(this.colorCanvas,7-this.frameTimer,this._heap.col<<3,this._heap.row<<3);
						}
					}
				}
				else{
					if (this.callback) this.callback(-1,this.retval);
					return 1;
				}
				this.frameTimer++;
			break;
		}

		while (this.textRows.length>this.height){
			this.textRows.shift();
		}
		this.sfx.Update();

		return 0;
	};

	Dialogue.prototype.drawIntoCanvas = function(canvas){
		this._heap.ctx = canvas.getContext('2d');
		this._heap.ctx.drawImage(this.titleColorCanvas,this.x,this.y-8);
		this._heap.ctx.drawImage(this.frameCanvas,this.x-8,this.y-8);
		this._heap.ctx.drawImage(this.colorCanvas,this.x,this.y);
		this._heap.ctx.drawImage(this.textCanvas,this.x,this.y);
	};

/*
for(var i=128;i<255;++i){console.log(i.toString(16)+": "+String.fromCharCode(i))}
80: 
81: 
82: 
83: 
84: 
85: 
86: 
87: 
88: 
89: 
8a: 
8b: 
8c: 
8d: 
8e: 
8f: 
90: 
91: 
92: 
93: 
94: 
95: 
96: 
97: 
98: 
99: 
9a: 
9b: 
9c: 
9d: 
9e: 
9f: 
a0:  
a1: ¡
a2: ¢
a3: £
a4: ¤
a5: ¥
a6: ¦
a7: §
a8: ¨
a9: ©
aa: ª
ab: «
ac: ¬
ad: ­
ae: ®
af: ¯
b0: °
b1: ±
b2: ²
b3: ³
b4: ´
b5: µ
b6: ¶
b7: ·
b8: ¸
b9: ¹
ba: º
bb: »
bc: ¼
bd: ½
be: ¾
bf: ¿
c0: À
c1: Á
c2: Â
c3: Ã
c4: Ä
c5: Å
c6: Æ
c7: Ç
c8: È
c9: É
ca: Ê
cb: Ë
cc: Ì
cd: Í
ce: Î
cf: Ï
d0: Ð
d1: Ñ
d2: Ò
d3: Ó
d4: Ô
d5: Õ
d6: Ö
d7: ×
d8: Ø
d9: Ù
da: Ú
db: Û
dc: Ü
dd: Ý
de: Þ
df: ß
e0: à
e1: á
e2: â
e3: ã
e4: ä
e5: å
e6: æ
e7: ç
e8: è
e9: é
ea: ê
eb: ë
ec: ì
ed: í
ee: î
ef: ï
f0: ð
f1: ñ
f2: ò
f3: ó
f4: ô
f5: õ
f6: ö
f7: ÷
f8: ø
f9: ù
fa: ú
fb: û
fc: ü
fd: ý
fe: þ
*/
	