/*
 * Copyright 2011 The Netty Project
 *
 * The Netty Project 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.jboss.netty.handler.codec.compression;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.oneone.OneToOneDecoder;
import org.jboss.netty.util.internal.jzlib.JZlib;
import org.jboss.netty.util.internal.jzlib.ZStream;


/**
 * Decompresses a {@link ChannelBuffer} using the deflate algorithm.
 * @apiviz.landmark
 * @apiviz.has org.jboss.netty.handler.codec.compression.ZlibWrapper
 */
public class ZlibDecoder extends OneToOneDecoder {

    private final ZStream z = new ZStream();
    private volatile boolean finished;

    /**
     * Creates a new instance with the default wrapper ({@link ZlibWrapper#ZLIB}).
     *
     * @throws CompressionException if failed to initialize zlib
     */
    public ZlibDecoder() {
        this(ZlibWrapper.ZLIB);
    }

    /**
     * Creates a new instance with the specified wrapper.
     *
     * @throws CompressionException if failed to initialize zlib
     */
    public ZlibDecoder(ZlibWrapper wrapper) {
        if (wrapper == null) {
            throw new NullPointerException("wrapper");
        }

        synchronized (z) {
            int resultCode = z.inflateInit(ZlibUtil.convertWrapperType(wrapper));
            if (resultCode != JZlib.Z_OK) {
                ZlibUtil.fail(z, "initialization failure", resultCode);
            }
        }
    }

    /**
     * Creates a new instance with the specified preset dictionary. The wrapper
     * is always {@link ZlibWrapper#ZLIB} because it is the only format that
     * supports the preset dictionary.
     *
     * @throws CompressionException if failed to initialize zlib
     */
    public ZlibDecoder(byte[] dictionary) {
        if (dictionary == null) {
            throw new NullPointerException("dictionary");
        }

        synchronized (z) {
            int resultCode;
            resultCode = z.inflateInit(JZlib.W_ZLIB);
            if (resultCode != JZlib.Z_OK) {
                ZlibUtil.fail(z, "initialization failure", resultCode);
            } else {
                resultCode = z.inflateSetDictionary(dictionary, dictionary.length);
                if (resultCode != JZlib.Z_OK) {
                    ZlibUtil.fail(z, "failed to set the dictionary", resultCode);
                }
            }
        }
    }

    /**
     * Returns {@code true} if and only if the end of the compressed stream
     * has been reached.
     */
    public boolean isClosed() {
        return finished;
    }

    @Override
    protected Object decode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception {
        if (!(msg instanceof ChannelBuffer) || finished) {
            return msg;
        }

        synchronized (z) {
            try {
                // Configure input.
                ChannelBuffer compressed = (ChannelBuffer) msg;
                byte[] in = new byte[compressed.readableBytes()];
                compressed.readBytes(in);
                z.next_in = in;
                z.next_in_index = 0;
                z.avail_in = in.length;

                // Configure output.
                byte[] out = new byte[in.length << 1];
                ChannelBuffer decompressed = ChannelBuffers.dynamicBuffer(
                        compressed.order(), out.length,
                        ctx.getChannel().getConfig().getBufferFactory());
                z.next_out = out;
                z.next_out_index = 0;
                z.avail_out = out.length;


                loop: for (;;) {
                    // Decompress 'in' into 'out'
                    int resultCode = z.inflate(JZlib.Z_SYNC_FLUSH);
                    if (z.next_out_index > 0) {
                        decompressed.writeBytes(out, 0, z.next_out_index);
                        z.avail_out = out.length;
                    }
                    z.next_out_index = 0;

                    switch (resultCode) {
                    case JZlib.Z_STREAM_END:
                        finished = true; // Do not decode anymore.
                        z.inflateEnd();
                        break loop;
                    case JZlib.Z_OK:
                        break;
                    case JZlib.Z_BUF_ERROR:
                        if (z.avail_in <= 0) {
                            break loop;
                        }
                        break;
                    default:
                        ZlibUtil.fail(z, "decompression failure", resultCode);
                    }
                }

                if (decompressed.writerIndex() != 0) { // readerIndex is always 0
                    return decompressed;
                } else {
                    return null;
                }
            } finally {
                // Deference the external references explicitly to tell the VM that
                // the allocated byte arrays are temporary so that the call stack
                // can be utilized.
                // I'm not sure if the modern VMs do this optimization though.
                z.next_in = null;
                z.next_out = null;
            }
        }
    }
}

@@ -1287,24 +1287,55 @@
  ZStream();%0A
+    private byte%5B%5D dictionary;%0A
     private 
@@ -1361,16 +1361,16 @@
 nished;%0A
-
 %0A    /**
@@ -2565,32 +2565,70 @@
 ary%22);%0A        %7D
+%0A        this.dictionary = dictionary;
 %0A%0A        synchr
@@ -2842,261 +2842,8 @@
 e);%0A
-            %7D else %7B%0A                resultCode = z.inflateSetDictionary(dictionary, dictionary.length);%0A                if (resultCode != JZlib.Z_OK) %7B%0A                    ZlibUtil.fail(z, %22failed to set the dictionary%22, resultCode);%0A                %7D%0A
     
@@ -4426,16 +4426,16 @@
 x = 0;%0A%0A
-
         
@@ -4464,24 +4464,571 @@
 sultCode) %7B%0A
+                    case JZlib.Z_NEED_DICT:%0A                        if (dictionary == null) %7B%0A                            ZlibUtil.fail(z, %22decompression failure%22, resultCode);%0A                        %7D else %7B%0A                            resultCode = z.inflateSetDictionary(dictionary, dictionary.length);%0A                            if (resultCode != JZlib.Z_OK) %7B%0A                                ZlibUtil.fail(z, %22failed to set the dictionary%22, resultCode);%0A                            %7D%0A                        %7D%0A                        break;%0A
             

Fix #157: ZlibDecoder does not support preset dictionary