
if (!Array.prototype.indexOf)
{
  Array.prototype.indexOf = function(elt /*, from*/)
  {
    var len = this.length >>> 0;

    var from = Number(arguments[1]) || 0;
    from = (from < 0)
         ? Math.ceil(from)
         : Math.floor(from);
    if (from < 0)
      from += len;

    for (; from < len; from++)
    {
      if (from in this &&
          this[from] === elt)
        return from;
    }
    return -1;
  };
}

var KEYCODEMAP = {
	32: 32, //space
	33: 49, //!
	34: 50, //"
	35: 51, //#
	36: 52, //¤
	37: 53, //%
	38: 54, //&re
	47: [55, 111], // /
	40: 56, //(
	41: 57, //)
	61: 48, //=
	63: 189, //?

	49: [49, 97], //1
	50: [50, 98], //2
	51: [51, 99], //3
	52: [52, 100], //4
	53: [53, 101], //5
	54: [54, 102], //6
	55: [55, 103],//7
	56: [56, 104], //8
	57: [57, 105], //9
	48: [48, 96], //0
	43: [189, 107], //+

	64: 50, //@
	163: 51, //£
	36: 52, //$
	123: 55, //{
	91: 56, //[
	93: 57, //]
	125: 48, //}
	92: 189, //\

	65: 65, //A
	66: 66, //B
	67: 67, //C
	68: 68, //D
	69: 69, //E
	70: 70, //F
	71: 71, //G
	72: 72, //H
	73: 73, //I
	74: 74, //J
	75: 75, //K
	76: 76, //L
	77: 77, //M
	78: 78, //N
	79: 79, //O
	80: 80, //P
	81: 81, //Q
	82: 82, //R
	83: 83, //S
	84: 84, //T
	85: 85, //U
	86: 86, //V
	87: 87, //W
	88: 88, //X
	89: 89, //Y
	90: 90, //Z

	97: 65, //a
	98: 66, //b
	99: 67, //c
	100: 68, //d
	101: 69, //e
	102: 70, //f
	103: 71, //g
	104: 72, //h
	105: 73, //i
	106: 74, //j
	107: 75, //k
	108: 76, //l
	109: 77, //m
	110: 78, //n
	111: 79, //o
	112: 80, //p
	113: 81, //q
	114: 82, //r
	115: 83, //s
	116: 84, //t
	117: 85, //u
	118: 86, //v
	119: 87, //w
	120: 88, //x
	121: 89, //y
	122: 90, //z

	213: 219, //Õ
	245: 219, //õ
	196: 191, //Ä
	228: 191, //ä
	214: 186, //Ö
	246: 186, //ö
	220: 192, //Ü
	252: 192, //ü
	352: 83, //Š
	353: 83, //š
	381: 90, //Ž
	382: 90, //ž

	8364: 69, //€
	39: 220, //'
	42: [220, 106], //*
	60: 226, //<
	62: 226, //>
	44: [188, 110], //,
	59: 188, //;
	46: 190, //.
	58: 190, //:
	45: [221, 109], //-
	95: 221 //_

};

// not used
var keymap_chrome = {
	213: 221, //Õ
	245: 221, //õ
	196: 222, //Ä
	228: 222, //ä
	220: 219,//Ü
	252: 219,//ü
	39: 222, //'
	42: [56, 106], //*

	43: [187, 107], //+
	92: 220, //\
	60: 188, //<
	62: 190, //>
	59: 186, //;
	46: 190, //.
	58: 186, //:
	45: [189, 109], //-
	95: 189, //_

	123: 219, //{
	91: 219, //[
	93: 221, //]
	125: 221, //}
	92: 189 //\
};

// not used
keymap_firefox = {
  	43: [171, 107], //+
	92: 171, //\
	63: 171, //?
	213: 0, //Õ
	245: 0, //õ
	196: 0, //Ä
	228: 0, //ä
	214: 0, //Ö
	246: 0, //ö
	220: 0, //Ü
	252: 0, //ü
	60: 60, //<
	62: 60, //>
	45: [173, 109], //-

	39: 222, //'
	42: [222, 106]//*
};


