Public MustInherit Class WorldObject
    Inherits DrawableGameComponent
    Implements ICollidable

    Private _World As VirtualWorld
    Private _OriginalRasterizerState As RasterizerState
    Private _DrawBoundingBox As Boolean = False
    Private _DrawWireFrame As Boolean = False
    Private _UseBoundingBoxForCollisions As Boolean = True
    Private _IsSolid As Boolean = True
    Protected _BoundingBox As BoundingBox
    Protected _BoundingSphere As BoundingSphere
    Private _Position As Vector3
    Private _Rotation As Vector3

    Public Property UpdateBoundingBoxFirst As Boolean = True
    Public Property RotationOffset As Vector3 = Vector3.Zero
    Public Property BoundingBoxRenderColor As Color = Color.White

#Region "Rotation and Position"

    Public Property Rotation As Vector3
        Get
            Return Me._Rotation
        End Get
        Set(value As Vector3)
            Me._Rotation = value
            Me.OnRotationChanged()
        End Set
    End Property

    Public Property Position As Vector3
        Get
            Return Me._Position
        End Get
        Set(value As Vector3)
            Me._Position = value
            Me.OnPositionChanged()
        End Set
    End Property

    Public Overridable Sub OnRotationChanged()
        'invoked whenever the object rotation has changed
        Me.UpdateBoundingObjects()
    End Sub

    Public Overridable Sub OnPositionChanged()
        Me.UpdateBoundingObjects()
    End Sub

    Public Property RotationX As Single
        Get
            Return Me.Rotation.X
        End Get
        Set(value As Single)
            Me.Rotation = New Vector3(value, Me.Rotation.Y, Me.Rotation.Z)
        End Set
    End Property

    Public Property RotationY As Single
        Get
            Return Me.Rotation.Y
        End Get
        Set(value As Single)
            Me.Rotation = New Vector3(Me.RotationX, value, Me.Rotation.Z)
        End Set
    End Property

    Public Property RotationZ As Single
        Get
            Return Me.Rotation.Z
        End Get
        Set(value As Single)
            Me.Rotation = New Vector3(Me.RotationX, Me.Rotation.Y, value)
        End Set
    End Property

#End Region

    Public Property DrawWireFrame As Boolean
        Get
            Return Me._DrawWireFrame
        End Get
        Set(value As Boolean)
            Me._DrawWireFrame = value
            If value = True And Me.DrawBoundingBox = True Then
                'prevent simultaneous rendering of wireframe and bounding box
                Me.DrawBoundingBox = False
            End If
        End Set
    End Property

    Public Property DrawBoundingBox As Boolean
        Get
            Return Me._DrawBoundingBox
        End Get
        Set(value As Boolean)
            Me._DrawBoundingBox = value
            'prevent simultaneous rendering of wireframe and bounding box
            If value = True And Me.DrawWireFrame = True Then
                Me.DrawWireFrame = False
            End If
        End Set
    End Property

