using System;
using System.IO;
using Microsoft.SqlServer.Server;
using System.Data.SqlTypes;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;

// this is using the simplest serialization
// provided by System.Runtime.InteropServices
[SqlUserDefinedType(Format.Native,
	// IsByteOrdered is required to do
	// Equality  and Magnitude comparisons.
	// To control how Magnitude comparisons are
	// done you must implement IBinarySerialize to
	// make your representation of the Serialized
	// form be ordered the way that you want
	IsByteOrdered = true, IsFixedLength = true)]
// LayoutKind.Sequential is required
// when Format.Native is used
[StructLayout(LayoutKind.Sequential)]
// required for Native formatting
[Serializable]
public struct ComplexNumber: INullable
{
	// These two fields are the entire state for
	// complex number.
	// if _realpart == SqlDouble.Null, then
	// the complex number is considered null
	private SqlDouble _realpart;
	private SqlDouble _imagpart;
	// this is Nullable
	// Required for compatible SqlType
	public SqlBoolean IsNullable
	{
		get { return SqlBoolean.True; }
	}
	// no implementation of default constructor
	// because sturcts are not allowed
	// to have them. However the initial
	// value of structs is all zeros
	// by definition so this corresponds
	// to SqlDouble.Null for _realpart and
	// _ imagpart
	// This returns a ComplexNumber that 
	// Null. Note that because ComplexNumber
	// is a struct new, in this case,
	// does not allocate any heap memory
	/*
	public static ComplexNumber Null
	{
		// hand out the same null all the time;
		get {  }
	}
	*/
	// Used internally to set ComplexNumber
	// to null
	private void setToNull()
	{
		_realpart = SqlDouble.Null;

		// this is not required to 
		// mark the ComplexNumber as
		// null, but is required so
		// that all null ComplexNumbers
		// will have the same hash code
		_imagpart = SqlDouble.Null;
	}
	/*
	// realpart property
	public SqlDouble realpart
	{
		set
		{
			if (value.IsNull)
			{
			}
			else
			{
			}
			// update the hash code because 
			// you have changed the value
		}
		get { }
	}
	// imagpart property
	public SqlDouble imagpart
	{
		set
		{
			if (value.IsNull)
			{
			}
			else
			{
			}
			// update the hash code because
			// you have changed the value
	
		}
		get { }
	}
	*/
	// 2. INullable
	public bool IsNull
	{
		get
		{
			// Needs to be implemented
			// just test _realpart
			// because that is what
			// we have defined as being
			// the Null marker for
			// ComplexNumber
			return true;
		}
	}
	// 3. Required for UDT
	public override string ToString()
	{
		if (IsNull)
			return "Null";
		else

			// note that this returns the same format as
			// is expected by Convert
			// TO DO: Implement Me
			return "";
	}
	// required to be a SqlType so TSql can use Convert/Cast
	public static ComplexNumber Parse(SqlString s)
	{
		ComplexNumber cnum = new ComplexNumber();
		// Convert/Cast of NULL is NULL
		// format for ComplexNumber is two simple floating 
		// point numbers separated by a : and
		// terminated with i. Only simple floating point
		// numbers supported, i,e no 2.33e3
		if (s.Value == "Null")
		{
		}

		// regular expression to test, extract
		string fp = @"-?([0-9]+(\.[0-9]*)?|\.[0-9]+)";
		Regex ci = new Regex(@"(?<real>" + fp + @"):(?<imag>" + fp + @")i");

		// if it does not match regex then it is not properly formatted
		if (!ci.IsMatch(s.Value))
		{
			// it would be better to RaiseError with a custom
			// error added by sp_addmessage. But it appears
			// that in Beta 1 this is not possible in the 
			// Parse function of a SqlType
			// This works and the "bad format" string is passed
			// back to TSQL, but with a generic error code
			throw new ApplicationException("bad format", null);
		}

		// if you get to here input is properly formatted
		// Extract the values
		Match m = ci.Match(s.Value);

		// make a complex number to return
		// parse the real part of the input.
		// not that no exception can occur here because
		// you already know that the real part is propertly
		// formattted for double.Parse
		// Uncomment
		//cnum._realpart = double.Parse(m.Result("${real}"));
		// parse the imaginary part of the input
		//cnum._imagpart = double.Parse(m.Result("${imag}"));
		// calculate a new hash that corresponds to 
		// the new values.
		return cnum;
	}
	// Equals 
	// returns true if of both realparts and imagparts
	// are equal. It duplicate the Equal semanitcs of
	// SqlDouble. Any comparison to a NULL is always
	// false
	public override bool Equals(Object ct)
	{
		bool retValue = false;

		// anything might be passed in
		// if it is not a ComplexNumber it
		// does not compare
		if ((ct is ComplexNumber) || (ct is SqlString))
		{
			ComplexNumber value;

			if (ct is SqlString)
			{
				value = Parse((SqlString)ct);
			}
			else
			{
				value = (ComplexNumber)ct;
			}
			// cast to ComplexNumber so
			// it is less typing to access
			// fields
			// if the number is NULL then compare is false
			// Checking for hash code compare
			// first speeds up the detection of
			// complex numbers that are differnt
			// as it is just a simple compare.
			//
			// ComplexNumber is so simple this
			// probably won't make any difference,
			// but it can be a big win for more
			// tedious compares.
			// if the hash codes are the same they
			// still could be different numbers
			// so the actual values must be checked
			//
			// compares of SqlTypes always produce
			// SqlBoolean. Use the SqlBoolean.Ands
			// to test that both real/imag compare.
		}

		return retValue;
	}
	// calculates the hash code
	private void setHashCode()
	{
	}
	// Holds the hash code for the obejct.
	// must be updated whenever the realpart
	// or imagpart is changed
	private int hashCode;
	// just return the hashCode from the
	// last time the object was changed
	public override int GetHashCode()
	{
		return hashCode;
	}
	// Mutator method
	[SqlMethod(IsMutator = true, OnNullCall = false)]
	public void SetValue(SqlDouble r, SqlDouble i)
	{
		// recalculate the hash codes, you have changed the values
		setHashCode();
	}
	[SqlMethod(IsMutator = true, OnNullCall = false)]
	public void SetNull()
	{
	}
}