/*
 * The contents of this file are subject to the Mozilla Public License Version 1.1
 * (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.mozilla.org/MPL/>.
 * 
 * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT
 * WARRANTY OF ANY KIND, either express or implied. See the License for the specific
 * language governing rights and limitations under the License.
 * 
 * The Original Code is the Venice Web Communities System.
 * 
 * The Initial Developer of the Original Code is Eric J. Bowersox <erbo@silcom.com>,
 * for Silverwrist Design Studios.  Portions created by Eric J. Bowersox are
 * Copyright (C) 2002 Eric J. Bowersox/Silverwrist Design Studios.  All Rights Reserved.
 * 
 * Contributor(s): 
 */
package com.silverwrist.dynamo.xmlrpc;

import java.io.*;
import java.lang.ref.Reference;
import java.sql.Blob;
import java.sql.SQLException;
import java.text.*;
import java.util.*;
import javax.mail.*;
import javax.mail.internet.*;
import com.silverwrist.util.*;
import com.silverwrist.dynamo.HttpStatusCode;
import com.silverwrist.dynamo.except.*;
import com.silverwrist.dynamo.iface.*;

public class XmlRpcResult implements SelfRenderable, XmlRpcSelfSerializing
{
  /*--------------------------------------------------------------------------------
   * Static data members
   *--------------------------------------------------------------------------------
   */

  private static final String SERIALIZED_NULL = "<string>null</string>";
  private static final String START_ARRAY = "<array><data>\r\n";
  private static final String END_ARRAY = "</data></array>";
  private static final String START_VALUE = "<value>";
  private static final String END_VALUE = "</value>\r\n";

  private static final DateFormat s_iso8601;

  /*--------------------------------------------------------------------------------
   * Attributes
   *--------------------------------------------------------------------------------
   */

  private Object m_obj;

  /*--------------------------------------------------------------------------------
   * Constructor
   *--------------------------------------------------------------------------------
   */

  public XmlRpcResult(Object obj)
  {
    m_obj = obj;

  } // end constructor

  /*--------------------------------------------------------------------------------
   * Implementations from interface SelfRenderable
   *--------------------------------------------------------------------------------
   */

  public void render(SelfRenderControl control) throws IOException, RenderingException
  {
    // Serialize the output value to a StringWriter.
    StringWriter wr = new StringWriter();
    wr.write("<methodResponse><params><param>\r\n<value>");
    serialize(wr,m_obj);
    wr.write("</value>\r\n</param></params></methodResponse>\r\n");

    // Now render the binary equivalent of this structure.
    byte[] data = null;
    try
    { // get a complete rendering of this data
      data = wr.toString().getBytes("UTF-8");

    } // end try
    catch (UnsupportedEncodingException e)
    { // WTF?  shouldn't happen
      data = wr.toString().getBytes();

    } // end catch

    // send out the bytes as an XML message body
    control.status(HttpStatusCode.S_OK);
    BinaryRenderControl bctrl = control.getBinaryRender();
    bctrl.setContentType("text/xml; charset=UTF-8");
    bctrl.setContentLength(data.length);
    OutputStream stm = bctrl.getStream();
    stm.write(data);
    stm.flush();
    
  } // end render

  /*--------------------------------------------------------------------------------
   * Implementations from interface XmlRpcSelfSerializing
   *--------------------------------------------------------------------------------
   */

  public void serializeXmlRpc(Writer wr) throws IOException
  {
    serialize(wr,m_obj);

  } // end serializeXmlRpc

  /*--------------------------------------------------------------------------------
   * External static operations
   *--------------------------------------------------------------------------------
   */

  public static void serializeBinary(Writer wr, InputStream stm) throws IOException
  {
    try
    { // Encode the data as BASE-64.
      ByteArrayOutputStream internal_stm = new ByteArrayOutputStream();
      OutputStream encode_stm = MimeUtility.encode(internal_stm,"base64");
      IOUtils.copy(stm,encode_stm);
      encode_stm.flush();

      // turn our encoded output into an InputStream
      ByteArrayInputStream internal2_stm = new ByteArrayInputStream(internal_stm.toByteArray());
      IOUtils.shutdown(encode_stm);
      IOUtils.shutdown(internal_stm);

      // write out the data
      InputStreamReader rd = new InputStreamReader(internal2_stm,"US-ASCII");
      wr.write("<base64>");
      IOUtils.copy(rd,wr);
      wr.write("</base64>");
      IOUtils.shutdown(rd);
      IOUtils.shutdown(internal2_stm);

    } // end try
    catch (MessagingException e)
    { // encoder might have an error
      throw new IOException("error encoding binary data");

    } // end catch

  } // end serializeBinary

