Imports System
Imports System.Linq
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Media.Animation


Imports Microsoft.Kinect

' リスト 5-3
Public Enum GamePhase
    GameOver = 0
    SimonInstructing = 1
    PlayerPerforming = 2
End Enum

Class MainWindow
#Region "メンバー変数"
    Private _KinectDevice As KinectSensor
    Private _FrameSkeletons() As Skeleton
    Private _CurrentPhase As GamePhase
    Private _InstructionSequence() As UIElement
    Private _InstructionPosition As Integer
    Private _CurrentLevel As Integer
    Private rnd As Random = New Random()
    Private _LeftHandTarget As IInputElement
    Private _RightHandTarget As IInputElement
#End Region


#Region "コンストラクタ"
    Sub New()

        ' この呼び出しはデザイナーで必要です。
        InitializeComponent()

        ' InitializeComponent() 呼び出しの後で初期化を追加します。
        AddHandler KinectSensor.KinectSensors.StatusChanged, AddressOf KinectSensors_StatusChanged
        Me.KinectDevice = KinectSensor.KinectSensors.FirstOrDefault(Function(x)
                                                                        Return x.Status = KinectStatus.Connected
                                                                    End Function)
        Me._CurrentLevel = 0
        ChangePhase(GamePhase.GameOver)
    End Sub
#End Region


