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


' this is using the simplest serialization
' provided by System.Runtime.InteropServices

' 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

' LayoutKind.Sequential is required
' when Format.Native is used

' 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

' LayoutKind.Sequential is required
' when Format.Native is used
<SqlUserDefinedType(Format.Native, _
 IsByteOrdered:=True, _
 IsFixedLength:=True)> _
<StructLayout(LayoutKind.Sequential)> _
<Serializable()> _
Public Structure ComplexNumber
    Implements INullable
    ' These two fields are the entire state for
    ' complex number.
    ' if _realpart == SqlDouble.Null, then
    ' the complex number is considered null
    Private _realpart As SqlDouble
    Private _imagpart As SqlDouble
    ' this is Nullable
    ' Required for compatible SqlType
    Public ReadOnly Property IsNullable() As SqlBoolean
        Get
            Return SqlBoolean.True
        End Get
    End Property
    ' 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 Shared ReadOnly Property Null() As ComplexNumber
        Get
            Return New ComplexNumber
        End Get
    End Property


    ' Used internally to set ComplexNumber
    ' to null
    Private Sub 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
    End Sub

    ' realpart property
    Public Property realpart() As SqlDouble
        Get
            Return _realpart
        End Get
        Set(ByVal Value As SqlDouble)
            If (Value.IsNull = True) Then
                setToNull()
            Else
                _realpart = Value
            End If
            ' update the hash code because 
            ' you have changed the value
            setHashCode()

        End Set
    End Property

    ' imagpart property
    Public Property imagpart() As SqlDouble
        Get
            Return _imagpart
        End Get
        Set(ByVal Value As SqlDouble)
            If (Value.IsNull = True) Then
                setToNull()
            Else
                _imagpart = Value
            End If
            ' update the hash code because
            ' you have changed the value
            setHashCode()
        End Set
    End Property

    ' 2. INullable
    Public ReadOnly Property IsNull() As Boolean Implements INullable.IsNull
        Get
            ' just test _realpart
            ' because that is what
            ' we have defined as being
            ' the Null marker for
            ' ComplexNumber
            Return _realpart.IsNull
        End Get
    End Property

    ' 3. Required for UDT
    Public Overrides Function ToString() As String
        If (IsNull) Then
            Return "Null"
        Else
            ' note that this returns the same format as
            ' is expected by Convert
            Return String.Format("{0}:{1}i", _realpart, _imagpart)
        End If
    End Function

    ' required to be a SqlType so TSql can use Convert/Cast
    Public Shared Function Parse(ByVal s As SqlString) As ComplexNumber
        ' Convert/Cast of NULL is NULL
        If (s.IsNull = True) Then
            Return ComplexNumber.Null
        End If
        ' 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") Then
            Return ComplexNumber.Null
        End If
        ' regular expression to test, extract
        Dim fp As String = "-?([0-9]+(\.[0-9]*)?|\.[0-9]+)"
        Dim ci As Regex = New Regex("(?<real>" + fp + "):(?<imag>" + fp + ")i")
        ' if it does not match regex then it is not properly formatted
        If ci.IsMatch(s.Value) = False Then

            ' 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", Nothing)
        End If
        ' if you get to here input is properly formatted
        ' Extract the values
        Dim m As Match = ci.Match(s.Value)
        ' make a complex number to return
        Dim cnum As ComplexNumber = New ComplexNumber
        ' 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
        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.
        cnum.setHashCode()

        Return cnum
    End Function

    ' 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 Overrides Function Equals(ByVal ct As Object) As Boolean

  Dim s As New SqlString
        Dim retValue As Boolean = False
        ' anything might be passed in
        ' if it is not a ComplexNumber it
        ' does not compare
        If (ct.GetType() Is Me.GetType()) Or (ct.GetType() Is s.GetType()) Then

            Dim value As ComplexNumber
            If (ct.GetType() Is s.GetType()) Then
                value = Parse(CType(ct, SqlString))
            Else
                value = CType(ct, ComplexNumber)
            End If
            ' cast to ComplexNumber so
            ' it is less typing to access
            ' fields
            Dim v As ComplexNumber = CType(value, ComplexNumber)
            ' if the number is NULL then compare is false
            If (v.IsNull = False) Then
                ' 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 (v.GetHashCode() = GetHashCode()) Then

                    ' 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.
                    retValue = (SqlBoolean.And(v.imagpart = _imagpart, v.realpart = _realpart)).Value
                End If
            End If
        End If
        Return retValue

    End Function

    ' calculates the hash code
    Private Sub setHashCode()

  hashCode = CType((realpart.GetHashCode() / 2 + imagpart.GetHashCode() / 2), Integer)
    End Sub

    ' Holds the hash code for the obejct.
    ' must be updated whenever the realpart
    ' or imagpart is changed
    Private hashCode As Integer
    ' just return the hashCode from the
    ' last time the object was changed

    Public Overrides Function GetHashCode() As Integer
        Return hashCode
    End Function

    ' Mutator method
    <SqlMethodAttribute(IsMutator:=True, OnNullCall:=False)> _
    Public Sub SetValue(ByVal r As SqlDouble, ByVal i As SqlDouble)
        If (r.IsNull = True Or i.IsNull = True) Then
            Throw New ApplicationException("parts of complex number may not be Nothing")
        End If
        _realpart = r
        _imagpart = i
        ' recalculate the hash codes, you have changed the values
        setHashCode()
    End Sub
    <SqlMethodAttribute(IsMutator:=True, OnNullCall:=False)> _
    Public Sub SetNull()
        setToNull()
        setHashCode()
    End Sub
End Structure