package gnu.crypto.assembly;

// ----------------------------------------------------------------------------
// $Id: Transformer.java,v 1.3 2005/10/06 04:24:13 rsdio Exp $
//
// 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 gnu.crypto.pad.IPad;

import java.io.ByteArrayOutputStream;
import java.util.Map;

/**
 * <p>A <code>Transformer</code> is an abstract representation of a two-way
 * <i>transformation</i> that can be chained together with other instances of
 * this type. Examples of such transformations in this library are:
 * {@link Cascade} cipher, {@link gnu.crypto.pad.IPad} algorithm, and a
 * ZLib-based deflater/inflater algorithm. A special implementation of a
 * <code>Transformer</code> to close a chain is also provided.</p>
 *
 * <p>A <code>Transformer</code> is characterised by the followings:<p>
 * <ul>
 *    <li>It can be chained to other instances, to form an {@link Assembly}.</li>
 *    <li>When configured in an {@link Assembly}, it can be set to apply its
 *    internal transformation on the input data stream before (pre-processing)
 *    or after (post-processing) passing the input data to the next element in
 *    the chain. Note that the same type <code>Transformer</code> can be used as
 *    either in pre-processing or a post-processing modes.</li>
 *    <li>A special transformer --<code>LoopbackTransformer</code>-- is used to
 *    close the chain.</li>
 *    <li>A useful type of <code>Transformer</code> --one we're interested in--
 *    has internal buffers. The distinction between a casual push (update)
 *    operation and the last one allows to correctly flush any intermediate
 *    bytes that may exist in those buffers.</li>
 * </ul>
 *
 * <p>To allow wiring <code>Transformer</code> instances together, a
 * <i>minimal-output-size</i> in bytes is necessary. The trivial case of a
 * value of <code>1</code> for such attribute practically means that no output
 * buffering, from the previous element, is needed --which is independant of
 * buffering the input if the <code>Transformer</code> implementation itself is
 * block-based.</p>
 *
 * @see CascadeTransformer
 * @see PaddingTransformer
 * @see DeflateTransformer
 * @version $Revision: 1.3 $
 */
public abstract class Transformer {

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

   public static final String DIRECTION = "gnu.crypto.assembly.transformer.direction";
//   public static final String MODE = "gnu.crypto.assembly.transformer.mode";

   protected Direction wired;
   protected Operation mode;
   protected Transformer tail = null;
   protected ByteArrayOutputStream inBuffer = new ByteArrayOutputStream(2048);
   protected ByteArrayOutputStream outBuffer = new ByteArrayOutputStream(2048);

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

   /** Trivial protected constructor. */
   protected Transformer() {
      super();

      this.wired = null;
   }

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

   public static final Transformer getCascadeTransformer(Cascade cascade) {
      return new CascadeTransformer(cascade);
   }

   public static final Transformer getPaddingTransformer(IPad padding) {
      return new PaddingTransformer(padding);
   }

   public static final Transformer getDeflateTransformer() {
      return new DeflateTransformer();
   }

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

   /**
    * Sets the operational mode of this <code>Transformer</code>.
    *
    * @param mode the processing mode this <code>Transformer</code> is required
    * to operate in.
    * @throws IllegalStateException if this instance has already been assigned
    * an operational mode.
    */
   public void setMode(final Operation mode) {
      if (this.mode != null) {
         throw new IllegalStateException();
      }
      this.mode = mode;
   }

   /**
    * Returns <code>true</code> if this <code>Transformer</code> was wired in
    * pre-processing mode; <code>false</code> otherwise.
    *
    * @return <code>true</code> if this <code>Transformer</code> has been wired
    * in pre-processing mode; <code>false</code> otherwise.
    * @throws IllegalStateException if this instance has not yet been assigned
    * an operational <i>type</i>.
    */
   public boolean isPreProcessing() {
      if (mode == null) {
         throw new IllegalStateException();
      }
      return (mode == Operation.PRE_PROCESSING);
   }