var Keyreader = {
	CURSOR_TOGGLE: 300,
	KEYCODE_BACKSPACE: 8,
	currentLetter: null,
	currentElement: null,
	currentTimeout: null,
	keyDownSet: {},
	keyDownCount: 0,
	strokeList: [],
	hash: null,
	properties: {},

	reset: function() {
		Keyreader.strokeList = [];
		Keyreader.keyDownSet = {};
		Keyreader.keyDownCount = 0;
		Keyreader.hash = null;
	},

	pull: function(container, code) {
		var getData = code ? {excerptCode: code} : {};
		$.ajax(webroot + 'excerpt/random', {
			cache: false,
			dataType: 'json',
			data: getData,
			success: function(data) {
				if (data.length <= 0) return;
				Keyreader.reset();
				Keyreader.hash = data.hash;
				var splitText = data.text.split('');
				var spanText = '<span class="futureLetter">' + splitText.join('</span><span class="futureLetter">') + '</span>';
				container.html(spanText);
				Keyreader.start(container);

			},
			error: function(xhr, statusText, message) {
				alert('Ühenduse viga');
			}
		});
	},

	take: function(fromContainer, toContainer) {
		var splitText = fromContainer.text().split('');
		var spanText = '<span class="futureLetter">' + splitText.join('</span><span class="futureLetter">') + '</span>';
		toContainer.html(spanText);

	},

	start: function(container, properties) {
		var props = properties || {};
		Keyreader.properties = $.extend({
			'allowIncorrect': true,
			'allowBacktrack': true
		}, props);

		Keyreader.currentElement = $(container).find('span:first')
			.removeClass('futureLetter')
			.addClass('currentLetter');
		Keyreader.currentLetter = Keyreader.currentElement.text();

		var addHightlight = function() {
			Keyreader.currentTimeout = setTimeout(function() {
				if (!(Keyreader.currentElement.length)) return;

				Keyreader.currentElement.addClass('highlighted');
				removeHightlight();
			}, Keyreader.CURSOR_TOGGLE);
		};

		var removeHightlight = function() {
			Keyreader.currentTimeout = setTimeout(function() {
				if (!(Keyreader.currentElement.length)) return;

				Keyreader.currentElement.removeClass('highlighted');
				addHightlight();
			}, Keyreader.CURSOR_TOGGLE);

		};
		Keyreader.firstLetterEvent = Keyreader.firstLetterFunc;

		addHightlight();
	},

	advance: function(isCorrect) {
		isCorrect = isCorrect || false;
		if (!isCorrect && !Keyreader.properties.allowIncorrect) return;

		var prevElement = Keyreader.currentElement;
		Keyreader.currentElement = Keyreader.currentElement.next();
		Keyreader.currentLetter = Keyreader.currentElement.text();

		prevElement
			.removeClass('currentLetter highlighted')
			.addClass(isCorrect ? 'pastLetter pastLetterCorrect' : 'pastLetter pastLetterFaulty')
		.next()
			.removeClass('futureLetter')
			.addClass('currentLetter highlighted');

		if (Keyreader.currentElement.length <= 0) {
			prevElement.parent().trigger("lastLetter", {
				'element': prevElement
			});
		}

		Keyreader.firstLetterEvent(prevElement);

	},

	firstLetterFunc: function(prevElement) {
		if (prevElement.prev().length <= 0) {
			prevElement.parent().trigger("firstLetter", {
				'element': prevElement
			});
		}
		Keyreader.firstLetterEvent = (function() {});
	},

	backtrack: function() {
		if (!Keyreader.properties.allowBacktrack) return;

		var nextElement = Keyreader.currentElement;
		if (!nextElement.prev().length
//				  || nextElement.prev().hasClass('pastLetterCorrect')
			) {
			return;
		}

		Keyreader.currentElement = Keyreader.currentElement.prev();
		Keyreader.currentLetter = Keyreader.currentElement.text();

		nextElement
			.removeClass('currentLetter highlighted')
			.addClass('futureLetter')
		.prev()
			.removeClass('pastLetter pastLetterCorrect pastLetterFaulty')
			.addClass('currentLetter highlighted');
	},

	keyDownListener: function(e) {
		e.downTimeStamp = (new Date()).getTime(); // downTimeStamp

		if (!Keyreader.keyDownSet[e.which]) {
			e.togetherWith = []; // togetherWith
			Keyreader.keyDownSet[e.which] = e;
			Keyreader.keyDownCount++;
		}

		if (e.which === Keyreader.KEYCODE_BACKSPACE) {
			e.preventDefault();
			Keyreader.backtrack();
		}
	},

	keyPressListener: function(e) {
		var currentTime = (new Date()).getTime();
		var symbol = String.fromCharCode(e.which);
		var isCorrect = (symbol === Keyreader.currentLetter);

		Keyreader.advance(isCorrect);

		var keyPress = {
			pressTimeStamp: currentTime, // pressTimeStamp
			charCode: e.which, //charCode
			symbol: symbol, //symbol
			isShift: (e.shiftKey ? 1 : 0), // isShift
			isLeftShift: (e.originalEvent.shiftLeft ? 1 : 0), // isLeftShift
			isCtrl: (e.ctrlKey ? 1 : 0), // isCtrl
			isAlt: (e.altKey ? 1 : 0), // isAlt
			isCorrect: (isCorrect ? 1 : 0), // isCorrect
			togetherWith: [] // togetherWith
		};

		var keyCodes = $.makeArray(KEYCODEMAP[e.which] || e.which);

		var currentKeyCode = null;
		// cycle through possible keycodes that this symbol may have
		// and match it with the one currently pressed down
		for (var i = 0; i < keyCodes.length; i++ ) {
			if (Keyreader.keyDownSet[keyCodes[i]]) {
				Keyreader.keyDownSet[keyCodes[i]].keypress = keyPress;
				currentKeyCode = keyCodes[i];
				break;
			}
		}

		for (var keyCode in Keyreader.keyDownSet) {
			switch (keyCode) {
				case '16': // shift
				case '17': // ctrl
				case '18': // alt
				case '225': // altgr
					Keyreader.keyDownSet[keyCode].togetherWith.push(keyPress);
					break;
				default:
					if (!currentKeyCode) {
						// attach keypress to first pressed down key currently in the list
						// that is not a modifier
						Keyreader.keyDownSet[keyCode].keypress = keyPress;
						currentKeyCode = keyCode;
					} else {
						if (keyCode != currentKeyCode) {
							 Keyreader.keyDownSet[keyCode].togetherWith.push(keyPress);
						}
					}
					break;
			}

			e.preventDefault();
		}
	},

	keyUpListener: function(e) {
		var upTimeStamp = (new Date()).getTime();
		if (Keyreader.keyDownSet[e.which]) {
			var keyStroke = {};
			if (Keyreader.keyDownSet[e.which].keypress) {
				keyStroke = Keyreader.keyDownSet[e.which].keypress;
			}

			keyStroke.downTimeStamp = Keyreader.keyDownSet[e.which].downTimeStamp; // downTimeStamp
			keyStroke.upTimeStamp = upTimeStamp; // upTimeStamp
			keyStroke.keyCode = e.which; // keyCode
			var togetherWithLength = Keyreader.keyDownSet[e.which].togetherWith.length;
			for (var i = 0; i < togetherWithLength; i++ ) {
				 Keyreader.keyDownSet[e.which].togetherWith[i].togetherWith.push(keyStroke);
			}
			Keyreader.strokeList.push(keyStroke);

			delete Keyreader.keyDownSet[e.which];
			Keyreader.keyDownCount--;
		}

	}
};

