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

package com.google.crypto.tink.streamingaead;

import java.nio.channels.NonWritableChannelException;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.nio.ByteBuffer;
import java.security.GeneralSecurityException;
import java.io.IOException;
import java.util.Iterator;
import java.util.ArrayDeque;
import java.util.List;
import com.google.crypto.tink.StreamingAead;
import java.util.Deque;
import javax.annotation.concurrent.GuardedBy;
import java.nio.channels.SeekableByteChannel;

final class SeekableByteChannelDecrypter implements SeekableByteChannel
{
    @GuardedBy("this")
    SeekableByteChannel attemptingChannel;
    @GuardedBy("this")
    SeekableByteChannel matchingChannel;
    @GuardedBy("this")
    SeekableByteChannel ciphertextChannel;
    @GuardedBy("this")
    long cachedPosition;
    @GuardedBy("this")
    long startingPosition;
    Deque<StreamingAead> remainingPrimitives;
    byte[] associatedData;
    
    public SeekableByteChannelDecrypter(final List<StreamingAead> allPrimitives, final SeekableByteChannel ciphertextChannel, final byte[] associatedData) throws IOException {
        this.attemptingChannel = null;
        this.matchingChannel = null;
        this.remainingPrimitives = new ArrayDeque<StreamingAead>();
        for (final StreamingAead primitive : allPrimitives) {
            this.remainingPrimitives.add(primitive);
        }
        this.ciphertextChannel = ciphertextChannel;
        this.cachedPosition = -1L;
        this.startingPosition = ciphertextChannel.position();
        this.associatedData = associatedData.clone();
    }
    
    @GuardedBy("this")
    private synchronized SeekableByteChannel nextAttemptingChannel() throws IOException {
        while (!this.remainingPrimitives.isEmpty()) {
            this.ciphertextChannel.position(this.startingPosition);
            final StreamingAead streamingAead = this.remainingPrimitives.removeFirst();
            try {
                final SeekableByteChannel decChannel = streamingAead.newSeekableDecryptingChannel(this.ciphertextChannel, this.associatedData);
                if (this.cachedPosition >= 0L) {
                    decChannel.position(this.cachedPosition);
                }
                return decChannel;
            }
            catch (final GeneralSecurityException ex) {
                continue;
            }
            break;
        }
        throw new IOException("No matching key found for the ciphertext in the stream.");
    }
    
    @GuardedBy("this")
    @Override
    public synchronized int read(final ByteBuffer dst) throws IOException {
        if (dst.remaining() == 0) {
            return 0;
        }
        if (this.matchingChannel != null) {
            return this.matchingChannel.read(dst);
        }
        if (this.attemptingChannel == null) {
            this.attemptingChannel = this.nextAttemptingChannel();
        }
        while (true) {
            try {
                final int retValue = this.attemptingChannel.read(dst);
                if (retValue == 0) {
                    return 0;
                }
                this.matchingChannel = this.attemptingChannel;
                this.attemptingChannel = null;
                return retValue;
            }
            catch (final IOException e) {
                this.attemptingChannel = this.nextAttemptingChannel();
                continue;
            }
            break;
        }
    }
    
    @CanIgnoreReturnValue
    @GuardedBy("this")
    @Override
    public synchronized SeekableByteChannel position(final long newPosition) throws IOException {
        if (this.matchingChannel != null) {
            this.matchingChannel.position(newPosition);
        }
        else {
            if (newPosition < 0L) {
                throw new IllegalArgumentException("Position must be non-negative");
            }
            this.cachedPosition = newPosition;
            if (this.attemptingChannel != null) {
                this.attemptingChannel.position(this.cachedPosition);
            }
        }
        return this;
    }
    
    @GuardedBy("this")
    @Override
    public synchronized long position() throws IOException {
        if (this.matchingChannel != null) {
            return this.matchingChannel.position();
        }
        return this.cachedPosition;
    }
    
    @GuardedBy("this")
    @Override
    public synchronized long size() throws IOException {
        if (this.matchingChannel != null) {
            return this.matchingChannel.size();
        }
        throw new IOException("Cannot determine size before first read()-call.");
    }
    
    @Override
    public SeekableByteChannel truncate(final long size) throws IOException {
        throw new NonWritableChannelException();
    }
    
    @Override
    public int write(final ByteBuffer src) throws IOException {
        throw new NonWritableChannelException();
    }
    
    @GuardedBy("this")
    @Override
    public synchronized void close() throws IOException {
        this.ciphertextChannel.close();
    }
    
    @GuardedBy("this")
    @Override
    public synchronized boolean isOpen() {
        return this.ciphertextChannel.isOpen();
    }
}
