/*
 * wota_test.cm – self‑contained test‑suite for the Wota encode/decode module
 *                *** rewritten to run in an environment that ONLY supports
 *                Blobs; TypedArrays / ArrayBuffers / DataView are GONE. ***
 *
 * Exit status 0 → all tests passed, non‑zero otherwise.
 */

'use strict'

var wota = use('wota')
var os   = use('os')
var Blob = use('blob')

/*──────────────────────────────────────────────────────────────────────────*/
/*  Helper utilities                                                        */
/*──────────────────────────────────────────────────────────────────────────*/

def EPSILON = 1e-12

function stone_if_needed(b) { if (!stone.p(b)) stone(b) }

/* Convert an array of octets to a stone Blob */
function bytes_to_blob(bytes) {
  var b = new Blob()
  for (var i = 0; i < bytes.length; i++) {
    var byte = bytes[i]
    for (var bit = 7; bit >= 0; bit--) b.write_bit((byte >> bit) & 1)
  }
  stone(b)
  return b
}

/* Parse hex → Blob */
function hex_to_blob(hex) {
  if (hex.length % 2) hex = '0' + hex // odd nibble safety
  var bytes = []
  for (var i = 0; i < hex.length; i += 2)
    bytes.push(parseInt(hex.substr(i, 2), 16))
  return bytes_to_blob(bytes)
}

/* Blob → lower‑case hex */
function blob_to_hex(blob) {
  stone_if_needed(blob)
  var bytes = []
  for (var i = 0; i < blob.length; i += 8) {
    var byte = 0
    for (var bit = 0; bit < 8; bit++) byte = (byte << 1) | (blob.read_logical(i + bit) ? 1 : 0)
    bytes.push(byte)
  }
  return bytes.map(b => b.toString(16).padStart(2, '0')).join('').toLowerCase()
}

function is_blob(x) { return x && typeof x == 'object' && typeof x.length == 'number' && typeof x.read_logical == 'function' }

/* Deep comparison capable of Blobs + tolerance for floating diff */
function deep_compare(expected, actual, path = '') {
  if (expected == actual) return { passed: true, messages: [] }

  if (typeof expected == 'number' && typeof actual == 'number') {
    if (isNaN(expected) && isNaN(actual)) return { passed: true, messages: [] }
    var diff = Math.abs(expected - actual)
    if (diff <= EPSILON) return { passed: true, messages: [] }
    return { passed: false, messages: [`Value mismatch at ${path}: ${expected} vs ${actual} (diff ${diff})`] }
  }

  if (is_blob(expected) && is_blob(actual)) {
    stone_if_needed(expected); stone_if_needed(actual)
    if (expected.length != actual.length)
      return { passed: false, messages: [`Blob length mismatch at ${path}: ${expected.length} vs ${actual.length}`] }
    for (var i = 0; i < expected.length; i++) {
      if (expected.read_logical(i) != actual.read_logical(i))
        return { passed: false, messages: [`Blob bit mismatch at ${path}[${i}]`] }
    }
    return { passed: true, messages: [] }
  }

  if (Array.isArray(expected) && Array.isArray(actual)) {
    if (expected.length != actual.length)
      return { passed: false, messages: [`Array length mismatch at ${path}: ${expected.length} vs ${actual.length}`] }
    var msgs = []
    for (var i = 0; i < expected.length; i++) {
      var res = deep_compare(expected[i], actual[i], `${path}[${i}]`)
      if (!res.passed) msgs.push(...res.messages)
    }
    return { passed: msgs.length == 0, messages: msgs }
  }

  if (typeof expected == 'object' && expected && typeof actual == 'object' && actual) {
    var expKeys = Object.keys(expected).sort()
    var actKeys = Object.keys(actual).sort()
    if (JSON.stringify(expKeys) != JSON.stringify(actKeys))
      return { passed: false, messages: [`Object keys mismatch at ${path}: ${expKeys} vs ${actKeys}`] }
    var msgs = []
    for (var k of expKeys) {
      var res = deep_compare(expected[k], actual[k], `${path}.${k}`)
      if (!res.passed) msgs.push(...res.messages)
    }
    return { passed: msgs.length == 0, messages: msgs }
  }

  return { passed: false, messages: [`Value mismatch at ${path}: ${JSON.stringify(expected)} vs ${JSON.stringify(actual)}`] }
}

/*──────────────────────────────────────────────────────────────────────────*/
/*  Test matrix                                                             */
/*──────────────────────────────────────────────────────────────────────────*/

var testarr = []
var hex = 'a374'
for (var i = 0; i < 500; i++) { testarr.push(1); hex += '61' }

function bb() { return bytes_to_blob.apply(null, arguments) } // shorthand

