# Copyright (c) 2019-2022 Status Research & Development GmbH
# Licensed and distributed under either of
#   * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
#   * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.

{.used.}

import
  unittest2,
  stew/byteutils, nimcrypto/[keccak, hash],
  ../../eth/trie/[db, binary, binaries, branches]

suite "examples":

  var db = newMemoryDB()
  var trie = initBinaryTrie(db)

  test "basic set/get":
    trie.set("key1".toBytes(), "value1".toBytes())
    trie.set("key2".toBytes(), "value2".toBytes())
    check trie.get("key1".toBytes) == "value1".toBytes
    check trie.get("key2".toBytes) == "value2".toBytes

  test "check branch exists":
    check checkIfBranchExist(db, trie.getRootHash(), "key".toBytes) == true
    check checkIfBranchExist(db, trie.getRootHash(), "key1".toBytes) == true
    check checkIfBranchExist(db, trie.getRootHash(), "ken".toBytes) == false
    check checkIfBranchExist(db, trie.getRootHash(), "key123".toBytes) == false

  test "branches utils":
    var branchA = getBranch(db, trie.getRootHash(), "key1".toBytes)
    # ==> [A, B, C1, D1]
    check branchA.len == 4

    var branchB = getBranch(db, trie.getRootHash(), "key2".toBytes)
    # ==> [A, B, C2, D2]
    check branchB.len == 4

    check isValidBranch(branchA, trie.getRootHash(), "key1".toBytes, "value1".toBytes) == true
    check isValidBranch(branchA, trie.getRootHash(), "key5".toBytes, "".toBytes) == true

    expect InvalidNode:
      check isValidBranch(branchB, trie.getRootHash(), "key1".toBytes, "value1".toBytes)

    var x = getBranch(db, trie.getRootHash(), "key".toBytes)
    # ==> [A]
    check x.len == 1

    expect InvalidKeyError:
      x = getBranch(db, trie.getRootHash(), "key123".toBytes) # InvalidKeyError

    x = getBranch(db, trie.getRootHash(), "key5".toBytes) # there is still branch for non-exist key
    # ==> [A]
    check x.len == 1

  test "getWitness":
    var branch = getWitness(db, trie.getRootHash(), "key1".toBytes)
    # equivalent to `getBranch(db, trie.getRootHash(), "key1")`
    # ==> [A, B, C1, D1]
    check branch.len == 4

    branch = getWitness(db, trie.getRootHash(), "key".toBytes)
    # this will include additional nodes of "key2"
    # ==> [A, B, C1, D1, C2, D2]
    check branch.len == 6

    branch = getWitness(db, trie.getRootHash(), "".toBytes)
    # this will return the whole trie
    # ==> [A, B, C1, D1, C2, D2]
    check branch.len == 6

  let beforeDeleteLen = db.totalRecordsInMemoryDB
  test "verify intermediate entries existence":
    var branchs = getWitness(db, trie.getRootHash, [])
    # set operation create new intermediate entries
    check branchs.len < beforeDeleteLen

    var node = branchs[1]
    let nodeHash = keccak256.digest(node)
    var nodes = getTrieNodes(db, @(nodeHash.data))
    check nodes.len == branchs.len - 1

  test "delete sub trie":
    # delete all subtrie with key prefixes "key"
    trie.deleteSubtrie("key".toBytes)
    check trie.get("key1".toBytes) == []
    check trie.get("key2".toBytes) == []

  test "prove the lie":
    # `delete` and `deleteSubtrie` not actually delete the nodes
    check db.totalRecordsInMemoryDB == beforeDeleteLen
    var branchs = getWitness(db, trie.getRootHash, [])
    check branchs.len == 0

  test "dictionary syntax API":
    # dictionary syntax API
    trie["moon".toBytes] = "sun".toBytes
    check "moon".toBytes in trie
    check trie["moon".toBytes] == "sun".toBytes