'use strict'

const { test } = require('node:test')
const Streamsearch = require('../deps/streamsearch/sbmh')

test('streamsearch', async t => {
  t.plan(20)

  await t.test('should throw an error if the needle is not a String or Buffer', t => {
    t.plan(1)

    t.assert.throws(() => new Streamsearch(2), { message: 'The needle has to be a String or a Buffer.' })
  })
  await t.test('should throw an error if the needle is an empty String', t => {
    t.plan(1)

    t.assert.throws(() => new Streamsearch(''), { message: 'The needle cannot be an empty String/Buffer.' })
  })
  await t.test('should throw an error if the needle is an empty Buffer', t => {
    t.plan(1)

    t.assert.throws(() => new Streamsearch(Buffer.from('')), { message: 'The needle cannot be an empty String/Buffer.' })
  })
  await t.test('should throw an error if the needle is bigger than 256 characters', t => {
    t.plan(1)

    t.assert.throws(() => new Streamsearch(Buffer.from(Array(257).fill('a').join(''))), { message: 'The needle cannot have a length bigger than 256.' })
  })

  await t.test('should represent the maximum skip distance', t => {
    t.plan(1)

    const search = new Streamsearch(Buffer.alloc(256, 0x41))

    t.assert.strictEqual(search._occ[0x42], 256)
  })

  await t.test('should process a Buffer without a needle', t => {
    t.plan(5)
    const expected = [
      [false, Buffer.from('bar hello'), 0, 9]
    ]
    const needle = '\r\n'
    const s = new Streamsearch(needle)
    const chunks = [
      Buffer.from('bar hello')
    ]
    let i = 0
    s.on('info', (isMatched, data, start, end) => {
      t.assert.deepStrictEqual(isMatched, expected[i][0])
      t.assert.deepStrictEqual(data, expected[i][1])
      t.assert.deepStrictEqual(start, expected[i][2])
      t.assert.deepStrictEqual(end, expected[i][3])
      i++
      if (i >= 1) {
        t.assert.ok('pass')
      }
    })

    s.push(chunks[0])
  })

  await t.test('should cast a string without a needle', t => {
    t.plan(5)

    const expected = [
      [false, Buffer.from('bar hello'), 0, 9]
    ]
    const needle = '\r\n'
    const s = new Streamsearch(needle)
    const chunks = [
      'bar hello'
    ]
    let i = 0
    s.on('info', (isMatched, data, start, end) => {
      t.assert.deepStrictEqual(isMatched, expected[i][0])
      t.assert.deepStrictEqual(data, expected[i][1])
      t.assert.deepStrictEqual(start, expected[i][2])
      t.assert.deepStrictEqual(end, expected[i][3])
      i++
      if (i >= 1) {
        t.assert.ok('pass')
      }
    })

    s.push(chunks[0])
  })

  await t.test('should process a chunk with a needle at the beginning', t => {
    t.plan(9)

    const expected = [
      [true, undefined, undefined, undefined],
      [false, Buffer.from('\r\nbar hello'), 2, 11]
    ]
    const needle = '\r\n'
    const s = new Streamsearch(needle)
    const chunks = [
      Buffer.from('\r\nbar hello')
    ]
    let i = 0
    s.on('info', (isMatched, data, start, end) => {
      t.assert.deepStrictEqual(isMatched, expected[i][0])
      t.assert.deepStrictEqual(data, expected[i][1])
      t.assert.deepStrictEqual(start, expected[i][2])
      t.assert.deepStrictEqual(end, expected[i][3])
      i++
      if (i >= 2) {
        t.assert.ok('pass')
      }
    })

    s.push(chunks[0])
  })

  await t.test('should process a chunk with a needle in the middle', t => {
    t.plan(9)
    const expected = [
      [true, Buffer.from('bar\r\n hello'), 0, 3],
      [false, Buffer.from('bar\r\n hello'), 5, 11]
    ]
    const needle = '\r\n'
    const s = new Streamsearch(needle)
    const chunks = [
      Buffer.from('bar\r\n hello')
    ]
    let i = 0
    s.on('info', (isMatched, data, start, end) => {
      t.assert.deepStrictEqual(isMatched, expected[i][0])
      t.assert.deepStrictEqual(data, expected[i][1])
      t.assert.deepStrictEqual(start, expected[i][2])
      t.assert.deepStrictEqual(end, expected[i][3])
      i++
      if (i >= 2) {
        t.assert.ok('pass')
      }
    })

    s.push(chunks[0])
  })

  await t.test('should process a chunk with a needle at the end', t => {
    t.plan(5)
    const expected = [
      [true, Buffer.from('bar hello\r\n'), 0, 9]
    ]
    const needle = '\r\n'
    const s = new Streamsearch(needle)
    const chunks = [
      Buffer.from('bar hello\r\n')
    ]
    let i = 0
    s.on('info', (isMatched, data, start, end) => {
      t.assert.deepStrictEqual(isMatched, expected[i][0])
      t.assert.deepStrictEqual(data, expected[i][1])
      t.assert.deepStrictEqual(start, expected[i][2])
      t.assert.deepStrictEqual(end, expected[i][3])
      i++
      if (i >= 1) {
        t.assert.ok('pass')
      }
    })

    s.push(chunks[0])
  })

  await t.test('should process a chunk with multiple needle at the end', t => {
    t.plan(9)
    const expected = [
      [true, Buffer.from('bar hello\r\n\r\n'), 0, 9],
      [true, Buffer.from('bar hello\r\n\r\n'), 11, 11]
    ]
    const needle = '\r\n'
    const s = new Streamsearch(needle)
    const chunks = [
      Buffer.from('bar hello\r\n\r\n')
    ]
    let i = 0
    s.on('info', (isMatched, data, start, end) => {
      t.assert.deepStrictEqual(isMatched, expected[i][0])
      t.assert.deepStrictEqual(data, expected[i][1])
      t.assert.deepStrictEqual(start, expected[i][2])
      t.assert.deepStrictEqual(end, expected[i][3])
      i++
      if (i >= 2) {
        t.assert.ok('pass')
      }
    })

    s.push(chunks[0])
  })

  await t.test('should process two chunks without a needle', t => {
    t.plan(9)
    const expected = [
      [false, Buffer.from('bar'), 0, 3],
      [false, Buffer.from('hello'), 0, 5]
    ]
    const needle = '\r\n'
    const s = new Streamsearch(needle)
    const chunks = [
      Buffer.from('bar'),
      Buffer.from('hello')
    ]
    let i = 0
    s.on('info', (isMatched, data, start, end) => {
      t.assert.deepStrictEqual(isMatched, expected[i][0])
      t.assert.deepStrictEqual(data, expected[i][1])
      t.assert.deepStrictEqual(start, expected[i][2])
      t.assert.deepStrictEqual(end, expected[i][3])
      i++
      if (i >= 2) {
        t.assert.ok('pass')
      }
    })

    s.push(chunks[0])
    s.push(chunks[1])
  })

  await t.test('should process two chunks with an overflowing needle', t => {
    t.plan(13)
    const expected = [
      [false, Buffer.from('bar\r'), 0, 3],
      [true, undefined, undefined, undefined],
      [false, Buffer.from('\nhello'), 1, 6]
    ]
    const needle = '\r\n'
    const s = new Streamsearch(needle)
    const chunks = [
      Buffer.from('bar\r'),
      Buffer.from('\nhello')
    ]
    let i = 0
    s.on('info', (isMatched, data, start, end) => {
      t.assert.deepStrictEqual(isMatched, expected[i][0])
      t.assert.deepStrictEqual(data, expected[i][1])
      t.assert.deepStrictEqual(start, expected[i][2])
      t.assert.deepStrictEqual(end, expected[i][3])
      i++
      if (i >= 3) {
        t.assert.ok('pass')
      }
    })

    s.push(chunks[0])
    s.push(chunks[1])
  })

  await t.test('should process two chunks with an overflowing needle /2', t => {
    t.plan(9)
    const expected = [
      [false, Buffer.from('t\0\0'), 0, 1],
      [false, Buffer.from('eshello'), 0, 7]
    ]
    const needle = 'test'
    const s = new Streamsearch(needle)
    const chunks = [
      Buffer.from('t'),
      Buffer.from('eshello')
    ]
    let i = 0
    s.on('info', (isMatched, data, start, end) => {
      t.assert.deepStrictEqual(isMatched, expected[i][0])
      t.assert.deepStrictEqual(data, expected[i][1])
      t.assert.deepStrictEqual(start, expected[i][2])
      t.assert.deepStrictEqual(end, expected[i][3])
      i++
      if (i >= 2) {
        t.assert.ok('pass')
      }
    })

    s.push(chunks[0])
    s.push(chunks[1])
  })

  await t.test('should process two chunks with a potentially overflowing needle', t => {
    t.plan(13)

    const expected = [
      [false, Buffer.from('bar\r'), 0, 3],
      [false, Buffer.from('\r\0'), 0, 1],
      [false, Buffer.from('\n\r\nhello'), 0, 8]
    ]
    const needle = '\r\n\n'
    const s = new Streamsearch(needle)
    const chunks = [
      Buffer.from('bar\r'),
      Buffer.from('\n\r\nhello')
    ]
    let i = 0
    s.on('info', (isMatched, data, start, end) => {
      t.assert.deepStrictEqual(isMatched, expected[i][0])
      t.assert.deepStrictEqual(data, expected[i][1])
      t.assert.deepStrictEqual(start, expected[i][2])
      t.assert.deepStrictEqual(end, expected[i][3])
      i++
      if (i >= 3) {
        t.assert.ok('pass')
      }
    })

    s.push(chunks[0])
    s.push(chunks[1])
  })

  await t.test('should process three chunks with a overflowing needle', t => {
    t.plan(13)

    const expected = [
      [false, Buffer.from('bar\r'), 0, 3],
      [true, undefined, undefined, undefined],
      [false, Buffer.from('\nhello'), 1, 6]
    ]
    const needle = '\r\n\n'
    const s = new Streamsearch(needle)
    const chunks = [
      Buffer.from('bar\r'),
      Buffer.from('\n'),
      Buffer.from('\nhello')
    ]
    let i = 0
    s.on('info', (isMatched, data, start, end) => {
      t.assert.deepStrictEqual(isMatched, expected[i][0])
      t.assert.deepStrictEqual(data, expected[i][1])
      t.assert.deepStrictEqual(start, expected[i][2])
      t.assert.deepStrictEqual(end, expected[i][3])
      i++
      if (i >= 3) {
        t.assert.ok('pass')
      }
    })

    s.push(chunks[0])
    s.push(chunks[1])
    s.push(chunks[2])
  })

  await t.test('should process four chunks with a overflowing needle', t => {
    t.plan(13)

    const expected = [
      [false, Buffer.from('bar\r'), 0, 3],
      [true, undefined, undefined, undefined],
      [false, Buffer.from('hello'), 0, 5]
    ]
    const needle = '\r\n\n'
    const s = new Streamsearch(needle)
    const chunks = [
      Buffer.from('bar\r'),
      Buffer.from('\n'),
      Buffer.from('\n'),
      Buffer.from('hello')
    ]
    let i = 0
    s.on('info', (isMatched, data, start, end) => {
      t.assert.deepStrictEqual(isMatched, expected[i][0])
      t.assert.deepStrictEqual(data, expected[i][1])
      t.assert.deepStrictEqual(start, expected[i][2])
      t.assert.deepStrictEqual(end, expected[i][3])
      i++
      if (i >= 3) {
        t.assert.ok('pass')
      }
    })

    s.push(chunks[0])
    s.push(chunks[1])
    s.push(chunks[2])
    s.push(chunks[3])
  })

  await t.test('should process four chunks with repeted starting overflowing needle', t => {
    t.plan(13)

    const expected = [
      [false, Buffer.from('\n\n'), 0, 1],
      [true, undefined, undefined, undefined],
      [false, Buffer.from('\r\nhello'), 1, 7]
    ]
    const needle = '\n\n\r'
    const s = new Streamsearch(needle)
    const chunks = [
      Buffer.from('\n'),
      Buffer.from('\n'),
      Buffer.from('\n'),
      Buffer.from('\r\nhello')
    ]
    let i = 0
    s.on('info', (isMatched, data, start, end) => {
      t.assert.deepStrictEqual(isMatched, expected[i][0])
      t.assert.deepStrictEqual(data, expected[i][1])
      t.assert.deepStrictEqual(start, expected[i][2])
      t.assert.deepStrictEqual(end, expected[i][3])
      i++
      if (i >= 3) {
        t.assert.ok('pass')
      }
    })

    s.push(chunks[0])
    s.push(chunks[1])
    s.push(chunks[2])
    s.push(chunks[3])
  })

  await t.test('should process four chunks with a potentially overflowing needle', t => {
    t.plan(17)

    const expected = [
      [false, Buffer.from('bar\r'), 0, 3],
      [false, Buffer.from('\r\n'), 0, 2],
      [false, Buffer.from('\r\n'), 0, 1],
      [false, Buffer.from('hello'), 0, 5]
    ]
    const needle = '\r\n\n'
    const s = new Streamsearch(needle)
    const chunks = [
      Buffer.from('bar\r'),
      Buffer.from('\n'),
      Buffer.from('\r'),
      Buffer.from('hello')
    ]
    let i = 0
    s.on('info', (isMatched, data, start, end) => {
      t.assert.deepStrictEqual(isMatched, expected[i][0])
      t.assert.deepStrictEqual(data, expected[i][1])
      t.assert.deepStrictEqual(start, expected[i][2])
      t.assert.deepStrictEqual(end, expected[i][3])
      i++
      if (i >= 4) {
        t.assert.ok('pass')
      }
    })

    s.push(chunks[0])
    s.push(chunks[1])
    s.push(chunks[2])
    s.push(chunks[3])
  })

  await t.test('should reset the internal values if .reset() is called', t => {
    t.plan(9)

    const s = new Streamsearch('test')

    t.assert.deepStrictEqual(s._lookbehind_size, 0)
    t.assert.deepStrictEqual(s.matches, 0)
    t.assert.deepStrictEqual(s._bufpos, 0)

    s._lookbehind_size = 1
    s._bufpos = 1
    s.matches = 1

    t.assert.deepStrictEqual(s._lookbehind_size, 1)
    t.assert.deepStrictEqual(s.matches, 1)
    t.assert.deepStrictEqual(s._bufpos, 1)

    s.reset()

    t.assert.deepStrictEqual(s._lookbehind_size, 0)
    t.assert.deepStrictEqual(s.matches, 0)
    t.assert.deepStrictEqual(s._bufpos, 0)
  })
})
