// 
// Decompiled by Procyon v0.6.0
// 

package org.bouncycastle.pqc.crypto.picnic;

import org.bouncycastle.util.Pack;
import org.bouncycastle.util.Arrays;
import java.util.logging.Logger;

class Tree
{
    private static final Logger LOG;
    private static final int MAX_SEED_SIZE_BYTES = 32;
    private int depth;
    byte[][] nodes;
    private int dataSize;
    private boolean[] haveNode;
    private boolean[] exists;
    private int numNodes;
    private int numLeaves;
    private PicnicEngine engine;
    
    protected byte[][] getLeaves() {
        return this.nodes;
    }
    
    protected int getLeavesOffset() {
        return this.numNodes - this.numLeaves;
    }
    
    public Tree(final PicnicEngine engine, final int numLeaves, final int dataSize) {
        this.engine = engine;
        this.depth = Utils.ceil_log2(numLeaves) + 1;
        this.numNodes = (1 << this.depth) - 1 - ((1 << this.depth - 1) - numLeaves);
        this.numLeaves = numLeaves;
        this.dataSize = dataSize;
        this.nodes = new byte[this.numNodes][dataSize];
        for (int i = 0; i < this.numNodes; ++i) {
            this.nodes[i] = new byte[dataSize];
        }
        this.haveNode = new boolean[this.numNodes];
        Arrays.fill(this.exists = new boolean[this.numNodes], this.numNodes - this.numLeaves, this.numNodes, true);
        for (int j = this.numNodes - this.numLeaves; j > 0; --j) {
            if (this.exists(2 * j + 1) || this.exists(2 * j + 2)) {
                this.exists[j] = true;
            }
        }
        this.exists[0] = true;
    }
    
    protected void buildMerkleTree(final byte[][] array, final byte[] array2) {
        final int n = this.numNodes - this.numLeaves;
        for (int i = 0; i < this.numLeaves; ++i) {
            if (array[i] != null) {
                System.arraycopy(array[i], 0, this.nodes[n + i], 0, this.dataSize);
                this.haveNode[n + i] = true;
            }
        }
        for (int j = this.numNodes; j > 0; --j) {
            this.computeParentHash(j, array2);
        }
    }
    
    protected int verifyMerkleTree(final byte[][] array, final byte[] array2) {
        final int n = this.numNodes - this.numLeaves;
        for (int i = 0; i < this.numLeaves; ++i) {
            if (array[i] != null) {
                if (this.haveNode[n + i]) {
                    return -1;
                }
                if (array[i] != null) {
                    System.arraycopy(array[i], 0, this.nodes[n + i], 0, this.dataSize);
                    this.haveNode[n + i] = true;
                }
            }
        }
        for (int j = this.numNodes; j > 0; --j) {
            this.computeParentHash(j, array2);
        }
        if (!this.haveNode[0]) {
            return -1;
        }
        return 0;
    }
    
    protected int reconstructSeeds(final int[] array, final int n, final byte[] array2, final int n2, final byte[] array3, final int n3) {
        final int n4 = 0;
        int n5 = n2;
        final int[] array4 = { 0 };
        final int[] revealedNodes = this.getRevealedNodes(array, n, array4);
        for (int i = 0; i < array4[0]; ++i) {
            n5 -= this.engine.seedSizeBytes;
            if (n5 < 0) {
                return -1;
            }
            System.arraycopy(array2, i * this.engine.seedSizeBytes, this.nodes[revealedNodes[i]], 0, this.engine.seedSizeBytes);
            this.haveNode[revealedNodes[i]] = true;
        }
        this.expandSeeds(array3, n3);
        return n4;
    }
    
    protected byte[] openMerkleTree(final int[] array, final int n, final int[] array2) {
        final int[] array3 = { 0 };
        final int[] revealedMerkleNodes = this.getRevealedMerkleNodes(array, n, array3);
        array2[0] = array3[0] * this.dataSize;
        final byte[] array4 = new byte[array2[0]];
        for (int i = 0; i < array3[0]; ++i) {
            System.arraycopy(this.nodes[revealedMerkleNodes[i]], 0, array4, i * this.dataSize, this.dataSize);
        }
        return array4;
    }
    
