Public Class VirtualWorld
    Inherits DrawableGameComponent

    Private _SkyBox As SkyBox
    Private _FloorBlock As FloorBlock
    Private _CeilingBlock As CeilingBlock
    Private _WallBlocks As New List(Of WallBlock)

    Dim _ContentBuilder As New WinFormsContentLoading.ContentBuilder

    Public Property Camera As Camera
    Public Property CameraCarrier As CameraCarrier

    Public Random As New Random
    Public Effect As BasicEffect

    Public SpriteBatch As SpriteBatch

    'walls
    Public Property WallTextureName As String = "Textures/Wall"
    Public Property WallTexture As Texture2D
    Public Property DrawWalls As Boolean = True
    Public Property WallBlockSize As Vector3 = New Vector3(12, 12, 12)

    Public ReadOnly Property WallBlocks As List(Of WallBlock)
        Get
            Return Me._WallBlocks
        End Get
    End Property

    'ceiling
    Public Property CeilingTextureName As String = "Textures/Ceiling"
    Public Property CeilingTexture As Texture2D
    Public Property DrawCeiling As Boolean = True
    Public ReadOnly Property CeilingBlock As CeilingBlock
        Get
            Return Me._CeilingBlock
        End Get
    End Property

    'floor
    Public Property FloorTexture As Texture2D
    Public Property FloorTextureName As String = "Textures/Floor"
    Public Property FloorPlan As Integer(,)
    Public Property DrawFloor As Boolean = True
    Public Property AutoGeneratePerimeter As Boolean = False

    Public ReadOnly Property FloorBlock As FloorBlock
        Get
            Return Me._FloorBlock
        End Get
    End Property

    Public ReadOnly Property WorldWidthUnits As Integer
        Get
            Return Me.FloorPlan.GetLength(0)
        End Get
    End Property

    Public ReadOnly Property WorldLengthUnits As Integer
        Get
            Return Me.FloorPlan.GetLength(1)
        End Get
    End Property

    'explosion particle system
    Public ExplosionParticleSystem As ExplosionParticleSystem

    'crosshair
    Public CrossHairTexture As Texture2D
    Public CrossHairTextureName As String = "Textures/Crosshair"
    Public DrawCrossHair As Boolean = True

    'sky box
    Public SkyBoxModelName As String = "SkyBoxes/SkyCube"
    Public SkyBoxEffectName As String = "SkyBoxes/SkyBox"
    Public SkyBoxTextureName As String = "SkyBoxes/Sunset"
    Public DrawSkyBox As Boolean = True
    Public Property SkyBoxSize As Single = 500.0F

    Public ReadOnly Property SkyBox As SkyBox
        Get
            Return Me._SkyBox
        End Get
    End Property

    'bullets
    Public BulletPower As Integer = 100
    Public BulletList As New List(Of Bullet)
    Public LastBulletTime As Double = 0
    Public BulletSpeed As Single = 1.0F
    Public BulletModel As Model
    Public BulletModelName As String = "Models/bullet"
    Public BulletRange As Single = 100.0F

    'sound
    Public GunShotSound As SoundEffect
    Public GunShotSoundName As String = "Audio/gunshot"

    Public BoundingBoxRender As BoundingBoxRenderer

    Private _Pictures As New List(Of Picture)

    Public ReadOnly Property Pictures As List(Of Picture)
        Get
            Return Me._Pictures
        End Get
    End Property

    Sub New(game As Game)
        MyBase.New(game)
        Me.Game.Components.Add(Me)
        Me.DrawOrder = Integer.MaxValue
    End Sub

    Public ReadOnly Property CameraPosition As Vector3
        Get
            Return Me.CameraCarrier.Position
        End Get
    End Property

    Public ReadOnly Property ViewMatrix As Matrix
        Get
            Return Me.Camera.ViewMatrix
        End Get
    End Property

    Public ReadOnly Property ProjectionMatrix As Matrix
        Get
            Return Me.Camera.ProjectionMatrix
        End Get
    End Property

    Public Overrides Sub Initialize()
        Me.Effect = New BasicEffect(Me.Game.GraphicsDevice)
        Me.BoundingBoxRender = New BoundingBoxRenderer()
        Me.BoundingBoxRender.Effect = New BasicEffect(Me.Game.GraphicsDevice)
        If Me.DrawSkyBox Then
            Me._SkyBox = New SkyBox(Me.Game, Me, Me.SkyBoxModelName, Me.SkyBoxEffectName, Me.SkyBoxTextureName, Me.SkyBoxSize)
        End If
        MyBase.Initialize()
    End Sub

    Public Sub CreateExplosion(position As Vector3)
        Dim mPos As Vector3 = Me.GraphicsDevice.Viewport.Project(position, Me.Camera.ProjectionMatrix, Me.Camera.ViewMatrix, Matrix.Identity)
        Dim where As Vector2 = New Vector2(mPos.X, mPos.Y)
        Me.ExplosionParticleSystem.AddParticles(where)
    End Sub

    Protected Overrides Sub LoadContent()
        Me.WallTexture = Me.Game.Content.Load(Of Texture2D)(Me.WallTextureName)
        Me.CeilingTexture = Me.Game.Content.Load(Of Texture2D)(Me.CeilingTextureName)
        Me.FloorTexture = Me.Game.Content.Load(Of Texture2D)(Me.FloorTextureName)
        Me.CrossHairTexture = Me.Game.Content.Load(Of Texture2D)(Me.CrossHairTextureName)
        Me.BulletModel = Me.Game.Content.Load(Of Model)(Me.BulletModelName)
        Me.GunShotSound = Me.Game.Content.Load(Of SoundEffect)(Me.GunShotSoundName)
        Dim baseSize As Vector3 = New Vector3(Me.WorldWidthUnits * Me._WallBlockSize.X, 3, Me.WorldWidthUnits * Me._WallBlockSize.Z)
        Dim baseCube As New Cube(baseSize)
        If Me.DrawFloor Then
            Me._FloorBlock = New FloorBlock(Me.Game, Me, New Vector3(0, -1.5, 0), Vector3.Zero, Me.FloorTexture, baseCube, Me.Effect)
            Me.FloorBlock.IsSolid = False
        End If
        If Me.DrawCeiling Then
            Me._CeilingBlock = New CeilingBlock(Me.Game, Me, New Vector3(0, CSng(Me.WallBlockSize.Y + 1.5), 0), Vector3.Zero, Me.CeilingTexture, baseCube, Me.Effect)
        End If
        Me.ProcessFloorPlan()
        Me.CreateShapesGallery(10)
        Me.SpriteBatch = New SpriteBatch(Me.GraphicsDevice)
        Me.ExplosionParticleSystem = New ExplosionParticleSystem(Me.Game, 1, Me.Random, Me.SpriteBatch, Me)
        Me.ExplosionParticleSystem.Initialize()
        Me.ExplosionParticleSystem.Load()
        MyBase.LoadContent()
    End Sub

    Public Overrides Sub Update(gameTime As Microsoft.Xna.Framework.GameTime)
        MyBase.Update(gameTime)
    End Sub

    Public Overrides Sub Draw(gameTime As Microsoft.Xna.Framework.GameTime)
        If Me.DrawCrossHair Then
            Me.SpriteBatch.Begin()
            Me.SpriteBatch.Draw(Me.CrossHairTexture, New Vector2(CSng(Me.Game.Window.ClientBounds.Width / 2) - CSng(Me.CrossHairTexture.Width / 2), CSng(Me.Game.Window.ClientBounds.Height / 2) - CSng(Me.CrossHairTexture.Height / 2)), Color.White)
            Me.SpriteBatch.End()
            Me.GraphicsDevice.BlendState = BlendState.Opaque
            Me.GraphicsDevice.DepthStencilState = DepthStencilState.Default
            Me.GraphicsDevice.SamplerStates(0) = SamplerState.LinearWrap
        End If
        MyBase.Draw(gameTime)
    End Sub

    Public Function RandomBetween(min As Single, max As Single) As Single
        Return min + CSng(Me.Random.NextDouble()) * (max - min)
    End Function

    Public Function GetShootableObjects() As List(Of WorldObject)
        Dim l As New List(Of WorldObject)
        For Each o As GameComponent In Me.GetAllWorldObjects
            If TypeOf o Is WorldObject Then
                Dim ow As WorldObject = DirectCast(o, WorldObject)
                If ow.IsSolid AndAlso ow.Visible Then
                    l.Add(ow)
                End If
            End If
        Next
        Return l
    End Function

    Public Function GetAllWorldObjects() As List(Of WorldObject)
        Dim components(Me.Game.Components.Count - 1) As GameComponent 'copy to array to avoid bullets being disposed while processing this loop
        Me.Game.Components.CopyTo(components, 0)
        Dim l As New List(Of WorldObject)
        For Each o As GameComponent In components
            If TypeOf o Is WorldObject Then
                l.Add(DirectCast(o, WorldObject))
            End If
        Next
        Return l
    End Function

    Public Sub CreateShapesGallery(numberOfShapes As Integer)
        Dim baseQuad As New Quad(New Vector3(10, 10, 0))
        Dim current As Integer = 1
        Dim target As Integer = numberOfShapes
        Do While current <= target
            Dim block As WallBlock = Me.SelectWallBlockWithOpenFreeFace(Me.Random)
            If block Is Nothing Then
                Exit Do
            End If
            Dim shapeNum As Integer = Me.Random.Next(0, 3)
            Dim texture As Texture2D
            Select Case shapeNum
                Case 0
                    texture = Me.CreateCircleImageTexture(Drawing.Color.Red, Drawing.Color.Black)
                Case 1
                    texture = Me.CreateSquareImageTexture(Drawing.Color.Blue, Drawing.Color.Black)
                Case 2
                    texture = Me.CreateTriangleImageTexture(Drawing.Color.Yellow, Drawing.Color.Black)
                Case Else
                    texture = Me.CreateCircleImageTexture(Drawing.Color.Red, Drawing.Color.Black)
            End Select
            Dim p As New Picture(Me.Game, Me, Vector3.Zero, Vector3.Zero, texture, baseQuad, Me.Effect)
            Me.Pictures.Add(p)
            block.AttachPictureToFace(p, block.SelectFreeFaceAtRandom(Me.Random))
            current += 1
        Loop
    End Sub

    Public Sub ProcessFloorPlan()
        Dim wallCube As New Cube(Me.WallBlockSize)
        Dim baseWallBlock As New WallBlock(Me.Game, Me, New Vector3(0, 6, 0), Vector3.Zero, Me.WallTexture, wallCube, Me.Effect)
        baseWallBlock.Visible = False
        Dim pictureQuad As New Quad(New Vector3(10, 10, 0))

        'initialize world layout
        For x As Integer = 0 To Me.WorldWidthUnits - 1
            For z As Integer = 0 To Me.WorldLengthUnits - 1
                Dim posx, posy, posz As Single
                posx = CSng((CSng((x * Me.WallBlockSize.X) - ((Me.WorldWidthUnits * Me.WallBlockSize.X) / 2))) + (Me.WallBlockSize.X / 2))
                posz = CSng((CSng((z * 12) - ((Me.WorldLengthUnits * 12) / 2))) + (Me.WallBlockSize.Z / 2))
                posy = CSng(Me.WallBlockSize.Y / 2)
                If Me.AutoGeneratePerimeter Then
                    If Me.IsPerimeterBlock(x, z, wallCube) Then
                        Me._FloorPlan(x, z) = 1
                    End If
                End If
                Dim pos As New Vector3(posx, posy, posz)
                If Me._FloorPlan(x, z) = 1 Then
                    If Me.DrawWalls Then
                        Dim o As WallBlock = CType(baseWallBlock.Clone(pos, Vector3.Zero, True), WallBlock)
                        o.MapPositionX = x
                        o.MapPositionZ = z
                        Me.WallBlocks.Add(o)
                    End If
                End If
            Next
        Next
        baseWallBlock.Dispose()
    End Sub

    Private Function IsPerimeterBlock(x As Integer, z As Integer, shape As Cube) As Boolean
        Using b As New WallBlock(Me.Game, Me, Nothing, Vector3.Zero, Me.WallTexture, shape, Me.Effect)
            b.MapPositionX = x
            b.MapPositionZ = z
            Return b.IsPerimeterBlock
        End Using
    End Function

    Public Function GetWallBlockByMapPosition(x As Integer, z As Integer) As WallBlock
        Return Me.WallBlocks.Find(Function(o) o.MapPositionX = x And o.MapPositionZ = z)
    End Function

    Private Function SelectWallBlockWithOpenFreeFace(r As Random) As WallBlock
        Dim l As List(Of WallBlock)
        l = Me.WallBlocks.FindAll(Function(o) o.HasFreeFaces And o.HasOpenFaces)
        If l.Count = 0 Then Return Nothing
        If r Is Nothing Then
            Return l.First
        Else
            Return l.Item(Me.Random.Next(0, l.Count - 1))
        End If
    End Function

    Private Function LoadTextureFromStream(stream As System.IO.Stream) As Texture2D
        Return Texture2D.FromStream(Me.Game.GraphicsDevice, stream)
    End Function

    Public Function CreateColoredTexture(c As Drawing.Color, width As Integer, height As Integer) As Texture2D
        Dim bmp As New Drawing.Bitmap(width, height)
        Dim g As Drawing.Graphics = Drawing.Graphics.FromImage(bmp)
        g.Clear(c)
        g.Flush()
        g.Dispose()
        Dim t As Texture2D
        Using ms As New IO.MemoryStream
            bmp.Save(ms, Drawing.Imaging.ImageFormat.Jpeg)
            ms.Position = 0
            t = Me.LoadTextureFromStream(ms)
        End Using
        Return t
    End Function

    Public Function CreateCircleImageTexture(color As Drawing.Color, backColor As Drawing.Color) As Texture2D
        Dim bmp As New Drawing.Bitmap(500, 500)
        Dim g As Drawing.Graphics = Drawing.Graphics.FromImage(bmp)
        g.Clear(backColor)
        Dim rect As New Drawing.Rectangle(100, 100, 300, 300)
        g.FillEllipse(New Drawing.SolidBrush(color), rect)
        g.Flush()
        g.Dispose()
        Dim t As Texture2D
        Using ms As New IO.MemoryStream
            bmp.Save(ms, Drawing.Imaging.ImageFormat.Png)
            ms.Position = 0
            t = Me.LoadTextureFromStream(ms)
        End Using
        Return t
    End Function

    Public Function CreateSquareImageTexture(color As Drawing.Color, backColor As Drawing.Color) As Texture2D
        Dim bmp As New Drawing.Bitmap(500, 500)
        Dim g As Drawing.Graphics = Drawing.Graphics.FromImage(bmp)
        g.Clear(backColor)
        Dim rect As New Drawing.Rectangle(100, 100, 300, 300)
        g.FillRectangle(New Drawing.SolidBrush(color), rect)
        g.Flush()
        g.Dispose()
        Dim t As Texture2D
        Using ms As New IO.MemoryStream
            bmp.Save(ms, Drawing.Imaging.ImageFormat.Png)
            ms.Position = 0
            t = Me.LoadTextureFromStream(ms)
        End Using
        Return t
    End Function

    Public Function CreateTriangleImageTexture(color As Drawing.Color, backColor As Drawing.Color) As Texture2D
        Dim bmp As New Drawing.Bitmap(500, 500)
        Dim g As Drawing.Graphics = Drawing.Graphics.FromImage(bmp)
        g.Clear(backColor)
        Dim points(3) As Drawing.PointF
        points(0) = New Drawing.PointF(250, 100) 'top
        points(1) = New Drawing.PointF(400, 400)
        points(2) = New Drawing.PointF(100, 400)
        points(3) = New Drawing.PointF(250, 100)
        Dim gp As New System.Drawing.Drawing2D.GraphicsPath()
        gp.AddLines(points)
        gp.CloseFigure()
        g.FillPath(New Drawing.SolidBrush(color), gp)
        g.Flush()
        g.Dispose()
        Dim t As Texture2D
        Using ms As New IO.MemoryStream
            bmp.Save(ms, Drawing.Imaging.ImageFormat.Png)
            ms.Position = 0
            t = Me.LoadTextureFromStream(ms)
        End Using
        Return t
    End Function


End Class
