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

package org.bouncycastle.crypto.digests;

import org.bouncycastle.util.Strings;
import org.bouncycastle.crypto.DataLengthException;
import org.bouncycastle.util.Memoable;
import org.bouncycastle.util.Pack;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.crypto.SavableDigest;
import org.bouncycastle.crypto.Xof;

public class TupleHash implements Xof, SavableDigest
{
    private static final byte[] N_TUPLE_HASH;
    private final CSHAKEDigest cshake;
    private int bitLength;
    private int outputLength;
    private boolean firstOutput;
    
    public TupleHash(final int n, final byte[] array) {
        this(n, array, n * 2);
    }
    
    public TupleHash(final int bitLength, final byte[] array, final int n) {
        this.cshake = new CSHAKEDigest(bitLength, TupleHash.N_TUPLE_HASH, array);
        this.bitLength = bitLength;
        this.outputLength = (n + 7) / 8;
        this.reset();
    }
    
    public TupleHash(final TupleHash tupleHash) {
        this.cshake = new CSHAKEDigest(tupleHash.cshake);
        this.bitLength = tupleHash.bitLength;
        this.outputLength = tupleHash.outputLength;
        this.firstOutput = tupleHash.firstOutput;
    }
    
    public TupleHash(final byte[] array) {
        this.cshake = new CSHAKEDigest(Arrays.copyOfRange(array, 0, array.length - 9));
        this.bitLength = Pack.bigEndianToInt(array, array.length - 9);
        this.outputLength = Pack.bigEndianToInt(array, array.length - 5);
        this.firstOutput = (array[array.length - 1] != 0);
    }
    
    private void copyIn(final TupleHash tupleHash) {
        this.cshake.reset(tupleHash.cshake);
        this.bitLength = this.cshake.fixedOutputLength;
        this.outputLength = this.bitLength * 2 / 8;
        this.firstOutput = tupleHash.firstOutput;
    }
    
    @Override
    public String getAlgorithmName() {
        return "TupleHash" + this.cshake.getAlgorithmName().substring(6);
    }
    
    @Override
    public int getByteLength() {
        return this.cshake.getByteLength();
    }
    
    @Override
    public int getDigestSize() {
        return this.outputLength;
    }
    
    @Override
    public void update(final byte b) throws IllegalStateException {
        final byte[] encode = XofUtils.encode(b);
        this.cshake.update(encode, 0, encode.length);
    }
    
    @Override
    public void update(final byte[] array, final int n, final int n2) throws DataLengthException, IllegalStateException {
        final byte[] encode = XofUtils.encode(array, n, n2);
        this.cshake.update(encode, 0, encode.length);
    }
    
    private void wrapUp(final int n) {
        final byte[] rightEncode = XofUtils.rightEncode(n * 8L);
        this.cshake.update(rightEncode, 0, rightEncode.length);
        this.firstOutput = false;
    }
    
    @Override
    public int doFinal(final byte[] array, final int n) throws DataLengthException, IllegalStateException {
        if (this.firstOutput) {
            this.wrapUp(this.getDigestSize());
        }
        final int doFinal = this.cshake.doFinal(array, n, this.getDigestSize());
        this.reset();
        return doFinal;
    }
    
    @Override
    public int doFinal(final byte[] array, final int n, final int n2) {
        if (this.firstOutput) {
            this.wrapUp(this.getDigestSize());
        }
        final int doFinal = this.cshake.doFinal(array, n, n2);
        this.reset();
        return doFinal;
    }
    
    @Override
    public int doOutput(final byte[] array, final int n, final int n2) {
        if (this.firstOutput) {
            this.wrapUp(0);
        }
        return this.cshake.doOutput(array, n, n2);
    }
    
    @Override
    public void reset() {
        this.cshake.reset();
        this.firstOutput = true;
    }
    
    @Override
    public byte[] getEncodedState() {
        final byte[] encodedState = this.cshake.getEncodedState();
        final byte[] array = new byte[9];
        Pack.intToBigEndian(this.bitLength, array, 0);
        Pack.intToBigEndian(this.outputLength, array, 4);
        array[8] = (byte)(this.firstOutput ? 1 : 0);
        return Arrays.concatenate(encodedState, array);
    }
    
    @Override
    public Memoable copy() {
        return new TupleHash(this);
    }
    
    @Override
    public void reset(final Memoable memoable) {
        this.copyIn((TupleHash)memoable);
    }
    
    static {
        N_TUPLE_HASH = Strings.toByteArray("TupleHash");
    }
}
