const isHighSurrogate = (charCode) =>
  charCode >= 0xD800 && charCode <= 0xDBFF

const isLowSurrogate = (charCode) =>
  charCode >= 0xDC00 && charCode <= 0xDFFF

const isVariationSelector = (charCode) =>
  charCode >= 0xFE00 && charCode <= 0xFE0F

const isCombiningCharacter = charCode =>
  (charCode >= 0x0300 && charCode <= 0x036F) ||
  (charCode >= 0x1AB0 && charCode <= 0x1AFF) ||
  (charCode >= 0x1DC0 && charCode <= 0x1DFF) ||
  (charCode >= 0x20D0 && charCode <= 0x20FF) ||
  (charCode >= 0xFE20 && charCode <= 0xFE2F)

// Are the given character codes a high/low surrogate pair?
//
// * `charCodeA` The first character code {Number}.
// * `charCode2` The second character code {Number}.
//
// Return a {Boolean}.
const isSurrogatePair = (charCodeA, charCodeB) =>
  isHighSurrogate(charCodeA) && isLowSurrogate(charCodeB)

// Are the given character codes a variation sequence?
//
// * `charCodeA` The first character code {Number}.
// * `charCode2` The second character code {Number}.
//
// Return a {Boolean}.
const isVariationSequence = (charCodeA, charCodeB) =>
  !isVariationSelector(charCodeA) && isVariationSelector(charCodeB)

// Are the given character codes a combined character pair?
//
// * `charCodeA` The first character code {Number}.
// * `charCode2` The second character code {Number}.
//
// Return a {Boolean}.
const isCombinedCharacter = (charCodeA, charCodeB) =>
  !isCombiningCharacter(charCodeA) && isCombiningCharacter(charCodeB)

// Is the character at the given index the start of high/low surrogate pair
// a variation sequence, or a combined character?
//
// * `string` The {String} to check for a surrogate pair, variation sequence,
//            or combined character.
// * `index`  The {Number} index to look for a surrogate pair, variation
//            sequence, or combined character.
//
// Return a {Boolean}.
const isPairedCharacter = (string, index = 0) => {
  const charCodeA = string.charCodeAt(index)
  const charCodeB = string.charCodeAt(index + 1)
  return isSurrogatePair(charCodeA, charCodeB) ||
    isVariationSequence(charCodeA, charCodeB) ||
    isCombinedCharacter(charCodeA, charCodeB)
}

const IsJapaneseKanaCharacter = charCode =>
  charCode >= 0x3000 && charCode <= 0x30FF

const isCJKUnifiedIdeograph = charCode =>
  charCode >= 0x4E00 && charCode <= 0x9FFF

const isFullWidthForm = charCode =>
  (charCode >= 0xFF01 && charCode <= 0xFF5E) ||
  (charCode >= 0xFFE0 && charCode <= 0xFFE6)

const isDoubleWidthCharacter = (character) => {
  const charCode = character.charCodeAt(0)

  return IsJapaneseKanaCharacter(charCode) ||
  isCJKUnifiedIdeograph(charCode) ||
  isFullWidthForm(charCode)
}

const isHalfWidthCharacter = (character) => {
  const charCode = character.charCodeAt(0)

  return (charCode >= 0xFF65 && charCode <= 0xFFDC) ||
    (charCode >= 0xFFE8 && charCode <= 0xFFEE)
}

const isKoreanCharacter = (character) => {
  const charCode = character.charCodeAt(0)

  return (charCode >= 0xAC00 && charCode <= 0xD7A3) ||
    (charCode >= 0x1100 && charCode <= 0x11FF) ||
    (charCode >= 0x3130 && charCode <= 0x318F) ||
    (charCode >= 0xA960 && charCode <= 0xA97F) ||
    (charCode >= 0xD7B0 && charCode <= 0xD7FF)
}

const isCJKCharacter = (character) =>
  isDoubleWidthCharacter(character) ||
  isHalfWidthCharacter(character) ||
  isKoreanCharacter(character)

const isWordStart = (previousCharacter, character) =>
  (
    previousCharacter === ' ' ||
    previousCharacter === '\t' ||
    previousCharacter === '-' ||
    previousCharacter === '/'
  ) &&
  (
    character !== ' ' &&
    character !== '\t'
  )

const isWrapBoundary = (previousCharacter, character) =>
  isWordStart(previousCharacter, character) || isCJKCharacter(character)

// Does the given string contain at least surrogate pair, variation sequence,
// or combined character?
//
// * `string` The {String} to check for the presence of paired characters.
//
// Returns a {Boolean}.
const hasPairedCharacter = (string) => {
  let index = 0
  while (index < string.length) {
    if (isPairedCharacter(string, index)) { return true }
    index++
  }
  return false
}

module.exports = {
  isPairedCharacter,
  hasPairedCharacter,
  isDoubleWidthCharacter,
  isHalfWidthCharacter,
  isKoreanCharacter,
  isWrapBoundary
}
