module.exports = safe

var patternCompile = require('./pattern-compile')
var patternInScope = require('./pattern-in-scope')

function safe(context, input, config) {
  var value = (config.before || '') + (input || '') + (config.after || '')
  var positions = []
  var result = []
  var infos = {}
  var index = -1
  var before
  var after
  var position
  var pattern
  var expression
  var match
  var start
  var end

  while (++index < context.unsafe.length) {
    pattern = context.unsafe[index]

    if (!patternInScope(context.stack, pattern)) {
      continue
    }

    expression = patternCompile(pattern)

    while ((match = expression.exec(value))) {
      before = 'before' in pattern || pattern.atBreak
      after = 'after' in pattern

      position = match.index + (before ? match[1].length : 0)

      if (positions.indexOf(position) === -1) {
        positions.push(position)
        infos[position] = {before: before, after: after}
      } else {
        if (infos[position].before && !before) {
          infos[position].before = false
        }

        if (infos[position].after && !after) {
          infos[position].after = false
        }
      }
    }
  }

  positions.sort(numerical)

  start = config.before ? config.before.length : 0
  end = value.length - (config.after ? config.after.length : 0)
  index = -1

  while (++index < positions.length) {
    position = positions[index]

    if (
      // Character before or after matched:
      position < start ||
      position >= end
    ) {
      continue
    }

    // If this character is supposed to be escaped because it has a condition on
    // the next character, and the next character is definitly being escaped,
    // then skip this escape.
    if (
      position + 1 < end &&
      positions[index + 1] === position + 1 &&
      infos[position].after &&
      !infos[position + 1].before &&
      !infos[position + 1].after
    ) {
      continue
    }

    if (start !== position) {
      // If we have to use a character reference, an ampersand would be more
      // correct, but as backslashes only care about punctuation, either will
      // do the trick
      result.push(escapeBackslashes(value.slice(start, position), '\\'))
    }

    start = position

    if (
      /[!-/:-@[-`{-~]/.test(value.charAt(position)) &&
      (!config.encode || config.encode.indexOf(value.charAt(position)) === -1)
    ) {
      // Character escape.
      result.push('\\')
    } else {
      // Character reference.
      result.push(
        '&#x' + value.charCodeAt(position).toString(16).toUpperCase() + ';'
      )
      start++
    }
  }

  result.push(escapeBackslashes(value.slice(start, end), config.after))

  return result.join('')
}

function numerical(a, b) {
  return a - b
}

function escapeBackslashes(value, after) {
  var expression = /\\(?=[!-/:-@[-`{-~])/g
  var positions = []
  var results = []
  var index = -1
  var start = 0
  var whole = value + after
  var match

  while ((match = expression.exec(whole))) {
    positions.push(match.index)
  }

  while (++index < positions.length) {
    if (start !== positions[index]) {
      results.push(value.slice(start, positions[index]))
    }

    results.push('\\')
    start = positions[index]
  }

  results.push(value.slice(start))

  return results.join('')
}