  public static void serialize(Writer wr, boolean b) throws IOException
  {
    wr.write("<boolean>");
    wr.write(b ? "1" : "0");
    wr.write("</boolean>");

  } // end serialize

  public static void serialize(Writer wr, boolean[] b) throws IOException
  {
    wr.write(START_ARRAY);
    for (int i=0; i<b.length; i++)
    { // serialize the values we contain
      wr.write(START_VALUE);
      serialize(wr,b[i]);
      wr.write(END_VALUE);

    } // end for

    wr.write(END_ARRAY);

  } // end serialize

  public static void serialize(Writer wr, byte b) throws IOException
  {
    serialize(wr,(int)b);

  } // end serialize

  public static void serialize(Writer wr, byte[] b) throws IOException
  {
    serializeBinary(wr,new ByteArrayInputStream(b));

  } // end serialize

  public static void serialize(Writer wr, char c) throws IOException
  {
    wr.write("<string>");
    wr.write(c);
    wr.write("</string>");

  } // end serialize

  public static void serialize(Writer wr, char[] c) throws IOException
  {
    wr.write(START_ARRAY);
    for (int i=0; i<c.length; i++)
    { // serialize the values we contain
      wr.write(START_VALUE);
      serialize(wr,c[i]);
      wr.write(END_VALUE);

    } // end for

    wr.write(END_ARRAY);

  } // end serialize

  public static void serialize(Writer wr, short s) throws IOException
  {
    serialize(wr,(int)s);

  } // end serialize

  public static void serialize(Writer wr, short[] s) throws IOException
  {
    wr.write(START_ARRAY);
    for (int i=0; i<s.length; i++)
    { // serialize the values we contain
      wr.write(START_VALUE);
      serialize(wr,s[i]);
      wr.write(END_VALUE);

    } // end for

    wr.write(END_ARRAY);

  } // end serialize

  public static void serialize(Writer wr, int i) throws IOException
  {
    wr.write("<int>");
    wr.write(String.valueOf(i));
    wr.write("</int>");

  } // end serialize

  public static void serialize(Writer wr, int[] ia) throws IOException
  {
    wr.write(START_ARRAY);
    for (int i=0; i<ia.length; i++)
    { // serialize the values we contain
      wr.write(START_VALUE);
      serialize(wr,ia[i]);
      wr.write(END_VALUE);

    } // end for

    wr.write(END_ARRAY);

  } // end serialize

  public static void serialize(Writer wr, long l) throws IOException
  {
    serialize(wr,(double)l);

  } // end serialize

  public static void serialize(Writer wr, long[] l) throws IOException
  {
    wr.write(START_ARRAY);
    for (int i=0; i<l.length; i++)
    { // serialize the values we contain
      wr.write(START_VALUE);
      serialize(wr,l[i]);
      wr.write(END_VALUE);

    } // end for

    wr.write(END_ARRAY);

  } // end serialize

  public static void serialize(Writer wr, float f) throws IOException
  {
    serialize(wr,(double)f);

  } // end serialize

  public static void serialize(Writer wr, float[] f) throws IOException
  {
    wr.write(START_ARRAY);
    for (int i=0; i<f.length; i++)
    { // serialize the values we contain
      wr.write(START_VALUE);
      serialize(wr,f[i]);
      wr.write(END_VALUE);

    } // end for

    wr.write(END_ARRAY);

  } // end serialize

  public static void serialize(Writer wr, double d) throws IOException
  {
    wr.write("<double>");
    wr.write(String.valueOf(d));
    wr.write("</double>");

  } // end serialize

