/*
 * AidaTuple.java
 *
 * Created on December 2, 2002, 3:20 PM
 */

package hep.aida.ref.tuple;

import hep.aida.*;
import hep.aida.ref.*;
import org.freehep.util.Value;

import java.util.Map;
import java.util.StringTokenizer;
import java.util.Hashtable;

import hep.tuple.interfaces.*;

/**
 *
 * @author The AIDA Team @ SLAC
 *
 */
public class Tuple extends AbstractTuple implements FTuple, FillableTuple {
    
    private Value tupleValue = new Value();
    private String tupColumnsString = "";
    private String[] columnDefaultValues;
    
    private String options;
    private Map optionMap;
    
    private FillableTuple fTuple;
    private FTupleCursor cursor;
    private FTupleFactory fTupleFactory;
    
    private Hashtable folderHash = new Hashtable();
    
    
    public Tuple(String name, String title, String[] columnName, Class[] columnType, String options) {
        this(name,title,columnName,columnType,options,null);
    }
    
    
    public Tuple(String name, String title, String[] columnName, Class[] columnType, String options, FTupleFactory fTupleFactory) {
        super(name);
        
        if ( fTupleFactory == null )
            fTupleFactory = new hep.tuple.TupleFactory();

        initTuple(name, title,columnName,columnType,options, fTupleFactory);
    }
    
    public Tuple(String name, String title, String columnsString, String options) {
        this(name,title,columnsString,options,null);
    }
    
    public Tuple(String name, String title, String columnsString, String options, FTupleFactory fTupleFactory) {
        super(name);
        if ( fTupleFactory == null ) 
            fTupleFactory = new hep.tuple.TupleFactory();
        initTuple(name, title,columnsString,options, fTupleFactory);
    }
    
