'use strict'

const data = (function () {
  const d = []
  let i = 0
  let k
  for (; i < 100; i++) {
    k = (i < 10 ? '0' : '') + i
    d.push({
      key: k,
      value: String(Math.random())
    })
  }
  return d
}())

exports.range = function (test, testCommon) {
  function rangeTest (name, opts, expected) {
    test('clear() range with ' + name, async function (t) {
      const db = await prepare()

      await db.clear(opts)
      await verify(t, db, expected)

      return db.close()
    })
  }

  async function prepare (t) {
    const db = testCommon.factory()

    await db.open()
    await db.batch(data.map(function ({ key, value }) {
      return { type: 'put', key, value }
    }))

    return db
  }

  async function verify (t, db, expected) {
    const it = db.iterator({ keyEncoding: 'utf8', valueEncoding: 'utf8' })
    const entries = await it.all()

    t.is(entries.length, expected.length, 'correct number of entries')
    t.same(entries, expected.map(kv => [kv.key, kv.value]))
  }

  function exclude (data, start, end, expectedLength) {
    data = data.slice()
    const removed = data.splice(start, end - start + 1) // Inclusive
    if (expectedLength != null) checkLength(removed, expectedLength)
    return data
  }

  // For sanity checks on test arguments
  function checkLength (arr, length) {
    if (arr.length !== length) {
      throw new RangeError('Expected ' + length + ' elements, got ' + arr.length)
    }

    return arr
  }

  rangeTest('no options', {}, [])

  // Reversing has no effect without limit
  rangeTest('reverse=true', {
    reverse: true
  }, [])

  rangeTest('gte=00', {
    gte: '00'
  }, [])

  rangeTest('gte=50', {
    gte: '50'
  }, data.slice(0, 50))

  rangeTest('lte=50 and reverse=true', {
    lte: '50',
    reverse: true
  }, data.slice(51))

  rangeTest('gte=49.5 (midway)', {
    gte: '49.5'
  }, data.slice(0, 50))

  rangeTest('gte=49999 (midway)', {
    gte: '49999'
  }, data.slice(0, 50))

  rangeTest('lte=49.5 (midway) and reverse=true', {
    lte: '49.5',
    reverse: true
  }, data.slice(50))

  rangeTest('lt=49.5 (midway) and reverse=true', {
    lt: '49.5',
    reverse: true
  }, data.slice(50))

  rangeTest('lt=50 and reverse=true', {
    lt: '50',
    reverse: true
  }, data.slice(50))

  rangeTest('lte=50', {
    lte: '50'
  }, data.slice(51))

  rangeTest('lte=50.5 (midway)', {
    lte: '50.5'
  }, data.slice(51))

  rangeTest('lte=50555 (midway)', {
    lte: '50555'
  }, data.slice(51))

  rangeTest('lt=50555 (midway)', {
    lt: '50555'
  }, data.slice(51))

  rangeTest('gte=50.5 (midway) and reverse=true', {
    gte: '50.5',
    reverse: true
  }, data.slice(0, 51))

  rangeTest('gt=50.5 (midway) and reverse=true', {
    gt: '50.5',
    reverse: true
  }, data.slice(0, 51))

  rangeTest('gt=50 and reverse=true', {
    gt: '50',
    reverse: true
  }, data.slice(0, 51))

  // First key is actually '00' so it should avoid it
  rangeTest('lte=0', {
    lte: '0'
  }, data)

  // First key is actually '00' so it should avoid it
  rangeTest('lt=0', {
    lt: '0'
  }, data)

  rangeTest('gte=30 and lte=70', {
    gte: '30',
    lte: '70'
  }, exclude(data, 30, 70))

  // The gte and lte options should take precedence over gt and lt respectively.
  rangeTest('gte=30 and lte=70 and gt=40 and lt=60', {
    gte: '30',
    lte: '70',
    gt: '40',
    lt: '60'
  }, exclude(data, 30, 70))

  // Also test the other way around: if gt and lt were to select a bigger range.
  rangeTest('gte=30 and lte=70 and gt=20 and lt=80', {
    gte: '30',
    lte: '70',
    gt: '20',
    lt: '80'
  }, exclude(data, 30, 70))

  rangeTest('gt=29 and lt=71', {
    gt: '29',
    lt: '71'
  }, exclude(data, 30, 70))

  rangeTest('gte=30 and lte=70 and reverse=true', {
    lte: '70',
    gte: '30',
    reverse: true
  }, exclude(data, 30, 70))

  rangeTest('gt=29 and lt=71 and reverse=true', {
    lt: '71',
    gt: '29',
    reverse: true
  }, exclude(data, 30, 70))

  rangeTest('limit=20', {
    limit: 20
  }, data.slice(20))

  rangeTest('limit=20 and gte=20', {
    limit: 20,
    gte: '20'
  }, exclude(data, 20, 39, 20))

  rangeTest('limit=20 and reverse=true', {
    limit: 20,
    reverse: true
  }, data.slice(0, -20))

  rangeTest('limit=20 and lte=79 and reverse=true', {
    limit: 20,
    lte: '79',
    reverse: true
  }, exclude(data, 60, 79, 20))

  rangeTest('limit=-1 should clear whole database', {
    limit: -1
  }, [])

  rangeTest('limit=0 should not clear anything', {
    limit: 0
  }, data)

  rangeTest('lte after limit', {
    limit: 20,
    lte: '50'
  }, data.slice(20))

  rangeTest('lte before limit', {
    limit: 50,
    lte: '19'
  }, data.slice(20))

  rangeTest('gte after database end', {
    gte: '9a'
  }, data)

  rangeTest('gt after database end', {
    gt: '9a'
  }, data)

  rangeTest('lte after database end and reverse=true', {
    lte: '9a',
    reverse: true
  }, [])

  rangeTest('lte and gte after database and reverse=true', {
    lte: '9b',
    gte: '9a',
    reverse: true
  }, data)

  rangeTest('lt and gt after database and reverse=true', {
    lt: '9b',
    gt: '9a',
    reverse: true
  }, data)

  rangeTest('gt greater than lt', {
    gt: '20',
    lt: '10'
  }, data)

  rangeTest('gte greater than lte', {
    gte: '20',
    lte: '10'
  }, data)
}

exports.all = function (test, testCommon) {
  exports.range(test, testCommon)
}
