text: /* jslint node: true */
  'use strict';
  
  const ANSI = require('./ansi_term.js');
  const { getPredefinedMCIValue } = require('./predefined_mci.js');
  
  //  deps
  const _ = require('lodash');
  
  exports.stripMciColorCodes = stripMciColorCodes;
  exports.pipeStringLength = pipeStringLength;
  exports.pipeToAnsi = exports.renegadeToAnsi = renegadeToAnsi;
  exports.controlCodesToAnsi = controlCodesToAnsi;
  
  //  :TODO: Not really happy with the module name of "color_codes". Would like something better ... control_code_string?
  
  function stripMciColorCodes(s) {
      return s.replace(/\|[A-Z\d]{2}/g, '');
  }
  
  function pipeStringLength(s) {
      return stripMciColorCodes(s).length;
  }
  
  function ansiSgrFromRenegadeColorCode(cc) {
      return ANSI.sgr(
          {
              0: ['reset', 'black'],
              1: ['reset', 'blue'],
              2: ['reset', 'green'],
              3: ['reset', 'cyan'],
              4: ['reset', 'red'],
              5: ['reset', 'magenta'],
              6: ['reset', 'yellow'],
              7: ['reset', 'white'],
  
              8: ['bold', 'black'],
              9: ['bold', 'blue'],
              10: ['bold', 'green'],
              11: ['bold', 'cyan'],
              12: ['bold', 'red'],
              13: ['bold', 'magenta'],
              14: ['bold', 'yellow'],
              15: ['bold', 'white'],
  
              16: ['blackBG'],
              17: ['blueBG'],
              18: ['greenBG'],
              19: ['cyanBG'],
              20: ['redBG'],
              21: ['magentaBG'],
              22: ['yellowBG'],
              23: ['whiteBG'],
  
              24: ['blink', 'blackBG'],
              25: ['blink', 'blueBG'],
              26: ['blink', 'greenBG'],
              27: ['blink', 'cyanBG'],
              28: ['blink', 'redBG'],
              29: ['blink', 'magentaBG'],
              30: ['blink', 'yellowBG'],
              31: ['blink', 'whiteBG'],
          }[cc] || 'normal'
      );
  }
  
  function ansiSgrFromCnetStyleColorCode(cc) {
      return ANSI.sgr(
          {
              c0: ['reset', 'black'],
              c1: ['reset', 'red'],
              c2: ['reset', 'green'],
              c3: ['reset', 'yellow'],
              c4: ['reset', 'blue'],
              c5: ['reset', 'magenta'],
              c6: ['reset', 'cyan'],
              c7: ['reset', 'white'],
  
              c8: ['bold', 'black'],
              c9: ['bold', 'red'],
              ca: ['bold', 'green'],
              cb: ['bold', 'yellow'],
              cc: ['bold', 'blue'],
              cd: ['bold', 'magenta'],
              ce: ['bold', 'cyan'],
              cf: ['bold', 'white'],
  
              z0: ['blackBG'],
              z1: ['redBG'],
              z2: ['greenBG'],
              z3: ['yellowBG'],
              z4: ['blueBG'],
              z5: ['magentaBG'],
              z6: ['cyanBG'],
              z7: ['whiteBG'],
          }[cc] || 'normal'
      );
  }
  
  function renegadeToAnsi(s, client) {
      if (-1 == s.indexOf('|')) {
          return s; //  no pipe codes present
      }
  
      let result = '';
      const re = /\|(?:(C[FBUD])([0-9]{1,2})|([0-9]{2})|([A-Z]{2})|(\|))/g;
      let m;
      let lastIndex = 0;
      while ((m = re.exec(s))) {
          if (m[3]) {
              //  |## color
              const val = parseInt(m[3], 10);
              const attr = ansiSgrFromRenegadeColorCode(val);
              result += s.substr(lastIndex, m.index - lastIndex) + attr;
          } else if (m[4] || m[1]) {
              //  |AA MCI code or |Cx## movement where ## is in m[1]
              let val = getPredefinedMCIValue(client, m[4] || m[1], m[2]);
              val = _.isString(val) ? val : m[0]; //  value itself or literal
              result += s.substr(lastIndex, m.index - lastIndex) + val;
          } else if (m[5]) {
              //  || -- literal '|', that is.
              result += '|';
          }
  
          lastIndex = re.lastIndex;
      }
  
      return 0 === result.length ? s : result + s.substr(lastIndex);
  }
  
  //
  //  Converts various control codes popular in BBS packages
  //  to ANSI escape sequences. Additionally supports ENiGMA style
  //  MCI codes.
  //
  //  Supported control code formats:
  //  * Renegade      : |##
  //  * PCBoard       : @X## where the first number/char is BG color, and second is FG
  //  * WildCat!      : @##@ the same as PCBoard without the X prefix, but with a @ suffix
  //  * WWIV          : ^#
  //  * CNET Control-Y: AKA Y-Style -- 0x19## where ## is a specific set of codes (older format)
  //  * CNET Control-Q: AKA Q-style -- 0x11##} where ## is a specific set of codes (newer format)
  //
  //  TODO: Add Synchronet and Celerity format support
  //
  //  Resources:
  //  * http://wiki.synchro.net/custom:colors
  //  * https://archive.org/stream/C-Net_Pro_3.0_1994_Perspective_Software/C-Net_Pro_3.0_1994_Perspective_Software_djvu.txt
  //
  function controlCodesToAnsi(s, client) {
      const RE =
          /(\|([A-Z0-9]{2})|\|)|(@X([0-9A-F]{2}))|(@([0-9A-F]{2})@)|(\x03[0-9]|\x03)|(\x19(c[0-9a-f]|z[0-7]|n1|f1|q1)|\x19)|(\x11(c[0-9a-f]|z[0-7]|n1|f1|q1)}|\x11)/g; //  eslint-disable-line no-control-regex
  
      let m;
      let result = '';
      let lastIndex = 0;
      let v;
      let fg;
      let bg;
  
      while ((m = RE.exec(s))) {
          switch (m[0].charAt(0)) {
              case '|':
                  //  Renegade |##
                  v = parseInt(m[2], 10);
  
                  if (isNaN(v)) {
                      v = getPredefinedMCIValue(client, m[2]) || m[0]; //  value itself or literal
                  }
  
                  if (_.isString(v)) {
                      result += s.substr(lastIndex, m.index - lastIndex) + v;
                  } else {
                      v = ansiSgrFromRenegadeColorCode(v);
                      result += s.substr(lastIndex, m.index - lastIndex) + v;
                  }
                  break;
  
              case '@':
                  //  PCBoard @X## or Wildcat! @##@
                  if ('@' === m[0].substr(-1)) {
                      //  Wildcat!
                      v = m[6];
                  } else {
                      v = m[4];
                  }
  
                  bg = {
                      0: ['blackBG'],
                      1: ['blueBG'],
                      2: ['greenBG'],
                      3: ['cyanBG'],
                      4: ['redBG'],
                      5: ['magentaBG'],
                      6: ['yellowBG'],
                      7: ['whiteBG'],
  
                      8: ['bold', 'blackBG'],
                      9: ['bold', 'blueBG'],
                      A: ['bold', 'greenBG'],
                      B: ['bold', 'cyanBG'],
                      C: ['bold', 'redBG'],
                      D: ['bold', 'magentaBG'],
                      E: ['bold', 'yellowBG'],
                      F: ['bold', 'whiteBG'],
                  }[v.charAt(0)] || ['normal'];
  
                  fg = {
                      0: ['reset', 'black'],
                      1: ['reset', 'blue'],
                      2: ['reset', 'green'],
                      3: ['reset', 'cyan'],
                      4: ['reset', 'red'],
                      5: ['reset', 'magenta'],
                      6: ['reset', 'yellow'],
                      7: ['reset', 'white'],
  
                      8: ['blink', 'black'],
                      9: ['blink', 'blue'],
                      A: ['blink', 'green'],
                      B: ['blink', 'cyan'],
                      C: ['blink', 'red'],
                      D: ['blink', 'magenta'],
                      E: ['blink', 'yellow'],
                      F: ['blink', 'white'],
                  }[v.charAt(1)] || ['normal'];
  
                  v = ANSI.sgr(fg.concat(bg));
                  result += s.substr(lastIndex, m.index - lastIndex) + v;
                  break;
  
              case '\x03':
                  //  WWIV
                  v = parseInt(m[8], 10);
  
                  if (isNaN(v)) {
                      v += m[0];
                  } else {
                      v = ANSI.sgr(
                          {
                              0: ['reset', 'black'],
                              1: ['bold', 'cyan'],
                              2: ['bold', 'yellow'],
                              3: ['reset', 'magenta'],
                              4: ['bold', 'white', 'blueBG'],
                              5: ['reset', 'green'],
                              6: ['bold', 'blink', 'red'],
                              7: ['bold', 'blue'],
                              8: ['reset', 'blue'],
                              9: ['reset', 'cyan'],
                          }[v] || 'normal'
                      );
                  }
  
                  result += s.substr(lastIndex, m.index - lastIndex) + v;
                  break;
  
              case '\x19':
              case '\0x11':
                  //  CNET "Y-Style" & "Q-Style"
                  v = m[9] || m[11];
                  if (v) {
                      if ('n1' === v) {
                          v = '\n';
                      } else if ('f1' === v) {
                          v = ANSI.clearScreen();
                      } else {
                          v = ansiSgrFromCnetStyleColorCode(v);
                      }
                  } else {
                      v = m[0];
                  }
                  result += s.substr(lastIndex, m.index - lastIndex) + v;
                  break;
          }
  
          lastIndex = RE.lastIndex;
      }
  
      return 0 === result.length ? s : result + s.substr(lastIndex);
  }
  