Public MustInherit Class ParticleSystem
    Inherits DrawableGameComponent

    Public Const AlphaBlendDrawOrder As Integer = 100
    Public Const AdditiveDrawOrder As Integer = 200
    Public SpriteBatch As SpriteBatch

    Public Random As Random

    Private _Game As Game
    Private _Texture As Texture2D
    Private _Origin As Vector2
    Private _HowManyEffects As Integer
    Dim Particles() As Particle
    Dim FreeParticles As New Queue(Of Particle)

    Protected MinNumberParticles As Integer
    Protected MaxNumberParticles As Integer
    Protected TextureFileName As String
    Protected MinInitialSpeed As Single
    Protected MaxInitialSpeed As Single
    Protected MinAcceleration As Single
    Protected MaxAcceleration As Single
    Protected MinRotationSpeed As Single
    Protected MaxRotationSpeed As Single
    Protected MinLifeTime As Single
    Protected MaxLifeTime As Single
    Protected MinScale As Single
    Protected MaxScale As Single
    Protected BlendState As BlendState

    Public World As VirtualWorld

    Public ReadOnly Property FreeParticleCount As Integer
        Get
            Return FreeParticles.Count
        End Get
    End Property

    Sub New(g As Game, howManyEffects As Integer, r As Random, spriteBatch As SpriteBatch, world As VirtualWorld)
        MyBase.New(g)
        Me._Game = g
        Me._HowManyEffects = howManyEffects
        Me.Random = r
        Me.SpriteBatch = spriteBatch
        Me.World = world
        Me.Game.Components.Add(Me)
        Me.DrawOrder = Integer.MaxValue
    End Sub

    Public Overrides Sub Initialize()
        Me.InitializeConstants()
        ReDim Me.Particles((_HowManyEffects * Me.MaxNumberParticles) - 1)
        Me.FreeParticles = New Queue(Of Particle)(Me._HowManyEffects * MaxNumberParticles)
        For i As Integer = 0 To Particles.Length - 1
            Particles(i) = New Particle(Me.World)
            Me.FreeParticles.Enqueue(Particles(i))
        Next

    End Sub

    Protected MustOverride Sub InitializeConstants()

    Public Sub Load()
        Me.LoadContent()
    End Sub

    Protected Overrides Sub LoadContent()
        If String.IsNullOrEmpty(Me.TextureFileName) Then
            Dim s As String = "TextureFileName was not set."
            Throw New InvalidOperationException(s)
        End If
        Me._Texture = Me._Game.Content.Load(Of Texture2D)(Me.TextureFileName)
        Me._Origin.X = CSng(Me._Texture.Width / 2)
        Me._Origin.Y = CSng(Me._Texture.Height / 2)

    End Sub

    Public Sub AddParticles(where As Vector2)
        Dim numParticles As Integer = Me.Random.Next(Me.MinNumberParticles, MaxNumberParticles)
        Dim i As Integer

        Do While i < numParticles AndAlso Me.FreeParticles.Count > 0
            Dim p As Particle = Me.FreeParticles.Dequeue
            Me.InitializeParticle(p, where)
            i += 1
        Loop
    End Sub



    Protected Overridable Sub InitializeParticle(p As Particle, where As Vector2)
        Dim direction As Vector2 = Me.PickRandomDirection
        Dim velocity As Single = Me.World.RandomBetween(Me.MinInitialSpeed, MaxInitialSpeed)
        Dim acceleration As Single = Me.World.RandomBetween(Me.MinAcceleration, MaxAcceleration)
        Dim lifetime As Single = Me.World.RandomBetween(Me.MinLifeTime, MaxLifeTime)
        Dim scale As Single = Me.World.RandomBetween(Me.MinScale, MaxScale)
        Dim rotationSpeed As Single = Me.World.RandomBetween(Me.MinRotationSpeed, MaxRotationSpeed)
        p.Initialize(where, velocity * direction, acceleration * direction, lifetime, scale, rotationSpeed, Me)
    End Sub

    Protected Overridable Function PickRandomDirection() As Vector2
        Dim angle As Single = Me.World.RandomBetween(0, MathHelper.TwoPi)
        Return New Vector2(CSng(Math.Cos(angle)), CSng(Math.Sin(angle)))
    End Function

    Public Overrides Sub Update(gameTime As Microsoft.Xna.Framework.GameTime)
        Dim dt As Single = CSng(gameTime.ElapsedGameTime.TotalSeconds)
        For Each p As Particle In Me.Particles
            If p.IsActive Then
                p.Update(dt)
                If p.IsActive = False Then
                    Me.FreeParticles.Enqueue(p)
                End If
            End If
        Next

    End Sub

    Public Overrides Sub Draw(gameTime As Microsoft.Xna.Framework.GameTime)
        Me.SpriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Additive)
        For Each p As Particle In Me.Particles
            If p.IsActive = False Then
                Continue For
            End If
            Dim normalizedLifetime As Single = p.TimeSinceStart
            Dim alpha As Single = 4 * normalizedLifetime * (1 - normalizedLifetime)
            Dim color As Color = color.White * alpha
            'Dim scale As Single = p.Scale * (0.75F + 0.25F * normalizedLifetime)
            Dim scale As Single = p.Scale * (0.75F + 0.25F * normalizedLifetime)
            Me.SpriteBatch.Draw(Me._Texture, p.Position, Nothing, color, p.Rotation, Me._Origin, scale, SpriteEffects.None, 0.0F)
        Next
        Me.SpriteBatch.End()
        Me.Game.GraphicsDevice.BlendState = BlendState.Opaque
        Me.Game.GraphicsDevice.DepthStencilState = DepthStencilState.Default
        Me.Game.GraphicsDevice.SamplerStates(0) = SamplerState.LinearWrap
    End Sub

End Class
