/*
 * Copyright (c) 2001-2008 Caucho Technology, Inc.  All rights reserved.
 *
 * The Apache Software License, Version 1.1
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:
 *       "This product includes software developed by the
 *        Caucho Technology (http://www.caucho.com/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "Hessian", "Resin", and "Caucho" must not be used to
 *    endorse or promote products derived from this software without prior
 *    written permission. For written permission, please contact
 *    info@caucho.com.
 *
 * 5. Products derived from this software may not be called "Resin"
 *    nor may "Resin" appear in their names without prior written
 *    permission of Caucho Technology.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL CAUCHO TECHNOLOGY OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @author Scott Ferguson
 */

package com.caucho.hessian4.io;

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

/**
 * Abstract output stream for Hessian requests.
 *
 * <pre>
 * OutputStream os = ...; // from http connection
 * AbstractOutput out = new HessianSerializerOutput(os);
 * String value;
 *
 * out.startCall("hello");  // start hello call
 * out.writeString("arg1"); // write a string argument
 * out.completeCall();      // complete the call
 * </pre>
 */
abstract public class AbstractHessianOutput {
  // serializer factory
  private SerializerFactory _defaultSerializerFactory;
  
  // serializer factory
  protected SerializerFactory _serializerFactory;

  private byte []_byteBuffer;

  /**
   * Sets the serializer factory.
   */
  public void setSerializerFactory(SerializerFactory factory)
  {
    _serializerFactory = factory;
  }

  /**
   * Gets the serializer factory.
   */
  public SerializerFactory getSerializerFactory()
  {
    // the default serializer factory cannot be modified by external
    // callers
    if (_serializerFactory == _defaultSerializerFactory) {
      _serializerFactory = new SerializerFactory();
    }
    
    return _serializerFactory;
  }

  /**
   * Gets the serializer factory.
   */
  protected final SerializerFactory findSerializerFactory()
  {
    SerializerFactory factory = _serializerFactory;

    if (factory == null) {
      factory = SerializerFactory.createDefault();
      _defaultSerializerFactory = factory;
      _serializerFactory = factory;
    }

    return factory;
  }
  
  /**
   * Initialize the output with a new underlying stream.
   */
  public void init(OutputStream os)
  {
  }
  
  public boolean setUnshared(boolean isUnshared)
  {
    throw new UnsupportedOperationException(getClass().getSimpleName());
  }

  /**
   * Writes a complete method call.
   */
  public void call(String method, Object []args)
    throws IOException
  {
    int length = args != null ? args.length : 0;
    
    startCall(method, length);
    
    for (int i = 0; i < length; i++)
      writeObject(args[i]);
    
    completeCall();
  }

  /**
   * Starts the method call:
   *
   * <code><pre>
   * C
   * </pre></code>
   *
   * @param method the method name to call.
   */
  abstract public void startCall()
    throws IOException;

  /**
   * Starts the method call:
   *
   * <code><pre>
   * C string int
   * </pre></code>
   *
   * @param method the method name to call.
   */
  abstract public void startCall(String method, int length)
    throws IOException;

  /**
   * For Hessian 2.0, use the Header envelope instead
   *
   * @deprecated
   */
  public void writeHeader(String name)
    throws IOException
  {
    throw new UnsupportedOperationException(getClass().getSimpleName());
  }

  /**
   * Writes the method tag.
   *
   * <code><pre>
   * string
   * </pre></code>
   *
   * @param method the method name to call.
   */
  abstract public void writeMethod(String method)
    throws IOException;

  /**
   * Completes the method call:
   *
   * <code><pre>
   * </pre></code>
   */
  abstract public void completeCall()
    throws IOException;

  /**
   * Writes a boolean value to the stream.  The boolean will be written
   * with the following syntax:
   *
   * <code><pre>
   * T
   * F
   * </pre></code>
   *
   * @param value the boolean value to write.
   */
  abstract public void writeBoolean(boolean value)
    throws IOException;