    private int[] getRevealedNodes(final int[] array, final int n, final int[] array2) {
        final int n2 = this.depth - 1;
        final int[][] array3 = new int[n2][n];
        for (int i = 0; i < n; ++i) {
            int n3 = 0;
            int parent = array[i] + (this.numNodes - this.numLeaves);
            array3[n3][i] = parent;
            ++n3;
            while ((parent = this.getParent(parent)) != 0) {
                array3[n3][i] = parent;
                ++n3;
            }
        }
        final int[] array4 = new int[this.numLeaves];
        int n4 = 0;
        for (int j = 0; j < n2; ++j) {
            for (int k = 0; k < n; ++k) {
                if (this.hasSibling(array3[j][k])) {
                    int sibling = this.getSibling(array3[j][k]);
                    if (!this.contains(array3[j], n, sibling)) {
                        while (!this.hasRightChild(sibling) && !this.isLeafNode(sibling)) {
                            sibling = 2 * sibling + 1;
                        }
                        if (!this.contains(array4, n4, sibling)) {
                            array4[n4] = sibling;
                            ++n4;
                        }
                    }
                }
            }
        }
        array2[0] = n4;
        return array4;
    }
    
    private int getSibling(final int n) {
        if (!this.isLeftChild(n)) {
            return n - 1;
        }
        if (n + 1 < this.numNodes) {
            return n + 1;
        }
        Tree.LOG.fine("getSibling: request for node with not sibling");
        return 0;
    }
    
    private boolean isLeafNode(final int n) {
        return 2 * n + 1 >= this.numNodes;
    }
    
    private boolean hasSibling(final int n) {
        return this.exists(n) && (!this.isLeftChild(n) || this.exists(n + 1));
    }
    
    protected int revealSeedsSize(final int[] array, final int n) {
        final int[] array2 = { 0 };
        this.getRevealedNodes(array, n, array2);
        return array2[0] * this.engine.seedSizeBytes;
    }
    
    protected int revealSeeds(final int[] array, final int n, final byte[] array2, final int n2) {
        final int[] array3 = { 0 };
        int n3 = n2;
        final int[] revealedNodes = this.getRevealedNodes(array, n, array3);
        for (int i = 0; i < array3[0]; ++i) {
            n3 -= this.engine.seedSizeBytes;
            if (n3 < 0) {
                Tree.LOG.fine("Insufficient sized buffer provided to revealSeeds");
                return 0;
            }
            System.arraycopy(this.nodes[revealedNodes[i]], 0, array2, i * this.engine.seedSizeBytes, this.engine.seedSizeBytes);
        }
        return array2.length - n3;
    }
    
    protected int openMerkleTreeSize(final int[] array, final int n) {
        final int[] array2 = { 0 };
        this.getRevealedMerkleNodes(array, n, array2);
        return array2[0] * this.engine.digestSizeBytes;
    }
    
    private int[] getRevealedMerkleNodes(final int[] array, final int n, final int[] array2) {
        final int n2 = this.numNodes - this.numLeaves;
        final boolean[] array3 = new boolean[this.numNodes];
        for (int i = 0; i < n; ++i) {
            array3[n2 + array[i]] = true;
        }
        for (int j = this.getParent(this.numNodes - 1); j > 0; --j) {
            if (this.exists(j)) {
                if (this.exists(2 * j + 2)) {
                    if (array3[2 * j + 1] && array3[2 * j + 2]) {
                        array3[j] = true;
                    }
                }
                else if (array3[2 * j + 1]) {
                    array3[j] = true;
                }
            }
        }
        final int[] array4 = new int[this.numLeaves];
        int n3 = 0;
        int k = 0;
    Label_0162:
        while (k < n) {
            int parent = array[k] + n2;
            while (true) {
                while (array3[this.getParent(parent)]) {
                    if ((parent = this.getParent(parent)) == 0) {
                        ++k;
                        continue Label_0162;
                    }
                }
                if (!this.contains(array4, n3, parent)) {
                    array4[n3] = parent;
                    ++n3;
                }
                continue;
            }
        }
        array2[0] = n3;
        return array4;
    }
    
    private boolean contains(final int[] array, final int n, final int n2) {
        for (int i = 0; i < n; ++i) {
            if (array[i] == n2) {
                return true;
            }
        }
        return false;
    }
    
