/*
 * Behave.js
 *
 * Copyright 2013, Jacob Kelley - http://jakiestfu.com/
 * Released under the MIT Licence
 * http://opensource.org/licenses/MIT
 *
 * Github:  http://github.com/jakiestfu/Behave.js/
 * Version: 1.5
 */


(function(undefined){

	'use strict';

	var BehaveHooks = BehaveHooks || (function(){
				var hooks = {};

				return {
					add: function(hookName, fn){
						if(typeof hookName == "object"){
							var i;
							for(i=0; i<hookName.length; i++){
								var theHook = hookName[i];
								if(!hooks[theHook]){
									hooks[theHook] = [];
								}
								hooks[theHook].push(fn);
							}
						} else {
							if(!hooks[hookName]){
								hooks[hookName] = [];
							}
							hooks[hookName].push(fn);
						}
					},
					get: function(hookName){
						if(hooks[hookName]){
							return hooks[hookName];
						}
					}
				};

			})(),
		Behave = Behave || function (userOpts) {

				if (typeof String.prototype.repeat !== 'function') {
					String.prototype.repeat = function(times) {
						if(times < 1){
							return '';
						}
						if(times % 2){
							return this.repeat(times - 1) + this;
						}
						var half = this.repeat(times / 2);
						return half + half;
					};
				}

				if (typeof Array.prototype.filter !== 'function') {
					Array.prototype.filter = function(func /*, thisp */) {
						if (this === null) {
							throw new TypeError();
						}

						var t = Object(this),
							len = t.length >>> 0;
						if (typeof func != "function"){
							throw new TypeError();
						}
						var res = [],
							thisp = arguments[1];
						for (var i = 0; i < len; i++) {
							if (i in t) {
								var val = t[i];
								if (func.call(thisp, val, i, t)) {
									res.push(val);
								}
							}
						}
						return res;
					};
				}

				var defaults = {
						textarea: null,
						replaceTab: true,
						softTabs: true,
						tabSize: 4,
						autoOpen: true,
						overwrite: true,
						autoStrip: true,
						autoIndent: true,
						fence: false
					},
					tab,
					newLine,
					charSettings = {

						keyMap: [
							{ open: "\"", close: "\"", canBreak: false },
							{ open: "'", close: "'", canBreak: false },
							{ open: "(", close: ")", canBreak: false },
							{ open: "[", close: "]", canBreak: true },
							{ open: "{", close: "}", canBreak: true }
						]

					},
					utils = {

						_callHook: function(hookName, passData){
							var hooks = BehaveHooks.get(hookName);
							passData = typeof passData=="boolean" && passData === false ? false : true;

							if(hooks){
								if(passData){
									var theEditor = defaults.textarea,
										textVal = theEditor.value,
										caretPos = utils.cursor.get(),
										i;

									for(i=0; i<hooks.length; i++){
										hooks[i].call(undefined, {
											editor: {
												element: theEditor,
												text: textVal,
												levelsDeep: utils.levelsDeep()
											},
											caret: {
												pos: caretPos
											},
											lines: {
												current: utils.cursor.getLine(textVal, caretPos),
												total: utils.editor.getLines(textVal)
											}
										});
									}
								} else {
									for(i=0; i<hooks.length; i++){
										hooks[i].call(undefined);
									}
								}
							}
						},

						defineNewLine: function(){
							var ta = document.createElement('textarea');
							ta.value = "\n";

							if(ta.value.length==2){
								newLine = "\r\n";
							} else {
								newLine = "\n";
							}
						},
						defineTabSize: function(tabSize){
							if(typeof defaults.textarea.style.OTabSize != "undefined"){
								defaults.textarea.style.OTabSize = tabSize; return;
							}
							if(typeof defaults.textarea.style.MozTabSize != "undefined"){
								defaults.textarea.style.MozTabSize = tabSize; return;
							}
							if(typeof defaults.textarea.style.tabSize != "undefined"){
								defaults.textarea.style.tabSize = tabSize; return;
							}
						},
						cursor: {
							getLine: function(textVal, pos){
								return ((textVal.substring(0,pos)).split("\n")).length;
							},
							get: function() {

								if (typeof document.createElement('textarea').selectionStart==="number") {
									return defaults.textarea.selectionStart;
								} else if (document.selection) {
									var caretPos = 0,
										range = defaults.textarea.createTextRange(),
										rangeDupe = document.selection.createRange().duplicate(),
										rangeDupeBookmark = rangeDupe.getBookmark();
									range.moveToBookmark(rangeDupeBookmark);

									while (range.moveStart('character' , -1) !== 0) {
										caretPos++;
									}
									return caretPos;
								}
							},
							set: function (start, end) {
								if(!end){
									end = start;
								}
								if (defaults.textarea.setSelectionRange) {
									defaults.textarea.focus();
									defaults.textarea.setSelectionRange(start, end);
								} else if (defaults.textarea.createTextRange) {
									var range = defaults.textarea.createTextRange();
									range.collapse(true);
									range.moveEnd('character', end);
									range.moveStart('character', start);
									range.select();
								}
							},
							selection: function(){
								var textAreaElement = defaults.textarea,
									start = 0,
									end = 0,
									normalizedValue,
									range,
									textInputRange,
									len,
									endRange;

								if (typeof textAreaElement.selectionStart == "number" && typeof textAreaElement.selectionEnd == "number") {
									start = textAreaElement.selectionStart;
									end = textAreaElement.selectionEnd;
								} else {
									range = document.selection.createRange();

									if (range && range.parentElement() == textAreaElement) {

										normalizedValue = utils.editor.get();
										len = normalizedValue.length;

										textInputRange = textAreaElement.createTextRange();
										textInputRange.moveToBookmark(range.getBookmark());

										endRange = textAreaElement.createTextRange();
										endRange.collapse(false);

										if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
											start = end = len;
										} else {
											start = -textInputRange.moveStart("character", -len);
											start += normalizedValue.slice(0, start).split(newLine).length - 1;

											if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) {
												end = len;
											} else {
												end = -textInputRange.moveEnd("character", -len);
												end += normalizedValue.slice(0, end).split(newLine).length - 1;
											}
										}
									}
								}

								return start==end ? false : {
									start: start,
									end: end
								};
							}
						},
						editor: {
							getLines: function(textVal){
								return (textVal).split("\n").length;
							},
							get: function(){
								return defaults.textarea.value.replace(/\r/g,'');
							},
							set: function(data){
								defaults.textarea.value = data;
							}
						},
						fenceRange: function(){
							if(typeof defaults.fence == "string"){

								var data = utils.editor.get(),
									pos = utils.cursor.get(),
									hacked = 0,
									matchedFence = data.indexOf(defaults.fence),
									matchCase = 0;

								while(matchedFence>=0){
									matchCase++;
									if( pos < (matchedFence+hacked) ){
										break;
									}

									hacked += matchedFence+defaults.fence.length;
									data = data.substring(matchedFence+defaults.fence.length);
									matchedFence = data.indexOf(defaults.fence);

								}

								if( (hacked) < pos && ( (matchedFence+hacked) > pos ) && matchCase%2===0){
									return true;
								}
								return false;
							} else {
								return true;
							}
						},
						isEven: function(_this,i){
							return i%2;
						},
						levelsDeep: function(){
							var pos = utils.cursor.get(),
								val = utils.editor.get();

							var left = val.substring(0, pos),
								levels = 0,
								i, j;

							for(i=0; i<left.length; i++){
								for (j=0; j<charSettings.keyMap.length; j++) {
									if(charSettings.keyMap[j].canBreak){
										if(charSettings.keyMap[j].open == left.charAt(i)){
											levels++;
										}

										if(charSettings.keyMap[j].close == left.charAt(i)){
											levels--;
										}
									}
								}
							}

							var toDecrement = 0,
								quoteMap = ["'", "\""];
							for(i=0; i<charSettings.keyMap.length; i++) {
								if(charSettings.keyMap[i].canBreak){
									for(j in quoteMap){
										toDecrement += left.split(quoteMap[j]).filter(utils.isEven).join('').split(charSettings.keyMap[i].open).length - 1;
									}
								}
							}

							var finalLevels = levels - toDecrement;

							return finalLevels >=0 ? finalLevels : 0;
						},
						deepExtend: function(destination, source) {
							for (var property in source) {
								if (source[property] && source[property].constructor &&
									source[property].constructor === Object) {
									destination[property] = destination[property] || {};
									utils.deepExtend(destination[property], source[property]);
								} else {
									destination[property] = source[property];
								}
							}
							return destination;
						},
						addEvent: function addEvent(element, eventName, func) {
							if (element.addEventListener){
								element.addEventListener(eventName,func,false);
							} else if (element.attachEvent) {
								element.attachEvent("on"+eventName, func);
							}
						},
						removeEvent: function addEvent(element, eventName, func){
							if (element.addEventListener){
								element.removeEventListener(eventName,func,false);
							} else if (element.attachEvent) {
								element.detachEvent("on"+eventName, func);
							}
						},

						preventDefaultEvent: function(e){
							if(e.preventDefault){
								e.preventDefault();
							} else {
								e.returnValue = false;
							}
						}
					},
					intercept = {
						tabKey: function (e) {

							if(!utils.fenceRange()){ return; }

							if (e.keyCode == 9) {
								utils.preventDefaultEvent(e);

								var toReturn = true;
								utils._callHook('tab:before');

								var selection = utils.cursor.selection(),
									pos = utils.cursor.get(),
									val = utils.editor.get();

								if(selection){

									var tempStart = selection.start;
									while(tempStart--){
										if(val.charAt(tempStart)=="\n"){
											selection.start = tempStart + 1;
											break;
										}
									}

									var toIndent = val.substring(selection.start, selection.end),
										lines = toIndent.split("\n"),
										i;

									if(e.shiftKey){
										for(i = 0; i<lines.length; i++){
											if(lines[i].substring(0,tab.length) == tab){
												lines[i] = lines[i].substring(tab.length);
											}
										}
										toIndent = lines.join("\n");

										utils.editor.set( val.substring(0,selection.start) + toIndent + val.substring(selection.end) );
										utils.cursor.set(selection.start, selection.start+toIndent.length);

									} else {
										for(i in lines){
											lines[i] = tab + lines[i];
										}
										toIndent = lines.join("\n");

										utils.editor.set( val.substring(0,selection.start) + toIndent + val.substring(selection.end) );
										utils.cursor.set(selection.start, selection.start+toIndent.length);
									}
								} else {
									var left = val.substring(0, pos),
										right = val.substring(pos),
										edited = left + tab + right;

									if(e.shiftKey){
										if(val.substring(pos-tab.length, pos) == tab){
											edited = val.substring(0, pos-tab.length) + right;
											utils.editor.set(edited);
											utils.cursor.set(pos-tab.length);
										}
									} else {
										utils.editor.set(edited);
										utils.cursor.set(pos + tab.length);
										toReturn = false;
									}
								}
								utils._callHook('tab:after');
							}
							return toReturn;
						},
						enterKey: function (e) {

							if(!utils.fenceRange()){ return; }

							if (e.keyCode == 13) {

								utils.preventDefaultEvent(e);
								utils._callHook('enter:before');

								var pos = utils.cursor.get(),
									val = utils.editor.get(),
									left = val.substring(0, pos),
									right = val.substring(pos),
									leftChar = left.charAt(left.length - 1),
									rightChar = right.charAt(0),
									numTabs = utils.levelsDeep(),
									ourIndent = "",
									closingBreak = "",
									finalCursorPos,
									i;
								if(!numTabs){
									finalCursorPos = 1;
								} else {
									while(numTabs--){
										ourIndent+=tab;
									}
									ourIndent = ourIndent;
									finalCursorPos = ourIndent.length + 1;

									for(i=0; i<charSettings.keyMap.length; i++) {
										if (charSettings.keyMap[i].open == leftChar && charSettings.keyMap[i].close == rightChar){
											closingBreak = newLine;
										}
									}

								}

								var edited = left + newLine + ourIndent + closingBreak + (ourIndent.substring(0, ourIndent.length-tab.length) ) + right;
								utils.editor.set(edited);
								utils.cursor.set(pos + finalCursorPos);
								utils._callHook('enter:after');
							}
						},
						deleteKey: function (e) {

							if(!utils.fenceRange()){ return; }

							if(e.keyCode == 8){
								utils.preventDefaultEvent(e);

								utils._callHook('delete:before');

								var pos = utils.cursor.get(),
									val = utils.editor.get(),
									left = val.substring(0, pos),
									right = val.substring(pos),
									leftChar = left.charAt(left.length - 1),
									rightChar = right.charAt(0),
									i;

								if( utils.cursor.selection() === false ){
									for(i=0; i<charSettings.keyMap.length; i++) {
										if (charSettings.keyMap[i].open == leftChar && charSettings.keyMap[i].close == rightChar) {
											var edited = val.substring(0,pos-1) + val.substring(pos+1);
											utils.editor.set(edited);
											utils.cursor.set(pos - 1);
											return;
										}
									}
									var edited = val.substring(0,pos-1) + val.substring(pos);
									utils.editor.set(edited);
									utils.cursor.set(pos - 1);
								} else {
									var sel = utils.cursor.selection(),
										edited = val.substring(0,sel.start) + val.substring(sel.end);
									utils.editor.set(edited);
									utils.cursor.set(pos);
								}

								utils._callHook('delete:after');

							}
						}
					},
					charFuncs = {
						openedChar: function (_char, e) {
							utils.preventDefaultEvent(e);
							utils._callHook('openChar:before');
							var pos = utils.cursor.get(),
								val = utils.editor.get(),
								left = val.substring(0, pos),
								right = val.substring(pos),
								edited = left + _char.open + _char.close + right;

							defaults.textarea.value = edited;
							utils.cursor.set(pos + 1);
							utils._callHook('openChar:after');
						},
						closedChar: function (_char, e) {
							var pos = utils.cursor.get(),
								val = utils.editor.get(),
								toOverwrite = val.substring(pos, pos + 1);
							if (toOverwrite == _char.close) {
								utils.preventDefaultEvent(e);
								utils._callHook('closeChar:before');
								utils.cursor.set(utils.cursor.get() + 1);
								utils._callHook('closeChar:after');
								return true;
							}
							return false;
						}
					},
					action = {
						filter: function (e) {

							if(!utils.fenceRange()){ return; }

							var theCode = e.which || e.keyCode;

							if(theCode == 39 || theCode == 40 && e.which===0){ return; }

							var _char = String.fromCharCode(theCode),
								i;

							for(i=0; i<charSettings.keyMap.length; i++) {

								if (charSettings.keyMap[i].close == _char) {
									var didClose = defaults.overwrite && charFuncs.closedChar(charSettings.keyMap[i], e);

									if (!didClose && charSettings.keyMap[i].open == _char && defaults.autoOpen) {
										charFuncs.openedChar(charSettings.keyMap[i], e);
									}
								} else if (charSettings.keyMap[i].open == _char && defaults.autoOpen) {
									charFuncs.openedChar(charSettings.keyMap[i], e);
								}
							}
						},
						listen: function () {

							if(defaults.replaceTab){ utils.addEvent(defaults.textarea, 'keydown', intercept.tabKey); }
							if(defaults.autoIndent){ utils.addEvent(defaults.textarea, 'keydown', intercept.enterKey); }
							if(defaults.autoStrip){ utils.addEvent(defaults.textarea, 'keydown', intercept.deleteKey); }

							utils.addEvent(defaults.textarea, 'keypress', action.filter);

							utils.addEvent(defaults.textarea, 'keydown', function(){ utils._callHook('keydown'); });
							utils.addEvent(defaults.textarea, 'keyup', function(){ utils._callHook('keyup'); });
						}
					},
					init = function (opts) {

						if(opts.textarea){
							utils._callHook('init:before', false);
							utils.deepExtend(defaults, opts);
							utils.defineNewLine();

							if (defaults.softTabs) {
								tab = " ".repeat(defaults.tabSize);
							} else {
								tab = "\t";

								utils.defineTabSize(defaults.tabSize);
							}

							action.listen();
							utils._callHook('init:after', false);
						}

					};

				this.destroy = function(){
					utils.removeEvent(defaults.textarea, 'keydown', intercept.tabKey);
					utils.removeEvent(defaults.textarea, 'keydown', intercept.enterKey);
					utils.removeEvent(defaults.textarea, 'keydown', intercept.deleteKey);
					utils.removeEvent(defaults.textarea, 'keypress', action.filter);
				};

				init(userOpts);

			};

	if (typeof module !== 'undefined' && module.exports) {
		module.exports = Behave;
	}

	if (typeof ender === 'undefined') {
		this.Behave = Behave;
		this.BehaveHooks = BehaveHooks;
	}

	if (typeof define === "function" && define.amd) {
		define("behave", [], function () {
			return Behave;
		});
	}

}).call(this);

/* global jQuery, soWidgets */

( function( $ ) {

	$(document).on( 'sowsetupformfield', '.siteorigin-widget-field-type-code', function(e) {
		var $$ = $( this );
		new Behave({
			textarea: $$.find( '.siteorigin-widget-code-input' ).get( 0 ),
		});
	} );

} )( jQuery );