var GridData = {
	compact: function(objectList, paramNamesArray, rowsOnly) {

		rowsOnly = rowsOnly || false;
		var table = {
			cols: paramNamesArray,
			rows: []
		};

		var rows = [];

		$.each(objectList, function(i, stroke) {
			var row = [];
			$.each(paramNamesArray, function (j, name) {
				row.push(stroke[name]);
			});
			rows.push(row);
		});
		table.rows = rows;
		return (rowsOnly ? rows : table);
	},

	decompact: function(table, sortKey) {
		var strokeList = [];

		$.each(table.rows, function (i, row) {
			var stroke = {};
			$.each(table.cols, function (j, colName) {
				stroke[colName] = row[j];
			});
			strokeList.push(stroke);
		});

		if (sortKey && table.cols.indexOf(sortKey) > -1) {
			strokeList.sort(function (a, b) {
				return (a[sortKey] - b[sortKey]);
			});
		}

		return strokeList;
	}
};

var KeySummary = {
	collectPressDownTime: function(strokeList) {
		var letterMap = {};
		$.each(strokeList, function (i, stroke) {
			if (!stroke.pressTimeStamp) return;
			if (!letterMap[stroke.symbol]) {
				letterMap[stroke.symbol] = [];
			}
			letterMap[stroke.symbol].push((stroke.upTimeStamp - stroke.downTimeStamp));
		});
		var averageList = [];

		var allTotal = 0;
		var allCounter = 0;
		$.each(letterMap, function (s, values) {
			var total = 0;
			var counter = 0;
			$.each(values, function (j, duration) {
				total += duration;
				counter += 1;
			});
			allTotal += total;
			allCounter += counter;

			averageList.push({
				symbol: s,
				average: (total/counter)
			});
		});

		averageList.sort(function (a, b) {
			return (a.average - b.average);
		});

		return {
			symbolaverage: averageList,
			average: (allTotal/allCounter)
		};
	},

	collectPairTime: function(strokeList) {
		var pairMap = {};
		var prevStroke = strokeList[0];
		var key = null;

		$.each(strokeList.slice(1), function (j, stroke) {
			if (prevStroke.pressTimeStamp && prevStroke.charCode
					  && stroke.pressTimeStamp && stroke.charCode) {
				key = (prevStroke.symbol + stroke.symbol);
				if (!pairMap[key]) pairMap[key] = [];
				pairMap[key].push((stroke.upTimeStamp - prevStroke.downTimeStamp));
			}
			prevStroke = strokeList[j + 1];
		});

		var timings = [];
		var allTotal = 0;
		var allTimes = [];
		var allCounter = 0;
		$.each(pairMap, function (keyPair, values) {
			var total = 0;
			var counter = 0;
			$.each(values, function (i, val) {
				total += val;
				counter += 1;
				allTimes.push(val);
			});
			allTotal += total;
			allCounter += counter;

			timings.push({
				pair: keyPair,
				duration: (total/counter)
			});
		});

		timings.sort(function (a, b) {
			return (a.duration - b.duration);
		});

		allTimes.sort();
		var medianVal = null;
		var halfIndex = Math.floor(allTimes.length/2);

		if(allTimes.length % 2) {
			medianVal = allTimes[halfIndex];
		} else {
			medianVal = (allTimes[halfIndex-1] + allTimes[halfIndex]) / 2.
		}

		return {
			pairaverage: timings,
			average: (allTotal/allCounter),
			median: medianVal
		};
	},

	collectSpeed: function(runData) {

		var keyCounter = 0;
		var symbolCounter = 0;
		var correctSymbolCounter = 0;

		var keyTimeDiff = 0;
		var symbolTimeDiff = 0;
		var correctSymbolTimeDiff = 0;

		$.each(runData, function(i, block) {

			var firstKeyTime = null;
			var firstSymbolTime = null;
			var firstCorrectSymbolTime = null;

			var lastKeyTime = null;
			var lastSymbolTime = null;
			var lastCorrectSymbolTime = null;

			var strokeList = GridData.decompact(block.data, 'downTimeStamp');
			$.each(strokeList, function(j, stroke) {
				keyCounter += 1;
				if (!firstKeyTime) firstKeyTime = stroke.downTimeStamp;
				lastKeyTime = stroke.upTimeStamp;

				if (stroke.pressTimeStamp && stroke.charCode) {
					symbolCounter += 1;
					if (!firstSymbolTime) firstSymbolTime = stroke.downTimeStamp;
					lastSymbolTime = stroke.upTimeStamp;
				}

				if (stroke.isCorrect) {
					correctSymbolCounter += 1;
					if (!firstCorrectSymbolTime) firstCorrectSymbolTime = stroke.downTimeStamp;
					lastCorrectSymbolTime = stroke.upTimeStamp;
				}
			});

			keyTimeDiff += (lastKeyTime - firstKeyTime);
			symbolTimeDiff += (lastSymbolTime - firstSymbolTime);
			correctSymbolTimeDiff += (lastCorrectSymbolTime - firstCorrectSymbolTime);

		});

		return {
			keypermin: (keyCounter * 1000 * 60) / keyTimeDiff,
			symbolpermin: (symbolCounter * 1000 * 60) / symbolTimeDiff,
			correctsymbolpermin: (correctSymbolCounter * 1000 * 60) / correctSymbolTimeDiff
		};
	}
};