  /**
   * Writes an integer value to the stream.  The integer will be written
   * with the following syntax:
   *
   * <code><pre>
   * I b32 b24 b16 b8
   * </pre></code>
   *
   * @param value the integer value to write.
   */
  abstract public void writeInt(int value)
    throws IOException;

  /**
   * Writes a long value to the stream.  The long will be written
   * with the following syntax:
   *
   * <code><pre>
   * L b64 b56 b48 b40 b32 b24 b16 b8
   * </pre></code>
   *
   * @param value the long value to write.
   */
  abstract public void writeLong(long value)
    throws IOException;

  /**
   * Writes a double value to the stream.  The double will be written
   * with the following syntax:
   *
   * <code><pre>
   * D b64 b56 b48 b40 b32 b24 b16 b8
   * </pre></code>
   *
   * @param value the double value to write.
   */
  abstract public void writeDouble(double value)
    throws IOException;

  /**
   * Writes a date to the stream.
   *
   * <code><pre>
   * T  b64 b56 b48 b40 b32 b24 b16 b8
   * </pre></code>
   *
   * @param time the date in milliseconds from the epoch in UTC
   */
  abstract public void writeUTCDate(long time)
    throws IOException;

  /**
   * Writes a null value to the stream.
   * The null will be written with the following syntax
   *
   * <code><pre>
   * N
   * </pre></code>
   *
   * @param value the string value to write.
   */
  abstract public void writeNull()
    throws IOException;

  /**
   * Writes a string value to the stream using UTF-8 encoding.
   * The string will be written with the following syntax:
   *
   * <code><pre>
   * S b16 b8 string-value
   * </pre></code>
   *
   * If the value is null, it will be written as
   *
   * <code><pre>
   * N
   * </pre></code>
   *
   * @param value the string value to write.
   */
  abstract public void writeString(String value)
    throws IOException;

  /**
   * Writes a string value to the stream using UTF-8 encoding.
   * The string will be written with the following syntax:
   *
   * <code><pre>
   * S b16 b8 string-value
   * </pre></code>
   *
   * If the value is null, it will be written as
   *
   * <code><pre>
   * N
   * </pre></code>
   *
   * @param value the string value to write.
   */
  abstract public void writeString(char []buffer, int offset, int length)
    throws IOException;

  /**
   * Writes a byte array to the stream.
   * The array will be written with the following syntax:
   *
   * <code><pre>
   * B b16 b18 bytes
   * </pre></code>
   *
   * If the value is null, it will be written as
   *
   * <code><pre>
   * N
   * </pre></code>
   *
   * @param value the string value to write.
   */
  abstract public void writeBytes(byte []buffer)
    throws IOException;
  /**
   * Writes a byte array to the stream.
   * The array will be written with the following syntax:
   *
   * <code><pre>
   * B b16 b18 bytes
   * </pre></code>
   *
   * If the value is null, it will be written as
   *
   * <code><pre>
   * N
   * </pre></code>
   *
   * @param value the string value to write.
   */
  abstract public void writeBytes(byte []buffer, int offset, int length)
    throws IOException;
  
  /**
   * Writes a byte buffer to the stream.
   */
  abstract public void writeByteBufferStart()
    throws IOException;
  
  /**
   * Writes a byte buffer to the stream.
   *
   * <code><pre>
   * b b16 b18 bytes
   * </pre></code>
   *
   * @param value the string value to write.
   */
  abstract public void writeByteBufferPart(byte []buffer,
                                           int offset,
                                           int length)
    throws IOException;
  
  /**
   * Writes the last chunk of a byte buffer to the stream.
   *
   * <code><pre>
   * b b16 b18 bytes
   * </pre></code>
   *
   * @param value the string value to write.
   */
  abstract public void writeByteBufferEnd(byte []buffer,
                                          int offset,
                                          int length)
    throws IOException;

