“use strict”;

var punycode = require(“punycode”); var mappingTable = require(“./lib/mappingTable.json”);

var PROCESSING_OPTIONS = {

TRANSITIONAL: 0,
NONTRANSITIONAL: 1

};

function normalize(str) { // fix bug in v8

return str.split('\u0000').map(function (s) { return s.normalize('NFC'); }).join('\u0000');

}

function findStatus(val) {

var start = 0;
var end = mappingTable.length - 1;

while (start <= end) {
  var mid = Math.floor((start + end) / 2);

  var target = mappingTable[mid];
  if (target[0][0] <= val && target[0][1] >= val) {
    return target;
  } else if (target[0][0] > val) {
    end = mid - 1;
  } else {
    start = mid + 1;
  }
}

return null;

}

var regexAstralSymbols = /[uD800-uDBFF]/g;

function countSymbols(string) {

return string
  // replace every surrogate pair with a BMP symbol
  .replace(regexAstralSymbols, '_')
  // then get the length
  .length;

}

function mapChars(domain_name, useSTD3, processing_option) {

var hasError = false;
var processed = "";

var len = countSymbols(domain_name);
for (var i = 0; i < len; ++i) {
  var codePoint = domain_name.codePointAt(i);
  var status = findStatus(codePoint);

  switch (status[1]) {
    case "disallowed":
      hasError = true;
      processed += String.fromCodePoint(codePoint);
      break;
    case "ignored":
      break;
    case "mapped":
      processed += String.fromCodePoint.apply(String, status[2]);
      break;
    case "deviation":
      if (processing_option === PROCESSING_OPTIONS.TRANSITIONAL) {
        processed += String.fromCodePoint.apply(String, status[2]);
      } else {
        processed += String.fromCodePoint(codePoint);
      }
      break;
    case "valid":
      processed += String.fromCodePoint(codePoint);
      break;
    case "disallowed_STD3_mapped":
      if (useSTD3) {
        hasError = true;
        processed += String.fromCodePoint(codePoint);
      } else {
        processed += String.fromCodePoint.apply(String, status[2]);
      }
      break;
    case "disallowed_STD3_valid":
      if (useSTD3) {
        hasError = true;
      }

      processed += String.fromCodePoint(codePoint);
      break;
  }
}

return {
  string: processed,
  error: hasError
};

}

var combiningMarksRegex = /[u0300-u036Fu0483-u0489u0591-u05BDu05BFu05C1u05C2u05C4u05C5u05C7u0610-u061Au064B-u065Fu0670u06D6-u06DCu06DF-u06E4u06E7u06E8u06EA-u06EDu0711u0730-u074Au07A6-u07B0u07EB-u07F3u0816-u0819u081B-u0823u0825-u0827u0829-u082Du0859-u085Bu08E4-u0903u093A-u093Cu093E-u094Fu0951-u0957u0962u0963u0981-u0983u09BCu09BE-u09C4u09C7u09C8u09CB-u09CDu09D7u09E2u09E3u0A01-u0A03u0A3Cu0A3E-u0A42u0A47u0A48u0A4B-u0A4Du0A51u0A70u0A71u0A75u0A81-u0A83u0ABCu0ABE-u0AC5u0AC7-u0AC9u0ACB-u0ACDu0AE2u0AE3u0B01-u0B03u0B3Cu0B3E-u0B44u0B47u0B48u0B4B-u0B4Du0B56u0B57u0B62u0B63u0B82u0BBE-u0BC2u0BC6-u0BC8u0BCA-u0BCDu0BD7u0C00-u0C03u0C3E-u0C44u0C46-u0C48u0C4A-u0C4Du0C55u0C56u0C62u0C63u0C81-u0C83u0CBCu0CBE-u0CC4u0CC6-u0CC8u0CCA-u0CCDu0CD5u0CD6u0CE2u0CE3u0D01-u0D03u0D3E-u0D44u0D46-u0D48u0D4A-u0D4Du0D57u0D62u0D63u0D82u0D83u0DCAu0DCF-u0DD4u0DD6u0DD8-u0DDFu0DF2u0DF3u0E31u0E34-u0E3Au0E47-u0E4Eu0EB1u0EB4-u0EB9u0EBBu0EBCu0EC8-u0ECDu0F18u0F19u0F35u0F37u0F39u0F3Eu0F3Fu0F71-u0F84u0F86u0F87u0F8D-u0F97u0F99-u0FBCu0FC6u102B-u103Eu1056-u1059u105E-u1060u1062-u1064u1067-u106Du1071-u1074u1082-u108Du108Fu109A-u109Du135D-u135Fu1712-u1714u1732-u1734u1752u1753u1772u1773u17B4-u17D3u17DDu180B-u180Du18A9u1920-u192Bu1930-u193Bu19B0-u19C0u19C8u19C9u1A17-u1A1Bu1A55-u1A5Eu1A60-u1A7Cu1A7Fu1AB0-u1ABEu1B00-u1B04u1B34-u1B44u1B6B-u1B73u1B80-u1B82u1BA1-u1BADu1BE6-u1BF3u1C24-u1C37u1CD0-u1CD2u1CD4-u1CE8u1CEDu1CF2-u1CF4u1CF8u1CF9u1DC0-u1DF5u1DFC-u1DFFu20D0-u20F0u2CEF-u2CF1u2D7Fu2DE0-u2DFFu302A-u302Fu3099u309AuA66F-uA672uA674-uA67DuA69FuA6F0uA6F1uA802uA806uA80BuA823-uA827uA880uA881uA8B4-uA8C4uA8E0-uA8F1uA926-uA92DuA947-uA953uA980-uA983uA9B3-uA9C0uA9E5uAA29-uAA36uAA43uAA4CuAA4DuAA7B-uAA7DuAAB0uAAB2-uAAB4uAAB7uAAB8uAABEuAABFuAAC1uAAEB-uAAEFuAAF5uAAF6uABE3-uABEAuABECuABEDuFB1EuFE00-uFE0FuFE20-uFE2D]|uD800|uD802|uD804|uD805|uD81A|uD81B|uD82F|uD834|uD83A|uDB40/;

