'''
''' MBARK
'''
''' Multimodal Biometric Accuracy Research Kiosk
''
''' National Institute of Standards and Technology
'''
''' --
''' Author(s)
'''   Ross J. Micheals / rossm@nist.gov / NIST
''' 
Option Strict On

Imports System.Drawing
Imports System.Globalization
Imports System.Xml.Serialization

Imports Mbark.UI.GlobalUISettings
Imports Mbark.SensorMessages


Namespace Mbark.Sensors


    Public Enum SensorTaskStatus
        Unstarted   ' Task has not yet started
        Active      ' Task is underway

        Downloading ' Task result is currently being downloaded
        Pending     ' Task can be downloaded when ready
        Blocked     ' Task can't be downloaded b/c another task is downloading

        Done        ' Task is finished

        Skipped     ' Task was explicitly skipped
        Suspended   ' Task was active, and implicitly skipped 
    End Enum

    Module SensorTaskStatusSupport

        Public Function ToString(ByVal culture As CultureInfo, ByVal status As SensorTaskStatus) As String
            Dim returnValue As String = status.ToString(culture)
            Select Case status
                Case SensorTaskStatus.Active
                    returnValue = Messages.Active(culture)
                Case SensorTaskStatus.Blocked
                    returnValue = Messages.Blocked(culture)
                Case SensorTaskStatus.Done
                    returnValue = Messages.Done(culture)
                Case SensorTaskStatus.Downloading
                    returnValue = Messages.Downloading(culture)
                Case SensorTaskStatus.Pending
                    returnValue = Messages.Pending(culture)
                Case SensorTaskStatus.Skipped
                    returnValue = Messages.Skipped(culture)
                Case SensorTaskStatus.Suspended
                    returnValue = Messages.Suspended(culture)
                Case SensorTaskStatus.Unstarted
                    returnValue = Messages.NotStarted(culture)
            End Select
            Return returnValue
        End Function

    End Module

    <XmlType()> Public Class SensorTask
        Implements IComparable

        Friend mDefinition As New SensorTaskDefinition
        Friend mConditions As New ConditionCollection
        Friend mColors As SensorTaskColors
        Friend mAttempts As New SensorTaskAttemptCollection(Me)
        Friend mOriginatingFactory As SensorTaskFactory
        Friend mSensor As ISensor

        Friend Function CreateDefinition() As SensorTaskDefinition
            With mDefinition
                .TargetConditionsDefinition = mConditions.CreateDefinition()
                .ColorsDefinition = mColors.Definition()
                .AttemptsDefinition = mAttempts.CreateDefinition(Me)
            End With
            Return mDefinition.DeepCopy
        End Function


        Public Property DownloadsSuspended() As Boolean
            Get
                Return mDefinition.DownloadsSuspended
            End Get
            Set(ByVal value As Boolean)
                mDefinition.DownloadsSuspended = value
                If value Then mDefinition.Status = SensorTaskStatus.Suspended

                ' If the task is suspended, and we re-enable downloads, then set the task back to pending
                If Not value And mDefinition.Status = SensorTaskStatus.Suspended AndAlso Me.NumberOfPendingDownloads > 0 Then
                    mDefinition.Status = SensorTaskStatus.Pending
                End If
            End Set
        End Property


        Public Sub Block()
            mDefinition.IsBlocked = True
        End Sub

        Public Sub Unblock()
            mDefinition.IsBlocked = False
        End Sub




        Public ReadOnly Property LatestAttempt() As SensorTaskAttempt
            Get
                If Attempts.Count = 0 Then Return Nothing Else Return mAttempts(mAttempts.Count - 1)
            End Get
        End Property

        Public ReadOnly Property Attempts() As SensorTaskAttemptCollection
            Get
                Return mAttempts
            End Get
        End Property

        Friend ReadOnly Property OriginatingFactory() As SensorTaskFactory
            Get
                Return mOriginatingFactory
            End Get
        End Property

        Friend Sub WireOriginatingFactory(ByVal factory As SensorTaskFactory)
            mOriginatingFactory = factory
        End Sub

        Friend Sub PopulateConditions()
            If mOriginatingFactory Is Nothing Then Throw New MissingWiringException
            mConditions = mOriginatingFactory.ConditionFactories.CreateConditionCollection()
        End Sub

        Public Property Colors() As SensorTaskColors
            Get
                Return mColors
            End Get
            Set(ByVal value As SensorTaskColors)
                mColors = value
            End Set
        End Property

        Public ReadOnly Property IsEffectivelyUnstarted() As Boolean
            Get
                Return Status = SensorTaskStatus.Unstarted OrElse _
                       Status = SensorTaskStatus.Active AndAlso Attempts.Count = 0 OrElse _
                       Status = SensorTaskStatus.Suspended AndAlso mAttempts.Count = 0
            End Get
        End Property

        Public ReadOnly Property HasAttemptsInConflict(ByVal currentConditions As ConditionCollection, ByVal currentParts As BodyParts) _
        As Boolean
            Get
                For i As Integer = 0 To mAttempts.Count - 1
                    If mAttempts(i).IsConflictIgnored Then Return False
                    If mAttempts(i).InConflict(currentConditions, currentParts) Then Return True
                Next
            End Get
        End Property

        Public ReadOnly Property HasUnresolvedConflicts(ByVal currentConditions As ConditionCollection, ByVal currentParts As BodyParts) _
        As Boolean
            Get
                For i As Integer = 0 To mAttempts.Count - 1
                    If mAttempts(i).InConflict(currentConditions, currentParts) AndAlso Not mAttempts(i).IsConflictIgnored Then Return True
                Next
            End Get
        End Property

        Public ReadOnly Property HasOnlyCorrectedConflicts(ByVal currentConditions As ConditionCollection, ByVal currentParts As BodyParts) _
        As Boolean
            Get
                Dim notCorrected As Integer
                Dim corrected As Integer
                For i As Integer = 0 To mAttempts.Count - 1

                    If mAttempts(i).InConflict(currentConditions, currentParts) Then
                        If mAttempts(i).IsConflictIgnored Then
                            corrected += 1
                        Else
                            notCorrected += 1
                        End If
                    End If
                Next
                Return corrected > 0 AndAlso notCorrected = 0
            End Get
        End Property



        Public Property TargetCategory() As SensorTaskCategory
            Get
                Return mDefinition.TargetCategory
            End Get
            Set(ByVal value As SensorTaskCategory)
                With mDefinition
                    .TargetCategory = value
                    Select Case .TargetCategory
                        Case SensorTaskCategory.Face
                            .TargetParts = BodyParts.ForFaceTask
                        Case SensorTaskCategory.LeftIris
                            .TargetParts = BodyParts.ForLeftIrisTask
                        Case SensorTaskCategory.LeftSlap
                            .TargetParts = BodyParts.ForLeftSlapTask
                        Case SensorTaskCategory.RightIris
                            .TargetParts = BodyParts.ForRightIrisTask
                        Case SensorTaskCategory.RightSlap
                            .TargetParts = BodyParts.ForRightSlapTask
                        Case SensorTaskCategory.ThumbsSlap
                            .TargetParts = BodyParts.ForThumbsSlapTask
                        Case SensorTaskCategory.FlatLeftIndex
                            .TargetParts = BodyParts.ForFlatLeftIndex
                        Case SensorTaskCategory.FlatRightIndex
                            .TargetParts = BodyParts.ForFlatRightIndex
                        Case SensorTaskCategory.RolledLeftIndex
                            .TargetParts = BodyParts.ForRolledLeftIndex
                        Case SensorTaskCategory.RolledRightIndex
                            .TargetParts = BodyParts.ForRolledRightIndex
                        Case Else
                            Debugging.Break()
                    End Select
                End With
            End Set
        End Property

        Public Property Enabled() As Boolean
            Get
                Return mDefinition.Enabled
            End Get
            Set(ByVal value As Boolean)
                mDefinition.Enabled = value
            End Set
        End Property

        Public ReadOnly Property ReachedFailureLimit() As Boolean
            Get
                Return Attempts.Count >= mDefinition.MaximumAttempts
            End Get
        End Property

        Public Sub TallyAttempt( _
            ByVal parentTask As SensorTask, _
            ByVal conditions As ConditionCollection, _
            ByVal targetParts As BodyParts, _
            ByVal inaccessibleParts As BodyParts, _
            ByVal thumbnail As Image, _
            ByVal results As CaptureResultCollection, _
            ByVal captureFailure As SensorTaskFailure, _
            ByVal downloadId As Guid, _
            ByVal withDownloadStage As Boolean, _
            ByVal rejected As Boolean, _
            ByVal corrupt As Boolean)

            'ky
            Dim attempt As New SensorTaskAttempt( _
                     parentTask:=parentTask, _
                     conditions:=conditions, _
                     targetParts:=targetParts, _
                     inaccessibleParts:=inaccessibleParts, _
                     thumbnail:=thumbnail, _
                     results:=results, _
                     captureFailure:=captureFailure, _
                     downloadId:=downloadId, _
                     withDownloadStage:=withDownloadStage, _
                     rejected:=rejected, _
                     corrupt:=corrupt, _
                     conflictIgnore:=False)

            mAttempts.Add(attempt)

        End Sub


        Public ReadOnly Property Name(ByVal culture As CultureInfo) As String
            Get
                Return SensorTaskCategorySupport.ToString(culture, mDefinition.TargetCategory)
            End Get
        End Property

        Public Property Sensor() As ISensor
            Get
                Return mSensor
            End Get
            Set(ByVal value As ISensor)
                mSensor = value
            End Set
        End Property

        Public Property SensorConfiguration() As SensorConfiguration
            Get
                Return mDefinition.SensorConfiguration
            End Get
            Set(ByVal value As SensorConfiguration)
                mDefinition.SensorConfiguration = value
            End Set
        End Property

        Public ReadOnly Property Conditions() As ConditionCollection
            Get
                Return mConditions
            End Get
        End Property

        Public ReadOnly Property TargetParts() As BodyParts
            Get
                Return mDefinition.TargetParts
            End Get
        End Property
        Public Property TargetInaccessibleParts() As BodyParts
            Get
                Return mDefinition.TargetInaccessibleParts
            End Get
            Set(ByVal value As BodyParts)
                mDefinition.TargetInaccessibleParts = value
            End Set
        End Property

        Public Property Repetition() As Integer
            Get
                Return mDefinition.Repetition
            End Get
            Set(ByVal value As Integer)
                mDefinition.Repetition = value
            End Set
        End Property

        'Public ReadOnly Property CapturedParts() As BodyParts
        '    Get
        '        Return mDefinition.CapturedParts
        '    End Get
        'End Property


        Public Function FriendlyToString(ByVal culture As CultureInfo) As String
            Dim index As Integer = OriginatingFactory.ParentSensorTaskFactories.AllTasks.IndexOf(Me) + 1
            If index = 0 Then Debugging.Break()
            Dim writer As New IO.StringWriter(culture)
            With writer
                .Write(Parenthesize(culture, index.ToString(culture)))
                .Write(StringConstants.Space)
                .Write(Name(culture))
                If Conditions.FriendlyToString(culture) <> InfrastructureMessages.Messages.None(culture) Then
                    .Write(StringConstants.Space)
                    .Write(Parenthesize(culture, Conditions.FriendlyToString(culture)))
                End If
            End With
            Return writer.ToString
        End Function

        Public Overrides Function ToString() As String
            If Me.OriginatingFactory.GeneratedTasks.IndexOf(Me) = -1 Then Debugging.Break()
            Return "Factory """ & _
                Me.OriginatingFactory.Name & _
                """, Task " & _
                Me.OriginatingFactory.GeneratedTasks.IndexOf(Me) & " " & _
                Me.Status.ToString
        End Function

        Public ReadOnly Property IsBusy() As Boolean
            Get
                Return _
                    Status = SensorTaskStatus.Downloading OrElse _
                    Sensor.LatestStatus = SensorStatus.Capturing
                Status = SensorTaskStatus.Blocked
            End Get
        End Property

        Public ReadOnly Property IsExcess() As Boolean
            Get
                Return OriginatingFactory.GeneratedTasks.IndexOf(Me) >= OriginatingFactory.TaskCount
            End Get
        End Property


        Public ReadOnly Property HasSuccessfulCapture() As Boolean
            Get
                Dim count As Integer
                For i As Integer = 0 To mAttempts.Count - 1
                    Dim notRejected As Boolean = Not Attempts(i).IsRejected
                    Dim noCaptureFailure As Boolean = Not Attempts(i).HadCaptureFailure
                    Dim noDownloadFailure As Boolean = Not Attempts(i).HadDownloadFailure
                    Dim captureResultOkay As Boolean = Not Attempts(i).CaptureResults Is Nothing
                    If notRejected AndAlso noCaptureFailure AndAlso noDownloadFailure AndAlso captureResultOkay Then count += 1
                Next

                Return count > 0
            End Get
        End Property

        Public Sub RefreshStatus()


            If ReachedFailureLimit AndAlso NumberOfPendingDownloads = 0 Then
                mDefinition.Status = SensorTaskStatus.Done
                Return
            End If

            Dim notBusy As Boolean = Not IsBusy
            Dim successfulCapture As Boolean = HasSuccessfulCapture
            Dim noPendingDownloads As Boolean = NumberOfPendingDownloads = 0
            Dim downloadEnabled As Boolean = Not DownloadsSuspended

            If notBusy AndAlso successfulCapture Then
                If noPendingDownloads Then
                    mDefinition.Status = SensorTaskStatus.Done
                ElseIf downloadEnabled Then
                    mDefinition.Status = SensorTaskStatus.Pending
                End If
            End If

            ' Suspend a task that is pending, yet has no pending downloads
            Dim pending As Boolean = mDefinition.Status = SensorTaskStatus.Pending
            If pending AndAlso noPendingDownloads Then
                mDefinition.Status = SensorTaskStatus.Suspended
            End If

            ' Suspend a task that is done, but has no successful capture
            Dim done As Boolean = mDefinition.Status = SensorTaskStatus.Done
            If done AndAlso Not successfulCapture Then
                mDefinition.Status = SensorTaskStatus.Suspended
            End If

        End Sub

        Public ReadOnly Property IsSkippable() As Boolean
            Get
                If Status = SensorTaskStatus.Active OrElse Status = SensorTaskStatus.Unstarted Then Return True
                Return False
            End Get
        End Property
        Public ReadOnly Property NumberOfPendingDownloads() As Integer
            Get
                For i As Integer = 0 To mAttempts.Count - 1
                    If mAttempts(i).NeedsDownload Then NumberOfPendingDownloads += 1
                Next
            End Get
        End Property

        Public Property Status() As SensorTaskStatus
            Get
                ' Notice how we keep the information about 'IsBlocked' separate, so that we can return to the non-blocked state
                If mDefinition.IsBlocked AndAlso Not Me.HasSuccessfulCapture Then Return SensorTaskStatus.Blocked
                Return mDefinition.Status
            End Get
            Set(ByVal value As SensorTaskStatus)



                If value = SensorTaskStatus.Done Then
                    ' Automatically set the captured parts when the status is tagged as done
                    'mDefinition.CapturedParts.AddEachPartIfNotContainedInOtherSet( _
                    '    mDefinition.TargetInaccessibleParts, _
                    '    mDefinition.TargetParts)
                    mDefinition.Status = value

                ElseIf value = SensorTaskStatus.Skipped Then

                    Select Case Status
                        Case SensorTaskStatus.Active, SensorTaskStatus.Suspended, SensorTaskStatus.Unstarted
                            mDefinition.Status = value
                        Case Else
                            ' Leave as is
                    End Select


                Else
                    mDefinition.Status = value
                End If


            End Set
        End Property

        Public ReadOnly Property HasAccessibleParts() As Boolean
            Get
                Return Not TargetParts.Equals(TargetInaccessibleParts)
            End Get
        End Property

        Public ReadOnly Property IsActivatableLessMissingParts() As Boolean
            Get

                ' We can't activate (or keep activated) a done task 
                If Status = SensorTaskStatus.Done Then Return False

                ' Reaching the failure limit means we're not activatable
                If ReachedFailureLimit Then Return False

                ' Disabled tasks are not activatable
                If Not Enabled Then Return False

                ' Sensors are not disabled
                If mSensor.Disabled Then Return False

                ' Downloading tasks certainly aren't activatable
                If Status = SensorTaskStatus.Downloading Then Return False

                ' Sensor not only (and also not defered) are activatable
                If mSensor.LatestStatus <> SensorStatus.Online AndAlso Not mSensor.DeferInitialization Then
                    Return False
                End If

                ' Make sure our prerequisite has been met
                If OriginatingFactory.Prerequisite.Evaluate = False Then Return False

                If mDefinition.Status = SensorTaskStatus.Active Then
                    ' Active tasks, by definition, are activatable, by definition
                    Return True
                End If


                If mDefinition.Status = SensorTaskStatus.Suspended AndAlso mDefinition.DownloadsSuspended Then
                    ' Tasks that have their downloads suspended are *not* activatable if they've had a success
                    Return Not Me.HasSuccessfulCapture
                End If

                ' Tasks with a download pending are not activatable
                If mDefinition.Status = SensorTaskStatus.Pending Then Return False


                ' If we've made it this far, the task must be unstarted or below the failure limit
                Dim unstarted As Boolean = Status = SensorTaskStatus.Unstarted
                Dim suspended As Boolean = Status = SensorTaskStatus.Suspended
                Dim notAtFailureLimit As Boolean = Not ReachedFailureLimit

                Return unstarted Or suspended Or notAtFailureLimit
            End Get
        End Property

        Public ReadOnly Property IsActivatable() As Boolean
            Get
                ' With no accessible parts, give up
                If Not HasAccessibleParts Then Return False
                Return IsActivatableLessMissingParts
            End Get
        End Property

        Shared smDefaultMaximumAttempts As Integer = 3

        Public Property MaximumAttempts() As Integer
            Get
                Return mDefinition.MaximumAttempts
            End Get
            Set(ByVal value As Integer)
                mDefinition.MaximumAttempts = value
            End Set
        End Property

        Public Sub New()
            mDefinition.MaximumAttempts = smDefaultMaximumAttempts
        End Sub

        Public Function CompareTo(ByVal obj As Object) As Integer Implements System.IComparable.CompareTo
            Dim f1, f2, t1, t2 As Integer

            Dim other As SensorTask = DirectCast(obj, SensorTask)

            f1 = OriginatingFactory.ParentSensorTaskFactories.IndexOf(Me.OriginatingFactory)
            t1 = OriginatingFactory.GeneratedTasks.IndexOf(Me)

            f2 = other.OriginatingFactory.ParentSensorTaskFactories.IndexOf(other.OriginatingFactory)
            t2 = other.OriginatingFactory.GeneratedTasks.IndexOf(other)

            Dim rv As Integer
            If f1 = f2 Then
                If t1 = t2 Then
                    rv = 0
                ElseIf t1 < t2 Then
                    rv = -1
                Else
                    rv = 1
                End If

            ElseIf f1 < f2 Then
                rv = -1
            Else
                rv = 1
            End If

            Return rv
        End Function

        Public Property SkipDestination() As Boolean
            Get
                Return mDefinition.SkipDestination
            End Get
            Set(ByVal value As Boolean)
                mDefinition.SkipDestination = value
            End Set
        End Property

    End Class

End Namespace
