package org.reldb.rel.v0.values;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.reldb.rel.exceptions.ExceptionFatal;
import org.reldb.rel.v0.generator.Generator;
import org.reldb.rel.v0.types.*;
import org.reldb.rel.v0.vm.Context;

public class ValueTuple extends ValueAbstract implements Projectable {

	private static final long serialVersionUID = 0;

	private Value[] values;
	
	/** Create a new tuple with default attribute values. */
	public ValueTuple(Generator generator, TypeTuple type) {
		super(generator);
		Heading heading = type.getHeading();
		this.values = new Value[heading.getDegree()];
		int i = 0;
		for (Attribute attribute: heading.getAttributes())
			this.values[i++] = attribute.getType().getDefaultValue(generator);
		loaded(generator);
	}
	
	/** Create a new tuple given an array of tuple attribute values. */
	public ValueTuple(Generator generator, Value[] values) {
		super(generator);
		this.values = values;
		loaded(generator);
	}
	
	public void loaded(Generator generator) {
		super.loaded(generator);
		for (Value value: values)
			if (value != null)
				value.loaded(generator);
	}

	/** Obtain a serializable clone of this value. */
	public Value getSerializableClone() {
		Value[] newValues = new Value[values.length];
		for (int i=0; i<values.length; i++)
			newValues[i] = values[i].getSerializableClone();
		return new ValueTuple(getGenerator(), newValues);
	}
	
	public static ValueTuple getEmptyTuple(Generator generator) {
		return new ValueTuple(generator, TypeTuple.getEmptyTupleType());
	}
	
	/** Return the array of ValueS in this tuple. */
	public Value[] getValues() {
		return values;
	}

	/** Assign ValueS to this tuple from the source ValueTuple according to an AttributeMap. */
	public void assign(AttributeMap map, ValueTuple source) {
		map.assign(this.values, source);
	}

	/** Given a tuple and a regular expression, return true if the regular expression matches the CAST_AS_CHAR conversion of any of the tuple's attributes. */
	public boolean search(Context context, Type type, String regexp) {
		Vector<Attribute> attributes = ((TypeTuple)type).getHeading().getAttributes();
		int i = 0;
		Pattern compiledRegex = Pattern.compile(regexp);
		for (Attribute attribute: attributes) {
			String encoding = "utf-8";
			ByteArrayOutputStream buffer = new ByteArrayOutputStream();
			try {
				PrintStream ps = new PrintStream(buffer, true, encoding);
				values[i].toStream(context, attribute.getType(), ps, 1);
				Matcher matcher = compiledRegex.matcher(buffer.toString(encoding));
				if (matcher.find())
					return true;
			} catch (UnsupportedEncodingException e) {
				e.printStackTrace();
			}
			i++;
		}
		return false;
	}
	
	/** Create a new tuple by projecting this ValueTuple according to an AttributeMap. */
	public Value project(AttributeMap map) {
		return map.project(getGenerator(), this);
	}
	
	/** Create a new tuple by a disjoint join of this and another ValueTuple. */
	public ValueTuple joinDisjoint(ValueTuple right) {
		Value[] valueArray = new Value[values.length + right.values.length];
		System.arraycopy(values, 0, valueArray, 0, values.length);
		System.arraycopy(right.values, 0, valueArray, values.length, right.values.length);
		return new ValueTuple(getGenerator(), valueArray);
	}
	
	/** Create a new tuple by a join of this and another ValueTuple.  Assume that common attributes
	 * have matching values, as (perhaps) determined by isJoinable(). */
	public ValueTuple join(JoinMap map, ValueTuple right) {
		return map.join(getGenerator(), this, right);
	}
		
	/** Create a new tuple by a join of this and another ValueTuple.  Throw an exception if
	 * common attributes do not have matching values. */
	public ValueTuple joinChecked(JoinMap map, ValueTuple right) {
		return map.joinChecked(getGenerator(), this, right);
	}

	/** Create a new tuple by removing an attribute specified by a given zero-based index. */
	public ValueTuple shrink(int attributeIndex) {
		Value[] valueArray = new Value[values.length - 1];
		System.arraycopy(values, 0, valueArray, 0, attributeIndex);
		System.arraycopy(values, attributeIndex + 1, valueArray, attributeIndex, values.length - attributeIndex - 1);
		return new ValueTuple(getGenerator(), valueArray);
	}
	
	public String getTypeName() {
		return "TUPLE";
	}

	/** Output this Value to a PrintStream. */
	public void toStream(Context context, Type type, PrintStream p, int depth) {
		p.print("TUPLE {");
		Vector<Attribute> attributes = ((TypeTuple)type).getHeading().getAttributes();
		if (values.length != attributes.size())
			throw new ExceptionFatal("RS0452: Bad tuple. Heading " + type + " says degree = " + attributes.size() + " but tuple " + toString() + " says degree = " + values.length + ".");
		int i = 0;
		for (Attribute attribute: attributes) {
			if (i > 0)
				p.print(", ");
			p.print(attribute.getName() + " ");
			values[i].toStream(context, attribute.getType(), p, depth + 1);
			i++;
		}
		p.print("}");
	}
	
	public int hashCode() {
		int code = 0;
		for (Value value: values)
			code += value.hashCode();
		return code;
	}
	
	public int compareTo(Value v) {
		int comparison = 0;
		for (int i=0; i<values.length; i++) {
			comparison = values[i].compareTo(((ValueTuple)v).values[i]);
			if (comparison != 0)
				break;
		}
		return comparison;
	}
	
	public String toString() {
		String out = null;
		for (Value value: values)
			out = ((out == null) ? "" : out + ", ") + ((value == null) ? "null" : value.toString());
		return "TUPLE {" + ((out == null) ? "" : out) + "}";
	}
	
	public String toCSV() {
		StringBuffer str = new StringBuffer("");
		for (Value value: values) {
			str.append(value.toString() + ",");
		}
		str.deleteCharAt(str.length()-1);
		return str.toString().trim();
	}
}
