/*
 * Tuple.java
 *
 * Created on May 6, 2002, 2:05 pm
 */

package hep.aida.ref.tuple;

import hep.aida.*;
import hep.tuple.interfaces.*;
import org.freehep.util.Value;

/**
 *
 * @author  The AIDA team @ SLAC.
 *
 */
public class ChainedTuple extends AbstractTuple {
    
    private ITuple[] set;
    private int currentRow;
    private ITuple currentTuple;
    private int currentTupleID;
    private int currentTupleRow;
    private ChainedColumn[] columns;
    private ChainedTuple[] tuples;
    private Value v = new Value();
    
    /**
     * Tuple constructor.
     * @param name the Tuple's name
     * @param title the Tuple's title
     * @param set Array of ITuples to be chained
     */
    public ChainedTuple(String name, String title, ITuple[] set) {
        // TODO: Consistency checks?
        super(name);

        int nCol = 0;;        
        
        // Check if ITuples are compatible
        if (set.length > 1) {
            nCol = set[0].columns();        
            String[] colNames = new String[nCol];
            Class[] colTypes = new Class[nCol];
            for (int i=0; i<nCol; i++) {
                colNames[i] = set[0].columnName(i);
                colTypes[i] = set[0].columnType(i);
            }
            
            for (int n=1; n<set.length; n++) {
                if (set[n].columns() != nCol)
                    throw new IllegalArgumentException("ITuples in the set have different number of columns!");
                
                for (int i=0; i<nCol; i++) {
                    if (!(colNames[i].equals(set[n].columnName(i))))
                        throw new IllegalArgumentException("ITuples in the set have different column names!");
                    
                    if (colTypes[i] != set[n].columnType(i))
                        throw new IllegalArgumentException("ITuples in the set have different column types!");
                }
            }
        } else
            throw new IllegalArgumentException("Not enough tuples provided. Two or more tuples must be chained together.");
        
        
        columns = new ChainedColumn[nCol];
        tuples = new ChainedTuple[nCol];
        for ( int i = 0; i < nCol; i++ ) {
            columns[i] = new ChainedColumn(set,i);
            
            if ( columns[i].type() == ITuple.class ) {
                ITuple[] folders = new ITuple[set.length];
                for ( int j = 0; j < set.length; j++ ) {
                    set[j].start();
                    folders[j] = set[j].getTuple(i);
                }
                tuples[i] = new ChainedTuple(columns[i].name(),"",folders);
            }
        }
        
        setTitle(title);
        this.set = set;
        currentRow = -1;
        currentTuple = set[0];
        currentTuple.start();
        currentTupleID = 0;
        currentTupleRow = -1;
    }
    
    public String columnDefaultString( int column ) {
        columns[column].defaultValue(v);
        return v.toString();
    }
    
    /**
     * Get the number of columns in the Tuple.
     * @return the number of columns in the Tuple
     */
    public int columns() {
        return columns.length;
    }
    
    /**
     * Get the name of a column from its index
     * @param column the column's index
     * @return the column's name
     */
    public String columnName(int column) {
        return set[0].columnName(column);
    }
    
    public String[] columnNames() {
        return set[0].columnNames();
    }
    
    /**
     * Get the type of the column from its index
     * @param column the column's index
     * @return the column's type
     */
    public Class columnType(int column) {
        return set[0].columnType(column);
    }
    
    public Class[] columnTypes() {
        return set[0].columnTypes();
    }
    
    /**
     * Get the minimum value of a column.
     * @param column The column's index.
     * @return The minimum value of the column. If the minimum
     *         cannot be calculated Double.NaN is returned.
     *
     */
    public double columnMin(int column) {
        double min = Double.NaN;
        for (int i=0; i<set.length; i++) {
            double tmp = set[i].columnMin(column);
            if (Double.isNaN(min) || tmp<min) min = tmp;
        }
        return min;
    }
    
    /**
     * Get the maximum value of a column.
     * @param column The column's index.
     * @return The maximum value of the column. If the maximum
     *         cannot be calculated Double.NaN is returned.
     *
     */
    public double columnMax(int column) {
        double max = Double.NaN;
        for (int i=0; i<set.length; i++) {
            double tmp = set[i].columnMax(column);
            if (Double.isNaN(max) || tmp>max) max = tmp;
        }
        return max;
    }
    
