import AccessorUtilities from '../../lib/utils/accessor';

const GL_EMOJI_VERSION = '0.2.0';

const unicodeSupportTestMap = {
  // man, student (emojione does not have any of these yet), http://emojipedia.org/emoji-zwj-sequences/
  // occupationZwj: '\u{1F468}\u{200D}\u{1F393}',
  // woman, biking (emojione does not have any of these yet), http://emojipedia.org/emoji-zwj-sequences/
  // sexZwj: '\u{1F6B4}\u{200D}\u{2640}',
  // family_mwgb
  // Windows 8.1, Firefox 51.0.1 does not support `family_`, `kiss_`, `couple_`
  personZwj: '\u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F466}',
  // horse_racing_tone5
  // Special case that is not supported on macOS 10.12 even though `skinToneModifier` succeeds
  horseRacing: '\u{1F3C7}\u{1F3FF}',
  // US flag, http://emojipedia.org/flags/
  flag: '\u{1F1FA}\u{1F1F8}',
  rainbowFlag: '\u{1F3F3}\u{1F308}',
  // http://emojipedia.org/modifiers/
  skinToneModifier: [
    // spy_tone5
    '\u{1F575}\u{1F3FF}',
    // person_with_ball_tone5
    '\u{26F9}\u{1F3FF}',
    // angel_tone5
    '\u{1F47C}\u{1F3FF}',
  ],
  // rofl, http://emojipedia.org/unicode-9.0/
  '9.0': '\u{1F923}',
  // metal, http://emojipedia.org/unicode-8.0/
  '8.0': '\u{1F918}',
  // spy, http://emojipedia.org/unicode-7.0/
  '7.0': '\u{1F575}',
  // expressionless, http://emojipedia.org/unicode-6.1/
  6.1: '\u{1F611}',
  // japanese_goblin, http://emojipedia.org/unicode-6.0/
  '6.0': '\u{1F47A}',
  // sailboat, http://emojipedia.org/unicode-5.2/
  5.2: '\u{26F5}',
  // mahjong, http://emojipedia.org/unicode-5.1/
  5.1: '\u{1F004}',
  // gear, http://emojipedia.org/unicode-4.1/
  4.1: '\u{2699}',
  // zap, http://emojipedia.org/unicode-4.0/
  '4.0': '\u{26A1}',
  // recycle, http://emojipedia.org/unicode-3.2/
  3.2: '\u{267B}',
  // information_source, http://emojipedia.org/unicode-3.0/
  '3.0': '\u{2139}',
  // heart, http://emojipedia.org/unicode-1.1/
  1.1: '\u{2764}',
};

function checkPixelInImageDataArray(pixelOffset, imageDataArray) {
  // `4 *` because RGBA
  const indexOffset = 4 * pixelOffset;
  const hasColor =
    imageDataArray[indexOffset + 0] ||
    imageDataArray[indexOffset + 1] ||
    imageDataArray[indexOffset + 2];
  const isVisible = imageDataArray[indexOffset + 3];
  // Check for some sort of color other than black
  if (hasColor && isVisible) {
    return true;
  }
  return false;
}

const chromeMatches = navigator.userAgent.match(/Chrom(?:e|ium)\/([0-9]+)\./);
const isChrome = chromeMatches && chromeMatches.length > 0;
const chromeVersion = chromeMatches && chromeMatches[1] && parseInt(chromeMatches[1], 10);

// We use 16px because mobile Safari (iOS 9.3) doesn't properly scale emojis :/
// See 32px, https://i.imgur.com/htY6Zym.png
// See 16px, https://i.imgur.com/FPPsIF8.png
const fontSize = 16;
function generateUnicodeSupportMap(testMap) {
  const testMapKeys = Object.keys(testMap);
  const numTestEntries = testMapKeys.reduce((list, testKey) => list.concat(testMap[testKey]), [])
    .length;

  const canvas = document.createElement('canvas');
  (window.gl || window).testEmojiUnicodeSupportMapCanvas = canvas;
  const ctx = canvas.getContext('2d');
  canvas.width = 2 * fontSize;
  canvas.height = numTestEntries * fontSize;
  ctx.fillStyle = '#000000';
  ctx.textBaseline = 'middle';
  ctx.font = `${fontSize}px "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"`;
  // Write each emoji to the canvas vertically
  let writeIndex = 0;
  testMapKeys.forEach((testKey) => {
    const testEntry = testMap[testKey];
    [].concat(testEntry).forEach((emojiUnicode) => {
      ctx.fillText(emojiUnicode, 0, writeIndex * fontSize + fontSize / 2);
      writeIndex += 1;
    });
  });

  // Read from the canvas
  const resultMap = {};
  let readIndex = 0;
  testMapKeys.forEach((testKey) => {
    const testEntry = testMap[testKey];
    // This needs to be a `reduce` instead of `every` because we need to
    // keep the `readIndex` in sync from the writes by running all entries
    const isTestSatisfied = [].concat(testEntry).reduce((isSatisfied) => {
      // Sample along the vertical-middle for a couple of characters
      const imageData = ctx.getImageData(0, readIndex * fontSize + fontSize / 2, 2 * fontSize, 1)
        .data;

      let isValidEmoji = false;
      for (let currentPixel = 0; currentPixel < 64; currentPixel += 1) {
        const isLookingAtFirstChar = currentPixel < fontSize;
        const isLookingAtSecondChar = currentPixel >= fontSize + fontSize / 2;
        // Check for the emoji somewhere along the row
        if (isLookingAtFirstChar && checkPixelInImageDataArray(currentPixel, imageData)) {
          isValidEmoji = true;

          // Check to see that nothing is rendered next to the first character
          // to ensure that the ZWJ sequence rendered as one piece
        } else if (isLookingAtSecondChar && checkPixelInImageDataArray(currentPixel, imageData)) {
          isValidEmoji = false;
          break;
        }
      }

      readIndex += 1;
      return isSatisfied && isValidEmoji;
    }, true);

    resultMap[testKey] = isTestSatisfied;
  });

  resultMap.meta = {
    isChrome,
    chromeVersion,
  };

  return resultMap;
}

export default function getUnicodeSupportMap() {
  const isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe();

  let glEmojiVersionFromCache;
  let userAgentFromCache;
  if (isLocalStorageAvailable) {
    glEmojiVersionFromCache = window.localStorage.getItem('gl-emoji-version');
    userAgentFromCache = window.localStorage.getItem('gl-emoji-user-agent');
  }

  let unicodeSupportMap;
  try {
    unicodeSupportMap = JSON.parse(window.localStorage.getItem('gl-emoji-unicode-support-map'));
  } catch (err) {
    // swallow
  }

  if (
    !unicodeSupportMap ||
    glEmojiVersionFromCache !== GL_EMOJI_VERSION ||
    userAgentFromCache !== navigator.userAgent
  ) {
    unicodeSupportMap = generateUnicodeSupportMap(unicodeSupportTestMap);

    if (isLocalStorageAvailable) {
      window.localStorage.setItem('gl-emoji-version', GL_EMOJI_VERSION);
      window.localStorage.setItem('gl-emoji-user-agent', navigator.userAgent);
      window.localStorage.setItem(
        'gl-emoji-unicode-support-map',
        JSON.stringify(unicodeSupportMap),
      );
    }
  }

  return unicodeSupportMap;
}