function validateLabel(label, processing_option) {

if (label.substr(0, 4) === "xn--") {
  label = punycode.toUnicode(label);
  processing_option = PROCESSING_OPTIONS.NONTRANSITIONAL;
}

var error = false;

if (normalize(label) !== label ||
    (label[3] === "-" && label[4] === "-") ||
    label[0] === "-" || label[label.length - 1] === "-" ||
    label.indexOf(".") !== -1 ||
    label.search(combiningMarksRegex) === 0) {
  error = true;
}

var len = countSymbols(label);
for (var i = 0; i < len; ++i) {
  var status = findStatus(label.codePointAt(i));
  if ((processing === PROCESSING_OPTIONS.TRANSITIONAL && status[1] !== "valid") ||
      (processing === PROCESSING_OPTIONS.NONTRANSITIONAL &&
       status[1] !== "valid" && status[1] !== "deviation")) {
    error = true;
    break;
  }
}

return {
  label: label,
  error: error
};

}

function processing(domain_name, useSTD3, processing_option) {

var result = mapChars(domain_name, useSTD3, processing_option);
result.string = normalize(result.string);

var labels = result.string.split(".");
for (var i = 0; i < labels.length; ++i) {
  try {
    var validation = validateLabel(labels[i]);
    labels[i] = validation.label;
    result.error = result.error || validation.error;
  } catch(e) {
    result.error = true;
  }
}

return {
  string: labels.join("."),
  error: result.error
};

}

module.exports.toASCII = function(domain_name, useSTD3, processing_option, verifyDnsLength) {

var result = processing(domain_name, useSTD3, processing_option);
var labels = result.string.split(".");
labels = labels.map(function(l) {
  try {
    return punycode.toASCII(l);
  } catch(e) {
    result.error = true;
    return l;
  }
});

if (verifyDnsLength) {
  var total = labels.slice(0, labels.length - 1).join(".").length;
  if (total.length > 253 || total.length === 0) {
    result.error = true;
  }

  for (var i=0; i < labels.length; ++i) {
    if (labels.length > 63 || labels.length === 0) {
      result.error = true;
      break;
    }
  }
}

if (result.error) return null;
return labels.join(".");

};

module.exports.toUnicode = function(domain_name, useSTD3) {

var result = processing(domain_name, useSTD3, PROCESSING_OPTIONS.NONTRANSITIONAL);

return {
  domain: result.string,
  error: result.error
};

};

module.exports.PROCESSING_OPTIONS = PROCESSING_OPTIONS;