    /**
     * Get the mean value of a column.
     * @param column The column's index.
     * @return The mean value of the column. If the mean
     *         cannot be calculated Double.NaN is returned.
     *
     */
    public double columnMean(int column) {
        double mean = 0.;
        int  rows = 0;
        for (int i=0; i<set.length; i++) {
            double m = set[i].columnMean(column);
            int r = set[i].rows();
            if (r<=0) continue;
            if (!Double.isNaN(m)) {
                mean += m*r;
                rows += r;
            } else return Double.NaN;
        }
        return mean/rows;
    }
    
    /**
     * Get the rms of a column.
     * @param column The column's index.
     * @return The rms of the column. If the rms
     *         cannot be calculated Double.NaN is returned.
     *
     */
    public double columnRms(int column) {
        double rms = 0;
        int  rows = 0;
        for (int i=0; i<set.length; i++) {
            double m = set[i].columnMean(column);
            double rm = set[i].columnRms(column);
            int r = set[i].rows();
            if (r<=0) continue;
            if (!Double.isNaN(m) && !Double.isNaN(rm)) {
                rms += (rm*rm + m*m)*r;
                rows += r;
            } else return Double.NaN;
        }
        return Math.sqrt(rms/rows-Math.pow(columnMean(column),2));
    }
    
    /**
     * The number of rows currently in the ntuple.
     * @return -1 if cannot be determined.
     */
    public int rows() {
        int rows = 0;
        for (int i=0; i<set.length; i++) {
            int r = set[i].rows();
            if (r>0) rows += r;
        }
        return rows;
    }
    
    /**
     * Get the current row.
     * @return The current row;
     *
     */
    public int getRow() {
        return currentRow;
    }
    
    public ITuple getTuple(int column) {
        return tuples[column];
//        return currentTuple.getTuple(column);
    }
    
    
    /**
     * Set the current row.
     * @param row The current row;
     * @return True if the opeartion was succesfull.
     *
     */
    public void setRow( int row ) {
        if (row > rows())
            throw new IllegalArgumentException("Row "+row+" is bigger than the length of this ChainedTuple ("+rows()+")");
        int rows = 0;
        int i = 0;
        int r = 0;
        ITuple tup = null;
        for (i=0; i<set.length; i++) {
            r = set[i].rows();
            if (rows<row && (rows+r)>row) break;
            if (r>0) rows += r;
        }
        currentRow = row;
        currentTupleID = i;
        currentTuple = set[currentTupleID];
        currentTupleRow = row-rows;
        currentTuple.setRow(currentTupleRow);
    }
    
    /**
     * Positions the read cursor immediately before the first row.
     */
    public void start() {
        currentRow = -1;
        currentTupleID = 0;
        currentTuple = set[currentTupleID];
        currentTuple.start();
        currentTupleRow = -1;
    }
    
    /**
     * Skips rows.
     * @param rows number of rows to skip, greater than 0.
     * @return false if it cannot skip rows.
     */
    public void skip(int rows) {
        setRow(currentRow+rows);
    }
    
    /**
     * Positions the cursor at the next row.
     * @return false if there is no next row.
     */
    public boolean next() {
        if (currentTuple.next()) {
            currentRow++;
            currentTupleRow++;
            return true;
        } else if (currentTupleID < set.length-1) {
            currentTupleID++;
            currentTuple = set[currentTupleID];
            currentTuple.start();
            currentTupleRow = 0;
            if ( !currentTuple.next()) return next();
            currentRow++;
            return true;
        }
        return false;
    }
    
    /**
     * Convert a name to a column index.
     * Note: C++ version may return -1 if column not found
     * @return the column number for a given name
     */
    public int findColumn(String name) throws IllegalArgumentException {
        return set[0].findColumn(name);
    }
    
    
    /**
     * Get the column's value corresponding to the
     * cursor's current position.
     * @param column the column's index
     * @return the corresponding int value
     */
    public int getInt(int column) throws ClassCastException {
        return currentTuple.getInt(column);
    }
    
    /**
     * Get the column's value corresponding to the
     * cursor's current position.
     * @param column the column's index
     * @return the corresponding short value
     */
    public short getShort(int column) throws ClassCastException {
        return currentTuple.getShort(column);
    }
    
