Option Strict On
Option Explicit On

Imports System
Imports System.Collections
Imports System.Globalization

Namespace Nexamas.UI.DataBinding

    ''' <summary>
    ''' Delegate-owned target endpoint for simple Text, Enabled, Selection, and List bindings.
    ''' </summary>
    ''' <remarks>
    ''' This endpoint deliberately receives delegates instead of wrapping controls. The owning control/application surface
    ''' remains responsible for its properties, invalidation, layout refresh, and rendering behavior.
    ''' </remarks>
    Friend NotInheritable Class MASViewModelBindingTarget

        Private ReadOnly _writeValue As Action(Of Object)
        Private ReadOnly _readValue As Func(Of Object)

        Friend Sub New(kind As MASViewModelBindingKind,
                       targetName As String,
                       writeValue As Action(Of Object),
                       Optional readValue As Func(Of Object) = Nothing)
            Me.Kind = kind
            Me.TargetName = If(targetName, String.Empty).Trim()
            _writeValue = writeValue
            _readValue = readValue
        End Sub

        Friend ReadOnly Property Kind As MASViewModelBindingKind
        Friend ReadOnly Property TargetName As String

        Friend ReadOnly Property CanWrite As Boolean
            Get
                Return _writeValue IsNot Nothing
            End Get
        End Property

        Friend ReadOnly Property CanRead As Boolean
            Get
                Return _readValue IsNot Nothing
            End Get
        End Property

        Friend Shared Function Text(targetName As String,
                                    setText As Action(Of String),
                                    Optional getText As Func(Of String) = Nothing) As MASViewModelBindingTarget
            Dim writer As Action(Of Object) = Nothing
            If setText IsNot Nothing Then
                writer = Sub(value As Object)
                             setText.Invoke(If(value Is Nothing, String.Empty, Convert.ToString(value, CultureInfo.InvariantCulture)))
                         End Sub
            End If

            Dim reader As Func(Of Object) = Nothing
            If getText IsNot Nothing Then reader = Function() getText.Invoke()
            Return New MASViewModelBindingTarget(MASViewModelBindingKind.Text, targetName, writer, reader)
        End Function

        Friend Shared Function Enabled(targetName As String,
                                       setEnabled As Action(Of Boolean),
                                       Optional getEnabled As Func(Of Boolean) = Nothing) As MASViewModelBindingTarget
            Dim writer As Action(Of Object) = Nothing
            If setEnabled IsNot Nothing Then
                writer = Sub(value As Object)
                             setEnabled.Invoke(ToBooleanValue(value))
                         End Sub
            End If

            Dim reader As Func(Of Object) = Nothing
            If getEnabled IsNot Nothing Then reader = Function() getEnabled.Invoke()
            Return New MASViewModelBindingTarget(MASViewModelBindingKind.Enabled, targetName, writer, reader)
        End Function

        Friend Shared Function Selection(targetName As String,
                                         setSelection As Action(Of Object),
                                         Optional getSelection As Func(Of Object) = Nothing) As MASViewModelBindingTarget
            Dim writer As Action(Of Object) = Nothing
            If setSelection IsNot Nothing Then writer = Sub(value As Object) setSelection.Invoke(value)

            Dim reader As Func(Of Object) = Nothing
            If getSelection IsNot Nothing Then reader = Function() getSelection.Invoke()
            Return New MASViewModelBindingTarget(MASViewModelBindingKind.Selection, targetName, writer, reader)
        End Function

        Friend Shared Function List(targetName As String,
                                    setItems As Action(Of IEnumerable),
                                    Optional getItems As Func(Of IEnumerable) = Nothing) As MASViewModelBindingTarget
            Dim writer As Action(Of Object) = Nothing
            If setItems IsNot Nothing Then
                writer = Sub(value As Object)
                             If value Is Nothing Then
                                 setItems.Invoke(New Object() {})
                             ElseIf TypeOf value Is IEnumerable Then
                                 setItems.Invoke(DirectCast(value, IEnumerable))
                             Else
                                 Throw New InvalidOperationException("List binding value must implement IEnumerable.")
                             End If
                         End Sub
            End If

            Dim reader As Func(Of Object) = Nothing
            If getItems IsNot Nothing Then reader = Function() getItems.Invoke()
            Return New MASViewModelBindingTarget(MASViewModelBindingKind.List, targetName, writer, reader)
        End Function

        Friend Function TryWrite(value As Object, ByRef failureMessage As String) As Boolean
            If _writeValue Is Nothing Then
                failureMessage = "Target does not expose a writer."
                Return False
            End If

            Try
                _writeValue.Invoke(value)
                failureMessage = String.Empty
                Return True
            Catch ex As Exception
                failureMessage = ex.Message
                Return False
            End Try
        End Function

        Friend Function TryRead(ByRef value As Object, ByRef failureMessage As String) As Boolean
            If _readValue Is Nothing Then
                failureMessage = "Target does not expose a reader."
                value = Nothing
                Return False
            End If

            Try
                value = _readValue.Invoke()
                failureMessage = String.Empty
                Return True
            Catch ex As Exception
                failureMessage = ex.Message
                value = Nothing
                Return False
            End Try
        End Function

        Private Shared Function ToBooleanValue(value As Object) As Boolean
            If value Is Nothing Then Return False
            If TypeOf value Is Boolean Then Return DirectCast(value, Boolean)
            Return Convert.ToBoolean(value, CultureInfo.InvariantCulture)
        End Function

    End Class

End Namespace
