package com.riaektiv.crypto.tree

import org.apache.commons.codec.binary.Hex
import org.bouncycastle.jcajce.provider.digest.SHA3
import java.io.UnsupportedEncodingException
import java.util.*

/**
 * Coding With Passion Since 1991
 * Created: 6/26/2016, 7:31 PM Eastern Time

 * @author Sergey Chuykov
 */
abstract class Tree<E : Entity> : EntityFactory<E> {

    var root = Node<E>()

    fun path(s: String): IntArray {
        val hex = Hex.encodeHexString(s.toByteArray())
        val encoded = IntArray(hex.length)
        for (i in 0..hex.length - 1) {
            val h = hex[i]
            encoded[i] = Integer.parseInt(h.toString(), 16)
        }
        return encoded
    }

    protected fun nvl(s: String?): String {
        return s ?: ""
    }

    fun sha3(message: String): String {
        val s = nvl(message)
        val md = SHA3.DigestSHA3(224)
        try {
            md.update(s.toByteArray(charset("UTF-8")))
        } catch (ex: UnsupportedEncodingException) {
            md.update(s.toByteArray())
        }

        val digest = md.digest()
        return Hex.encodeHexString(digest)
    }

    protected fun hash(node: Node<E>): String {
        return if (node.entity != null) node.entity!!.hash() else ""
    }

    protected fun message(node: Node<E>): String {
        val sb = StringBuilder()
        for (ref in node.refs) {
            sb.append(nvl(ref)).append(':')
        }
        sb.append(hash(node))
        return sb.toString()
    }

    fun sha3(node: Node<E>): String {
        return sha3(message(node))
    }

    protected fun find(n: Node<E>?, vararg path: Int): Node<E>? {
        var node = n
        for (idx in path) {
            node = node!!.next[idx]
            if (node == null) {
                break
            }
        }
        return node
    }

    fun nodes(n: Node<E>, vararg path: Int): LinkedList<Node<E>> {
        var node = n
        val nodes = LinkedList<Node<E>>()
        nodes.offer(node)
        for (idx in path) {
            var next: Node<E>? = node.next[idx]
            if (next == null) {
                next = Node<E>()
                node.next[idx] = next
            }
            node = next
            nodes.offer(node)
        }
        return nodes
    }

    fun find(vararg path: Int): Node<E>? {
        return find(root, *path)
    }

    fun get(key: String): E? {
        return find(*path(key))?.entity
    }

    fun put(key: String, entity: E) {
        val path = path(key)
        val nodes = nodes(root, *path)

        var node = nodes.pollLast()
        node.entity = entity
        node.hash = sha3(node)

        for (i in path.indices.reversed()) {
            node = nodes[i]
            val idx = path[i]
            node.setRef(idx, node.next[idx]?.hash!!)
            node.hash = sha3(node)
        }
    }

    fun validateHash(node: Node<E>?) {
        if (node == null) {
            return
        }
        val expected = sha3(node)
        if (expected != node.hash) {
            throw IllegalStateException("Invalid hash value:" + node)
        }
    }

    private inner class NodeCtx(var idx: Int, var node: Node<E>?) {

        override fun toString(): String {
            return "NodeCtx{" +
                    "idx=" + idx +
                    ", node=" + node +
                    '}'
        }
    }


    @JvmOverloads fun validate(n: Node<E>? = root) {
        var node = n

        val stack = LinkedList<NodeCtx>()

        var i = 0
        while (i < 0xF) {

            if (node!!.next[i] != null) {
                validateHash(node)
                val ctx = NodeCtx(i, node)
                println(ctx)
                stack.push(ctx)
                node = node.next[i]
                i = 0
            } else {
                if (i == 0xF - 1 && !stack.isEmpty()) {
                    if (node.entity != null) {
                        println(node)
                    }
                    val ctx = stack.pop()
                    node = ctx.node
                    i = ctx.idx
                }
            }
            i++

        }

    }

}