    /**
     * Get the column's value corresponding to the
     * cursor's current position.
     * @param column the column's index
     * @return the corresponding long value
     */
    public long getLong(int column) throws ClassCastException {
        return currentTuple.getLong(column);
    }
    
    /**
     * Get the column's value corresponding to the
     * cursor's current position.
     * @param column the column's index
     * @return the corresponding float value
     */
    public float getFloat(int column) throws ClassCastException {
        return currentTuple.getFloat(column);
    }
    
    /**
     * Get the column's value corresponding to the
     * cursor's current position.
     * @param column the column's index
     * @return the corresponding double value
     */
    public double getDouble(int column) throws ClassCastException {
        return currentTuple.getDouble(column);
    }
    
    /**
     * Get the column's value corresponding to the
     * cursor's current position.
     * @param column the column's index
     * @return the corresponding boolean value
     */
    public boolean getBoolean(int column) throws ClassCastException {
        return currentTuple.getBoolean(column);
    }
    
    /**
     * Get the column's value corresponding to the
     * cursor's current position.
     * @param column the column's index
     * @return the corresponding byte value
     */
    public byte getByte(int column) throws ClassCastException {
        return currentTuple.getByte(column);
    }
    
    /**
     * Get the column's value corresponding to the
     * cursor's current position.
     * @param column the column's index
     * @return the corresponding char
     */
    public char getChar(int column) throws ClassCastException {
        return currentTuple.getChar(column);
    }
    
    /**
     * Get the column's value corresponding to the
     * cursor's current position.
     * @param column the column's index
     * @return the corresponding String
     */
    public String getString(int column) throws ClassCastException {
        return currentTuple.getString(column);
    }
    
    /**tupleValue
     * @param column the column's index
     * @return the corresponding Object
     */
    public Object getObject(int column) throws ClassCastException {
        return currentTuple.getObject(column);
    }
    
    
    public void addColumn(FTupleColumn column) {
        throw new UnsupportedOperationException("Operation not supported");
    }
    
    public void addTuple(FillableTuple tuple) {
        throw new UnsupportedOperationException("Operation not supported");
    }
    
    public void close() {
        throw new UnsupportedOperationException("Operation not supported");
    }
    
    public FTupleColumn column(int index) {
        throw new UnsupportedOperationException("Operation not supported");
    }
    
    public FTupleColumn columnByName(String name) {
        throw new UnsupportedOperationException("Operation not supported");
    }
    
    public int columnIndexByName(String name) {
        return findColumn(name);
    }
    
    public void columnMaxValue(int column, Value value) {
        value.set( columnMax(column) );
    }
    
    public void columnMeanValue(int column, Value value) {
        value.set( columnMean(column) );
    }
    
    public void columnMinValue(int column, Value value) {
        value.set( columnMin(column) );
    }
    
    public void columnRmsValue(int column, Value value) {
        value.set( columnRms(column) );
    }
    
    public void columnValue(int column, FTupleCursor cursor, Value value) {
        if ( currentTuple instanceof FTuple ) ((FTuple) currentTuple).columnValue(column, cursor, value);
    }
    
    public FTupleCursor cursor() throws IllegalStateException {
        throw new UnsupportedOperationException("Operation not supported");
    }
    
    public void fill(int column, Value value) {
        throw new UnsupportedOperationException("Operation not supported");
    }
    
    public void removeColumn(FTupleColumn column) {
        throw new UnsupportedOperationException("Operation not supported");
    }
    
    public void removeTuple(FillableTuple tuple) {
        throw new UnsupportedOperationException("Operation not supported");
    }
    
    public boolean supportsMultipleCursors() {
        return false;
    }
    
    public boolean supportsRandomAccess() {
        return false;
    }
    
    public FTuple tuple(int index) {
        throw new UnsupportedOperationException("Operation not supported");
    }
    
    
    private class ChainedColumn implements FTupleColumn {
        
        private ITuple[] set;
        private int index;
        private boolean hasDefaultValue = false;
        