var testCases = [
  { input: 0,                        expectedHex: '60' },
  { input: 2023,                     expectedHex: 'e08f67' },
  { input: -1,                       expectedHex: '69' },
  { input: 7,                        expectedHex: '67' },
  { input: -7,                       expectedHex: '6f' },
  { input: 1023,                     expectedHex: 'e07f' },
  { input: -1023,                    expectedHex: 'ef7f' },
  { input: Math.pow(2, 55) - 1,      expectedHex: 'e0ffffffffffffff' },
  { input: -Math.pow(2, 55),         expectedHex: 'e000000000000000' },

  { input: null,                expectedHex: '70' },
  { input: false,                    expectedHex: '72' },
  { input: true,                     expectedHex: '73' },

  { input: -1.01,                    expectedHex: '5a65' },
  { input: 98.6,                     expectedHex: '51875a' },
  { input: -0.5772156649,            expectedHex: 'd80a95c0b0bd69' },
  { input: -1.00000000000001,        expectedHex: 'd80e96deb183e98001' },
  { input: -10000000000000,          expectedHex: 'c80d01' },
  { input: Math.pow(2, 55),          expectedHex: 'd80e01' },

  { input: '',                       expectedHex: '10' },
  { input: 'cat',                    expectedHex: '13636174' },
  { input: 'U+1F4A9 πÇîπüåπéôπüíτ╡╡µûçσ¡ùπÇì ┬½≡ƒÆ⌐┬╗', expectedHex: '9014552b314634413920e00ce046e113e06181fa7581cb0781b657e00d20812b87e929813b' },

  { input: bytes_to_blob([0xff, 0xaa]),                                                                           expectedHex: '8010ffaa' },
  { input: bytes_to_blob([0xf0, 0xe3, 0x20, 0x80]),                                                                expectedHex: '8019f0e32080' },

  { input: testarr,                  expectedHex: hex },

  { input: [],                       expectedHex: '20' },
  { input: [1, 2, 3],                expectedHex: '23616263' },
  { input: [-1, 0, 1.5],             expectedHex: '2369605043' },

  { input: {},                       expectedHex: '30' },
  { input: { a: 1, b: 2 },           expectedHex: '32116161116262' },

  { input: { num: 42, arr: [1, -1, 2.5], str: 'test', obj: { x: true } }, expectedHex: '34216e756d622a2173747214746573742161727223616965235840216f626a21117873' },

  { input: new Blob(),               expectedHex: '00' },
  { input: [[]],                     expectedHex: '2120' },
  { input: { '': '' },               expectedHex: '311010' },
  { input: 1e-10,                    expectedHex: 'd00a01' },

  { input: { a: 1, b: 2 },           replacer: (k, v) => typeof v == 'number' ? v * 2 : v, expected: { a: 2, b: 4 }, testType: 'replacer' },
  { input: { x: 'test', y: 5 },      replacer: (k, v) => k == 'x' ? v + '!' : v,           expected: { x: 'test!', y: 5 }, testType: 'replacer' },

  { input: { a: 1, b: 2 },           reviver: (k, v) => typeof v == 'number' ? v * 3 : v, expected: { a: 3, b: 6 }, testType: 'reviver' },
  { input: { x: 'test', y: 10 },     reviver: (k, v) => k == 'y' ? v + 1 : v,             expected: { x: 'test', y: 11 }, testType: 'reviver' }
]

/*──────────────────────────────────────────────────────────────────────────*/
/*  Execution                                                               */
/*──────────────────────────────────────────────────────────────────────────*/

var results   = []
var testCount = 0

for (var t of testCases) {
  testCount++
  var name   = `Test ${testCount}: ${JSON.stringify(t.input)}${t.testType ? ' (' + t.testType + ')' : ''}`
  var passed = true
  var msgs   = []

  try {
    var enc = t.replacer ? wota.encode(t.input, t.replacer) : wota.encode(t.input)
    if (!is_blob(enc)) { passed = false; msgs.push('encode() should return a Blob') }
    else {
      if (t.expectedHex) {
        var gotHex = blob_to_hex(enc)
        if (gotHex != t.expectedHex.toLowerCase())
          msgs.push(`Hex encoding differs (info): exp ${t.expectedHex}, got ${gotHex}`)
      }

      var dec = t.reviver ? wota.decode(enc, t.reviver) : wota.decode(enc)
      var exp = t.expected != null ? t.expected : t.input

      var cmp = deep_compare(exp, dec)
      if (!cmp.passed) { passed = false; msgs.push(...cmp.messages) }
    }
  } catch (e) { passed = false; msgs.push('Exception: ' + e) }

  results.push({ name, passed, msgs })
  if (!passed) {
    log.console('\nFailure detail for ' + name + '\n' + msgs.join('\n') + '\n')
  }
}

/*──────────────────────────────────────────────────────────────────────────*/
/*  Summary                                                                 */
/*──────────────────────────────────────────────────────────────────────────*/

log.console('\nTest Summary:')
var passCount = 0
for (var r of results) {
  log.console(`${r.name} – ${r.passed ? 'Passed' : 'Failed'}`)
  if (r.msgs.length && r.passed) log.console('  ' + r.msgs.join('\n  '))
  if (r.passed) passCount++
}

log.console(`\nResult: ${passCount}/${testCount} tests passed`)
if (passCount == testCount) { log.console('Overall: PASSED'); os.exit(0) }
log.console('Overall: FAILED')

$_.stop()