Imports System
Imports System.Linq
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Media
Imports System.Windows.Shapes

Imports Microsoft.Kinect

''' <summary>
''' Interaction logic for MainWindow.xaml
''' </summary>
''' <remarks></remarks>
Class MainWindow
#Region "メンバー変数"
    Private _KinectDevice As KinectSensor
    Private _Puzzle As DotPuzzle
    Private _PuzzleDotIndex As Integer
    Private _FrameSkeletons() As Skeleton
#End Region


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

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

        ' InitializeComponent() 呼び出しの後で初期化を追加します。
        'サンプルパズル
        Me._Puzzle = New DotPuzzle()
        Me._Puzzle.Dots.Add(New Point(200, 300))
        Me._Puzzle.Dots.Add(New Point(1600, 300))
        Me._Puzzle.Dots.Add(New Point(1650, 400))
        Me._Puzzle.Dots.Add(New Point(1600, 500))
        Me._Puzzle.Dots.Add(New Point(1000, 500))
        Me._Puzzle.Dots.Add(New Point(1000, 600))
        Me._Puzzle.Dots.Add(New Point(1200, 700))
        Me._Puzzle.Dots.Add(New Point(1150, 800))
        Me._Puzzle.Dots.Add(New Point(750, 800))
        Me._Puzzle.Dots.Add(New Point(700, 700))
        Me._Puzzle.Dots.Add(New Point(900, 600))
        Me._Puzzle.Dots.Add(New Point(900, 500))
        Me._Puzzle.Dots.Add(New Point(200, 500))
        Me._Puzzle.Dots.Add(New Point(150, 400))

        Me._PuzzleDotIndex = -1

        AddHandler Me.Loaded, Sub(s, e)
                                  AddHandler KinectSensor.KinectSensors.StatusChanged, AddressOf KinectSensors_StatusChanged
                                  Me.KinectDevice = KinectSensor.KinectSensors.FirstOrDefault(Function(x)
                                                                                                  Return x.Status = KinectStatus.Connected
                                                                                              End Function)
                                  DrawPuzzle(Me._Puzzle)
                              End Sub
    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：Kinectデバイスを接続するようユーザーに求める    
                Me.KinectDevice = Nothing
            Case Else
                'TODO：エラー状態を表示する
        End Select
    End Sub

    ' Listing 4-5
    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
                    HandCursorElement.Visibility = Visibility.Collapsed
                Else
                    Dim primaryHand As Joint = GetPrimaryHand(_skeleton)
                    TrackHand(primaryHand)
                    TrackPuzzle(primaryHand.Position)
                End If
            End If
            end using
    End Sub

    ' Listing 4-5
    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

    ' Listing 4-6                       
    Private Shared Function GetPrimaryHand(_skeleton As Skeleton) As Joint
        Dim primaryHand As Joint = New Joint()

        If _skeleton IsNot Nothing Then
            primaryHand = _skeleton.Joints(JointType.HandLeft)
            Dim righHand As Joint = _skeleton.Joints(JointType.HandRight)

            If righHand.TrackingState <> JointTrackingState.NotTracked Then
                If primaryHand.TrackingState = JointTrackingState.NotTracked Then
                    primaryHand = righHand
                Else
                    If primaryHand.Position.Z > righHand.Position.Z Then
                        primaryHand = righHand
                    End If
                End If
            End If
        End If
        Return primaryHand
    End Function


    ' Listing 4-7
    Private Sub TrackHand(hand As Joint)
        If hand.TrackingState = JointTrackingState.NotTracked Then
            HandCursorElement.Visibility = Visibility.Collapsed
        Else
            HandCursorElement.Visibility = Visibility.Visible


            Dim _point As DepthImagePoint = Me._KinectDevice.MapSkeletonPointToDepth(hand.Position, Me._KinectDevice.DepthStream.Format)
            _point.X = Fix((_point.X * LayoutRoot.ActualWidth / _KinectDevice.DepthStream.FrameWidth) - (HandCursorElement.ActualWidth / 2.0))
            _point.Y = Fix((_point.Y * LayoutRoot.ActualHeight / _KinectDevice.DepthStream.FrameHeight) - (HandCursorElement.ActualHeight / 2.0))

            Canvas.SetLeft(HandCursorElement, _point.X)
            Canvas.SetTop(HandCursorElement, _point.Y)

            If hand.JointType = JointType.HandRight Then
                HandCursorScale.ScaleX = 1
            Else
                HandCursorScale.ScaleX = -1
            End If
        End If
    End Sub


    ' Listing 4-10 
    Private Sub DrawPuzzle(puzzle As DotPuzzle)
        PuzzleBoardElement.Children.Clear()

        If puzzle IsNot Nothing Then
            For i As Integer = 0 To puzzle.Dots.Count - 1
                Dim dotContainer As Grid = New Grid()
                dotContainer.Width = 50
                dotContainer.Height = 50
                dotContainer.Children.Add(New Ellipse With {.Fill = Brushes.Gray})

                Dim dotLabel As TextBlock = New TextBlock()
                dotLabel.Text = (i + 1).ToString()
                dotLabel.Foreground = Brushes.White
                dotLabel.FontSize = 24
                dotLabel.HorizontalAlignment = HorizontalAlignment.Center
                dotLabel.VerticalAlignment = VerticalAlignment.Center
                dotContainer.Children.Add(dotLabel)

                'UI要素の位置を点の中央にする
                Canvas.SetTop(dotContainer, puzzle.Dots(i).Y - (dotContainer.Height / 2))
                Canvas.SetLeft(dotContainer, puzzle.Dots(i).X - (dotContainer.Width / 2))
                PuzzleBoardElement.Children.Add(dotContainer)
            Next
        End If
    End Sub


    ' Listing 4-11
    Private Sub TrackPuzzle(_position As SkeletonPoint)
        If Me._PuzzleDotIndex = Me._Puzzle.Dots.Count Then
            '何もしない(ゲームオーバー)
        Else
            Dim dot As Point

            If Me._PuzzleDotIndex + 1 < Me._Puzzle.Dots.Count Then
                dot = Me._Puzzle.Dots(Me._PuzzleDotIndex + 1)
            Else
                dot = Me._Puzzle.Dots(0)
            End If

            Dim _point As DepthImagePoint = Me._KinectDevice.MapSkeletonPointToDepth(_position,
                                                                                     _KinectDevice.DepthStream.Format)
            _point.X = Fix(_point.X * LayoutRoot.ActualWidth / _KinectDevice.DepthStream.FrameWidth)
            _point.Y = Fix(_point.Y * LayoutRoot.ActualHeight / _KinectDevice.DepthStream.FrameHeight)
            Dim handPoint As Point = New Point(_point.X, _point.Y)


            '2点間の距離(length)を計算する。この処理は、
            '以下のように手動で行うか、System.Windows.Vectorオブジェクトを使用して長さを取得する
            'System.Windows.Media.Media3D.Vector3Dは、3Dベクトル演算に使用できる
            Dim dotDiff As Point = New Point(dot.X - handPoint.X, dot.Y - handPoint.Y)
            Dim length As Double = Math.Sqrt(dotDiff.X * dotDiff.X + dotDiff.Y * dotDiff.Y)

            Dim lastPoint As Integer = Me.CrayonElement.Points.Count - 1

            If length < 25 Then
                'カーソルがヒットゾーン内に入った

                If lastPoint > 0 Then
                    '現在の終点を削除する
                    Me.CrayonElement.Points.RemoveAt(lastPoint)
                End If
                '線の終点を設定する
                Me.CrayonElement.Points.Add(New Point(dot.X, dot.Y))

                '線の新しい始点を設定する
                Me.CrayonElement.Points.Add(New Point(dot.X, dot.Y))

                '次の点に移動する 
                Me._PuzzleDotIndex += 1

                If Me._PuzzleDotIndex = Me._Puzzle.Dots.Count Then
                    'ゲームオーバーをプレイヤーに知らせる
                End If
            Else
                If lastPoint > 0 Then
                    'Polylineの表示を最新状態に更新するには、終点を削除して、
                    '更新してから再度追加する
                    Dim lineEndpoint As Point = Me.CrayonElement.Points(lastPoint)
                    Me.CrayonElement.Points.RemoveAt(lastPoint)
                    lineEndpoint.X = handPoint.X
                    lineEndpoint.Y = handPoint.Y
                    Me.CrayonElement.Points.Add(lineEndpoint)
                End If
            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
                'リソースを解放する
                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

                '初期化する
                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