  public static void serialize(Writer wr, double[] d) throws IOException
  {
    wr.write(START_ARRAY);
    for (int i=0; i<d.length; i++)
    { // serialize the values we contain
      wr.write(START_VALUE);
      serialize(wr,d[i]);
      wr.write(END_VALUE);

    } // end for

    wr.write(END_ARRAY);

  } // end serialize

  public static void serializeCalendar(Writer wr, Calendar cal) throws IOException
  {
    wr.write("<dateTime.iso8601>");

    // Encode the year first.
    StringBuffer conv = new StringBuffer("0000");
    conv.append(cal.get(Calendar.YEAR));
    String c = conv.toString();
    wr.write(c.substring(c.length()-4));

    // Now the month...
    conv.setLength(0);
    conv.append("00").append(cal.get(Calendar.MONTH) - Calendar.JANUARY + 1);
    c = conv.toString();
    wr.write(c.substring(c.length()-2));

    // And the day...
    conv.setLength(0);
    conv.append("00").append(cal.get(Calendar.DAY_OF_MONTH));
    c = conv.toString();
    wr.write(c.substring(c.length()-2));
    wr.write("T");

    // And the hour...
    conv.setLength(0);
    conv.append("00").append(cal.get(Calendar.HOUR_OF_DAY));
    c = conv.toString();
    wr.write(c.substring(c.length()-2));
    wr.write(":");

    // And the minute...
    conv.setLength(0);
    conv.append("00").append(cal.get(Calendar.MINUTE));
    c = conv.toString();
    wr.write(c.substring(c.length()-2));
    wr.write(":");

    // And the second...
    conv.setLength(0);
    conv.append("00").append(cal.get(Calendar.SECOND));
    c = conv.toString();
    wr.write(c.substring(c.length()-2));
    
    // And we're done!
    wr.write("</dateTime.iso8601>");

  } // end serializeCalendar

  public static void serializeCollection(Writer wr, Collection coll) throws IOException
  {
    serializeIterator(wr,coll.iterator());

  } // end serializeCollection

  public static void serializeDate(Writer wr, Date d) throws IOException
  {
    wr.write("<dateTime.iso8601>"); 
    wr.write(s_iso8601.format(d));
    wr.write("</dateTime.iso8601>");

  } // end serializeDate

  public static void serializeEnumeration(Writer wr, Enumeration enum) throws IOException
  {
    wr.write(START_ARRAY);
    while (enum.hasMoreElements())
    { // serialize the values we contain
      wr.write(START_VALUE);
      serialize(wr,enum.nextElement());
      wr.write(END_VALUE);

    } // end for

    wr.write(END_ARRAY);

  } // end serializeIterator

  public static void serializeIterator(Writer wr, Iterator it) throws IOException
  {
    wr.write(START_ARRAY);
    while (it.hasNext())
    { // serialize the values we contain
      wr.write(START_VALUE);
      serialize(wr,it.next());
      wr.write(END_VALUE);

    } // end for

    wr.write(END_ARRAY);

  } // end serializeIterator

  public static void serializeMap(Writer wr, Map map) throws IOException
  {
    wr.write("<struct>\r\n");
    Iterator it = map.entrySet().iterator();
    while (it.hasNext())
    { // write each entry in turn
      Map.Entry ntry = (Map.Entry)(it.next());
      wr.write("<member>\r\n<name>");
      wr.write(StringUtils.encodeHTML(ntry.getKey().toString()));
      wr.write("</name>\r\n<value>");
      serialize(wr,ntry.getValue());
      wr.write("</value>\r\n</member>");

    } // end while

    wr.write("\r\n</struct>");

  } // end serializeMap

  public static void serializeString(Writer wr, String s) throws IOException
  {
    wr.write("<string>");
    wr.write(StringUtils.encodeHTML(s));
    wr.write("</string>");

  } // end serializeString