    private void computeParentHash(final int n, final byte[] array) {
        if (!this.exists(n)) {
            return;
        }
        final int parent = this.getParent(n);
        if (this.haveNode[parent]) {
            return;
        }
        if (!this.haveNode[2 * parent + 1]) {
            return;
        }
        if (this.exists(2 * parent + 2) && !this.haveNode[2 * parent + 2]) {
            return;
        }
        this.engine.digest.update((byte)3);
        this.engine.digest.update(this.nodes[2 * parent + 1], 0, this.engine.digestSizeBytes);
        if (this.hasRightChild(parent)) {
            this.engine.digest.update(this.nodes[2 * parent + 2], 0, this.engine.digestSizeBytes);
        }
        this.engine.digest.update(array, 0, 32);
        this.engine.digest.update(Pack.intToLittleEndian(parent), 0, 2);
        this.engine.digest.doFinal(this.nodes[parent], 0, this.engine.digestSizeBytes);
        this.haveNode[parent] = true;
    }
    
    protected byte[] getLeaf(final int n) {
        return this.nodes[this.numNodes - this.numLeaves + n];
    }
    
    protected int addMerkleNodes(final int[] array, final int n, final byte[] array2, final int n2) {
        int n3 = n2;
        final int[] array3 = { 0 };
        final int[] revealedMerkleNodes = this.getRevealedMerkleNodes(array, n, array3);
        for (int i = 0; i < array3[0]; ++i) {
            n3 -= this.dataSize;
            if (n3 < 0) {
                return -1;
            }
            System.arraycopy(array2, i * this.dataSize, this.nodes[revealedMerkleNodes[i]], 0, this.dataSize);
            this.haveNode[revealedMerkleNodes[i]] = true;
        }
        if (n3 != 0) {
            return -1;
        }
        return 0;
    }
    
    protected void generateSeeds(final byte[] array, final byte[] array2, final int n) {
        this.nodes[0] = array;
        this.haveNode[0] = true;
        this.expandSeeds(array2, n);
    }
    
    private void expandSeeds(final byte[] array, final int n) {
        final byte[] array2 = new byte[64];
        for (int parent = this.getParent(this.numNodes - 1), i = 0; i <= parent; ++i) {
            if (this.haveNode[i]) {
                this.hashSeed(array2, this.nodes[i], array, (byte)1, n, i);
                if (!this.haveNode[2 * i + 1]) {
                    System.arraycopy(array2, 0, this.nodes[2 * i + 1], 0, this.engine.seedSizeBytes);
                    this.haveNode[2 * i + 1] = true;
                }
                if (this.exists(2 * i + 2) && !this.haveNode[2 * i + 2]) {
                    System.arraycopy(array2, this.engine.seedSizeBytes, this.nodes[2 * i + 2], 0, this.engine.seedSizeBytes);
                    this.haveNode[2 * i + 2] = true;
                }
            }
        }
    }
    
    private void hashSeed(final byte[] array, final byte[] array2, final byte[] array3, final byte b, final int n, final int n2) {
        this.engine.digest.update(b);
        this.engine.digest.update(array2, 0, this.engine.seedSizeBytes);
        this.engine.digest.update(array3, 0, 32);
        this.engine.digest.update(Pack.shortToLittleEndian((short)(n & 0xFFFF)), 0, 2);
        this.engine.digest.update(Pack.shortToLittleEndian((short)(n2 & 0xFFFF)), 0, 2);
        this.engine.digest.doFinal(array, 0, 2 * this.engine.seedSizeBytes);
    }
    
    private boolean isLeftChild(final int n) {
        return n % 2 == 1;
    }
    
    private boolean hasRightChild(final int n) {
        return 2 * n + 2 < this.numNodes && this.exists(n);
    }
    
    boolean hasLeftChild(final Tree tree, final int n) {
        return 2 * n + 1 < this.numNodes;
    }
    
    private int getParent(final int n) {
        if (this.isLeftChild(n)) {
            return (n - 1) / 2;
        }
        return (n - 2) / 2;
    }
    
    private boolean exists(final int n) {
        return n < this.numNodes && this.exists[n];
    }
    
    static {
        LOG = Logger.getLogger(Tree.class.getName());
    }
}