#Region "メソッド"
    Private Sub KinectSensors_StatusChanged(sender As Object, e As StatusChangedEventArgs)
        Select Case e.Status
            Case KinectStatus.Initializing,
                KinectStatus.Connected,
                KinectStatus.NotPowered,
                KinectStatus.NotReady,
                KinectStatus.DeviceNotGenuine
                Me.KinectDevice = e.Sensor
            Case KinectStatus.Disconnected
                'TODO: Give the user feedback to plug-in a Kinect device.                    
                Me.KinectDevice = Nothing
            Case Else
                'TODO: Show an error state
        End Select
    End Sub


    ' リスト 5-2 & 5-4
    Private Sub KinectDevice_SkeletonFrameReady(sender As Object, e As SkeletonFrameReadyEventArgs)
        Using frame As SkeletonFrame = e.OpenSkeletonFrame()
            If frame IsNot Nothing Then
                frame.CopySkeletonDataTo(Me._FrameSkeletons)
                Dim _skeleton As Skeleton = GetPrimarySkeleton(Me._FrameSkeletons)

                If _skeleton Is Nothing Then
                    ChangePhase(GamePhase.GameOver)
                Else
                    If Me._CurrentPhase = GamePhase.SimonInstructing Then
                        LeftHandElement.Visibility = Visibility.Collapsed
                        RightHandElement.Visibility = Visibility.Collapsed
                    Else
                        TrackHand(_skeleton.Joints(JointType.HandLeft),
                                  LeftHandElement,
                                  LayoutRoot)
                        TrackHand(_skeleton.Joints(JointType.HandRight),
                                  RightHandElement,
                                  LayoutRoot)

                        Select Case Me._CurrentPhase
                            Case GamePhase.GameOver
                                ProcessGameOver(_skeleton)
                            Case GamePhase.PlayerPerforming
                                ProcessPlayerPerforming(_skeleton)
                        End Select
                    End If
                End If
            End If
        End Using
    End Sub


    ' リスト 5-2
    Private Shared Function GetPrimarySkeleton(skeletons() As Skeleton) As Skeleton
        Dim _skeleton As Skeleton = Nothing

        If skeletons IsNot Nothing Then
            'Find the closest skeleton       
            For i As Integer = 0 To skeletons.Length - 1
                If skeletons(i).TrackingState = SkeletonTrackingState.Tracked Then
                    If _skeleton Is Nothing Then
                        _skeleton = skeletons(i)
                    Else
                        If _skeleton.Position.Z > skeletons(i).Position.Z Then
                            _skeleton = skeletons(i)
                        End If
                    End If
                End If
            Next
        End If

        Return _skeleton
    End Function


    Private Shared Function GetJointPoint(kinectDevice As KinectSensor, _joint As Joint, containerSize As Size, offset As Point) As Point
        Dim _point As DepthImagePoint = kinectDevice.MapSkeletonPointToDepth(_joint.Position,
                                                                             kinectDevice.DepthStream.Format)
        _point.X = Fix((_point.X * containerSize.Width / kinectDevice.DepthStream.FrameWidth) - offset.X)
        _point.Y = Fix((_point.Y * containerSize.Height / kinectDevice.DepthStream.FrameHeight) - offset.Y)

        Return New Point(_point.X, _point.Y)
    End Function

    Private Sub TrackHand(hand As Joint, cursorElement As FrameworkElement, container As FrameworkElement)
        If hand.TrackingState = JointTrackingState.NotTracked Then
            cursorElement.Visibility = Visibility.Collapsed
        Else
            cursorElement.Visibility = Visibility.Visible
            Dim jointPoint As Point = GetJointPoint(Me.KinectDevice,
                                                    hand,
                                                    container.RenderSize,
                                                    New Point(cursorElement.ActualWidth / 2.0,
                                                              cursorElement.ActualHeight / 2.0))
            Canvas.SetLeft(cursorElement, jointPoint.X)
            Canvas.SetTop(cursorElement, jointPoint.Y)
        End If
    End Sub

    ' リスト 5-5
    Private Sub ProcessGameOver(_skeleton As Skeleton)
        'Determine if the user triggers to start of a new game
        If HitTest(_skeleton.Joints(JointType.HandLeft), LeftHandStartElement) AndAlso
            HitTest(_skeleton.Joints(JointType.HandRight), RightHandStartElement) Then
            ChangePhase(GamePhase.SimonInstructing)
        End If
    End Sub

    Private Function HitTest(_joint As Joint, target As UIElement) As Boolean
        Return (GetHitTarget(_joint, target) IsNot Nothing)
    End Function

    ' リスト 5-5
    Private Function GetHitTarget(_joint As Joint, target As UIElement) As IInputElement
        Dim targetPoint As Point = LayoutRoot.TranslatePoint(GetJointPoint(Me.KinectDevice,
                                                                           _joint,
                                                                           LayoutRoot.RenderSize,
                                                                           New Point()),
                                                                       target)
        Return target.InputHitTest(targetPoint)
    End Function


    ' リスト 5-6
    Private Sub ChangePhase(newPhase As GamePhase)
        If newPhase <> Me._CurrentPhase Then
            Me._CurrentPhase = newPhase

            Select Case Me._CurrentPhase
                Case GamePhase.GameOver
                    Me._CurrentLevel = 0
                    RedBlock.Opacity = 0.2
                    BlueBlock.Opacity = 0.2
                    GreenBlock.Opacity = 0.2
                    YellowBlock.Opacity = 0.2

                    GameStateElement.Text = "GAME OVER!"
                    ControlCanvas.Visibility = Visibility.Visible
                    GameInstructionsElement.Text = "ターゲットに手をかざすと新しいゲームを開始します。"

                Case GamePhase.SimonInstructing
                    Me._CurrentLevel += 1
                    GameStateElement.Text = String.Format("Level {0}", Me._CurrentLevel)
                    ControlCanvas.Visibility = Visibility.Collapsed
                    GameInstructionsElement.Text = "サイモンの指示順に動きましょう。"
                    GenerateInstructions()
                    DisplayInstructions()

                Case GamePhase.PlayerPerforming
                    Me._InstructionPosition = 0
                    GameInstructionsElement.Text = "サイモンの指示を繰り返して。"
            End Select
        End If
    End Sub

    ' リスト 5-7
    Private Sub GenerateInstructions()
        ReDim Me._InstructionSequence(Me._CurrentLevel - 1)

        For i As Integer = 0 To Me._CurrentLevel - 1
            Select rnd.Next(1, 4)
                Case 1
                    Me._InstructionSequence(i) = RedBlock
                Case 2
                    Me._InstructionSequence(i) = BlueBlock
                Case 3
                    Me._InstructionSequence(i) = GreenBlock
                Case 4
                    Me._InstructionSequence(i) = YellowBlock
            End Select
        Next
    End Sub

    ' リスト 5-7
    Private Sub DisplayInstructions()
        Dim instructionsSequence As Storyboard = New Storyboard()
        Dim Animation As DoubleAnimationUsingKeyFrames

        For i As Integer = 0 To Me._InstructionSequence.Length - 1
            Me._InstructionSequence(i).ApplyAnimationClock(FrameworkElement.OpacityProperty, Nothing)

            Animation = New DoubleAnimationUsingKeyFrames()
            Animation.FillBehavior = FillBehavior.Stop
            Animation.BeginTime = TimeSpan.FromMilliseconds(i * 1500)
            Storyboard.SetTarget(Animation, Me._InstructionSequence(i))
            Storyboard.SetTargetProperty(Animation, New PropertyPath("Opacity"))
            instructionsSequence.Children.Add(Animation)

            Animation.KeyFrames.Add(New EasingDoubleKeyFrame(0.3, KeyTime.FromTimeSpan(TimeSpan.Zero)))
            Animation.KeyFrames.Add(New EasingDoubleKeyFrame(1, KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(500))))
            Animation.KeyFrames.Add(New EasingDoubleKeyFrame(1, KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(1000))))
            Animation.KeyFrames.Add(New EasingDoubleKeyFrame(0.3, KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(1300))))
        Next

        AddHandler instructionsSequence.Completed, Sub(s, e)
                                                       ChangePhase(GamePhase.PlayerPerforming)
                                                   End Sub
        instructionsSequence.Begin(LayoutRoot)
    End Sub

    'As shown in リスト 5-8 with UI bug
    'Private Sub ProcessPlayerPerforming(_skeleton As Skeleton)
    '    'Determine if user is hitting a target and if that target is in the correct sequence.
    '    Dim correctTarget As UIElement = Me._InstructionSequence(Me._InstructionPosition)
    '    Dim leftTarget As IInputElement = GetHitTarget(_skeleton.Joints(JointType.HandLeft), GameCanvas)
    '    Dim rightTarget As IInputElement = GetHitTarget(_skeleton.Joints(JointType.HandRight), GameCanvas)


    '    If leftTarget IsNot Nothing AndAlso rightTarget IsNot Nothing Then
    '        ChangePhase(GamePhase.GameOver)
    '    ElseIf leftTarget Is Nothing AndAlso rightTarget Is Nothing Then
    '        'Do nothing - target found
    '    ElseIf (leftTarget Is correctTarget AndAlso rightTarget Is Nothing) OrElse
    '                (rightTarget Is correctTarget AndAlso leftTarget Is Nothing) Then
    '        Me._InstructionPosition += 1

    '        If Me._InstructionPosition >= Me._InstructionSequence.Length Then
    '            ChangePhase(GamePhase.SimonInstructing)
    '        End If
    '    Else
    '        ChangePhase(GamePhase.GameOver)
    '    End If
    'End Sub


    'As shown in リスト 5-9 that fixes the UI bug
    Private Sub ProcessPlayerPerforming(_skeleton As Skeleton)
        'Determine if user is hitting a target and if that target is in the correct sequence.
        Dim correctTarget As UIElement = Me._InstructionSequence(Me._InstructionPosition)
        Dim leftTarget As IInputElement = GetHitTarget(_skeleton.Joints(JointType.HandLeft), GameCanvas)
        Dim rightTarget As IInputElement = GetHitTarget(_skeleton.Joints(JointType.HandRight), GameCanvas)
        Dim hasTargetChange As Boolean = (leftTarget IsNot Me._LeftHandTarget) OrElse (rightTarget IsNot Me._RightHandTarget)


        If hasTargetChange Then
            If leftTarget IsNot Nothing AndAlso rightTarget IsNot Nothing Then
                ChangePhase(GamePhase.GameOver)
            ElseIf (_LeftHandTarget Is correctTarget AndAlso _RightHandTarget Is Nothing) OrElse
                    (_RightHandTarget Is correctTarget AndAlso _LeftHandTarget Is Nothing) Then
                Me._InstructionPosition += 1

                If Me._InstructionPosition >= Me._InstructionSequence.Length Then
                    ChangePhase(GamePhase.SimonInstructing)
                End If
            ElseIf leftTarget IsNot Nothing OrElse rightTarget IsNot Nothing Then
                '何も行わない - ターゲットを検出
            Else
                ChangePhase(GamePhase.GameOver)
            End If


            If leftTarget IsNot Me._LeftHandTarget Then
                If Me._LeftHandTarget IsNot Nothing Then
                    CType(Me._LeftHandTarget, FrameworkElement).Opacity = 0.2
                End If

                If leftTarget IsNot Nothing Then
                    CType(leftTarget, FrameworkElement).Opacity = 1
                End If

                Me._LeftHandTarget = leftTarget
            End If


            If rightTarget IsNot Me._RightHandTarget Then
                If Me._RightHandTarget IsNot Nothing Then
                    CType(Me._RightHandTarget, FrameworkElement).Opacity = 0.2
                End If

                If rightTarget IsNot Nothing Then
                    CType(rightTarget, FrameworkElement).Opacity = 1
                End If

                Me._RightHandTarget = rightTarget
            End If
        End If
    End Sub
