//
// $Id: StreamableUtil.java,v 1.3 2002/03/20 23:08:22 mdb Exp $

package com.threerings.presents.io;

import java.io.IOException;
import java.io.DataInputStream;
import java.io.DataOutputStream;

import java.lang.reflect.Array;

import com.samskivert.io.NestableIOException;
import com.samskivert.util.ListUtil;

/**
 * Utility functions useful for {@link Streamable} implementations.
 */
public class StreamableUtil
{
    /**
     * Writes the supplied int array to the data output stream.
     */
    public static void writeInts (DataOutputStream out, int[] values)
        throws IOException
    {
        int vcount = values.length;
        out.writeInt(vcount);
        for (int i = 0; i < vcount; i++) {
            out.writeInt(values[i]);
        }
    }

    /**
     * Reads an array of ints from the data input stream that was
     * previously written via {@link #writeInts}.
     */
    public static int[] readInts (DataInputStream in)
        throws IOException
    {
        int vcount = in.readInt();
        int[] values = new int[vcount];
        for (int i = 0; i < vcount; i++) {
            values[i] = in.readInt();
        }
        return values;
    }

    /**
     * Writes the supplied string array to the data output stream.
     */
    public static void writeStrings (DataOutputStream out, String[] values)
        throws IOException
    {
        int vcount = values.length;
        out.writeInt(vcount);
        for (int i = 0; i < vcount; i++) {
            out.writeUTF(values[i]);
        }
    }

    /**
     * Reads an array of strings from the data input stream that was
     * previously written via {@link #writeStrings}.
     */
    public static String[] readStrings (DataInputStream in)
        throws IOException
    {
        int vcount = in.readInt();
        String[] values = new String[vcount];
        for (int i = 0; i < vcount; i++) {
            values[i] = in.readUTF();
        }
        return values;
    }

    /**
     * Writes the supplied streamable array to the data output stream. The
     * array cannot be null, but can be of zero length.
     */
    public static void writeStreamables (
        DataOutputStream out, Streamable[] values)
        throws IOException
    {
        // create a list used to map class names to class ids
        Object[] cnames = new Object[DEFAULT_SIZE];

        // seed it with the component type of the array (which we write
        // out first so that we can recreate the array on the other side)
        String cname = values.getClass().getComponentType().getName();
        cnames[0] = cname;
        out.writeUTF(cname);

        // write out the size of the list
        int size = (values == null) ? 0 : values.length;
        out.writeInt(size);

        // write out each of the list elements
        short nextIdx = 1;
        for (int ii = 0; ii < size; ii++) {
            // get the next streamable to write out
            Streamable s = values[ii];

            // if this index is empty, write -1 as the class identifier to
            // indicate such
            if (s == null) {
                out.writeShort(-1);
                continue;
            }

            // look up the class id
            cname = s.getClass().getName();
            short cidx = (short)ListUtil.indexOfEqual(cnames, cname);
            if (cidx == -1) {
                // store this class name to class id mapping
                cidx = nextIdx++;
                cnames = ListUtil.add(cnames, cidx, cname);

                // write out the class id and class name
                out.writeShort(cidx);
                out.writeUTF(cname);

            } else {
                // we need only write out the class id
                out.writeShort(cidx);
            }

            // write out the object itself
            s.writeTo(out);
        }
    }

    /**
     * Reads an array of streamables from the data input stream that was
     * previously written via {@link #writeStreamables}.
     */
    public static Streamable[] readStreamables (DataInputStream in)
        throws IOException
    {
        // read in the component type and array size
        String ctype = in.readUTF();
        int size = in.readInt();
        Streamable[] values = null;

        // create the target array
        try {
            values = (Streamable[])
                Array.newInstance(Class.forName(ctype), size);
        } catch (Exception e) {
            String errmsg = "Unable to instantiate streamable array " +
                "[ctype=" + ctype + "]";
            throw new NestableIOException(errmsg, e);
        }

        // create a list used to map class ids to class names
        Object[] cnames = new Object[DEFAULT_SIZE];
        // seed our class names with the component type
        cnames[0] = ctype;

        // read in each of the list elements
        for (int ii = 0; ii < size; ii++) {
            // read the class id number
            short cidx = in.readShort();

            // if the class identifier is -1, that means this is a null
            // element
            if (cidx == -1) {
                continue;
            }

            // look up the class name
            String cname = (cidx > cnames.length - 1) ? null :
                (String)cnames[cidx];
            if (cname == null) {
                // store this class id to class name mapping
                cname = in.readUTF();
                cnames = ListUtil.add(cnames, cidx, cname);
            }

            try {
                // instantiate the streamable object
                Class clazz = Class.forName(cname);
                Streamable s = (Streamable)clazz.newInstance();
                // read in the object itself
                s.readFrom(in);
                // and add it to the list
                values[ii] = s;

            } catch (Exception e) {
                String errmsg = "Error instantiating streamable array " +
                    "element [ctype=" + ctype + ", index=" + ii +
                    ", etype=" + cname + "]";
                throw new NestableIOException(errmsg, e);
            }
        }

        return values;
    }

    /** The default size of the class name lists used when serializing or
     * unserializing the list. */
    protected static final int DEFAULT_SIZE = 8;
}
