var wota = use('wota')
var os   = use('os')
var blob = use('blob')
var math = use('math/radians')

var EPSILON = 1e-12

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

function deep_compare(expected, actual, _path) {
  var diff = null
  var msgs = null
  var expKeys = null
  var actKeys = null
  var i = 0
  var path = _path || ''
  if (expected == actual) return { passed: true, messages: [] }

  if (is_number(expected) && is_number(actual)) {
    if (isNaN(expected) && isNaN(actual)) return { passed: true, messages: [] }
    diff = 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 (length(expected) != length(actual))
      return { passed: false, messages: [`blob length mismatch at ${path}: ${length(expected)} vs ${length(actual)}`] }
    arrfor(array(expected), function(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 (is_array(expected) && is_array(actual)) {
    if (length(expected) != length(actual))
      return { passed: false, messages: [`Array length mismatch at ${path}: ${length(expected)} vs ${length(actual)}`] }
    msgs = []
    arrfor(array(expected), function(i) {
      var res = deep_compare(expected[i], actual[i], `${path}[${i}]`)
      if (!res.passed) array(msgs, res.messages)
    })
    return { passed: length(msgs) == 0, messages: msgs }
  }

  if (is_object(expected) && is_object(actual)) {
    expKeys = sort(array(expected))
    actKeys = sort(array(actual))
    if (JSON.stringify(expKeys) != JSON.stringify(actKeys))
      return { passed: false, messages: [`Object keys mismatch at ${path}: ${expKeys} vs ${actKeys}`] }
    msgs = []
    arrfor(expKeys, function(k) {
      var res = deep_compare(expected[k], actual[k], `${path}.${k}`)
      if (!res.passed) array(msgs, res.messages)
    })
    return { passed: length(msgs) == 0, messages: msgs }
  }

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

var testarr = []
var i = 0
var t = null
var name = null
for (i = 0; i < 500; i++) { push(testarr, 1) }

var testCases = [
  { name: 'zero',              input: 0 },
  { name: '2023',              input: 2023 },
  { name: 'neg1',              input: -1 },
  { name: '7',                 input: 7 },
  { name: 'neg7',              input: -7 },
  { name: '1023',              input: 1023 },
  { name: 'neg1023',           input: -1023 },
  { name: 'large_pos',         input: math.power(2, 55) - 1 },
  { name: 'large_neg',         input: -math.power(2, 55) },

  { name: 'null',              input: null },
  { name: 'false',             input: false },
  { name: 'true',              input: true },

  { name: 'float_neg1_01',     input: -1.01 },
  { name: 'float_98_6',        input: 98.6 },
  { name: 'float_euler',       input: -0.5772156649 },
  { name: 'float_precision',   input: -1.00000000000001 },
  { name: 'float_large_neg',   input: -10000000000000 },
  { name: 'float_pow2_55',     input: math.power(2, 55) },

  { name: 'empty_string',      input: '' },
  { name: 'string_cat',        input: 'cat' },
  { name: 'string_unicode',    input: 'U+1F4A9 「うんち絵文字」 «💩»' },

  { name: 'large_array',       input: testarr },

  { name: 'empty_array',       input: [] },
  { name: 'array_123',         input: [1, 2, 3] },
  { name: 'array_mixed',       input: [-1, 0, 1.5] },

  { name: 'empty_object',      input: {} },
  { name: 'object_ab',         input: { a: 1, b: 2 } },

  { name: 'nested_object',     input: { num: 42, arr: [1, -1, 2.5], str: 'test', obj: { x: true } } },

  { name: 'empty_blob',        input: blob() },
  { name: 'nested_array',      input: [[]] },
  { name: 'empty_key_val',     input: { '': '' } },
  { name: 'small_float',       input: 1e-10 }
]

function make_test(t) {
  return function() {
    var enc = wota.encode(t.input)
    if (!is_blob(enc)) disrupt

    var dec = wota.decode(enc)

    var cmp = deep_compare(t.input, dec)
    if (!cmp.passed) disrupt
  }
}

var tests = {}
for (i = 0; i < length(testCases); i++) {
  t = testCases[i]
  name = t.name || ('case_' + i)
  tests[name] = make_test(t)
}

return tests
