/**@license
 *       __ _____                     ________                              __
 *      / // _  /__ __ _____ ___ __ _/__  ___/__ ___ ______ __ __  __ ___  / /
 *  __ / // // // // // _  // _// // / / // _  // _//     // //  \/ // _ \/ /
 * /  / // // // // // ___// / / // / / // ___// / / / / // // /\  // // / /__
 * \___//____ \\___//____//_/ _\_  / /_//____//_/ /_/ /_//_//_/ /_/ \__\_\___/
 *           \/              /____/
 * http://terminal.jcubic.pl
 *
 * This is example of how to create custom formatter for jQuery Terminal
 *
 * Copyright (c) 2014-2018 Jakub Jankiewicz <http://jcubic.pl/me>
 * Released under the MIT license
 *
 */
/* global jQuery, define, global, require, module */
(function(factory) {
  var root = typeof window !== 'undefined' ? window : global;
  if (typeof define === 'function' && define.amd) {
    // AMD. Register as an anonymous module.
    // istanbul ignore next
    define(['jquery', 'jquery.terminal'], factory);
  } else if (typeof module === 'object' && module.exports) {
    // Node/CommonJS
    module.exports = function(root, jQuery) {
      if (jQuery === undefined) {
        // require('jQuery') returns a factory that requires window to
        // build a jQuery instance, we normalize how we use modules
        // that require this pattern but the window provided is a noop
        // if it's defined (how jquery works)
        if (typeof window !== 'undefined') {
          jQuery = require('jquery');
        } else {
          jQuery = require('jquery')(root);
        }
      }
      if (!jQuery.fn.terminal) {
        if (typeof window !== 'undefined') {
          require('jquery.terminal');
        } else {
          require('jquery.terminal')(jQuery);
        }
      }
      factory(jQuery);
      return jQuery;
    };
  } else {
    // Browser
    // istanbul ignore next
    factory(root.jQuery);
  }
})(function($) {
  $.terminal.defaults.unixFormattingEscapeBrackets = false;
  // we match characters and html entities because command line escape brackets
  // echo don't, when writing formatter always process html entitites so it work
  // for cmd plugin as well for echo
  var chr = '[^\\x08]|[\\r\\n]{2}|&[^;]+;';
  var backspace_re = new RegExp('^(' + chr + ')?\\x08');
  var overtyping_re = new RegExp('^(?:(' + chr + ')?\\x08(_|\\1)|' +
    '(_)\\x08(' + chr + '))');
  var new_line_re = /^(\r\n|\n\r|\r|\n)/;
  var clear_line_re = /[^\r\n]+\r\x1B\[K/g;
  // ---------------------------------------------------------------------
  function length(string) {
    return $.terminal.length(string);
  }
  // ---------------------------------------------------------------------
  // :: Replace overtyping (from man) formatting with terminal formatting
  // ---------------------------------------------------------------------
  $.terminal.overtyping = function overtyping(string, options) {
    string = $.terminal.unescape_brackets(string);
    var settings = $.extend({
      unixFormattingEscapeBrackets: false,
      position: 0
    }, options);
    var removed_chars = [];
    var new_position;
    var char_count = 0;
    var backspaces = [];
    function replace(string, position) {
      var result = '';
      var push = 0;
      var start;
      char_count = 0;
      function correct_position(start, match, rep_string) {
        // logic taken from $.terminal.tracking_replace
        if (start < position) {
          var last_index = start + length(match);
          if (last_index < position) {
            // It's after the replacement, move it
            new_position = Math.max(
              0,
              new_position +
              length(rep_string) -
              length(match)
            );
          } else {
            // It's *in* the replacement, put it just after
            new_position += length(rep_string) - (position - start);
          }
        }
      }
      for (var i = 0; i < string.length; ++i) {
        var partial = string.substring(i);
        var match = partial.match(backspace_re);
        var removed_char = removed_chars[0];
        if (match) {
          // we remove backspace and character or html entity before it
          // but we keep it in removed array so we can put it back
          // when we have caritage return or line feed
          if (match[1]) {
            start = i - match[1].length + push;
            removed_chars.push({
              index: start,
              string: match[1],
              overtyping: partial.match(overtyping_re)
            });
            correct_position(start, match[0], '', 1);
          }
          if (char_count < 0) {
            char_count = 0;
          }
          backspaces = backspaces.map(function(b) {
            return b - 1;
          });
          backspaces.push(start);
          return result + partial.replace(backspace_re, '');
        } else if (partial.match(new_line_re)) {
          // if newline we need to add at the end all characters
          // removed by backspace but only if there are no more
          // other characters than backspaces added between
          // backspaces and newline
          if (removed_chars.length) {
            var chars = removed_chars;
            removed_chars = [];
            chars.reverse().forEach(function(char) {
              if (i > char.index) {
                if (--char_count <= 0) {
                  correct_position(char.index, '', char.string, 2);
                  result += char.string;
                }
              } else {
                removed_chars.unshift(char);
              }
            });
          }
          var m = partial.match(new_line_re);
          result += m[1];
          i += m[1].length - 1;
        } else {
          if (backspaces.length) {
            var backspace = backspaces[0];
            if (i === backspace) {
              backspaces.shift();
            }
            if (i >= backspace) {
              char_count++;
            }
          }
          if (removed_chars.length) {
            // if we are in index of removed character we check if the
            // character is the same it will be bold or if removed char
            // or char at index is underscore then it will
            // be terminal formatting with underscore
            if (i > removed_char.index && removed_char.overtyping) {
              removed_chars.shift();
              correct_position(removed_char.index, '', removed_char.string);
              // if we add special character we need to correct
              // next push to removed_char array
              push++;
              // we use special characters instead of terminal
              // formatting so it's easier to proccess when removing
              // backspaces
              if (removed_char.string === string[i]) {
                result += string[i] + '\uFFF1';
                continue;
              } else if (removed_char.string === '_' ||
                string[i] === '_') {
                var chr;
                if (removed_char.string === '_') {
                  chr = string[i];
                } else {
                  chr = removed_char.string;
                }
                result += chr + '\uFFF2';
                continue;
              }
            }
          }
          result += string[i];
        }
      }
      return result;
    }
    var break_next = false;
    // loop until not more backspaces
    new_position = settings.position;
    // we need to clear line \x1b[K in overtyping because it need to be before
    // overtyping and from_ansi need to be called after so it escape stuff
    // between Escape Code and cmd will have escaped formatting typed by user
    var rep = $.terminal.tracking_replace(string, clear_line_re, '', new_position);
    string = rep[0];
    new_position = rep[1];
    while (string.match(/\x08/) || removed_chars.length) {
      string = replace(string, new_position);
      if (break_next) {
        break;
      }
      if (!string.match(/\x08/)) {
        // we break the loop so if removed_chars still chave items
        // we don't have infite loop
        break_next = true;
      }
    }
    function format(string, chr, style) {
      var re = new RegExp('((:?.' + chr + ')+)', 'g');
      return string.replace(re, function(_, string) {
        var re = new RegExp(chr, 'g');
        return '[[' + style + ']' + string.replace(re, '') + ']';
      });
    }
    // replace special characters with terminal formatting
    string = format(string, '\uFFF1', 'b;#fff;');
    string = format(string, '\uFFF2', 'u;;');
    if (settings.unixFormattingEscapeBrackets) {
      string = $.terminal.escape_brackets(string);
    }
    if (options && typeof options.position === 'number') {
      return [string, new_position];
    }
    return string;
  };
  // ---------------------------------------------------------------------
  // :: Html colors taken from ANSI formatting in Linux Terminal
  // ---------------------------------------------------------------------
  $.terminal.ansi_colors = {
    normal: {
      black: '#000',
      red: '#A00',
      green: '#008400',
      yellow: '#A50',
      blue: '#00A',
      magenta: '#A0A',
      cyan: '#0AA',
      white: '#AAA'
    },
    faited: {
      black: '#000',
      red: '#640000',
      green: '#006100',
      yellow: '#737300',
      blue: '#000087',
      magenta: '#650065',
      cyan: '#008787',
      white: '#818181'
    },
    bold: {
      black: '#000',
      red: '#F55',
      green: '#44D544',
      yellow: '#FF5',
      blue: '#55F',
      magenta: '#F5F',
      cyan: '#5FF',
      white: '#FFF'
    },
    // XTerm 8-bit pallete
    palette: [
      '#000000', '#AA0000', '#00AA00', '#AA5500', '#0000AA', '#AA00AA',
      '#00AAAA', '#AAAAAA', '#555555', '#FF5555', '#55FF55', '#FFFF55',
      '#5555FF', '#FF55FF', '#55FFFF', '#FFFFFF', '#000000', '#00005F',
      '#000087', '#0000AF', '#0000D7', '#0000FF', '#005F00', '#005F5F',
      '#005F87', '#005FAF', '#005FD7', '#005FFF', '#008700', '#00875F',
      '#008787', '#0087AF', '#0087D7', '#0087FF', '#00AF00', '#00AF5F',
      '#00AF87', '#00AFAF', '#00AFD7', '#00AFFF', '#00D700', '#00D75F',
      '#00D787', '#00D7AF', '#00D7D7', '#00D7FF', '#00FF00', '#00FF5F',
      '#00FF87', '#00FFAF', '#00FFD7', '#00FFFF', '#5F0000', '#5F005F',
      '#5F0087', '#5F00AF', '#5F00D7', '#5F00FF', '#5F5F00', '#5F5F5F',
      '#5F5F87', '#5F5FAF', '#5F5FD7', '#5F5FFF', '#5F8700', '#5F875F',
      '#5F8787', '#5F87AF', '#5F87D7', '#5F87FF', '#5FAF00', '#5FAF5F',
      '#5FAF87', '#5FAFAF', '#5FAFD7', '#5FAFFF', '#5FD700', '#5FD75F',
      '#5FD787', '#5FD7AF', '#5FD7D7', '#5FD7FF', '#5FFF00', '#5FFF5F',
      '#5FFF87', '#5FFFAF', '#5FFFD7', '#5FFFFF', '#870000', '#87005F',
      '#870087', '#8700AF', '#8700D7', '#8700FF', '#875F00', '#875F5F',
      '#875F87', '#875FAF', '#875FD7', '#875FFF', '#878700', '#87875F',
      '#878787', '#8787AF', '#8787D7', '#8787FF', '#87AF00', '#87AF5F',
      '#87AF87', '#87AFAF', '#87AFD7', '#87AFFF', '#87D700', '#87D75F',
      '#87D787', '#87D7AF', '#87D7D7', '#87D7FF', '#87FF00', '#87FF5F',
      '#87FF87', '#87FFAF', '#87FFD7', '#87FFFF', '#AF0000', '#AF005F',
      '#AF0087', '#AF00AF', '#AF00D7', '#AF00FF', '#AF5F00', '#AF5F5F',
      '#AF5F87', '#AF5FAF', '#AF5FD7', '#AF5FFF', '#AF8700', '#AF875F',
      '#AF8787', '#AF87AF', '#AF87D7', '#AF87FF', '#AFAF00', '#AFAF5F',
      '#AFAF87', '#AFAFAF', '#AFAFD7', '#AFAFFF', '#AFD700', '#AFD75F',
      '#AFD787', '#AFD7AF', '#AFD7D7', '#AFD7FF', '#AFFF00', '#AFFF5F',
      '#AFFF87', '#AFFFAF', '#AFFFD7', '#AFFFFF', '#D70000', '#D7005F',
      '#D70087', '#D700AF', '#D700D7', '#D700FF', '#D75F00', '#D75F5F',
      '#D75F87', '#D75FAF', '#D75FD7', '#D75FFF', '#D78700', '#D7875F',
      '#D78787', '#D787AF', '#D787D7', '#D787FF', '#D7AF00', '#D7AF5F',
      '#D7AF87', '#D7AFAF', '#D7AFD7', '#D7AFFF', '#D7D700', '#D7D75F',
      '#D7D787', '#D7D7AF', '#D7D7D7', '#D7D7FF', '#D7FF00', '#D7FF5F',
      '#D7FF87', '#D7FFAF', '#D7FFD7', '#D7FFFF', '#FF0000', '#FF005F',
      '#FF0087', '#FF00AF', '#FF00D7', '#FF00FF', '#FF5F00', '#FF5F5F',
      '#FF5F87', '#FF5FAF', '#FF5FD7', '#FF5FFF', '#FF8700', '#FF875F',
      '#FF8787', '#FF87AF', '#FF87D7', '#FF87FF', '#FFAF00', '#FFAF5F',
      '#FFAF87', '#FFAFAF', '#FFAFD7', '#FFAFFF', '#FFD700', '#FFD75F',
      '#FFD787', '#FFD7AF', '#FFD7D7', '#FFD7FF', '#FFFF00', '#FFFF5F',
      '#FFFF87', '#FFFFAF', '#FFFFD7', '#FFFFFF', '#080808', '#121212',
      '#1C1C1C', '#262626', '#303030', '#3A3A3A', '#444444', '#4E4E4E',
      '#585858', '#626262', '#6C6C6C', '#767676', '#808080', '#8A8A8A',
      '#949494', '#9E9E9E', '#A8A8A8', '#B2B2B2', '#BCBCBC', '#C6C6C6',
      '#D0D0D0', '#DADADA', '#E4E4E4', '#EEEEEE'
    ]
  };
  // ---------------------------------------------------------------------
  // :: Replace ANSI formatting with terminal formatting
  // ---------------------------------------------------------------------
  $.terminal.from_ansi = (function() {
    var color_list = {
      30: 'black',
      31: 'red',
      32: 'green',
      33: 'yellow',
      34: 'blue',
      35: 'magenta',
      36: 'cyan',
      37: 'white',

      39: 'inherit' // default color
    };
    var background_list = {
      40: 'black',
      41: 'red',
      42: 'green',
      43: 'yellow',
      44: 'blue',
      45: 'magenta',
      46: 'cyan',
      47: 'white',

      49: 'transparent' // default background
    };
    function format_ansi(code) {
      var controls = code.split(';');
      var num;
      var faited = false;
      var reverse = false;
      var bold = false;
      var styles = [];
      var output_color = '';
      var output_background = '';
      var _process_true_color = -1;
      var _ex_color = false;
      var _ex_background = false;
      var _process_8bit = false;
      var palette = $.terminal.ansi_colors.palette;
      function set_styles(num) {
        switch (num) {
          case 1:
            styles.push('b');
            bold = true;
            faited = false;
            break;
          case 4:
            styles.push('u');
            break;
          case 3:
            styles.push('i');
            break;
          case 5:
            if (_ex_color || _ex_background) {
              _process_8bit = true;
            }
            break;
          case 38:
            _ex_color = true;
            break;
          case 48:
            _ex_background = true;
            break;
          case 2:
            if (_ex_color || _ex_background) {
              _process_true_color = 0;
            } else {
              faited = true;
              bold = false;
            }
            break;
          case 7:
            reverse = true;
            break;
          default:
            if (controls.indexOf('5') === -1) {
              if (color_list[num]) {
                output_color = color_list[num];
              }
              if (background_list[num]) {
                output_background = background_list[num];
              }
            }
        }
      }
      // -----------------------------------------------------------------
      function process_true_color() {
        if (_ex_color) {
          if (!output_color) {
            output_color = '#';
          }
          if (output_color.length < 7) {
            output_color += ('0' + num.toString(16)).slice(-2);
          }
        }
        if (_ex_background) {
          if (!output_background) {
            output_background = '#';
          }
          if (output_background.length < 7) {
            output_background += ('0' + num.toString(16)).slice(-2);
          }
        }
        if (_process_true_color === 2) {
          _process_true_color = -1;
        } else {
          _process_true_color++;
        }
      }
      // -----------------------------------------------------------------
      function should__process_8bit() {
        return _process_8bit && ((_ex_background && !output_background) ||
          (_ex_color && !output_color));
      }
      // -----------------------------------------------------------------
      function process_8bit() {
        if (_ex_color && palette[num] && !output_color) {
          output_color = palette[num];
        }
        if (_ex_background && palette[num] && !output_background) {
          output_background = palette[num];
        }
        _process_8bit = false;
      }
      // -----------------------------------------------------------------
      for (var i in controls) {
        if (controls.hasOwnProperty(i)) {
          num = parseInt(controls[i], 10);
          if (_process_true_color > -1) {
            process_true_color();
          } else if (should__process_8bit()) {
            process_8bit();
          } else {
            set_styles(num);
          }
        }
      }
      if (reverse) {
        if (output_color || output_background) {
          var tmp = output_background;
          output_background = output_color;
          output_color = tmp;
        } else {
          output_color = 'black';
          output_background = 'white';
        }
      }
      var colors, color, background, backgrounds;
      if (bold) {
        colors = backgrounds = $.terminal.ansi_colors.bold;
      } else if (faited) {
        colors = backgrounds = $.terminal.ansi_colors.faited;
      } else {
        colors = backgrounds = $.terminal.ansi_colors.normal;
      }
      if (_ex_color) {
        color = output_color;
      } else if (output_color === 'inherit') {
        color = output_color;
      } else {
        color = colors[output_color];
      }
      if (_ex_background) {
        background = output_background;
      } else if (output_background === 'transparent') {
        background = output_background;
      } else {
        background = backgrounds[output_background];
      }
      return [styles.join(''), color, background];
    }
    return function from_ansi(input, options) {
      options = options || {};
      input = $.terminal.unescape_brackets(input);
      var settings = $.extend({
        unixFormattingEscapeBrackets: false,
        position: 0
      }, options);
      var new_position = settings.position;
      var position = new_position;
      var result;
      //merge multiple codes
      /*input = input.replace(/((?:\x1B\[[0-9;]*[A-Za-z])*)/g, function(group) {
        return group.replace(/m\x1B\[/g, ';');
        });*/
      var splitted = input.split(/(\x1B\[[0-9;]*[A-Za-z])/g);
      if (splitted.length === 1) {
        if (settings.unixFormattingEscapeBrackets) {
          result = $.terminal.escape_formatting(input);
        } else {
          result = input;
        }
        if (typeof options.position === 'number') {
          return [result, new_position];
        }
        return result;
      }
      var output = [];
      //skip closing at the begining
      if (splitted.length > 3) {
        var str = splitted.slice(0, 3).join('');
        if (str.match(/^\[0*m$/)) {
          splitted = splitted.slice(3);
        }
      }
      var prev_color, prev_background, code, match;
      var inside = false;
      for (var i = 0; i < splitted.length; ++i) {
        match = splitted[i].match(/^\x1B\[([0-9;]*)([A-Za-z])$/);
        if (match) {
          var start = match.index;
          // logic taken from $.terminal.tracking_replace
          if (start < position) {
            var last_index = start + $.terminal.length(match[0]);
            if (last_index < position) {
              // It's after the replacement, move it
              new_position = Math.max(
                0,
                new_position +
                $.terminal.length(match[0])
              );
            } else {
              // It's *in* the replacement, put it just after
              new_position += position - start;
            }
          }
          switch (match[2]) {
            case 'm':
              if (+match[1] !== 0) {
                code = format_ansi(match[1]);
              }
              if (inside) {
                output.push(']');
                if (+match[1] === 0) {
                  //just closing
                  inside = false;
                  prev_color = prev_background = '';
                } else {
                  // someone forget to close - move to next
                  code[1] = code[1] || prev_color;
                  code[2] = code[2] || prev_background;
                  output.push('[[' + code.join(';') + ']');
                  // store colors to next use
                  if (code[1]) {
                    prev_color = code[1];
                  }
                  if (code[2]) {
                    prev_background = code[2];
                  }
                }
              } else if (+match[1] !== 0) {
                inside = true;
                code[1] = code[1] || prev_color;
                code[2] = code[2] || prev_background;
                output.push('[[' + code.join(';') + ']');
                // store colors to next use
                if (code[1]) {
                  prev_color = code[1];
                }
                if (code[2]) {
                  prev_background = code[2];
                }
              }
              break;
          }
        } else if (settings.unixFormattingEscapeBrackets) {
          output.push($.terminal.escape_formatting(splitted[i]));
        } else {
          output.push(splitted[i]);
        }
      }
      if (inside) {
        output.push(']');
      }
      result = output.join('');
      if (options && typeof options.position === 'number') {
        return [result, new_position];
      }
      return result;
    };
  })();

  $.terminal.defaults.formatters.unshift($.terminal.from_ansi);
  $.terminal.defaults.formatters.unshift($.terminal.overtyping);
});
