import React from 'react';

const formatChars = {
  bold: 0x02,
  italic: 0x1d,
  underline: 0x1f,
  strikethrough: 0x1e,
  color: 0x03,
  reverseColor: 0x16,
  reset: 0x0f
};

const colors = {
  0: 'white',
  1: 'black',
  2: 'blue',
  3: 'green',
  4: 'red',
  5: 'brown',
  6: 'magenta',
  7: 'orange',
  8: 'yellow',
  9: 'lightgreen',
  10: 'cyan',
  11: 'lightcyan',
  12: 'lightblue',
  13: 'pink',
  14: 'gray',
  15: 'lightgray',
  16: '#470000',
  17: '#472100',
  18: '#474700',
  19: '#324700',
  20: '#004700',
  21: '#00472c',
  22: '#004747',
  23: '#002747',
  24: '#000047',
  25: '#2e0047',
  26: '#470047',
  27: '#47002a',
  28: '#740000',
  29: '#743a00',
  30: '#747400',
  31: '#517400',
  32: '#007400',
  33: '#007449',
  34: '#007474',
  35: '#004074',
  36: '#000074',
  37: '#4b0074',
  38: '#740074',
  39: '#740045',
  40: '#b50000',
  41: '#b56300',
  42: '#b5b500',
  43: '#7db500',
  44: '#00b500',
  45: '#00b571',
  46: '#00b5b5',
  47: '#0063b5',
  48: '#0000b5',
  49: '#7500b5',
  50: '#b500b5',
  51: '#b5006b',
  52: '#ff0000',
  53: '#ff8c00',
  54: '#ffff00',
  55: '#b2ff00',
  56: '#00ff00',
  57: '#00ffa0',
  58: '#00ffff',
  59: '#008cff',
  60: '#0000ff',
  61: '#a500ff',
  62: '#ff00ff',
  63: '#ff0098',
  64: '#ff5959',
  65: '#ffb459',
  66: '#ffff71',
  67: '#cfff60',
  68: '#6fff6f',
  69: '#65ffc9',
  70: '#6dffff',
  71: '#59b4ff',
  72: '#5959ff',
  73: '#c459ff',
  74: '#ff66ff',
  75: '#ff59bc',
  76: '#ff9c9c',
  77: '#ffd39c',
  78: '#ffff9c',
  79: '#e2ff9c',
  80: '#9cff9c',
  81: '#9cffdb',
  82: '#9cffff',
  83: '#9cd3ff',
  84: '#9c9cff',
  85: '#dc9cff',
  86: '#ff9cff',
  87: '#ff94d3',
  88: '#000000',
  89: '#131313',
  90: '#282828',
  91: '#363636',
  92: '#4d4d4d',
  93: '#656565',
  94: '#818181',
  95: '#9f9f9f',
  96: '#bcbcbc',
  97: '#e2e2e2',
  98: '#ffffff'
};

function tokenize(str) {
  const tokens = [];

  let colorBuffer = '';
  let color = false;
  let background = false;
  let colorToken;

  let start = 0;
  let end = 0;

  const pushText = () => {
    if (end > start) {
      tokens.push({
        type: 'text',
        content: str.slice(start, end)
      });
      start = end;
    }
  };

  const pushToken = token => {
    pushText();
    tokens.push(token);
  };

  for (let i = 0; i < str.length; i++) {
    const charCode = str.charCodeAt(i);

    if (color) {
      if (charCode >= 48 && charCode <= 57 && colorBuffer.length < 2) {
        colorBuffer += str[i];
      } else if (charCode === 44 && !background) {
        colorToken.color = colors[parseInt(colorBuffer, 10)];
        colorBuffer = '';
        background = true;
      } else {
        if (background) {
          if (colorBuffer.length > 0) {
            colorToken.background = colors[parseInt(colorBuffer, 10)];
          } else {
            // Trailing comma
            start--;
          }
        } else {
          colorToken.color = colors[parseInt(colorBuffer, 10)];
        }

        start--;
        colorBuffer = '';
        color = false;
        tokens.push(colorToken);
      }
    } else {
      switch (charCode) {
        case formatChars.bold:
          pushToken({
            type: 'bold'
          });
          break;

        case formatChars.italic:
          pushToken({
            type: 'italic'
          });
          break;

        case formatChars.underline:
          pushToken({
            type: 'underline'
          });
          break;

        case formatChars.strikethrough:
          pushToken({
            type: 'strikethrough'
          });
          break;

        case formatChars.color:
          pushText();

          colorToken = {
            type: 'color'
          };
          color = true;
          background = false;
          break;

        case formatChars.reverseColor:
          pushToken({
            type: 'reverse'
          });
          break;

        case formatChars.reset:
          pushToken({
            type: 'reset'
          });
          break;

        default:
          start--;
      }
    }

    start++;
    end++;
  }

  if (start === 0) {
    return str;
  }

  pushText();

  return tokens;
}

function colorifyString(str, state = {}) {
  const tokens = tokenize(str);

  if (typeof tokens === 'string') {
    return [tokens, state];
  }

  const result = [];
  let style = state.style || {};
  let reverse = state.reverse || false;

  const toggle = (prop, value, multiple) => {
    if (style[prop]) {
      if (multiple) {
        const props = style[prop].split(' ');
        const i = props.indexOf(value);
        if (i !== -1) {
          props.splice(i, 1);
        } else {
          props.push(value);
        }
        style[prop] = props.join(' ');
      } else {
        delete style[prop];
      }
    } else {
      style[prop] = value;
    }
  };

  for (let i = 0; i < tokens.length; i++) {
    const token = tokens[i];

    switch (token.type) {
      case 'bold':
        toggle('fontWeight', 700);
        break;

      case 'italic':
        toggle('fontStyle', 'italic');
        break;

      case 'underline':
        toggle('textDecoration', 'underline', true);
        break;

      case 'strikethrough':
        toggle('textDecoration', 'line-through', true);
        break;

      case 'color':
        if (!token.color) {
          delete style.color;
          delete style.background;
        } else if (reverse) {
          style.color = token.background;
          style.background = token.color;
        } else {
          style.color = token.color;
          style.background = token.background;
        }
        break;

      case 'reverse':
        reverse = !reverse;
        if (style.color) {
          const bg = style.background;
          style.background = style.color;
          style.color = bg;
        }
        break;

      case 'reset':
        style = {};
        break;

      case 'text':
        if (Object.keys(style).length > 0) {
          result.push(<span style={style}>{token.content}</span>);
          style = { ...style };
        } else {
          result.push(token.content);
        }
        break;

      default:
    }
  }

  return [result, { style, reverse }];
}

export default function colorify(input) {
  if (typeof input === 'string') {
    const [colored] = colorifyString(input);
    return colored;
  }

  if (Array.isArray(input)) {
    const result = [];
    let state;

    for (let i = 0; i < input.length; i++) {
      if (typeof input[i] === 'string') {
        const [colored, nextState] = colorifyString(input[i], state);

        if (typeof colored === 'string') {
          result.push(colored);
        } else {
          result.push(...colored);
        }

        state = nextState;
      } else {
        result.push(input[i]);
      }
    }

    return result;
  }

  return input;
}