    protected void initTuple( String name, String title, String columnsString, String options, FTupleFactory fTupleFactory ) {
        setTitle(title);
        this.tupColumnsString = columnsString;
        
        this.fTupleFactory = fTupleFactory;
        
        fTuple = fTupleFactory.createFTuple(name, title, options);
        cursor = fTuple.cursor();
        
        optionMap = AidaUtils.parseOptions(options);
        inspectOptions();
        if (options != null) this.options = options;
        
        StringTokenizer strT = new StringTokenizer( columnsString,",;");
        int bracketCounter = 0;
        int nCol = 0;
        while( strT.hasMoreTokens() ) {
            String token = strT.nextToken();
            if ( bracketCounter == 0 ) nCol++;
            if ( token.indexOf("{") > 0 ) bracketCounter++;
            if ( token.indexOf("}") > 0 ) bracketCounter--;
        }
        if ( nCol < 1 ) throw new IllegalArgumentException("Cannot build Tuple with no columns\n");
        
        StringTokenizer st = new StringTokenizer(columnsString,",;");
        
        int columnIndex = 0;
        bracketCounter = 0;
        String iTupleColString = "";
        String type = "";
        
        int colDefCount = 0;
        columnDefaultValues = new String[nCol];
        
        while( st.hasMoreTokens() ) {
            String token = st.nextToken();
            token.trim();
            
            if ( token.indexOf("{") > 0 ) bracketCounter++;
            if ( token.indexOf("}") > 0 ) bracketCounter--;
            
            iTupleColString += token;
            
            if ( st.hasMoreTokens() ) iTupleColString += ";";
            
            if ( bracketCounter == 0 ) {
                String colName;
                String defaultValue = "";
                
                iTupleColString = iTupleColString.trim();
                int brPos = iTupleColString.indexOf("{");
                if ( brPos > 0 ) {
                    iTupleColString.trim();
                    int pos = iTupleColString.indexOf("(");
                    if ( pos > 0 && pos < brPos ) {
                        type = iTupleColString.substring(0,pos).trim();
                        colName = iTupleColString.substring( iTupleColString.indexOf(")")+1, brPos).trim();
                        if ( colName.endsWith("=") ) colName = colName.substring(0,colName.length()-1).trim();
                        defaultValue += iTupleColString.substring( pos+1, iTupleColString.indexOf(")") ).trim();
                        defaultValue += iTupleColString.substring( brPos );
                    } else {
                        int spPos = iTupleColString.indexOf(" ");
                        int eqPos = iTupleColString.indexOf("=");
                        type = iTupleColString.substring(0,spPos).trim();
                        colName = iTupleColString.substring(spPos+1,eqPos).trim();
                        defaultValue = iTupleColString.substring( brPos ).trim();
                    }
                } else {
                    StringTokenizer t = new StringTokenizer( token, "=");
                    int tokens = t.countTokens();
                    
                    String nameAndType = t.nextToken().trim();
                    
                    if ( tokens == 2 ) defaultValue = t.nextToken().trim();
                    else if ( tokens != 1 ) throw new IllegalArgumentException("Wrong format "+token+"\n");
                    
                    StringTokenizer tt = new StringTokenizer( nameAndType, " " );
                    if ( tt.countTokens() == 2 ) type = tt.nextToken().trim();
                    colName = tt.nextToken().trim();
                }
                
                Value value = new Value();
                
                Class colType = null;
                if ( type.endsWith("ITuple") ) {
                    String defVal = defaultValue.substring(defaultValue.indexOf("{")+1,defaultValue.lastIndexOf("}"));
                    Tuple ata = new Tuple( colName, "", defVal, options, fTupleFactory );
                    FillableTuple tup = ata.fTuple();
                    fTuple.addTuple( tup );
                    folderHash.put(tup,ata);
                    columnDefaultValues[colDefCount++] = defVal;
                }
                else {
                    if ( type.equals("int") )          colType = Integer.TYPE;
                    else if ( type.equals("short") )   colType = Short.TYPE;
                    else if ( type.equals("long") )    colType = Long.TYPE;
                    else if ( type.equals("float") )   colType = Float.TYPE;
                    else if ( type.equals("double") )  colType = Double.TYPE;
                    else if ( type.equals("boolean") ) colType = Boolean.TYPE;
                    else if ( type.equals("byte") )    colType = Byte.TYPE;
                    else if ( type.equals("char") )    colType = Character.TYPE;
                    else if ( type.equals("string") )  colType = String.class;
                    else {
                        try {
                            colType = Class.forName( type );
                        } catch ( ClassNotFoundException cnfe ) {
                            throw new IllegalArgumentException("Unsupported type "+type);
                        }
                    }
                    
                    columnDefaultValues[colDefCount++] = defaultValue;
                    fillDefaultValue(colType, defaultValue,value);
                    fTuple.addColumn( fTupleFactory.createFTupleColumn(colName, colType, value, options));
                }
                iTupleColString = "";
            }
        }
        
        start();
    }
    
    
    protected void initTuple(String name, String title, String[] columnName, Class[] columnType, String options, FTupleFactory fTupleFactory ){
        
        setTitle(title);
        
        this.fTupleFactory = fTupleFactory;
        
        fTuple = fTupleFactory.createFTuple(name,title,options);
        cursor = fTuple.cursor();
        
        optionMap = AidaUtils.parseOptions(options);
        inspectOptions();
        if (options != null) this.options = options;
        
        int colNames = columnName.length;
        int colTypes = columnType.length;
        
        if ( colNames < 1 ) throw new IllegalArgumentException("columnName cannot be empty\n");
        if ( colTypes < 1 ) throw new IllegalArgumentException("columnType cannot be empty\n");
        if ( colNames != colTypes ) throw new IllegalArgumentException("The number of column names "
        +colNames+" is different than the number of column types "+colTypes+"\n");
        
        int colDefCount = 0;
        columnDefaultValues = new String[colNames];
        
        for (int i=0; i<colNames; i++) {
            Class colType = columnType[i];
            String colName ="";
            String defaultValue = "";
            tupColumnsString += colType + " " + columnName[i];
            if ( i < colNames ) tupColumnsString += ";";
            
            Value value = new Value();
            
            if ( colType == ITuple.class ) {
                String iTupleString = columnName[i];
                int pos = iTupleString.indexOf(")");
                if ( pos > 0 ) {
                    colName = iTupleString.substring(pos+1,iTupleString.indexOf("{")).trim();
                    if ( colName.endsWith("=") ) colName = colName.substring(0,colName.length()-1).trim();
                    defaultValue += iTupleString.substring(iTupleString.indexOf("(")+1,pos);
                } else {
                    if (iTupleString.indexOf("=") > 0)
                        colName = iTupleString.substring(0,iTupleString.indexOf("=")).trim();
                    else colName = iTupleString.trim();
                }
                if (iTupleString.indexOf("{") > 0)
                    defaultValue += iTupleString.substring(iTupleString.indexOf("{"),iTupleString.lastIndexOf("}")+1).trim();
                
                String defVal = defaultValue.substring(defaultValue.indexOf("{")+1,defaultValue.lastIndexOf("}"));
                
                Tuple ata = new Tuple( colName, "", defVal, options, fTupleFactory );
                FillableTuple tup = ata.fTuple();
                fTuple.addTuple( tup );
                folderHash.put(tup,ata);
                columnDefaultValues[colDefCount++] = defVal;
                
            } else {
                colName = columnName[i];
                StringTokenizer st = new StringTokenizer(colName,"=");
                colName = st.nextToken().trim();
                if ( st.hasMoreTokens() ) defaultValue = st.nextToken().trim();
                fillDefaultValue(colType, defaultValue,value);
                columnDefaultValues[colDefCount++] = defaultValue;
                fTuple.addColumn( fTupleFactory.createFTupleColumn(colName, colType, value, options));
            }
            
        }
        start();
    }
    
    
    private void fillDefaultValue( Class type, String defaultValue, Value value ) {
        if ( type == Integer.TYPE )
            value.set( defaultValue.equals("") ? (int)0 : Integer.parseInt(defaultValue) );
        else if ( type == Short.TYPE )
            value.set( defaultValue.equals("") ? (short)0 : Short.parseShort(defaultValue) );
        else if ( type == Long.TYPE )
            value.set( defaultValue.equals("") ? (long)0 : Long.parseLong(defaultValue) );
        else if ( type == Float.TYPE )
            value.set( defaultValue.equals("") ? (float)0 : Float.parseFloat(defaultValue) );
        else if ( type == Double.TYPE )
            value.set( defaultValue.equals("") ? (double)0 : Double.parseDouble(defaultValue) );
        else if ( type == Boolean.TYPE )
            value.set( defaultValue.equals("") ? false : Boolean.valueOf(defaultValue).booleanValue() );
        else if ( type == Byte.TYPE )
            value.set( defaultValue.equals("") ? (byte)0 : Byte.parseByte(defaultValue) );
        else if ( type == Character.TYPE )
            value.set( defaultValue.equals("") ? (char)0 : defaultValue.charAt(0) );
        else value.set( defaultValue );
    }
    
