const constants = require('./constants')
const errors = require('./errors')
const Version = require('./version')
const Comparator = require('./comparator')

class Range {
  constructor(comparators = []) {
    this.comparators = comparators
  }

  test(version) {
    for (const set of this.comparators) {
      let matches = true

      for (const comparator of set) {
        if (comparator.test(version)) continue
        matches = false
        break
      }

      if (matches) return true
    }

    return false
  }

  toString() {
    let result = ''
    let first = true

    for (const set of this.comparators) {
      if (first) first = false
      else result += ' || '

      result += set.join(' ')
    }

    return result
  }
}

module.exports = exports = Range

exports.parse = function parse(input, state = { position: 0, partial: false }) {
  let i = state.position
  let c

  const unexpected = (expected) => {
    let msg

    if (i >= input.length) {
      msg = `Unexpected end of input in '${input}'`
    } else {
      msg = `Unexpected token '${input[i]}' in '${input}' at position ${i}`
    }

    if (expected) msg += `, ${expected}`

    throw errors.INVALID_VERSION(msg, unexpected)
  }

  const comparators = []

  while (i < input.length) {
    const set = []

    while (i < input.length) {
      c = input[i]

      let operator = constants.EQ

      if (c === '<') {
        operator = constants.LT
        c = input[++i]

        if (c === '=') {
          operator = constants.LTE
          c = input[++i]
        }
      } else if (c === '>') {
        operator = constants.GT
        c = input[++i]

        if (c === '=') {
          operator = constants.GTE
          c = input[++i]
        }
      } else if (c === '=') {
        c = input[++i]
      }

      const state = { position: i, partial: true, range: true }

      set.push(new Comparator(operator, Version.parse(input, state)))

      c = input[(i = state.position)]

      while (c === ' ') c = input[++i]

      if (c === '|' && input[i + 1] === '|') {
        c = input[(i += 2)]

        while (c === ' ') c = input[++i]

        break
      }

      if (c && c !== '<' && c !== '>') unexpected("expected '||', '<', or '>'")
    }

    if (set.length) comparators.push(set)
  }

  if (i < input.length && state.partial === false) {
    unexpected('expected end of input')
  }

  state.position = i

  return new Range(comparators)
}