        ChainedColumn(ITuple[] set, int index) {
            this.set = set;
            this.index = index;
            if ( set[0] instanceof AbstractTuple )
                hasDefaultValue = true;
        }
        
        
        public void defaultValue(Value value) {
            if ( hasDefaultValue )
                value.set( ((AbstractTuple)set[0]).columnDefaultString(index));
        }
        
        public boolean hasDefaultValue() {
            return hasDefaultValue();
        }
        
        public void maxValue(Value value) {
            double max = Double.NaN;
            for (int i=0; i<set.length; i++) {
                double tmp = set[i].columnMax(index);
                if (Double.isNaN(max) || tmp>max) max = tmp;
            }
            value.set(max);
        }
        
        public void meanValue(Value value) {
            double mean = 0.;
            int  rows = 0;
            for (int i=0; i<set.length; i++) {
                double m = set[i].columnMean(index);
                int r = set[i].rows();
                if (r<=0) continue;
                if (!Double.isNaN(m)) {
                    mean += m*r;
                    rows += r;
                } else {
                    value.set(Double.NaN);
                    return;
                }
            }
            value.set(mean/rows);
        }
        
        public void minValue(Value value) {
            double min = Double.NaN;
            for (int i=0; i<set.length; i++) {
                double tmp = set[i].columnMin(index);
                if (Double.isNaN(min) || tmp<min) min = tmp;
            }
            value.set(min);
        }
        
        public String name() {
            return set[0].columnName(index);
        }
        
        public void rmsValue(Value value) {
            double rms = 0;
            int  rows = 0;
            for (int i=0; i<set.length; i++) {
                double m = set[i].columnMean(index);
                double rm = set[i].columnRms(index);
                int r = set[i].rows();
                if (r<=0) continue;
                if (!Double.isNaN(m) && !Double.isNaN(rm)) {
                    rms += (rm*rm + m*m)*r;
                    rows += r;
                } else {
                    value.set(Double.NaN);
                    return;
                }
            }
            value.set(Math.sqrt(rms/rows-Math.pow(columnMean(index),2)));
        }
        
        public Class type() {
            return set[0].columnType(index);
        }
        
    }
    
    
    // Check how ChainedTuple works
    // TODO: This should be in test case.
    public static void main(String[] args) {
        
        IAnalysisFactory af = IAnalysisFactory.create();
        ITree tree = af.createTreeFactory().create();
        ITupleFactory tf = af.createTupleFactory( tree );
        
        java.util.Random r = new java.util.Random();
        
        // Create and fill 4 different ITuples
        ITuple tup1 = tf.create("tup1","tup1","int n, double x");
        ITuple tup2 = tf.create("tup2","tup2","int n, double x");
        ITuple tup3 = tf.create("tup3","tup3","int n, double x");
        ITuple tup4 = tf.create("tup4","tup4","int n, double x");
        
        for ( int i = 0; i < 20; i++ ) {
            tup1.fill(0,i);
            tup2.fill(0,i+20);
            tup3.fill(0,i+40);
            tup4.fill(0,i+60);
            
            tup1.fill(1, r.nextDouble()*10.);
            tup2.fill(1, r.nextDouble()*10.);
            tup3.fill(1, r.nextDouble()*10.);
            tup4.fill(1, r.nextDouble()*10.);
            
            tup1.addRow();
            tup2.addRow();
            tup3.addRow();
            tup4.addRow();
        }
        
        // Create a chain
        ITuple[] set = new ITuple[] { tup1, tup2, tup3, tup4};
        ITuple chain = tf.createChained("ChainedTuple", "New Chained Tuple", set);
        
        chain.start();
        System.out.println("\n\nChained Tuple:");
        while (chain.next()) System.out.println(chain.getInt(0) + "\t" + chain.getDouble(1));
        
        
        // Create IFilter and filtered ITuple
        IFilter filter = tf.createFilter("n>14 && n<46");
        filter.initialize(chain);
        ITuple filteredTuple = tf.createFiltered("FilteredTuple", chain, filter);
        
        filteredTuple.start();
        System.out.println("\n\nFiltered Tuple:");
        while (filteredTuple.next()) System.out.println(filteredTuple.getInt(0) + "\t" + filteredTuple.getDouble(1));
    }
    
    public boolean isInMemory() {
        throw new UnsupportedOperationException("This method has not been implemented. Please report this problem.");
    }
}