    public String columnDefaultString( int column ) {
        if ( columnType( column ) != ITuple.class )
            return columnDefaultValues[column];
        else {
            Tuple tup = (Tuple)folderHash.get( fTuple.tuple(column) );
            if ( tup != null )
                return "( "+tup.getOptions()+" ) "+tup.name()+" = {"+tup.getColString()+"}";
            else
                return "null";
        }
    }
    
    public boolean isInMemory() {
        return true;
    }
    
    protected void inspectOptions() {
        /*
        if ( optionMap.containsKey( "length" ) ) {
            columnLength = Integer.parseInt( ( String ) ( optionMap.get( "length" ) ) );
        } else {
            columnLength = COLUMN_ROWS;
        }
        if ( optionMap.containsKey( "maxlength" ) ) {
            columnMaxLength = Integer.parseInt( ( String ) ( optionMap.get( "maxlength" ) ) );
            if ( ! optionMap.containsKey( "length" ) )
                columnLength = columnMaxLength;
        } else {
            columnMaxLength = -1;
        }
         */
    }
    
    
    
    public String getOptions() {
        return options;
    }
    
    public String getColString() {
        return tupColumnsString;
    }
    
    public double columnMin(int column) {
        fTuple.column(column).minValue(tupleValue);
        return tupleValue.getDouble();
    }
    