   /**
    * Returns <code>true</code> if this <code>Transformer</code> was wired in
    * post-processing mode; <code>false</code> otherwise.
    *
    * @return <code>true</code> if this <code>Transformer</code> has been wired
    * in post-processing mode; <code>false</code> otherwise.
    * @throws IllegalStateException if this instance has not yet been assigned
    * an operational <i>type</i>.
    */
   public boolean isPostProcessing() {
      return !isPreProcessing();
   }

   /**
    * Initialises the <code>Transformer</code> for operation with specific
    * characteristics.
    *
    * @param attributes a set of name-value pairs that describes the desired
    * future behaviour of this instance.
    * @throws IllegalStateException if the instance is already initialised.
    */
   public void init(Map attributes) throws TransformerException {
      if (wired != null) {
         throw new IllegalStateException();
      }
      Direction flow = (Direction) attributes.get(DIRECTION);
      if (flow == null) {
         flow = Direction.FORWARD;
      }
      wired = flow;
      inBuffer.reset();
      outBuffer.reset();

      tail.init(attributes); // initialise tail first
      initDelegate(attributes); // initialise this instance
   }

   /**
    * Returns the block-size of this <code>Transformer</code>. A value of
    * <code>1</code> indicates that this instance is block-agnostic.
    *
    * @return the current minimal required block size.
    */
   public int currentBlockSize() {
      if (wired == null) {
         throw new IllegalStateException();
      }
      return delegateBlockSize();
   }

   /**
    * Resets the <code>Transformer</code> for re-initialisation and use with
    * other characteristics. This method always succeeds.
    */
   public void reset() {
      resetDelegate();
      wired = null;
      inBuffer.reset();
      outBuffer.reset();
      tail.reset(); // reset tail last
   }

   /**
    * Convenience method that calls the method with same name and three
    * arguments, using a byte array of length <code>1</code> whose contents are
    * the designated byte.
    *
    * @param b the byte to process.
    * @return the result of transformation.
    * @throws IllegalStateException if the instance is not initialised.
    * @throws TransformerException if a transformation-related exception occurs
    * during the operation.
    * @see #update(byte[], int, int)
    */
   public byte[] update(byte b) throws TransformerException {
      return update(new byte[] { b }, 0, 1);
   }

   /**
    * Convenience method that calls the same method with three arguments. All
    * bytes in <code>in</code>, starting from index position <code>0</code> are
    * considered.
    *
    * @param in the input data bytes.
    * @return the result of transformation.
    * @throws IllegalStateException if the instance is not initialised.
    * @throws TransformerException if a transformation-related exception occurs
    * during the operation.
    * @see #update(byte[], int, int)
    */
   public byte[] update(byte[] in) throws TransformerException {
      return update(in, 0, in.length);
   }

   /**
    * Processes a designated number of bytes from a given byte array.
    *
    * @param in the input data bytes.
    * @param offset index of <code>in</code> from which to start considering
    * data.
    * @param length the count of bytes to process.
    * @return the result of transformation.
    * @throws IllegalStateException if the instance is not initialised.
    * @throws TransformerException if a transformation-related exception occurs
    * during the operation.
    */
   public byte[] update(byte[] in, int offset, int length)
   throws TransformerException {
      if (wired == null) {
         throw new IllegalStateException();
      }
      byte[] result = (wired == Direction.FORWARD
            ? forwardUpdate(in, offset, length) : inverseUpdate(in, offset, length));
      return result;
   }

   /**
    * Convenience method that calls the same method with three arguments. A
    * zero-long byte array is used.
    *
    * @return the result of transformation.
    * @throws IllegalStateException if the instance is not initialised.
    * @throws TransformerException if a transformation-related exception occurs
    * during the operation.
    * @see #lastUpdate(byte[], int, int)
    */
   public byte[] lastUpdate() throws TransformerException {
      byte[] result = (wired == Direction.FORWARD
            ? lastForwardUpdate() : lastInverseUpdate());
      if (inBuffer.size() != 0) { // we still have some buffered bytes
         throw new TransformerException("lastUpdate(): input buffer not empty");
      }
      return result;
   }