  public static void serialize(Writer wr, Object obj) throws IOException
  {
    if (obj==null)
    { // null object - bye!
      wr.write(SERIALIZED_NULL);
      return;

    } // end if

    if (obj.getClass().isArray())
    { // for arrays, serialize them specially
      Class component = obj.getClass().getComponentType();
      if (component==Boolean.TYPE)
	serialize(wr,(boolean[])obj);
      else if (component==Byte.TYPE)
	serialize(wr,(byte[])obj);
      else if (component==Character.TYPE)
	serialize(wr,(char[])obj);
      else if (component==Short.TYPE)
	serialize(wr,(short[])obj);
      else if (component==Integer.TYPE)
	serialize(wr,(int[])obj);
      else if (component==Long.TYPE)
	serialize(wr,(long[])obj);
      else if (component==Float.TYPE)
	serialize(wr,(float[])obj);
      else if (component==Double.TYPE)
	serialize(wr,(double[])obj);
      else
	serialize(wr,(Object[])obj);
      return;

    } // end if

    if (obj instanceof XmlRpcSelfSerializing)
    { // some objects may be self-serializing
      ((XmlRpcSelfSerializing)obj).serializeXmlRpc(wr);
      return;

    } // end if

    if (obj instanceof DynamicWrapper)
    { // for dynamic wrappers, unwrap them
      serialize(wr,((DynamicWrapper)obj).unwrap());
      return;

    } // end if

    if (obj instanceof InputStream)
    { // for InputStream, turn it into binary
      serializeBinary(wr,(InputStream)obj);
      return;

    } // end if

    if (obj instanceof Blob)
    { // serialize the blob as a binary stream
      try
      { // just get its input stream
	serializeBinary(wr,((Blob)obj).getBinaryStream());
	return;

      } // end try
      catch (SQLException e)
      { // fault on error here
	throw new IOException("error writing binary data");

      } // end catch

    } // end if

    if (obj instanceof Calendar)
    { // serialize a Calendar
      serializeCalendar(wr,(Calendar)obj);
      return;

    } // end if

    if (obj instanceof Collection)
    { // serialize a Collection
      serializeCollection(wr,(Collection)obj);
      return;

    } // end if

    if (obj instanceof Date)
    { // serialize a Date
      serializeDate(wr,(Date)obj);
      return;

    } // end if

    if (obj instanceof Enumeration)
    { // serialize a Enumeration
      serializeEnumeration(wr,(Enumeration)obj);
      return;

    } // end if

    if (obj instanceof Iterator)
    { // serialize a Iterator
      serializeIterator(wr,(Iterator)obj);
      return;

    } // end if

    if (obj instanceof Map)
    { // serialize a Map
      serializeMap(wr,(Map)obj);
      return;

    } // end if

    if (obj instanceof Reference)
    { // for a Reference, serialize the referent
      serialize(wr,((Reference)obj).get());
      return;

    } // end if

    if (obj instanceof Boolean)
    { // serialize the boolean value
      serialize(wr,((Boolean)obj).booleanValue());
      return;

    } // end if

    if (obj instanceof Character)
    { // serialize the character value
      serialize(wr,((Character)obj).charValue());
      return;

    } // end if

    if ((obj instanceof Byte) || (obj instanceof Short) || (obj instanceof Integer))
    { // serialize the integer value
      serialize(wr,((Number)obj).intValue());
      return;

    } // end if

    if ((obj instanceof Long) || (obj instanceof Float) || (obj instanceof Double))
    { // serialize the double value
      serialize(wr,((Number)obj).doubleValue());
      return;

    } // end if

    // String and StringBuffer are handled properly by the fallback mechanism below

    // last-ditch fallback method - use toString, get the string, and encode it
    serializeString(wr,obj.toString());

  } // end serialize

  public static void serialize(Writer wr, Object[] arr) throws IOException
  {
    wr.write(START_ARRAY);
    for (int i=0; i<arr.length; i++)
    { // serialize the values we contain
      wr.write(START_VALUE);
      serialize(wr,arr[i]);
      wr.write(END_VALUE);

    } // end for

    wr.write(END_ARRAY);

  } // end serialize

  /*--------------------------------------------------------------------------------
   * Static initializer
   *--------------------------------------------------------------------------------
   */

  static
  { // Initialize the ISO 8601 date formatter.
    SimpleDateFormat iso = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss");
    iso.setCalendar(new GregorianCalendar(new SimpleTimeZone(0,"UTC")));
    s_iso8601 = iso;
    
  } // end static initializer

} // end class XmlRpcResult