    public double columnMax(int column) {
        fTuple.column(column).maxValue(tupleValue);
        return tupleValue.getDouble();
    }
    
    public double columnMean(int column) {
        fTuple.column(column).meanValue(tupleValue);
        return tupleValue.getDouble();
    }
    
    public double columnRms(int column) {
        fTuple.column(column).rmsValue(tupleValue);
        return tupleValue.getDouble();
    }
    
    public int findColumn(String str) throws java.lang.IllegalArgumentException {
        return fTuple.columnIndexByName(str);
    }
    
    public boolean getBoolean(int param) throws java.lang.ClassCastException {
        fTuple.columnValue(param, cursor, tupleValue);
        return tupleValue.getBoolean();
    }
    
    public byte getByte(int param) throws java.lang.ClassCastException {
        fTuple.columnValue(param, cursor, tupleValue);
        return tupleValue.getByte();
    }
    
    public char getChar(int param) throws java.lang.ClassCastException {
        fTuple.columnValue(param, cursor, tupleValue);
        return tupleValue.getChar();
    }
    
    public double getDouble(int param) throws java.lang.ClassCastException {
        fTuple.columnValue(param, cursor, tupleValue);
        return tupleValue.getDouble();
    }
    
    public float getFloat(int param) throws java.lang.ClassCastException {
        fTuple.columnValue(param, cursor, tupleValue);
        return tupleValue.getFloat();
    }
    
    public int getInt(int param) throws java.lang.ClassCastException {
        fTuple.columnValue(param, cursor, tupleValue);
        return tupleValue.getInt();
    }
    
    public long getLong(int param) throws java.lang.ClassCastException {
        fTuple.columnValue(param, cursor, tupleValue);
        return tupleValue.getLong();
    }
    
    public Object getObject(int param) throws java.lang.ClassCastException {
        fTuple.columnValue(param, cursor, tupleValue);
        if ( columnType( param ) != ITuple.class )
            return tupleValue.getObject();
        else
            return (ITuple)folderHash.get( tupleValue.getObject() );
    }
    
    public short getShort(int param) throws java.lang.ClassCastException {
        fTuple.columnValue(param, cursor, tupleValue);
        return tupleValue.getShort();
    }
    
    public String getString(int param) throws java.lang.ClassCastException {
        fTuple.columnValue(param, cursor, tupleValue);
        return tupleValue.getString();
    }
    
    public hep.aida.ITuple getTuple(int param) {
        return (ITuple)folderHash.get(fTuple.tuple(param));
    }
    
    public void fill(int column, int value) throws IllegalArgumentException {
        fTuple.fill(column, tupleValue.set(value));
    }
    
    public void fill(int column, short value) throws IllegalArgumentException {
        fTuple.fill(column, tupleValue.set(value));
    }
    
    public void fill(int column, long value) throws IllegalArgumentException {
        fTuple.fill(column, tupleValue.set(value));
    }
    
    public void fill(int column, double value) throws IllegalArgumentException {
        fTuple.fill(column, tupleValue.set(value));
    }
    
    public void fill(int column, float value) throws IllegalArgumentException {
        fTuple.fill(column, tupleValue.set(value));
    }
    
    public void fill(int column, boolean value) throws IllegalArgumentException {
        fTuple.fill(column, tupleValue.set(value));
    }
    
    public void fill(int column, byte value) throws IllegalArgumentException {
        fTuple.fill(column, tupleValue.set(value));
    }
    
