package prefuse.data.tuple;

import java.util.Iterator;
import java.util.logging.Logger;

import prefuse.data.Graph;
import prefuse.data.Table;
import prefuse.data.Tuple;
import prefuse.util.StringLib;
import prefuse.util.collections.IntIterator;

/**
 * Manager class for Tuples. There is a unique Tuple for each row of a table.
 * All data structures and Tuples are created lazily, on an as-needed basis.
 * When a row is deleted from the table, it's corresponding Tuple (if created)
 * is invalidated before being removed from this data structure, ensuring that
 * any other live references to the Tuple can't be used to corrupt the table.
 * 
 * @author <a href="http://jheer.org">jeffrey heer</a>
 */
public class TupleManager {

    protected Graph        m_graph;
    protected Table        m_table;
    protected Class        m_tupleType;
    
    private   TableTuple[] m_tuples;
    
    /**
     * Create a new TupleManager for the given Table.
     * @param t the data Table to generate Tuples for
     */
    public TupleManager(Table t, Graph g, Class tupleType) {
        init(t, g, tupleType);
    }
    
    /**
     * Initialize this TupleManager for use with a given Table.
     * @param t the data Table to generate Tuples for
     */
    public void init(Table t, Graph g, Class tupleType) {
        if ( m_table != null ) {
            throw new IllegalStateException(
                "This TupleManager has already been initialized");
        }
        m_table = t;
        m_graph = g;
        m_tupleType = tupleType;
        m_tuples = null;
    }
    
    /**
     * Get the type of Tuple instances to generate.
     * @return the tuple type, as a Class instance
     */
    public Class getTupleType() {
        return m_tupleType;
    }
    
    /**
     * Ensure the tuple array exists.
     */
    private void ensureTupleArray() {
        int nrows = m_table.getRowCount();
        if ( m_tuples == null ) {
            m_tuples = new TableTuple[nrows];
        } else if ( m_tuples.length < nrows ) {
            int capacity = Math.max((3*m_tuples.length)/2 + 1, nrows);
            TableTuple[] tuples = new TableTuple[capacity];
            System.arraycopy(m_tuples, 0, tuples, 0, m_tuples.length);
            m_tuples = tuples;
        }
    }
    
    /**
     * Get a Tuple corresponding to the given row index.
     * @param row the row index
     * @return the Tuple corresponding to the given row
     */
    public Tuple getTuple(int row) {
        if ( m_table.isValidRow(row) ) {
            ensureTupleArray();
            if ( m_tuples[row] == null ) {
                return (m_tuples[row] = newTuple(row));
            } else {
                return m_tuples[row];
            }
        } else {
            // TODO: return null instead?
            throw new IllegalArgumentException("Invalid row index: "+row);
        }
    }
    
    /**
     * Instantiate a new Tuple instance for the given row index.
     * @param row the row index of the tuple
     * @return the newly created Tuple
     */
    protected TableTuple newTuple(int row) {
        try {
            TableTuple t = (TableTuple)m_tupleType.newInstance();
            t.init(m_table, m_graph, row);
            return t;
        } catch ( Exception e ) {
            Logger.getLogger(getClass().getName()).warning(
                e.getMessage()+"\n"+StringLib.getStackTrace(e));
            return null;
        }
    }
    
    /**
     * Invalidate the tuple at the given row.
     * @param row the row index to invalidate
     */
    public void invalidate(int row) {
        if ( m_tuples == null || row < 0 || row >= m_tuples.length ) {
            return;
        } else if ( m_tuples[row] != null ) {
            m_tuples[row].invalidate();
            m_tuples[row] = null;
        }
    }
    
    /**
     * Invalidate all tuples managed by this TupleManager
     */
    public void invalidateAll() {
        if ( m_tuples == null ) return;
        for ( int i=0; i<m_tuples.length; ++i )
            invalidate(i);
    }
    
    /**
     * Return an iterator over the tuples in this manager.
     * @param rows an iterator over table rows
     * @return an iterator over the tuples indicated by the input row iterator
     */
    public Iterator iterator(IntIterator rows) {
        return new TupleManagerIterator(this, rows);
    }
    
    // ------------------------------------------------------------------------
    // TupleManagerIterator
    
    /**
     * Iterator instance for iterating over tuples managed in a TupleManager.
     */
    public class TupleManagerIterator implements Iterator {

        private TupleManager m_tuples;
        private IntIterator  m_rows;
        
        /**
         * Create a new TupleManagerIterator.
         * @param tuples the TupleManager from which to get the tuples
         * @param rows the rows to iterate over
         */
        public TupleManagerIterator(TupleManager tuples, IntIterator rows) {
            m_tuples = tuples;
            m_rows = rows;
        }
        
        /**
         * @see java.util.Iterator#hasNext()
         */
        public boolean hasNext() {
            return m_rows.hasNext();
        }

        /**
         * @see java.util.Iterator#next()
         */
        public Object next() {
            return m_tuples.getTuple(m_rows.nextInt());
        }

        /**
         * @see java.util.Iterator#remove()
         */
        public void remove() {
            // TODO: check to see if this is safe
            m_rows.remove();
        }

    } // end of inner class TupleManagerIterator
    
} // end of class TupleManager
