/*
 * This class understands the translations between the Base 64 encoded stream and the
 * intenral Java representations of integers.  It works with the translated output
 * of a Base64 object.
 * Taken from Xamin code.
 */
package heasarc.db.votable;

import java.io.DataInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

/**
 *
 * @author tmcglynn
 */
public class StreamToJava {

    class Column {
        String name;
        String type;
        String array;
        String nullValue;
        Column(String type, String array, String nullValue) {
            this.type = type; this.array=array; this.nullValue=nullValue;
        }
    };
    
    DataInputStream dis;
    List<Column> cols = new ArrayList<Column>();

    public StreamToJava(InputStream input) {
        dis = new DataInputStream(input);
    }
    
    /** Add a description o fone of the columns in the table */
    public void addColumn(String type, String array, String nullValue) {
        cols.add(new Column(type, array, nullValue));
    }
    
    /** Parse the table into a list of lists. */
    public List<List<Object>> readTable() throws IOException {
        List<List<Object>> tab = new ArrayList<List<Object>>();
        while (true) {
            List<Object> row = new ArrayList<Object>();
            boolean start = true;
            try {
                for (int i=0; i<cols.size(); i += 1) {
                    Column col = cols.get(i);
                    Object result = getStreamColumn(col.array, col.type);
                    if (col.nullValue != null &&  (""+result).equals(col.nullValue)) {
                        result = null;
                    }
                    row.add(result);
                }
            } catch (EOFException e) {
                if (start) {
                    return tab;
                } else {
                    throw new EOFException("EOF in middle of VOTable row");
                }
            }
            tab.add(row);
        }
    }

    public Object getStreamColumn(String arr, String type) throws IOException {
        if (type.equals("char")) {
            return stringColumn(arr);
        } else if (arr != null && !arr.equals(1)) {
            return arrayColumn(arr, type);
        } else {
            return scalarColumn(type);
        }
    }

    public String stringColumn(String arr) throws IOException {
        if (arr == null) {
            arr = "1";
        }
        int size;
        if (arr.indexOf('*') >= 0) {
            size = dis.readInt();
        } else {
            size = Integer.parseInt(arr);
        }
        byte[] buf = new byte[size];
        dis.readFully(buf);
        String val = new String(buf, "US-ASCII");
        int nul = val.indexOf('\0');
        if (nul >= 0) {
            val = val.substring(0, nul);
        }
        return val;
    }

    public Object scalarColumn(String type) throws IOException {

        if (type.equals("int")) {
            return new Integer(dis.readInt());
        } else if (type.equals("double")) {
            return new Double(dis.readDouble());
        } else if (type.equals("short")) {
            return new Integer(dis.readShort());
        } else if (type.equals("unsignedByte")) {
            int i = dis.readByte();
            if (i < 0) {
                i = 256 + i;
            }
            return new Integer(i);
        } else if (type.equals("float")) {
            return new Double(dis.readFloat());
        } else if (type.equals("long")) {
            return new Long(dis.readLong());
        } else if (type.equals("boolean")) {
            return readBoolean();
        }
        throw new IOException("Unsupported type in VOTable: " + type);
    }

    public Boolean readBoolean() throws IOException {
        int chr = dis.readByte();
        if (chr == 't' || chr == 'T' || chr == '1') {
            return Boolean.TRUE;
        } else if (chr == ' ' || chr == '?') {
            return null;
        } else {
            return Boolean.FALSE;
        }
    }

    public Object arrayColumn(String arr, String type) throws IOException {
        if (arr.indexOf("x") >= 0) {
            throw new IOException("Multidimensional arrays not supported");
        }
        int size;
        Object ret;
        byte[] barr = null;
        short[] sarr = null;
        int[] iarr = null;
        long[] larr = null;
        float[] farr = null;
        double[] darr = null;
        boolean[] boarr = null;
        if (arr.indexOf('*') >= 0) {
            size = dis.readInt();
        } else {
            size = Integer.parseInt(arr);
        }
        if (type.equals("unsignedByte")) {
            barr = new byte[size];
            ret = barr;
        } else if (type.equals("short")) {
            sarr = new short[size];
            ret = sarr;
        } else if (type.equals("int")) {
            iarr = new int[size];
            ret = iarr;
        } else if (type.equals("long")) {
            larr = new long[size];
            ret = larr;
        } else if (type.equals("float")) {
            farr = new float[size];
            ret = farr;
        } else if (type.equals("double")) {
            darr = new double[size];
            ret = darr;
        } else if (type.equals("boolean")) {
            boarr = new boolean[size];
            ret = boarr;
        } else {
            throw new IOException("Unsupported array type:" + type);
        }
        for (int i = 0; i < size; i += 1) {
            if (type.equals("unsignedByte")) {
                barr[i] = dis.readByte();
            } else if (type.equals("short")) {
                sarr[i] = dis.readShort();
            } else if (type.equals("int")) {
                iarr[i] = dis.readInt();
            } else if (type.equals("long")) {
                larr[i] = dis.readLong();
            } else if (type.equals("float")) {
                farr[i] = dis.readFloat();
            } else if (type.equals("double")) {
                darr[i] = dis.readDouble();
            } else if (type.equals("boolean")) {
                Boolean b = readBoolean();
                if (b != null && b) {  // In arrays treat nulls as false.
                    boarr[i] = true;
                } else {
                    boarr[i] = false;
                }
            }
        }
        return ret;
    }
}
