package relational;

import java.util.*;
import Tools.*;

/**
 * A list of rows that implement a complete FromClause. If you want
 * rows that perhaps only hold (or report) a couple of specific
 * columns, you need a ResultSetNarrow.
 */
public class ResultSetImpl
	implements ResultSet {

	public ResultSetImpl(ResultSet rs) {
		this(rs.getFromClause(), new Vector(rs.getVector()));
	}

	public ResultSetImpl(FromClause fc) {
		this(fc, new Vector());
	}

	// copies the collection into an internal vector
	public ResultSetImpl(FromClause fc, Collection c) {
		this.fromClause = fc;
		this.myVector = new Vector(c);
	}

	// 'borrows' v -- caller should discard his reference to it,
	// lest this ResultSet behave strangely by referencing a changing
	// vector.
	public ResultSetImpl(FromClause fc, Vector v) {
		this.fromClause = fc;
		this.myVector = v;
	}

	// should only be called when constructing this object;
	// after first read, some internal state may become sticky,
	// and not see new addMember() calls.
	public void addMember(Object o) {
		myVector.addElement(o);
	}

	public void removeMember(Object o) {
		myVector.removeElement(o);
	}

	public FromClause getFromClause() {
		return fromClause;
	}

	public ColumnSpec getColumnSpec() {
		return fromClause.getNaturalColumnSpec();
	}

	public int size() {
		return myVector.size();
	}

	/**
	 * @deprecated in favor of getIterator
	 */
	public Enumeration getEnumeration() {
		return myVector.elements();
	}

	public Iterator iterator() {
		return myVector.iterator();
	}

	public Vector getVector() {
		return myVector;
			// TODO: dangerous because caller can frob with the vector that's
			// my protected data?
	}

	public boolean hasMember(Row o) {
		// hash & cache!
		if (memberCache==null) {
			Timer timer = new Timer();
			fillCache();
			System.out.println("filling cache took: "+timer);
		}
		return memberCache.contains(hashKeyForRow(o));
	}

	public static ResultSet cross(ResultSet ra, ResultSet rb) {
		// A Painful Join with the output type determined automatically
		return cross(ra, rb,
			FromClause.union(ra.getFromClause(), rb.getFromClause()));
	}

	public static ResultSet cross(ResultSet ra, ResultSet rb,
		FromClause outputShape) {
		// The Painful Join.
		// return all combinations of a and b.
		// Tables are to be named and ordered as they are in outputShape,
		// although some tables may not appear (yet).
		// ra and rb's fromClauses should not overlap.

		FromClause fca = ra.getFromClause();
		FromClause fcb = rb.getFromClause();
		int numTables = fca.getNumTables() + fcb.getNumTables();
		String[] names = new String[numTables];
		Class[] tables = new Class[numTables];
		boolean[] ahint = new boolean[numTables];
		int index = 0;
		for (int i=0; i<outputShape.getNumTables(); i++) {
			boolean ahas = fca.getIndex(outputShape.getName(i))>=0;
			boolean bhas = fcb.getIndex(outputShape.getName(i))>=0;
			if (ahas && bhas) {
				throw new IllegalArgumentException("ra and rb overlap");
			} else if (ahas || bhas) {
				tables[index] = outputShape.getTable(i);
				names[index] = outputShape.getName(i);
				ahint[index] = ahas;
				index++;
			}
		}
		numTables = index;
		Class[] tables2 = new Class[numTables];
		String[] names2 = new String[numTables];
		System.arraycopy(tables, 0, tables2, 0, tables2.length);
		System.arraycopy(names, 0, names2, 0, names2.length);

		FromClause outfc = FromClause.create(names2, tables2);

		ResultSetImpl rs = new ResultSetImpl(outfc);

		Enumeration eb = rb.getEnumeration();
		while (eb.hasMoreElements()) {
			Row ob = (Row) eb.nextElement();
			Enumeration ea = ra.getEnumeration();
			while (ea.hasMoreElements()) {
				Row oa = (Row) ea.nextElement();
				Relational[] data = new Relational[numTables];
				for (int ti=0; ti<numTables; ti++) {
					data[ti] = ahint[ti]
						? fca.getTableFromRow(oa, names[ti])
						: fcb.getTableFromRow(ob, names[ti]);
				}
				rs.addMember(new BasicRow(outfc, data));
			}
		}
		return rs;
	}

	//////////////////////////////////////////////////////////////////
	//////////////////////////////////////////////////////////////////

	protected void fillCache() {
		// cache should have as a key one of every unique object appearing
		// as the primary key in any row of this set.
		memberCache = new HashSet();
		int numTables = fromClause.getNumTables();
		Iterator iter = iterator();
		while (iter.hasNext()) {
			Row row = (Row) iter.next();
			// memberCache.add(hashKeyForRow(row));
			memberCache.add(row);
		}
	}

	protected Object hashKeyForRow(Row row) {
		if (fromClause.getNumTables() == 1) {
			// if only one table in fromClause, just use the Relational
			// object as the key, since they're unique
			return fromClause.getTableFromRow(row, 0);
		} else {
			// else build up a hash key object from all the relational objects
			// doesn't have to be unique, since...
			HashKey hk = new HashKey();
			for (int ti=0; ti<fromClause.getNumTables(); ti++) {
				String tn = fromClause.getName(ti);
				Relational tbl = row.getTable(tn);
				hk.add(row);
			}
			return hk;
		}
	}

	protected HashSet memberCache;
	protected Vector myVector;
	protected FromClause fromClause;
}