#Region "Bounding Objects"

    Public Overridable Sub UpdateBoundingObjects()
        'updates the bouding objects
        If Me.UpdateBoundingBoxFirst Then
            Me.UpdateBoundingBox()
            Me.UpdateBoundingSphere()
        Else
            Me.UpdateBoundingSphere()
            Me.UpdateBoundingBox()
        End If
    End Sub

    Public ReadOnly Property BoundingBox As Microsoft.Xna.Framework.BoundingBox Implements ICollidable.BoundingBox
        Get
            Return Me._BoundingBox
        End Get
    End Property

    Public ReadOnly Property BoundingSphere As Microsoft.Xna.Framework.BoundingSphere Implements ICollidable.BoundingSphere
        Get
            Return Me._BoundingSphere
        End Get
    End Property

    Protected Friend MustOverride Sub UpdateBoundingBox() Implements ICollidable.UpdateBoundingBox

    Protected Friend Overridable Sub UpdateBoundingSphere() Implements ICollidable.UpdateBoundingSphere
        Me._BoundingSphere = BoundingSphere.CreateFromBoundingBox(Me.BoundingBox)
    End Sub

    Public Property UseBoundingBoxForCollisions As Boolean Implements ICollidable.UseBoundingBoxForCollisions
        Get
            Return Me._UseBoundingBoxForCollisions
        End Get
        Set(value As Boolean)
            Me._UseBoundingBoxForCollisions = value
        End Set
    End Property

    Public Property IsSolid As Boolean
        Get
            Return Me._IsSolid
        End Get
        Set(value As Boolean)
            Me._IsSolid = value
        End Set
    End Property

    Public Overridable ReadOnly Property IsImpassable As Boolean
        Get
            'an object must be visible and solid for it to be truly collidable
            If Me.Visible And Me.IsSolid Then Return True
            Return False
        End Get
    End Property

    Public Overridable Function ValidateMovement(futurePosition As Vector3, futureBoundingSphere As BoundingSphere, ignorableObjects As List(Of WorldObject)) As Boolean
        futureBoundingSphere.Center.X = futurePosition.X
        futureBoundingSphere.Center.Z = futurePosition.Z
        For Each o As GameComponent In Me.World.GetAllWorldObjects
            If Not o Is Me Then
                If TypeOf o Is WorldObject Then
                    Dim ow As WorldObject = DirectCast(o, WorldObject)
                    If ignorableObjects.Contains(ow) = False Then
                        If ow.UseBoundingBoxForCollisions Then
                            If futureBoundingSphere.Intersects(ow.BoundingBox) Then
                                Return False
                            End If
                        Else
                            If futureBoundingSphere.Intersects(ow.BoundingSphere) Then
                                Return False
                            End If
                        End If
                    End If
                End If
            End If

        Next
        Return True
    End Function

    Public Overridable Sub OnCollide(targetObject As WorldObject)
        'invoked whenever the current object collides with another object
    End Sub

    Public Function CollidesWith(boundingSphere As BoundingSphere, o As WorldObject) As Boolean
        'indicates whether the specified boundingsphere collides with the specified object
        If o.UseBoundingBoxForCollisions Then
            If boundingSphere.Intersects(o.BoundingBox) Then
                Return True
            End If
        Else
            If boundingSphere.Intersects(o.BoundingSphere) Then
                Return True
            End If
        End If
        Return False
    End Function

    Public Function DetectCollisions(boundingSphere As BoundingSphere, l As IEnumerable(Of WorldObject)) As List(Of WorldObject)
        Dim l2 As New List(Of WorldObject)
        For Each o As WorldObject In l
            If Me.CollidesWith(boundingSphere, o) = True Then
                l2.Add(o)
            End If
        Next
        Return l2
    End Function

#End Region

    Sub New(game As Game, world As VirtualWorld, position As Vector3, rotation As Vector3)
        MyBase.New(game)
        'add the object to the game components collection
        Me.Game.Components.Add(Me)
        Me._World = world
        Me._Position = position
        Me._Rotation = rotation
    End Sub

    Public Overridable Sub OnShot(bullet As Bullet)
        'invoked whenever the object is shot by a bullet
    End Sub

   
    Public ReadOnly Property World As VirtualWorld
        Get
            Return Me._World
        End Get
    End Property

    Public MustOverride ReadOnly Property Size As Vector3

    Public Function Clone() As WorldObject
        Dim o As WorldObject = DirectCast(Me.MemberwiseClone, WorldObject)
        o.Game.Components.Add(o)
        Return o
    End Function

    Public Function Clone(position As Vector3, rotation As Vector3, isVisible As Boolean) As WorldObject
        Dim o As WorldObject = Me.Clone
        o.Position = position
        o.Rotation = rotation
        o.Visible = isVisible
        Return o
    End Function

    Public Sub BeginDraw(view As Microsoft.Xna.Framework.Matrix, projection As Microsoft.Xna.Framework.Matrix, device As Microsoft.Xna.Framework.Graphics.GraphicsDevice, effect As Microsoft.Xna.Framework.Graphics.Effect)
        If Me.DrawWireFrame Then
            Dim rs As New RasterizerState
            rs.FillMode = FillMode.WireFrame
            Me._OriginalRasterizerState = device.RasterizerState
            device.RasterizerState = rs
        End If
        If Me.DrawBoundingBox Then
            Me.World.BoundingBoxRender.Render(Me.BoundingBox, device, view, projection, Me.BoundingBoxRenderColor)
        End If
    End Sub

    Public Sub EndDraw(view As Microsoft.Xna.Framework.Matrix, projection As Microsoft.Xna.Framework.Matrix, device As Microsoft.Xna.Framework.Graphics.GraphicsDevice, effect As Microsoft.Xna.Framework.Graphics.Effect)
        If Me.DrawWireFrame Then
            device.RasterizerState = Me._OriginalRasterizerState
        End If
    End Sub

End Class
