/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.commons.compress.compressors.z;

import java.io.IOException;
import java.io.InputStream;

import org.apache.commons.compress.compressors.CompressorInputStream;

/**
 * Base-class for traditional Unix ".Z" compression and the
 * Unshrinking method of ZIP archive.
 * @NotThreadSafe
 * @since 1.7
 */
public abstract class AbstractLZWInputStream extends CompressorInputStream {
    private final byte[] oneByte = new byte[1];

    protected final InputStream in;
    protected int clearCode = -1;
    protected int codeSize = 9;
    protected int bitsCached = 0;
    protected int bitsCachedSize = 0;
    protected int previousCode = -1;
    protected int tableSize = 0;
    protected int[] prefixes;
    protected byte[] characters;
    private byte[] outputStack;
    private int outputStackLocation;

    public AbstractLZWInputStream(InputStream inputStream) throws IOException {
        this.in = inputStream;
    }

    @Override
    public void close() throws IOException {
        in.close();
    }
    
    @Override
    public int read() throws IOException {
        int ret = read(oneByte);
        if (ret < 0) {
            return ret;
        }
        return 0xff & oneByte[0];
    }
    
    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        int bytesRead = 0;
        int remainingInStack = outputStack.length - outputStackLocation;
        if (remainingInStack > 0) {
            int maxLength = Math.min(remainingInStack, len);
            System.arraycopy(outputStack, outputStackLocation, b, off, maxLength);
            outputStackLocation += maxLength;
            off += maxLength;
            len -= maxLength;
            bytesRead += maxLength;
        }
        while (len > 0) {
            int result = decompressNextSymbol();
            if (result < 0) {
                if (bytesRead > 0) {
                    count(bytesRead);
                    return bytesRead;
                } else {
                    return result;
                }
            }
            remainingInStack = outputStack.length - outputStackLocation;
            if (remainingInStack > 0) {
                int maxLength = Math.min(remainingInStack, len);
                System.arraycopy(outputStack, outputStackLocation, b, off, maxLength);
                outputStackLocation += maxLength;
                off += maxLength;
                len -= maxLength;
                bytesRead += maxLength;
            }
        }
        count(bytesRead);
        return bytesRead;
    }

    /**
     * Read the next code and expand it.
     */
    protected abstract int decompressNextSymbol() throws IOException;

    /**
     * Add a new entry to the dictionary.
     */
    protected abstract int addEntry(int previousCode, byte character)
        throws IOException;

    /**
     * Sets the clear code based on the code size.
     */
    protected void setClearCode(int codeSize) {
        clearCode = (1 << (codeSize - 1));
    }

    /**
     * Initializes the arrays based on the maximum code size.
     */
    protected void initializeTables(int maxCodeSize) {
        final int maxTableSize = 1 << maxCodeSize;
        prefixes = new int[maxTableSize];
        characters = new byte[maxTableSize];
        outputStack = new byte[maxTableSize];
        outputStackLocation = maxTableSize;
        final int max = 1 << 8;
        for (int i = 0; i < max; i++) {
            prefixes[i] = -1;
            characters[i] = (byte) i;
        }
    }

    /**
     * Reads the next code from the stream.
     */
    protected int readNextCode() throws IOException {
        while (bitsCachedSize < codeSize) {
            final int nextByte = in.read();
            if (nextByte < 0) {
                return nextByte;
            }
            bitsCached |= (nextByte << bitsCachedSize);
            bitsCachedSize += 8;
        }
        final int mask = (1 << codeSize) - 1;
        final int code = (bitsCached & mask);
        bitsCached >>>= codeSize;
        bitsCachedSize -= codeSize;
        return code;
    }
    
    /**
     * Adds a new entry if the maximum table size hasn't been exceeded
     * and returns the new index.
     */
    protected int addEntry(int previousCode, byte character, int maxTableSize) {
        if (tableSize < maxTableSize) {
            final int index = tableSize;
            prefixes[tableSize] = previousCode;
            characters[tableSize] = character;
            tableSize++;
            return index;
        }
        return -1;
    }

    /**
     * Add entry for repeat of previousCode we haven't added, yet.
     */
    protected int addRepeatOfPreviousCode() throws IOException {
        if (previousCode == -1) {
            // can't have a repeat for the very first code
            throw new IOException("The first code can't be a reference to its preceding code");
        }
        byte firstCharacter = 0;
        for (int last = previousCode; last >= 0; last = prefixes[last]) {
            firstCharacter = characters[last];
        }
        return addEntry(previousCode, firstCharacter);
    }

    /**
     * Expands the entry with index code to the output stack and may
     * create a new entry
     */
    protected int expandCodeToOutputStack(int code, boolean addedUnfinishedEntry)
        throws IOException {
        for (int entry = code; entry >= 0; entry = prefixes[entry]) {
            outputStack[--outputStackLocation] = characters[entry];
        }
        if (previousCode != -1 && !addedUnfinishedEntry) {
            addEntry(previousCode, outputStack[outputStackLocation]);
        }
        previousCode = code;
        return outputStackLocation;
    }
}

@@ -2131,434 +2131,66 @@
 d = 
-0;%0A        int remainingInStack = outputStack.length - outputStackLocation;%0A        if (remainingInStack %3E 0) %7B%0A            int maxLength = Math.min(remainingInStack, len);%0A            System.arraycopy(outputStack, outputStackLocation, b, off, maxLength);%0A            outputStackLocation += maxLength;%0A            off += maxLength;%0A            len -= maxLength;%0A            bytesRead += maxLength;%0A        %7D%0A        while (len
+readFromStack(b, off, len);%0A        while (len - bytesRead
  %3E 0
@@ -2406,20 +2406,9 @@
    %7D
- else %7B%0A    
+%0A
     
@@ -2438,36 +2438,32 @@
 lt;%0A            
-    
 %7D%0A            %7D%0A
@@ -2464,446 +2464,72 @@
     
-%7D%0A            remainingInStack = outputStack.length - outputStackLocation;%0A            if (remainingInStack %3E 0) %7B%0A                int maxLength = Math.min(remainingInStack, len);%0A                System.arraycopy(outputStack, outputStackLocation, b, off, maxLength);%0A                outputStackLocation += maxLength;%0A                off += maxLength;%0A                len -= maxLength;%0A                bytesRead += maxLength;%0A            %7D
+bytesRead += readFromStack(b, off + bytesRead, len - bytesRead);
 %0A   
@@ -5753,11 +5753,435 @@
 n;%0A    %7D
+%0A%0A    private int readFromStack(byte%5B%5D b, int off, int len) %7B%0A        int remainingInStack = outputStack.length - outputStackLocation;%0A        if (remainingInStack %3E 0) %7B%0A            int maxLength = Math.min(remainingInStack, len);%0A            System.arraycopy(outputStack, outputStackLocation, b, off, maxLength);%0A            outputStackLocation += maxLength;%0A            return maxLength;%0A        %7D%0A        return 0;%0A    %7D
 %0A%7D%0A

reduce duplication in read method