Public Class Game1
    Inherits Microsoft.Xna.Framework.Game

    Private WithEvents graphics As GraphicsDeviceManager
    Private WithEvents spriteBatch As SpriteBatch
    Private WithEvents _Server As UNOLibs.Net.ServerClass
    Private ListenerPortNumber As Integer = 7000 'the port number that the game listens on
    Private _Client As New UNOLibs.Net.ClientClass
    Private ClientPortNumber As Integer = 6666 ' the port number of the ACT-R client
    Private ClientIPAddress As String = "127.0.0.1" 'the IP address of the ACT-R client

    Public Camera As Camera
    Public CameraCarrier As CameraCarrier
    'Public GameMode As GameMode = GameMode.GalleryShapes

    Public World As VirtualWorld

    'Public ACTRMode As Boolean = True

    Private F As frmMonitor

    Public Sub New()
        graphics = New GraphicsDeviceManager(Me)
        Content.RootDirectory = "Content"
        Me._graphics.PreferMultiSampling = True
        Me.Window.AllowUserResizing = True
        Me.IsMouseVisible = True
        Me.graphics.PreferMultiSampling = True
        Me.World = New VirtualWorld(Me)
        'If Me.ACTRMode Then
        Me.F = New frmMonitor(Me, Me.World)
        Me.F.Show()
        'End If
    End Sub

    Protected Overrides Sub Initialize()
        Me.CameraCarrier = New CameraCarrier(Me, Me.World, Vector3.Zero, Vector3.Zero, "Models/cameracarrier")
        Me.CameraCarrier.Load()
        Me.CameraCarrier.Position = Vector3.Transform(Me.CameraCarrier.Position, Matrix.CreateTranslation(New Vector3(0, 0, -20)))
        Me.Camera = New Camera
        Me.World.Camera = Me.Camera
        Me.World.CameraCarrier = Me.CameraCarrier
        Me.ConfigurePerimeterPictureShapesGalleryWorld(Me.World)
        'If Me.ACTRMode = True Then
        Me.InitServer()
        'End If
        MyBase.Initialize()
    End Sub

    Private Sub ConfigurePerimeterPictureShapesGalleryWorld(world As VirtualWorld)
        'basic world with shape pictures around perimeter
        Me.ConfigureBasicWorld(world)
        world.AutoGeneratePerimeter = True
        'world.GameMode = Enumerations.GameMode.GalleryShapes
    End Sub

    Private Sub ConfigureBasicWorld(world As VirtualWorld)
        'create the basic floor plan of the virtual world
        Dim floorplan(,) As Integer = {
                                      {0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
                                      {0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
                                      {0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
                                      {0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
                                      {0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
                                      {0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
                                      {0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
                                      {0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
                                      {0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
                                      {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
                                  }
        world.FloorPlan = floorplan
        'do not draw any ceiling blocks
        world.DrawCeiling = False
    End Sub

    Protected Overrides Sub LoadContent()
        ' Create a new SpriteBatch, which can be used to draw textures.
        spriteBatch = New SpriteBatch(GraphicsDevice)
        MyBase.LoadContent()
    End Sub

    Protected Overrides Sub UnloadContent()
        ' TODO: Unload any non ContentManager content here
    End Sub

    Protected Overrides Sub Update(ByVal gameTime As GameTime)
        ' Allows the game to exit
        If GamePad.GetState(PlayerIndex.One).Buttons.Back = ButtonState.Pressed Then
            Me.Exit()
        End If
        Dim aspectRatio As Single = Me._graphics.GraphicsDevice.Viewport.AspectRatio
        Me.Camera.Update(Me.CameraCarrier.ForwardDirection, Me.CameraCarrier.Position, aspectRatio, Mouse.GetState)
        If Not Me.F Is Nothing Then
            'show the mouse coordinates
            DirectCast(Me.F, IGameForm).ProcessMouseCoordinates(Mouse.GetState.X, Mouse.GetState.Y)
        End If
        MyBase.Update(gameTime)
    End Sub

    Protected Overrides Sub Draw(ByVal gameTime As GameTime)
        GraphicsDevice.Clear(Color.CornflowerBlue)
        MyBase.Draw(gameTime)
    End Sub

    Private Sub InitServer()
        Try
            Me.StartServer()
        Catch ex As Exception
            Windows.Forms.MessageBox.Show(ex.Message)
            Exit Sub
        End Try
        Dim sw As New Stopwatch
        sw.Start()
        Do Until Me._Server.isRunning Or sw.ElapsedMilliseconds = 3000
            Threading.Thread.Sleep(500)
        Loop
        If Me._Server.isRunning = False Then
            Windows.Forms.MessageBox.Show("Could not start local server.")
            Exit Sub
        End If
    End Sub

    Friend Sub StartServer()
        Me._Server = New UNOLibs.Net.ServerClass(Me.ListenerPortNumber, True)
    End Sub

    Private Sub Server_IncomingMessage(eventargs As UNOLibs.Net.ServerClass.InMessEvArgs) Handles _Server.IncomingMessage
        Dim data As String = eventargs.message
        Dim m As Message = Nothing
        Try
            m = Message.Parse(data)
        Catch ex As EmptyMessageException
            'ignore any empty messages
            Exit Sub
        Catch ex As MessageNotInterpretableException
            'ignore any non-interpretable messages
            Exit Sub
        End Try
        If m Is Nothing Then Exit Sub
        Me.ProcessIncomingMessage(m)
    End Sub

    Private Sub SendMessage(msg As Message)
        If Not msg Is Nothing Then
            Try
                Me._Client.SendMessage(Me.ClientIPAddress, Me.ClientPortNumber, msg.ToString)
                If Not Me.F Is Nothing Then
                    DirectCast(F, IGameForm).OnMessageSentByGame(msg)
                End If
            Catch ex As UNOLibs.Net.IPNotReachableException
                Windows.Forms.MessageBox.Show("The message could not be sent.")
            Catch ex As Exception
                Windows.Forms.MessageBox.Show(ex.Message)
            End Try
        End If
    End Sub

    Private Function GetLocalIPAddres() As String
        Return Me.GetIPv4Address
    End Function

    Private Function GetIPv4Address() As String
        GetIPv4Address = String.Empty
        Dim strHostName As String = System.Net.Dns.GetHostName()
        Dim iphe As System.Net.IPHostEntry = System.Net.Dns.GetHostEntry(strHostName)
        For Each ipheal As System.Net.IPAddress In iphe.AddressList
            If ipheal.AddressFamily = System.Net.Sockets.AddressFamily.InterNetwork Then
                GetIPv4Address = ipheal.ToString()
            End If
        Next
    End Function

    Public Sub ConnectToACTR()
        Dim m As New Message("XNA-MONITOR", "CONNECT", Me.ListenerPortNumber.ToString, Me.GetLocalIPAddres, String.Empty)
        Me.SendMessage(m)
    End Sub

    Public Sub FinishACTRInitialization()
        Dim m As New Message("XNA-MONITOR", "FINISH-INITIALIZATION", String.Empty, String.Empty, String.Empty)
        Me.SendMessage(m)
    End Sub

    Private Sub ProcessIncomingMessage(m As Message)
        If Not Me.F Is Nothing Then
            DirectCast(Me.F, IGameForm).OnMessageRecievedByGame(m)
        End If
        Dim turnSpeed As Single = 0.05F
        Dim velocity As Single = 1.5F
        Dim movement As Vector3 = Vector3.Zero
        Dim turnAmount As Single = 0
        Select Case m.Text.ToUpper
            Case "MOVEFORWARD"
                movement.Z = 1
            Case "MOVEBACKWARD"
                movement.Z = -1
            Case "MOVELEFT"
                movement.X = 1
            Case "MOVERIGHT"
                movement.X = -1
            Case "LOOKLEFT"
                turnAmount = 1
            Case "LOOKRIGHT"
                turnAmount = -1
            Case "GETVISICON"
                Me.GetVisicon()
                Me.UpdateACTRDisplay()
                Exit Sub
            Case "HIGHLIGHTVISICON"
                Me.HighlightVisicon()
                Exit Sub
            Case "RESETVISICON"
                Me.ResetVisiconHighlight()
                Exit Sub
            Case "SHOOT"
                Me.CameraCarrier.Shoot()
                Exit Sub
            Case Else
                Exit Sub
        End Select
        With Me.CameraCarrier
            Dim futurePosition As Vector3 = .Position
            .ForwardDirection += turnAmount * turnSpeed
            Dim orientationMatrix As Matrix = Matrix.CreateRotationY(.ForwardDirection)
            Dim speed As Vector3 = Vector3.Transform(movement, orientationMatrix)
            speed *= velocity
            futurePosition = .Position + speed
            Dim ignorableObjects As New List(Of WorldObject)
            ignorableObjects.Add(Me.World.FloorBlock)
            If .ValidateMovement(futurePosition, .BoundingSphere, ignorableObjects) Then
                .Position = futurePosition
            End If
        End With
    End Sub

    Private Sub HighlightVisicon()
        Me.ResetVisiconHighlight()
        For Each o As WorldObject In Me.GetVisiconObjects
            o.BoundingBoxRenderColor = Color.White
            o.DrawBoundingBox = True
        Next
    End Sub

    Private Sub ResetVisiconHighlight()
        For Each o As WorldObject In Me.GetPotentialVisiconObjects
            o.DrawBoundingBox = False
            o.BoundingBoxRenderColor = Color.White
        Next
    End Sub

    Private Sub UpdateACTRDisplay()
        Dim m As New Message("XNA-MONITOR", "UPDATEDISPLAY", String.Empty, String.Empty, String.Empty)
        Me.SendMessage(m)
    End Sub

    Private Sub GetVisicon()
        Dim view As Matrix = Me.Camera.ViewMatrix
        Dim projection As Matrix = Me.Camera.ProjectionMatrix
        Dim s As String = String.Empty
        Dim m As Message
        For Each o As WorldObject In Me.GetVisiconObjects
            s = String.Empty
            s &= o.Position.X & "," 'posx
            s &= o.Position.Y & "," 'posy
            s &= o.Position.Z & "," 'posz
            s &= o.Size.X & "," 'height
            s &= o.Size.Y & "," 'width
            s &= Vector3.Distance(Me.CameraCarrier.Position, o.Position) & "," 'distance
            Dim worldMatrix As Matrix = Matrix.Identity
            'get screen position of center point
            Dim screenCenterPosition As Vector3 = Me.GraphicsDevice.Viewport.Project(o.Position, projection, view, Matrix.Identity)
            s &= screenCenterPosition.X & "," 'screen-x
            s &= screenCenterPosition.Y & "," 'screen-y
            'get screen positions of min and max bounding box points
            Dim BBoXMin, BBoxMax As Vector3
            BBoXMin = Me.GraphicsDevice.Viewport.Project(o.BoundingBox.Min, projection, view, worldMatrix)
            BBoxMax = Me.GraphicsDevice.Viewport.Project(o.BoundingBox.Max, projection, view, worldMatrix)
            s &= Math.Abs(BBoxMax.X - BBoXMin.X).ToString & "," 'screen width
            s &= Math.Abs(BBoxMax.Y - BBoXMin.Y).ToString & "," 'screen height
            s &= o.GetType.Name.ToUpper & ","
            s &= "UNKNOWN"
            m = New Message("XNA-MONITOR", "GETVISICON", s, String.Empty, String.Empty)
            Me.SendMessage(m)
        Next
    End Sub

    Private Function GetVisiconObjects() As IEnumerable(Of WorldObject)
        Dim frustrum As BoundingFrustum = New BoundingFrustum(Me.Camera.ViewMatrix * Me.Camera.ProjectionMatrix)
        Dim l As New List(Of WorldObject)
        Return Me.GetPotentialVisiconObjects.Where(Function(o) frustrum.Contains(o.BoundingBox) <> ContainmentType.Disjoint)
    End Function

    Private Function GetPotentialVisiconObjects() As List(Of WorldObject)
        Dim l As New List(Of WorldObject)
        l.AddRange(Me.World.Pictures)
        Return l
    End Function
End Class

Public Interface IVirtualWorldGame

    ReadOnly Property SpriteBatch As SpriteBatch

End Interface
