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

package org.bouncycastle.crypto.macs;

import org.bouncycastle.util.Memoable;
import org.bouncycastle.crypto.engines.Zuc128CoreEngine;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.engines.Zuc256CoreEngine;
import org.bouncycastle.crypto.Mac;

public final class Zuc256Mac implements Mac
{
    private static final int TOPBIT = 128;
    private final InternalZuc256Engine theEngine;
    private final int theMacLength;
    private final int[] theMac;
    private final int[] theKeyStream;
    private Zuc256CoreEngine theState;
    private int theWordIndex;
    private int theByteIndex;
    
    public Zuc256Mac(final int theMacLength) {
        this.theEngine = new InternalZuc256Engine(theMacLength);
        this.theMacLength = theMacLength;
        final int n = theMacLength / 32;
        this.theMac = new int[n];
        this.theKeyStream = new int[n + 1];
    }
    
    @Override
    public String getAlgorithmName() {
        return "Zuc256Mac-" + this.theMacLength;
    }
    
    @Override
    public int getMacSize() {
        return this.theMacLength / 8;
    }
    
    @Override
    public void init(final CipherParameters cipherParameters) {
        this.theEngine.init(true, cipherParameters);
        this.theState = (Zuc256CoreEngine)this.theEngine.copy();
        this.initKeyStream();
    }
    
    private void initKeyStream() {
        for (int i = 0; i < this.theMac.length; ++i) {
            this.theMac[i] = this.theEngine.createKeyStreamWord();
        }
        for (int j = 0; j < this.theKeyStream.length - 1; ++j) {
            this.theKeyStream[j] = this.theEngine.createKeyStreamWord();
        }
        this.theWordIndex = this.theKeyStream.length - 1;
        this.theByteIndex = 3;
    }
    
    @Override
    public void update(final byte b) {
        this.shift4NextByte();
        final int n = this.theByteIndex * 8;
        for (int i = 128, n2 = 0; i > 0; i >>= 1, ++n2) {
            if ((b & i) != 0x0) {
                this.updateMac(n + n2);
            }
        }
    }
    
    private void shift4NextByte() {
        this.theByteIndex = (this.theByteIndex + 1) % 4;
        if (this.theByteIndex == 0) {
            this.theKeyStream[this.theWordIndex] = this.theEngine.createKeyStreamWord();
            this.theWordIndex = (this.theWordIndex + 1) % this.theKeyStream.length;
        }
    }
    
    private void shift4Final() {
        this.theByteIndex = (this.theByteIndex + 1) % 4;
        if (this.theByteIndex == 0) {
            this.theWordIndex = (this.theWordIndex + 1) % this.theKeyStream.length;
        }
    }
    
    private void updateMac(final int n) {
        for (int i = 0; i < this.theMac.length; ++i) {
            final int[] theMac = this.theMac;
            final int n2 = i;
            theMac[n2] ^= this.getKeyStreamWord(i, n);
        }
    }
    
    private int getKeyStreamWord(final int n, final int n2) {
        final int n3 = this.theKeyStream[(this.theWordIndex + n) % this.theKeyStream.length];
        if (n2 == 0) {
            return n3;
        }
        return n3 << n2 | this.theKeyStream[(this.theWordIndex + n + 1) % this.theKeyStream.length] >>> 32 - n2;
    }
    
    @Override
    public void update(final byte[] array, final int n, final int n2) {
        for (int i = 0; i < n2; ++i) {
            this.update(array[n + i]);
        }
    }
    
    @Override
    public int doFinal(final byte[] array, final int n) {
        this.shift4Final();
        this.updateMac(this.theByteIndex * 8);
        for (int i = 0; i < this.theMac.length; ++i) {
            Zuc128CoreEngine.encode32be(this.theMac[i], array, n + i * 4);
        }
        this.reset();
        return this.getMacSize();
    }
    
    @Override
    public void reset() {
        if (this.theState != null) {
            this.theEngine.reset(this.theState);
        }
        this.initKeyStream();
    }
    
    private static class InternalZuc256Engine extends Zuc256CoreEngine
    {
        public InternalZuc256Engine(final int n) {
            super(n);
        }
        
        int createKeyStreamWord() {
            return super.makeKeyStreamWord();
        }
    }
}