  /**
   * Writes a full output stream.
   */
  public void writeByteStream(InputStream is)
    throws IOException
  {
    writeByteBufferStart();

    if (_byteBuffer == null)
      _byteBuffer = new byte[1024];

    byte []buffer = _byteBuffer;
    
    int len;
    while ((len = is.read(buffer, 0, buffer.length)) > 0) {
      if (len < buffer.length) {
        int len2 = is.read(buffer, len, buffer.length - len);

        if (len2 < 0) {
          writeByteBufferEnd(buffer, 0, len);
          return;
        }

        len += len2;
      }

      writeByteBufferPart(buffer, 0, len);
    }
    
    writeByteBufferEnd(buffer, 0, 0);
  }

  /**
   * Writes a reference.
   *
   * <code><pre>
   * Q int
   * </pre></code>
   *
   * @param value the integer value to write.
   */
  abstract protected void writeRef(int value)
    throws IOException;

  /**
   * Removes a reference.
   */
  public boolean removeRef(Object obj)
    throws IOException
  {
    return false;
  }

  /**
   * Replaces a reference from one object to another.
   */
  abstract public boolean replaceRef(Object oldRef, Object newRef)
    throws IOException;

  /**
   * Adds an object to the reference list.  If the object already exists,
   * writes the reference, otherwise, the caller is responsible for
   * the serialization.
   *
   * <code><pre>
   * R b32 b24 b16 b8
   * </pre></code>
   *
   * @param object the object to add as a reference.
   *
   * @return true if the object has already been written.
   */
  abstract public boolean addRef(Object object)
    throws IOException;

  /**
   * @param obj
   * @return
   */
  abstract public int getRef(Object obj);

  /**
   * Resets the references for streaming.
   */
  public void resetReferences()
  {
  }

  /**
   * Writes a generic object to the output stream.
   */
  abstract public void writeObject(Object object)
    throws IOException;

  /**
   * Writes the list header to the stream.  List writers will call
   * <code>writeListBegin</code> followed by the list contents and then
   * call <code>writeListEnd</code>.
   *
   * <code><pre>
   * V
   *   x13 java.util.ArrayList   # type
   *   x93                       # length=3
   *   x91                       # 1
   *   x92                       # 2
   *   x93                       # 3
   * &lt;/list>
   * </pre></code>
   */
  abstract public boolean writeListBegin(int length, String type)
    throws IOException;

  /**
   * Writes the tail of the list to the stream.
   */
  abstract public void writeListEnd()
    throws IOException;

  /**
   * Writes the map header to the stream.  Map writers will call
   * <code>writeMapBegin</code> followed by the map contents and then
   * call <code>writeMapEnd</code>.
   *
   * <code><pre>
   * M type (<key> <value>)* Z
   * </pre></code>
   */
  abstract public void writeMapBegin(String type)
    throws IOException;

  /**
   * Writes the tail of the map to the stream.
   */
  abstract public void writeMapEnd()
    throws IOException;

  /**
   * Writes the object header to the stream (for Hessian 2.0), or a
   * Map for Hessian 1.0.  Object writers will call
   * <code>writeObjectBegin</code> followed by the map contents and then
   * call <code>writeObjectEnd</code>.
   *
   * <code><pre>
   * C type int <key>*
   * C int <value>*
   * </pre></code>
   *
   * @return true if the object has already been defined.
   */
  public int writeObjectBegin(String type)
    throws IOException
  {
    writeMapBegin(type);
    
    return -2;
  }

  /**
   * Writes the end of the class.
   */
  public void writeClassFieldLength(int len)
    throws IOException
  {
  }

  /**
   * Writes the tail of the object to the stream.
   */
  public void writeObjectEnd()
    throws IOException
  {
  }
  
  public void writeReply(Object o)
    throws IOException
  {
    startReply();
    writeObject(o);
    completeReply();
  }
  
  
  public void startReply()
    throws IOException
  {
  }
  
  public void completeReply()
    throws IOException
  {
  }
  
  public void writeFault(String code, String message, Object detail)
    throws IOException
  {
  }

  public void flush()
    throws IOException
  {
  }

  public void close()
    throws IOException
  {
  }
}
