/* eslint-env mocha */

import * as chai from 'chai'

import { decode, encode, encodeInto } from '../cborg.js'
import { fromHex, toHex } from '../lib/byte-utils.js'

const { assert } = chai

// Helper to get encoded bytes from encodeInto (which returns { written })
const encodeIntoBytes = (data, dest) => {
  const { written } = encodeInto(data, dest)
  return dest.subarray(0, written)
}

const fixtures = [
  { data: '80', expected: [], type: 'array empty' },
  { data: '8102', expected: [2], type: 'array 1 compact uint' },
  { data: '8118ff', expected: [255], type: 'array 1 uint8' },
  { data: '811901f4', expected: [500], type: 'array 1 uint16' },
  { data: '811a00010000', expected: [65536], type: 'array 1 uint32' },
  { data: '811b00000000000000ff', expected: [255], type: 'array 1 uint64', strict: false },
  { data: '811b0016db6db6db6db7', expected: [Number.MAX_SAFE_INTEGER / 1.4], type: 'array 1 uint64' },
  { data: '811b001fffffffffffff', expected: [Number.MAX_SAFE_INTEGER], type: 'array 1 uint64' },
  { data: '8403040506', expected: [3, 4, 5, 6], type: 'array 4 ints' },
  {
    data: '8c1b0016db6db6db6db71a000100001901f40200202238ff3aa5f702b33b0016db6db6db6db74261316fc48c6175657320c39f76c49b746521',
    expected: [Number.MAX_SAFE_INTEGER / 1.4, 65536, 500, 2, 0, -1, -3, -256, -2784428724, Number.MIN_SAFE_INTEGER / 1.4 - 1, new TextEncoder().encode('a1'), 'Čaues ßvěte!'],
    type: 'array mixed terminals',
    label: '[]'
  },
  {
    data: '8265617272617982626f66820582666e657374656482666172726179736121',
    expected: ['array', ['of', [5, ['nested', ['arrays', '!']]]]],
    type: 'array nested'
  },
  // testing lengths encoded as too-large ints
  { data: '980403040506', expected: [3, 4, 5, 6], type: 'array 4 ints, length8', strict: false },
  { data: '99000403040506', expected: [3, 4, 5, 6], type: 'array 4 ints, length16', strict: false },
  { data: '9a0000000403040506', expected: [3, 4, 5, 6], type: 'array 4 ints, length32', strict: false },
  { data: '9b000000000000000403040506', expected: [3, 4, 5, 6], type: 'array 4 ints, length64', strict: false }
]

const fixedDest = new Uint8Array(1024)

describe('array', () => {
  describe('decode', () => {
    for (const fixture of fixtures) {
      const data = fromHex(fixture.data)
      it(`should decode ${fixture.type}=${fixture.label || fixture.expected}`, () => {
        assert.deepStrictEqual(decode(data), fixture.expected, `decode ${fixture.type}`)
        if (fixture.strict === false) {
          assert.throws(() => decode(data, { strict: true }), Error, 'CBOR decode error: integer encoded in more bytes than necessary (strict decode)')
        } else {
          assert.deepStrictEqual(decode(data, { strict: true }), fixture.expected, `decode ${fixture.type}`)
        }
      })

      it('should fail to decode very large length', () => {
        assert.throws(
          () => decode(fromHex('9ba5f702b3a5f7020403040506')),
          /CBOR decode error: 64-bit integer array lengths not supported/)
      })
    }
  })

  describe('encode', () => {
    for (const fixture of fixtures) {
      it(`should encode ${fixture.type}=${fixture.label || fixture.expected}`, () => {
        if (fixture.unsafe) {
          assert.throws(encode.bind(null, fixture.expected), Error, /^CBOR encode error: number too large to encode \(\d+\)$/)
        } else if (fixture.strict === false) {
          assert.notDeepEqual(toHex(encode(fixture.expected)), fixture.data, `encode ${fixture.type} !strict`)
          assert.notDeepEqual(toHex(encodeIntoBytes(fixture.expected, fixedDest)), fixture.data, `encode ${fixture.type} !strict`)
        } else {
          assert.strictEqual(toHex(encode(fixture.expected)), fixture.data, `encode ${fixture.type}`)
          assert.strictEqual(toHex(encodeIntoBytes(fixture.expected, fixedDest)), fixture.data, `encode ${fixture.type}`)
        }
      })
    }
  })

  // mostly unnecessary, but feels good
  describe('roundtrip', () => {
    for (const fixture of fixtures) {
      if (!fixture.unsafe && fixture.strict !== false) {
        it(`should roundtrip ${fixture.type}=${fixture.label || fixture.expected}`, () => {
          assert.deepStrictEqual(decode(encode(fixture.expected)), fixture.expected, `roundtrip ${fixture.type}`)
        })
      }
    }
  })

  describe('specials', () => {
    it('can decode indefinite length items', () => {
      assert.deepStrictEqual(decode(fromHex('9f616f6174ff')), ['o', 't'])
    })

    it('can switch off indefinite length support', () => {
      assert.throws(() => decode(fromHex('9f616f6174ff'), { allowIndefinite: false }), /indefinite/)
    })
  })
})