   /**
    * Convenience method that calls the method with same name and three
    * arguments, using a byte array of length <code>1</code> whose contents are
    * the designated byte.
    *
    * @param b the byte to process.
    * @return the result of transformation.
    * @throws IllegalStateException if the instance is not initialised.
    * @throws TransformerException if a transformation-related exception occurs
    * during the operation.
    * @see #lastUpdate(byte[], int, int)
    */
   public byte[] lastUpdate(byte b) throws TransformerException {
      return lastUpdate(new byte[] { b }, 0, 1);
   }

   /**
    * Convenience method that calls the same method with three arguments. All
    * bytes in <code>in</code>, starting from index position <code>0</code> are
    * considered.
    *
    * @param in the input data bytes.
    * @return the result of transformation.
    * @throws IllegalStateException if the instance is not initialised.
    * @throws TransformerException if a transformation-related exception occurs
    * during the operation.
    * @see #lastUpdate(byte[], int, int)
    */
   public byte[] lastUpdate(byte[] in) throws TransformerException {
      return lastUpdate(in, 0, in.length);
   }

   /**
    * Processes a designated number of bytes from a given byte array and
    * signals, at the same time, that this is the last <i>push</i> operation on
    * this <code>Transformer</code>.
    *
    * @param in the input data bytes.
    * @param offset index of <code>in</code> from which to start considering
    * data.
    * @param length the count of bytes to process.
    * @return the result of transformation.
    * @throws IllegalStateException if the instance is not initialised.
    * @throws TransformerException if a transformation-related exception occurs
    * during the operation.
    */
   public byte[] lastUpdate(byte[] in, int offset, int length)
   throws TransformerException {
      byte[] result = update(in, offset, length);
      byte[] rest = lastUpdate();
      if (rest.length > 0) {
         byte[] newResult = new byte[result.length + rest.length];
         System.arraycopy(result, 0, newResult, 0, result.length);
         System.arraycopy(rest, 0, newResult, result.length, rest.length);
         result = newResult;
      }
      return result;
   }

   // helper methods ----------------------------------------------------------

   private byte[] forwardUpdate(byte[] in, int off, int len) throws TransformerException {
      return (isPreProcessing() ? preTransform(in, off, len) : postTransform(in, off, len));
   }

   private byte[] inverseUpdate(byte[] in, int off, int len) throws TransformerException {
      return (isPreProcessing() ? postTransform(in, off, len) : preTransform(in, off, len));
   }

   private byte[] preTransform(byte[] in, int off, int len) throws TransformerException {
      byte[] result = updateDelegate(in, off, len);
      result = tail.update(result);
      return result;
   }

   private byte[] postTransform(byte[] in, int off, int len) throws TransformerException {
      byte[] result = tail.update(in, off, len);
      result = updateDelegate(result, 0, result.length);
      return result;
   }

   private byte[] lastForwardUpdate() throws TransformerException {
      return (isPreProcessing() ? preLastTransform() : postLastTransform());
   }

   private byte[] lastInverseUpdate() throws TransformerException {
      return (isPreProcessing() ? postLastTransform() : preLastTransform());
   }

   private byte[] preLastTransform() throws TransformerException {
      byte[] result = lastUpdateDelegate();
      result = tail.lastUpdate(result);
      return result;
   }

   private byte[] postLastTransform() throws TransformerException {
      byte[] result = tail.lastUpdate();
      result = updateDelegate(result, 0, result.length);
      byte[] rest = lastUpdateDelegate();
      if (rest.length > 0) {
         byte[] newResult = new byte[result.length + rest.length];
         System.arraycopy(result, 0, newResult, 0, result.length);
         System.arraycopy(rest, 0, newResult, result.length, rest.length);
         result = newResult;
      }
      return result;
   }

   // abstract methods to be implemented by concrete subclasses ---------------

   abstract void initDelegate(Map attributes) throws TransformerException;
   abstract int delegateBlockSize();
   abstract void resetDelegate();
   abstract byte[] updateDelegate(byte[] in, int off, int len) throws TransformerException;
   abstract byte[] lastUpdateDelegate() throws TransformerException;
}
