text: /*
  * 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
  */
  
  'use strict';
  
  var hooks = {};
  export const BehaveHooks = {
    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];
      }
    }
  };
  
  export const Behave = function (userOpts) {  
    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);
  };
  
  