var KeyreaderPage = {
	run: function () {
		var container = $('#excerptContainer');
		Keyreader.pull(container);
		var runData = [];
		var paramNames = [
			'downTimeStamp', 'pressTimeStamp', 'upTimeStamp', 'charCode', 'keyCode', 'symbol',
			'isShift', 'isLeftShift', 'isCtrl', 'isAlt', 'isCorrect', 'togetherWith'
		];
		$('#excerptContainer').on('lastLetter', function(event, data) {
			var run = {
				'hash': Keyreader.hash,
				'data': GridData.compact(Keyreader.strokeList, paramNames)
			};
			runData.push(run);

			$('#keyWizardForwardButton').prop('disabled', false);
			$('#loadExcerpt').show();
		});

		$('#loadExcerpt').click(function(e) {
		  Keyreader.pull(container);
		  $('#keyWizardForwardButton').prop('disabled', true);
		  $(this).blur();
		  $('#loadExcerpt').hide();
		});

		$('#keyWizard').submit(function(event) {
			$('#KeyReader_runData').val(JSON.stringify(runData));
		});

		$(document).on('keypress', Keyreader.keyPressListener);
		$(document).on('keydown', Keyreader.keyDownListener);
		$(document).on('keyup', Keyreader.keyUpListener);
		container.focus();
	}
};

