package gnu.crypto.assembly;

// ----------------------------------------------------------------------------
// Id: $
//
// Copyright (C) 2003, Free Software Foundation, Inc.
//
// This file is part of GNU Crypto.
//
// GNU Crypto is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2, or (at your option)
// any later version.
//
// GNU Crypto is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; see the file COPYING.  If not, write to the
//
//    Free Software Foundation Inc.,
//    51 Franklin Street, Fifth Floor,
//    Boston, MA 02110-1301
//    USA
//
// Linking this library statically or dynamically with other modules is
// making a combined work based on this library.  Thus, the terms and
// conditions of the GNU General Public License cover the whole
// combination.
//
// As a special exception, the copyright holders of this library give
// you permission to link this library with independent modules to
// produce an executable, regardless of the license terms of these
// independent modules, and to copy and distribute the resulting
// executable under terms of your choice, provided that you also meet,
// for each linked independent module, the terms and conditions of the
// license of that module.  An independent module is a module which is
// not derived from or based on this library.  If you modify this
// library, you may extend this exception to your version of the
// library, but you are not obligated to do so.  If you do not wish to
// do so, delete this exception statement from your version.
// ----------------------------------------------------------------------------

import java.util.Map;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import java.util.zip.Inflater;

/**
 * <p>A {@link Transformer} Adapter allowing inclusion of a DEFLATE compression
 * algorithm in an {@link Assembly} chain. The {@link Direction#FORWARD}
 * transformation is a compression (deflate) of input data, while the
 * {@link Direction#REVERSED} one is a decompression (inflate) that restores
 * the original data.</p>
 *
 * <p>This {@link Transformer} uses a {@link Deflater} instance to carry on the
 * compression, and an {@link Inflater} to do the decompression.</p>
 *
 * <p>When using such a {@link Transformer}, in an {@link Assembly}, there must
 * be at least one element behind this instance in the constructed chain;
 * otherwise, a {@link TransformerException} is thrown at initialisation time.</p>
 *
 * @version Revision: $
 */
class DeflateTransformer extends Transformer {

   // Constants and variables
   // -------------------------------------------------------------------------

   private Deflater compressor;
   private Inflater decompressor;
   private int outputBlockSize = 512; // default zlib buffer size
   private byte[] zlibBuffer;

   // Constructor(s)
   // -------------------------------------------------------------------------

   DeflateTransformer() {
      super();

   }

   // Class methods
   // -------------------------------------------------------------------------

   // Instance methods
   // -------------------------------------------------------------------------

   void initDelegate(Map attributes) throws TransformerException {
      if (tail == null) {
         throw new TransformerException("initDelegate()",
               new IllegalStateException("Compression transformer missing its tail!"));
      }
      outputBlockSize = tail.currentBlockSize();
      zlibBuffer = new byte[outputBlockSize];
      Direction flow = (Direction) attributes.get(DIRECTION);
      if (flow == Direction.FORWARD) {
         compressor = new Deflater();
      } else {
         decompressor = new Inflater();
      }
   }

   int delegateBlockSize() {
//      return outputBlockSize;
      return 1;
   }

   void resetDelegate() {
      compressor = null;
      decompressor = null;
      outputBlockSize = 1;
      zlibBuffer = null;
   }

   byte[] updateDelegate(byte[] in, int offset, int length)
   throws TransformerException {
      byte[] result;
      if (wired == Direction.FORWARD) {
         compressor.setInput(in, offset, length);
         while (!compressor.needsInput()) {
            compress();
         }
      } else { // decompression: inflate first and then update tail
         decompress(in, offset, length);
      }

      result = inBuffer.toByteArray();
      inBuffer.reset();
      return result;
   }

//   byte[] lastUpdateDelegate(byte[] in, int offset, int length)
//   throws TransformerException {
//      // process multiples of blocksize as much as possible
//      byte[] result = this.updateDelegate(in, offset, length);
//      inBuffer.write(result, 0, result.length);
//      if (wired == Direction.FORWARD) { // compressing
//         if (!compressor.finished()) {
//            compressor.finish();
//            while (!compressor.finished()) {
//               compress();
//            }
//         }
//      } else { // decompressing
//         if (!decompressor.finished()) {
//            throw new TransformerException("lastUpdateDelegate()",
//                  new IllegalStateException("Compression transformer, after last "
//                  +"update, must be finished but isn't"));
//         }
//      }
//
//      result = inBuffer.toByteArray();
//      inBuffer.reset();
//      return result;
//   }
   byte[] lastUpdateDelegate()
   throws TransformerException {
      // process multiples of blocksize as much as possible
      if (wired == Direction.FORWARD) { // compressing
         if (!compressor.finished()) {
            compressor.finish();
            while (!compressor.finished()) {
               compress();
            }
         }
      } else { // decompressing
         if (!decompressor.finished()) {
            throw new TransformerException("lastUpdateDelegate()",
                  new IllegalStateException("Compression transformer, after last "
                  +"update, must be finished but isn't"));
         }
      }
      byte[] result = inBuffer.toByteArray();
      inBuffer.reset();
      return result;
   }

   private void compress() {
      int len = compressor.deflate(zlibBuffer);
      if (len > 0) {
         inBuffer.write(zlibBuffer, 0, len);
      }
   }

   private void decompress(byte[] in, int offset, int length)
   throws TransformerException {
      decompressor.setInput(in, offset, length);
      int len = 1;
      while (len > 0) {
         try {
            len = decompressor.inflate(zlibBuffer);
         } catch (DataFormatException x) {
            throw new TransformerException("decompress()", x);
         }
         if (len > 0) {
            inBuffer.write(zlibBuffer, 0, len);
         }
      }
   }
}