    public void fill(int column, char value) throws IllegalArgumentException {
        fTuple.fill(column, tupleValue.set(value));
    }
    
    public void fill(int column, String string) throws IllegalArgumentException {
        fTuple.fill(column, tupleValue.set(string));
    }
    
    public void fill(int column, Object obj) throws IllegalArgumentException {
        fTuple.fill(column, tupleValue.set(obj) );
    }
    
    public void fill(float[] values) throws IllegalArgumentException {
        int nCol = columns();
        if ( values.length != nCol ) throw new IllegalArgumentException("Wrong number of values provided "+values.length+". It has to match the number of columns "+nCol);
        for( int i = 0; i < nCol; i++ )
            fTuple.fill( i, tupleValue.set(values[i]) );
    }
    
    public void fill(double[] values) throws IllegalArgumentException {
        int nCol = columns();
        if ( values.length != nCol ) throw new IllegalArgumentException("Wrong number of values provided "+values.length+". It has to match the number of columns "+nCol);
        for( int i = 0; i < nCol; i++ )
            fTuple.fill( i, tupleValue.set(values[i]) );
    }
    
    
    public int row() {
        return cursor.row();
    }
    
    public void setRow( int row ) {
        cursor.setRow(row);
    }
    
    public void start() {
        cursor = fTuple.cursor();
        cursor.start();
    }
    
    public void skip(int rows) {
        cursor.skip(rows);
    }
    
    public boolean next() {
        return cursor.next();
    }
    
    
    public void addRow() {
        fTuple.addRow();
    }
    
    public String columnName(int param) {
        return fTuple.columnName(param);
    }
    
    public String[] columnNames() {
        String[] colNames = new String[fTuple.columns()];
        for ( int i = 0; i < colNames.length; i++ )
            colNames[i] = columnName(i);
        return colNames;
    }
    
    public Class columnType(int param) {
        Class type = fTuple.columnType(param);
        if ( type == FillableTuple.class ) return ITuple.class;
        return type;
    }
    
    public Class[] columnTypes() {
        Class[] colTypes = new Class[fTuple.columns()];
        for ( int i = 0; i < colTypes.length; i++ )
            colTypes[i] = columnType(i);
        return colTypes;
    }

    public int columns() {
        return fTuple.columns();
    }
    
    public void reset() {
        fTuple.reset();
    }
    
    public void resetRow() {
        fTuple.resetRow();
    }
    
    public int rows() {
        return fTuple.rows();
    }
    
    protected FillableTuple fTuple() {
        return fTuple;
    }
    
    public void addColumn(FillableTupleColumn column) {
        fTuple.addColumn(column);
    }
    
    public void addTuple(FillableTuple tuple) {
        fTuple.addTuple(tuple);
    }
    
    public void close() {
        fTuple.close();
    }
    
    public FTupleColumn column(int index) {
        return fTuple.column(index);
    }
    
    public FTupleColumn columnByName(String name) {
        return fTuple.columnByName(name);
    }
    
    public int columnIndexByName(String name) {
        return fTuple.columnIndexByName(name);
    }
    
    public void columnValue(int column, FTupleCursor cursor, Value value) {
        fTuple.columnValue(column, cursor, value);
    }
    
    public FTupleCursor cursor() throws IllegalStateException {
        return fTuple.cursor();
    }
    
    public void fill(int column, Value value) {
        fTuple.fill(column, value);
    }
    
    public void removeColumn(FillableTupleColumn column) {
        fTuple.removeColumn(column);
    }
    
    public void removeTuple(FillableTuple tuple) {
        fTuple.removeTuple(tuple);
    }
    
    public boolean supportsMultipleCursors() {
        return fTuple.supportsMultipleCursors();
    }
    
    public boolean supportsRandomAccess() {
        return fTuple.supportsRandomAccess();
    }
    
    public FTuple tuple(int index) {
        return fTuple.tuple(index);
    }
    
}
