import { ID, Item, MerkleTreeHashNode, MerkleTreeLayerNodeList, MerkleTreeLeafNodeList, Transaction, createFHash, getAtomicItemHash } from "../../internals";
import crypto from 'crypto'

export class MerkleTreeLeafNode {
     /**
     * @param {ID} id
     * @param {Array<{
     *   struct: Item,
     *   hash?: string
     * }>} targets
     * @param {{
     *   leafNodeList: MerkleTreeLeafNodeList,
     *   layerNodeList: MerkleTreeLayerNodeList,
     *   merkleTreeRoot: MerkleTreeHashNode | null | undefined,
     * }} store
     */
    constructor(id, targets, store){
        /**
         * leafNode所对应的client
         */
        this.client = id.client
        /**
         * leafNode搜索对应的clock
         */
        this.clock = id.clock

        for(let target of targets){
            if(!target.hash){
                this.getTargetHash(target)
            }
        }

        /**
         * targets首先需要按照hash进行排序，从小到大排序
         * @type {Array<{
        *   struct: Item,
        *   hash?: string
        * }>}
        */
        this.targets = targets.map(target => {
            this.getTargetHash(target)
            return target
        }).sort((tar1, tar2) => {
            if(tar1.hash && tar2.hash){
                return tar1.hash.localeCompare(tar2.hash)
            }
            return -1
        })
        /**
         * 所对应的byzantine client store
         */
        this.store = store
        /**
         * @type {MerkleTreeHashNode | MerkleTreeLeafNode | null | undefined}
         */
        this.parent = null
        /**
         * 所绑定的hash
         * @type {string}
         */
        this.hash = ''

        this.updateHash()
        /**
         * @type {string}
         */
        this._fhash = ''
    }

    get fHash(){
        if(this._fhash) return this._fhash
        const fhash = createFHash(this, this.store)
        this._fhash = fhash
        return fhash
    }

    clearFHash(){
        this._fhash = ''
    }

    /**
     * 添加一个target，如果添加成功，则返回true，没有添加，则返回false
     * @param {{
     *   struct: Item,
     *   hash?: string
     * }} target
     * @param {Transaction} transaction
     * @returns {boolean}
     */
    addTarget(target, transaction){
        const hash = target.hash ? target.hash : this.getTargetHash(target)
        const index = this.targets.findIndex(target => (target.hash ? target.hash : this.getTargetHash(target)).localeCompare(hash) <= 0)
        let haveAdded = false
        if(index > -1){
            if(this.targets[index].hash !== hash){
                this.targets.splice(index + 1, 0, target)
                haveAdded = true
            } else {
                this.targets[index].struct = target.struct
            }
        } else {
            this.targets.push(target)
            haveAdded = true
        }

        if(haveAdded){
            this.updateHash(transaction)
        }
        return haveAdded
    }

    /**
     * 
     * @param {Item} struct 
     * @param {Item} newStruct 
     */
    replaceTargetStruct(struct, newStruct){
        this.targets.forEach(target => {
            if(target.struct === struct){
                target.struct = newStruct
            }
        })
    }

    /**
     * 获得某一个target的hash
     * @param {{
     *  struct: Item,
     *  hash?: string
     * }} target
     * @returns {string}
     */
    getTargetHash(target){
        if(target.hash) return target.hash 
        const offset = this.clock - target.struct.id.clock
        const hash = getAtomicItemHash(target.struct, offset)
        target.hash = hash
        return hash
    }

    /**
     * 更新hash
     * @param {Transaction | undefined} transaction
     */
    updateHash(transaction = undefined){
        if(this.targets.length === 1){
            this.hash = this.getTargetHash(this.targets[0])
        } else {
            // 构建hash
            const hashStr = this.targets.map(target => target.hash).join('')
            this.hash = crypto.createHash("sha256").update(hashStr).digest('base64')
        }
        if(this.parent && this.parent instanceof MerkleTreeHashNode){
            this.parent.computeHash()
        }
        // 要求重新计算fhash
        this.clearFHash()

        if(transaction){
            let clientOldestMerkleTreeLeafNodes = transaction.oldestMerkleTreeLeafNodes.get(this.client)
            if(!clientOldestMerkleTreeLeafNodes){
                transaction.oldestMerkleTreeLeafNodes.set(this.client, this)
            } else {
                if(clientOldestMerkleTreeLeafNodes.clock > this.clock){
                    transaction.oldestMerkleTreeLeafNodes.set(this.client, this)
                }
            }
        }
        return this.hash
    }
}