#End Region


#Region "プロパティ"
    Public Property KinectDevice As KinectSensor
        Get
            Return Me._KinectDevice
        End Get
        Set(value As KinectSensor)
            If Me._KinectDevice IsNot value Then
                'Uninitialize
                If Me._KinectDevice IsNot Nothing Then
                    Me._KinectDevice.Stop()
                    RemoveHandler Me._KinectDevice.SkeletonFrameReady, AddressOf KinectDevice_SkeletonFrameReady
                    Me._KinectDevice.SkeletonStream.Disable()
                    SkeletonViewerElement.KinectDevice = Nothing
                    Me._FrameSkeletons = Nothing
                End If

                Me._KinectDevice = value

                'Initialize
                If Me._KinectDevice IsNot Nothing Then
                    If Me._KinectDevice.Status = KinectStatus.Connected Then
                        Me._KinectDevice.SkeletonStream.Enable()
                        ReDim Me._FrameSkeletons(Me._KinectDevice.SkeletonStream.FrameSkeletonArrayLength - 1)

                        Me._KinectDevice.Start()

                        SkeletonViewerElement.KinectDevice = Me.KinectDevice
                        AddHandler Me.KinectDevice.SkeletonFrameReady, AddressOf KinectDevice_SkeletonFrameReady
                    End If
                End If
            End If
        End Set
    End Property
#End Region

End Class