var SummaryPage = {
	run: function () {
		if (runData.length) {
			var mergeTable = {
				cols: runData[0].data.cols,
				rows: []
			};

			var downTimeStampIndex = mergeTable.cols.indexOf('downTimeStamp');
			if (downTimeStampIndex < 0) throw Exception('downTimeStamp is not stored');

			$.each(runData, function(i, block) {
				var lastDownTimeStamp = null;
				$.each(block.data.rows, function(j, row) {
					mergeTable.rows.push(row);
					lastDownTimeStamp = row[downTimeStampIndex];
				});
				mergeTable.rows.push([null, null, null, null, 16, null, null, null, null, null, null, null ]);
				mergeTable.rows[mergeTable.rows.length - 1][downTimeStampIndex] = (lastDownTimeStamp + 1);
			});

			var strokeList = GridData.decompact(mergeTable, 'downTimeStamp');
			var pressDowns = KeySummary.collectPressDownTime(strokeList);

			$('#averageDownTime').text(Math.round(pressDowns.average));
			$('#typeResultFormAvgDownTime').val(Math.round(pressDowns.average));
			if (pressDowns.symbolaverage.length > 5) {
				for (var i = 0; i < 3; i++) {
					$('#downLetter' + i).text('"' + pressDowns.symbolaverage[i].symbol + '"');
					$('#downTime' + i).text(Math.round(pressDowns.symbolaverage[i].average) + 'ms');

					$('#downLetter' + (i - 3)).text('"' + pressDowns.symbolaverage.slice(i - 3)[0].symbol + '"');
					$('#downTime' + (i - 3)).text(Math.round(pressDowns.symbolaverage.slice(i - 3)[0].average) + 'ms');
				}
			}

			var pairTimes = KeySummary.collectPairTime(strokeList);
			$('#averagePairTime').text(Math.round(pairTimes.average));
			$('#medianPairTime').text(Math.round(pairTimes.median));

			$('#typeResultFormAvgPairTime').val(Math.round(pairTimes.average));
			$('#typeResultFormMedianPairTime').val(Math.round(pairTimes.median));

			var numberOfPairs = 5;
			if (pairTimes.pairaverage.length > 10) {
				for (var i = 0; i < numberOfPairs; i++) {
					$('#pairSymbols' + i).text('"' + pairTimes.pairaverage[i].pair + '"');
					$('#pairTime' + i).text(Math.round(pairTimes.pairaverage[i].duration) + 'ms');

					$('#pairSymbols' + (i - numberOfPairs)).text('"' + pairTimes.pairaverage.slice(i - numberOfPairs)[0].pair + '"');
					$('#pairTime' + (i - numberOfPairs)).text(Math.round(pairTimes.pairaverage.slice(i - numberOfPairs)[0].duration) + 'ms');
				}
			}

			var speeds = KeySummary.collectSpeed(runData);
			$('#keySpeed').text(Math.round(speeds.keypermin));
			$('#symbolSpeed').text(Math.round(speeds.symbolpermin));
			$('#correctSymbolSpeed').text(Math.round(speeds.correctsymbolpermin));

			$('#typeResultFormKeyPerMin').val(Math.round(speeds.keypermin));
			$('#typeResultFormSymbolPerMin').val(Math.round(speeds.symbolpermin));
			$('#typeResultFormCorrectSymbolPerMin').val(Math.round(speeds.correctsymbolpermin));

			$.ajax({
				type: 'POST',
				url: webroot + 'keyWizard/enterTypeResult',
				cache: false,
				data: $('#typeResultForm').serialize()
			});


		}
	}
};

window.onerror = function (message, url, linenumber) {

	$(document).ready(function() {
		$('#errorFormMessage').val(message);
		$('#errorFormUrl').val(url);
		$('#errorFormLineNumber').val(linenumber);
		$('#errorFormUserAgent').val(navigator.userAgent);

		$.ajax({
			type: 'POST',
			url: webroot + 'site/pageError',
			cache: false,
			data: $('#errorForm').serialize()
		});

	})
}

// $.each(runData[0].data.rows, function (i, row) { console.log(row[5]);  })
