package uk.ac.ucl.excites.sapelli.storage.model.columns;

import java.io.IOException;

import uk.ac.ucl.excites.sapelli.shared.io.BitInputStream;
import uk.ac.ucl.excites.sapelli.shared.io.BitOutputStream;
import uk.ac.ucl.excites.sapelli.storage.model.Column;
import uk.ac.ucl.excites.sapelli.storage.model.ComparatorColumn;
import uk.ac.ucl.excites.sapelli.storage.model.Record;
import uk.ac.ucl.excites.sapelli.storage.visitors.ColumnVisitor;

/**
 * A column for 32 bit (float) or 64 bit (double) floating point numbers
 * 
 * @author mstevens
 */
public class FloatColumn extends ComparatorColumn<Double>
{	
	
	static private final long serialVersionUID = 2L;
	
	static public final boolean DEFAULT_DOUBLE_PRECISION = false; // 32 bit (float) by default
	private static final boolean DEFAULT_SIGNEDNESS = true; //allow signed values by default
	
	private boolean doublePrecision;
	private boolean signed;
	
	public FloatColumn(String name, boolean optional)
	{
		this(name, optional, DEFAULT_SIGNEDNESS, DEFAULT_DOUBLE_PRECISION);
	}
	
	public FloatColumn(String name, boolean optional, boolean signed, boolean doublePrecision)
	{
		super(Double.class, name, optional);
		this.doublePrecision = doublePrecision;
		this.signed = signed;
	}

	@Override
	public FloatColumn copy()
	{
		return new FloatColumn(name, optional, signed, doublePrecision);
	}
	
	/**
	 * Float version of {@link FloatColumn#storeValue(Record, Double)}
	 * 
	 * @param record
	 * @param value
	 * @throws IllegalArgumentException
	 * @throws NullPointerException
	 */
	public void storeValue(Record record, Float value) throws IllegalArgumentException, NullPointerException
	{
		Double doubleValue = (value != null ? Double.valueOf(value.floatValue()) : null);
		storeValue(record, doubleValue);
	}
	
	/**
	 * @param record
	 * @param nullReplacement
	 * @return
	 */
	public double getPrimitiveDouble(Record record, double nullReplacement)
	{
		Double doubleValue = retrieveValue(record);
		if(doubleValue == null)
			return nullReplacement;
		return doubleValue.doubleValue();
	}
	
	/**
	 * @param record
	 * @param nullReplacement
	 * @return
	 */
	public float getPrimitiveFloat(Record record, float nullReplacement)
	{
		Double doubleValue = retrieveValue(record);
		if(doubleValue == null)
			return nullReplacement;
		return doubleValue.floatValue();
	}

	/**
	 * @param value the String to parse (can be expected to be neither null nor "")
	 * @return the parsed value
	 * @throws NumberFormatException
	 */
	@Override
	public Double parse(String value) throws NumberFormatException
	{
		return Double.valueOf(value);
	}

	@Override
	protected void validate(Double value) throws IllegalArgumentException
	{
		if(!signed && value < 0.0d)
			throw new IllegalArgumentException("Cannot store negative values because column is unsigned");
		/*
		 * Note: I originally planned to also check whether the value could fit in a 32 bit float when in
		 * 		 single precision mode, but it seems there is no obvious (or even correct) way to do this. 
		 */
	}

	@Override
	protected void write(Double value, BitOutputStream bitStream) throws IOException
	{
		if(doublePrecision)
			bitStream.write(value);
		else
			bitStream.write(value.floatValue());
	}

	@Override
	protected Double read(BitInputStream bitStream) throws IOException
	{
		return doublePrecision ? bitStream.readDouble() : bitStream.readFloat();
	}

	@Override
	protected int _getMinimumSize()
	{
		return doublePrecision ? Double.SIZE : Float.SIZE;
	}
	
	@Override
	protected int _getMaximumSize()
	{
		return doublePrecision ? Double.SIZE : Float.SIZE;
	}

	@Override
	public String toString(Double value)
	{
		return value.toString();
	}

	@Override
	protected boolean equalRestrictions(Column<Double> otherColumn)
	{
		if(otherColumn instanceof FloatColumn)
			return this.doublePrecision == ((FloatColumn) otherColumn).doublePrecision;
		return false;
	}

	@Override
	protected Double copy(Double value)
	{
		return Double.valueOf(value);
	}
	
	/**
	 * Even though the type is actually Double we have called this column an "FloatColumn" 
	 * 
	 * @see uk.ac.ucl.excites.sapelli.storage.model.Column#getTypeString()
	 */
	@Override
	public String getTypeString()
	{
		return Float.class.getSimpleName();
	}
	
	@Override
	public void accept(ColumnVisitor visitor)
	{
		visitor.visit(this);
	}

	@Override
	protected int compareNonNullValues(Double lhs, Double rhs)
	{
		return lhs.compareTo(rhs);
	}
	
	@Override
    public int hashCode()
	{
		int hash = super.hashCode();
		hash = 31 * hash + (doublePrecision ? 0 : 1);
		return hash;
	}

	/**
	 * @return the doublePrecision
	 */
	public boolean isDoublePrecision()
	{
		return doublePrecision;
	}

	/**
	 * @return the signed
	 */
	public boolean isSigned()
	{
		return signed;
	}
	
}

@@ -779,22 +779,22 @@
 t%0D%0A%09
+static 
 private
- static
  fin
@@ -3941,16 +3941,72 @@
 olumn)%0D%0A
+%09%09%7B%0D%0A%09%09%09FloatColumn other = (FloatColumn) otherColumn;%0D%0A
 %09%09%09retur
@@ -4007,16 +4007,17 @@
 %09return 
+%09
 this.dou
@@ -4036,52 +4036,72 @@
  == 
-((FloatColumn) otherColumn).doublePrecision;
+other.doublePrecision &&%0D%0A%09%09%09%09%09this.signed == other.signed;%0D%0A%09%09%7D
 %0D%0A%09%09
@@ -4818,16 +4818,56 @@
  : 1);%0D%0A
+%09%09hash = 31 * hash + (signed ? 0 : 1);%0D%0A
 %09%09return

Fix in FloatColumn#hashCode() & FloatColumn#equalRestrictions()