Tweaked the 'chat' demo; still unfinished.

#Rem
	DISCLAIMER:
		* This example is unfinished and likely contains bugs, use at your own risk.
#End

Strict

Public

' Preprocessor related:
#MOJO_AUTO_SUSPEND_ENABLED = False

#GLFW_WINDOW_RESIZABLE = True

' Imports:
Import regal.networking

Import brl.asyncevent
Import brl.stream
Import mojo

' Classes:
Class Application Extends App Implements CoreNetworkListener, MetaNetworkListener, ClientNetworkListener Final
	' Constant variable(s):
	Const PORT:= 27015 ' 27016 ' 5029
	Const PROTOCOL:= NetworkEngine.SOCKET_TYPE_TCP ' NetworkEngine.SOCKET_TYPE_UDP

	Const NOTIFICATION_HEADER:= "{NOTIFICATION}: "

	' This is used to offset user-identifiers.
	Const USER_ID_ORIGIN:= 1

	' States:
	Const STATE_IDLE:= 0
	Const STATE_CHAT:= 1

	' Message types:
	Const MSG_TYPE_USER:= NetworkEngine.MSG_TYPE_CUSTOM ' + 1
	Const MSG_TYPE_DISCONNECT:= (MSG_TYPE_USER+1)
	Const MSG_TYPE_TEXT:= (MSG_TYPE_DISCONNECT+1)
	Const MSG_TYPE_RAW_LINES:= (MSG_TYPE_TEXT+1)
	Const MSG_TYPE_NOTIFICATION:= (MSG_TYPE_RAW_LINES+1)

	' Constructor(s):
	Method OnCreate:Int()
		SetUpdateRate(0)

		Users = New List<User>()
		ChatLog = New StringDeque()

		LocalInfo = Null

		State = STATE_IDLE
		NextUserID = USER_ID_ORIGIN

		UsernameInputComplete = False

		' Return the default response.
		Return 0
	End

	' Methods:
	Method InitNetwork:Void()
		Network = New NetworkEngine()

		Network.SetCoreCallback(Self)
		Network.SetMetaCallback(Self)
		Network.SetClientCallback(Self)

		Return
	End

	Method Host:Void(Port:Int=PORT)
		InitNetwork()

		Network.Host(Port, True, PROTOCOL)

		Print("Hosting server on port " + Port + " using " + NetworkEngine.ProtocolToString(PROTOCOL) + "...")

		Return
	End

	Method Connect:Void(Hostname:String, Port:Int=PORT)
		InitNetwork()

		Network.Connect(Hostname, Port, True, PROTOCOL)

		Print("Connecting to " + Hostname + ":" + Port + " using " + NetworkEngine.ProtocolToString(PROTOCOL) + "...")

		Return
	End

	Method OnRender:Int()
		Select State
			Case STATE_CHAT
				Render_Chat()
			Default ' STATE_IDLE
				Render_Idle()
		End Select

		' Return the default response.
		Return 0
	End

	Method OnUpdate:Int()
		UpdateAsyncEvents()

		Select State
			Case STATE_IDLE
				If (Update_Idle()) Then
					State = STATE_CHAT
				Endif
			Case STATE_CHAT
				Update_Chat()
		End Select

		' Return the default response.
		Return 0
	End

	Method OnResize:Int()
		MaintainChatLog()

		Return Super.OnResize()
	End

	Method Render_Idle:Void()
		Cls(205.0, 205.0, 205.0)

		Return
	End

	Method Render_Chat:Void()
		Const PREVIEW_USER_COUNT:= 4

		Cls(127.5, Cos(Float(Millisecs() / 10)) * 255.0, 200.0)

		Local UserCount:= Users.Count()

		If (UserCount > 0) Then
			If (UserCount > PREVIEW_USER_COUNT) Then
				DrawText("Users connected: " + UserCount, 8.0, 8.0)
			Else
				Local Names:String

				Local Users_Node:= Users.FirstNode()

				Local Iterations:= Min(PREVIEW_USER_COUNT, UserCount)

				For Local I:= 1 To Iterations
					Local U:= Users_Node.Value()

					Names += U.Name

					If (I < Iterations) Then
						Names += ", "
					Endif

					Users_Node = Users_Node.NextNode()
				Next

				DrawText("Users connected: " + Names, 8.0, 8.0)
			Endif
		Endif

		If (Not Network.IsClient) Then
			DrawText("Clients connected overall: " + Network.ClientCount, 8.0, 24.0)
		Endif

		DrawChatLog(8.0, ChatOffset)
		DrawUserInput()

		Return
	End

	Method DrawChatLog:Void(X:Float=8.0, Y:Float=8.0)
		PushMatrix()

		Translate(X, Y)

		For Local Line:= Eachin ChatLog
			DrawText(Line, 0.0, 0.0)

			Translate(0.0, 16.0)
		Next

		PopMatrix()

		Return
	End

	Method DrawUserInput:Void()
		Local X:= 8.0
		Local Y:= (Float(DeviceHeight()) - 24.0)

		If (LocalInfo = Null) Then ' Network.IsClient
			If (Not UsernameInputComplete) Then
				DrawText("Please input a username: " + UserInput, X, Y)
			Else
				If (Network.IsClient) Then
					DrawText("Waiting for authentication...", X, Y)
				Endif
			Endif
		Else
			DrawText(LocalInfo.Name + ": " + UserInput, X, Y)
		Endif

		Return
	End

	Method Update_Idle:Bool()
		If (KeyHit(KEY_1)) Then
			Host()

			ChatOffset = 64.0

			Return True
		Elseif (KeyHit(KEY_0)) Then
			Connect("127.0.0.1") ' "localhost"

			ChatOffset = 48.0

			Return True
		Endif

		' Return the default response.
		Return False
	End

	Method Update_Chat:Void()
		Network.Update()

		#Rem
			If (KeyHit(KEY_W)) Then
				SendText(String(Millisecs()))
			Endif
		#End

		UpdateUserInput()

		Return
	End

	Method UpdateUserInput:Void()
		Repeat
			Local KeyCode:= GetChar()

			If (Not KeyCode) Then
				Exit
			Elseif (KeyCode = 13) Then ' Enter
				If (UserInput.Length > 0) Then
					If (UsernameInputComplete) Then
						If (Network.HasClient) Then
							SendText(UserInput)
						Endif
					Else
						If (Network.IsClient) Then
							If (LocalInfo = Null) Then
								DisplayLine("Asking the server to authenticate us...")

								' Tell the host our username.
								AskForUser(UserInput)

								UsernameInputComplete = True
							Endif
						Else
							LocalInfo = New User(UserInput, GetNextUserID())

							BringClientUpToSpeed(Null)
							'NotifyAboutUser(LocalInfo, Null, False, True)

							UsernameInputComplete = True
						Endif
					Endif

					UserInput = ""
				Endif
			Elseif (KeyCode = 8) Then
				UserInput = UserInput[..UserInput.Length-1]
			Elseif (KeyCode >= 32) Then
				UserInput += String.FromChar(KeyCode)
			Endif
		Forever

		Return
	End

	Method MaintainChatLog:Void()
		' Calculate the number of entries allowed in the chat-log:
		MaxChatLogEntries = ((DeviceHeight() / 16) - (4 + (Int(ChatOffset) / 16)))

		' Reduce the chat-log size.
		While (ChatLog.Length >= MaxChatLogEntries)
			ChatLog.PopFirst()
		Wend

		Return
	End

	Method DisplayLine:Void(RawText:String)
		' Output to the console.
		Print("{CHAT}: " + RawText)

		MaintainChatLog()

		' Add 'RawText' to the chat-log.
		ChatLog.PushLast(RawText)

		Return
	End

	Method DisplayLine:Void(U:User, Data:String)
		DisplayLine(ProcessLine(U, Data))

		Return
	End

	Method ProcessLine:String(U:User, Data:String)
		Return (U.Name + ": " + Data)
	End

	Method GetUser:User(C:Client)
		If (C = Null) Then
			Return Null
		Endif

		For Local U:= Eachin Users
			If (U.Handle = C) Then
				Return U
			Endif
		Next

		' Return the default response.
		Return Null
	End

	Method GetUser:User(Name:String)
		For Local U:= Eachin Users
			If (U.Name = Name) Then
				Return U
			Endif
		Next

		' Return the default response.
		Return Null
	End

	Method GetUser:User(ID:Int)
		For Local U:= Eachin Users
			If (U.ID = ID) Then
				Return U
			Endif
		Next

		' Return the default response.
		Return Null
	End

	' This repeats text to every 'Client', excluding 'Origin'.
	Method RepeatText:Void(RawText:String, Origin:Client)
		Local P:= Network.AllocatePacket()

		P.WriteLine(RawText)

		For Local C:= Eachin Network
			If (C = Origin) Then
				Continue
			Endif

			Network.Send(P, C, MSG_TYPE_RAW_LINES)
		Next

		Network.ReleasePacket(P)

		Return
	End

	Method GetNextUserID:Int() ' UShort
		Local ID:= NextUserID

		NextUserID += 1

		Return ID
	End

	Method AcceptUser:Bool(Name:String, Origin:Client)
		' Only accept the user if we don't already know them:
		If (GetUser(Origin) = Null) Then
			Local Num:Int = 0 ' Long ' Short

			For Local ExistingUser:= Eachin Users
				If (ExistingUser.Name.StartsWith(Name)) Then
					Local SuffixNum:= Int(ExistingUser.Name[Name.Length..])

					If (SuffixNum > 0) Then
						Num = (SuffixNum + 1)
					Else
						If (ExistingUser.Name = Name) Then
							Num += 1
						Endif
					Endif
				Endif
			Next

			If (Num > 0) Then
				Name += String(Num)
			Endif

			Local U:= New User(Name, GetNextUserID(), Origin)

			Users.AddLast(U)

			SendNotification(U.Name + " has connected.")

			' Tell 'Origin' what we're calling them.
			NotifyAboutUser(U, Origin, True)

			' Tell everyone else that they exist.
			For Local C:= Eachin Network
				If (C = Origin) Then
					Continue
				Endif

				NotifyAboutUser(U, C, False)
			Next

			Return True
		Endif

		' Return the default response.
		Return False
	End

	' This is used by servers to notify users about someone disconnecting.
	Method ReleaseUser:Bool(U:User)
		' Tell everyone that 'U' has disconnected.
		NotifyAboutUserDisconnection(U)

		' Remove this 'User' from our collection.
		Users.RemoveEach(U)

		' Notify everyone.
		SendNotification(U.Name + " has disconnected.")

		' Return the default response.
		Return True
	End

	Method SendText:Void(Text:String, C:Client=Null)
		Local P:= Network.AllocatePacket()

		P.WriteLine(Text)

		Network.Send(P, C, MSG_TYPE_TEXT, True)

		Network.ReleasePacket(P)

		If (C = Null And LocalInfo <> Null) Then
			DisplayLine(LocalInfo, Text)
		Endif

		Return
	End

	Method SendNotification:Void(Text:String, C:Client=Null)
		Local P:= Network.AllocatePacket()

		P.WriteLine(Text)

		Network.Send(P, C, MSG_TYPE_NOTIFICATION, True)

		Network.ReleasePacket(P)

		If (C = Null And LocalInfo <> Null) Then
			DisplayLine(NOTIFICATION_HEADER + Text)
		Endif

		Return
	End

	Method NotifyAboutUserDisconnection:Void(U:User, C:Client=Null)
		Local P:= Network.AllocatePacket()

		P.WriteShort(U.ID)

		Network.Send(P, C, MSG_TYPE_DISCONNECT, True)

		Network.ReleasePacket(P)

		Return
	End

	' These two are effectively the same, they're just not a unified codebase (Laziness):
	Method NotifyAboutUser:Void(U:User, C:Client, IsTheirInfo:Bool, IsServer:Bool=False)
		Local P:= Network.AllocatePacket()

		NetworkSerial.WriteBool(P, IsTheirInfo)
		NetworkSerial.WriteBool(P, IsServer)

		P.WriteShort(U.ID)

		P.WriteLine(U.Name)

		Network.Send(P, C, MSG_TYPE_USER, True, False)

		Network.ReleasePacket(P)

		Return
	End

	Method AskForUser:Void(Name:String, C:Client=Null, IsTheirInfo:Bool=False, IsServer:Bool=False)
		Local P:= Network.AllocatePacket()

		NetworkSerial.WriteBool(P, IsTheirInfo)
		NetworkSerial.WriteBool(P, IsServer)

		P.WriteShort(User.ID_RESERVED)

		P.WriteLine(Name)

		Network.Send(P, C, MSG_TYPE_USER, True, False)

		Network.ReleasePacket(P)

		Return
	End

	Method BringClientUpToSpeed:Void(Incoming:Client)
		For Local U:= Eachin Users
			If (U.Handle <> Incoming) Then
				NotifyAboutUser(U, Incoming, False)
			Endif
		Next

		If (LocalInfo <> Null) Then
			' Tell 'Incoming' about us.
			NotifyAboutUser(LocalInfo, Incoming, False, True)
		Endif

		Return
	End

	' Call-backs:
	Method OnReceiveMessage:Void(Network:NetworkEngine, Origin:Client, Type:MessageType, Message:Stream, MessageSize:Int)
		Print("Message received of type " + Type + ", and size " + MessageSize)
		Print("Incoming Address: " + Origin.Address)

		Local U:= GetUser(Origin)

		If (Not Network.IsClient) Then
			If (U = Null) Then
				Select Type
					Case MSG_TYPE_USER
						Local IsMyInfo:= NetworkSerial.ReadBool(Message)
						Local Reserved:= NetworkSerial.ReadBool(Message)
						Local ID:= Message.ReadShort()

						If (Not IsMyInfo And ID = User.ID_RESERVED) Then
							If (GetUser(ID) = Null) Then
								AcceptUser(Message.ReadLine(), Origin)
							Else
								Return
							Endif
						Else
							' Nothing so far.
						Endif
				End Select

				Return
			Endif
		Else
			Local HasLocalInfo:Bool = (LocalInfo <> Null)

			Select Type
				Case MSG_TYPE_USER
					Local IsMyInfo:= NetworkSerial.ReadBool(Message)
					Local IsServerInfo:= NetworkSerial.ReadBool(Message)
					Local ID:= Message.ReadShort()
					Local Name:= Message.ReadLine()

					Print("NAME: " + Name)
					Print("IsServer: " + Int(IsServerInfo))
					Print("IsMyInfo: " + Int(IsMyInfo))
					Print("ID: " + ID)

					' Check if it's our information:
					If (IsMyInfo And Not IsServerInfo) Then
						If (Not HasLocalInfo) Then
							LocalInfo = New User(Name, ID)

							DisplayLine("User information established at: " + Origin.Address)
							DisplayLine("Obtained username: " + Name)
						Else
							' Invalid message; already have local information.

							Return
						Endif
					Else
						Local U:User

						U = GetUser(ID)

						If (U = Null) Then
							If (IsServerInfo And ID <> User.ID_RESERVED) Then
								U = New User(Name, ID, Origin)

								DisplayLine("Creating user-entry for " + Name + " at " + Origin.Address)
							Else
								U = New User(Name)

								DisplayLine("Creating user-entry for " + Name)
							Endif

							Users.AddLast(U)
						Endif
					Endif
				Case MSG_TYPE_DISCONNECT
					Local ID:= Message.ReadShort()

					Local U:= GetUser(ID)

					If (U <> Null) Then
						Users.RemoveEach(U)
					Else
						' Nothing so far.
					Endif
				Default
					' Safety net (Allows us to make assumptions later):
					If (Not HasLocalInfo) Then
						Return
					Endif
			End Select
		Endif

		Select Type
			Case MSG_TYPE_TEXT
				If (U = Null) Then
					' Unable to find a user to attach to this message.

					Return
				Endif

				Local Processed:= ProcessLine(U, Message.ReadLine())

				DisplayLine(Processed)

				If (Not Network.IsClient) Then
					' Repeat the message to everyone else.
					RepeatText(Processed, Origin)
				Endif
			Case MSG_TYPE_NOTIFICATION
				If (Not Network.IsClient) Then
					Return
				Endif

				While (Not Message.Eof)
					DisplayLine(NOTIFICATION_HEADER + Message.ReadLine())
				Wend
			Case MSG_TYPE_RAW_LINES
				If (Not Network.IsClient) Then
					Return
				Endif

				While (Not Message.Eof)
					DisplayLine(Message.ReadLine())
				Wend
			Default
				' Nothing so far.
		End Select

		Return
	End

	Method OnDisconnected:Void(Network:NetworkEngine)
		DisplayLine("Disconnected from server.")

		Return
	End

	Method OnClientConnect:Bool(Network:NetworkEngine, Address:NetworkAddress)
		Print("Accepting client at: " + Address)

		' Return the default response. (Accept the new connection)
		Return True
	End

	Method OnClientAccepted:Void(Network:NetworkEngine, C:Client)
		Print("Client accepted: " + C.Address)

		BringClientUpToSpeed(C)

		Return
	End

	Method OnClientDisconnected:Void(Network:NetworkEngine, C:Client)
		Local U:= GetUser(C)

		If (U <> Null) Then
			ReleaseUser(U)
		Endif

		Print("Client disconnected: " + C.Address)

		Return
	End

	Method OnNetworkBind:Void(Network:NetworkEngine, Successful:Bool)
		If (Not Successful) Then
			Print("Unable to bind socket.")

			Network.Close()

			Return
		Endif

		Print("Socket bound.")

		Return
	End

	Method OnSendComplete:Void(Network:NetworkEngine, P:Packet, Address:NetworkAddress, BytesSent:Int)
		'Print("Sending operation complete.")

		Return
	End

	' Properties:
	Method LocalInfo:User() Property
		Return Self._LocalInfo
	End

	Method LocalInfo:Void(Input:User) Property
		Local HasCollection:Bool = (Users <> Null)

		If (Input = Null And HasCollection) Then
			Users.RemoveEach(Self._LocalInfo)
		Endif

		Self._LocalInfo = Input

		If (Input <> Null And HasCollection) Then
			Users.AddLast(Input)
		Endif

		Return
	End

	' Fields (Public):

	' The active network, used for communication.
	Field Network:NetworkEngine

	' This contains other connected users. (Only the host for clients; could be changed easily)
	Field Users:List<User>

	' A log containing a limited number of messages output to the screen.
	Field ChatLog:StringDeque

	' Fields (Protected):
	Protected

	' This holds information about us. (Name, etc; backed by the 'LocalInfo' property)
	Field _LocalInfo:User

	' The user's current input.
	Field UserInput:String

	' This is actively updated with the number of log entries possible.
	' (Used to reduce log sizes, and keep things on screen)
	Field MaxChatLogEntries:Int

	' This is used to offset the chat, so we can render debug text.
	Field ChatOffset:Float

	' The state of the application.
	Field State:Int

	' This is used to keep track of user identifiers.
	Field NextUserID:Int ' UShort

	' Booleans / Flags:

	' This is used to request a username.
	Field UsernameInputComplete:Bool

	Public
End

Class User Final
	' Constant variable(s):
	Const ID_RESERVED:= 0 ' 65536

	' Constructor(s):
	Method New(Name:String, ID:Int=ID_RESERVED, Handle:Client=Null)
		Self.Name = Name
		Self.ID = ID
		Self.Handle = Handle
	End

	Method New(Name:String, Handle:Client)
		Self.Name = Name
		Self.Handle = Handle
	End

	' Fields:
	Field Handle:Client

	Field Name:String
	Field ID:Int = ID_RESERVED ' UShort
End

' Functions:
Function Main:Int()
	' Start the application.
	New Application()

	' Return the default response.
	Return 0
End

#Rem
	DISCLAIMER:
		* This example is unfinished and likely contains bugs, use at your own risk.
#End

Strict

Public

' Preprocessor related:
#MOJO_AUTO_SUSPEND_ENABLED = False

#GLFW_WINDOW_RESIZABLE = True

' Imports:
Import regal.networking

Import brl.asyncevent
Import brl.stream
Import mojo

' Classes:
Class Application Extends App Implements CoreNetworkListener, MetaNetworkListener, ClientNetworkListener Final
	' Constant variable(s):
	Const PORT:= 27015 ' 27016 ' 5029
	Const PROTOCOL:= NetworkEngine.SOCKET_TYPE_TCP ' NetworkEngine.SOCKET_TYPE_UDP

	Const NOTIFICATION_HEADER:= "{NOTIFICATION}: "

	' This is used to offset user-identifiers.
	Const USER_ID_ORIGIN:= 1

	' States:
	Const STATE_IDLE:= 0
	Const STATE_CHAT:= 1

	' Message types:
	Const MSG_TYPE_USER:= NetworkEngine.MSG_TYPE_CUSTOM ' + 1
	Const MSG_TYPE_DISCONNECT:= (MSG_TYPE_USER+1)
	Const MSG_TYPE_TEXT:= (MSG_TYPE_DISCONNECT+1)
	Const MSG_TYPE_RAW_LINES:= (MSG_TYPE_TEXT+1)
	Const MSG_TYPE_NOTIFICATION:= (MSG_TYPE_RAW_LINES+1)

	' Constructor(s):
	Method OnCreate:Int()
		SetUpdateRate(0)

		Users = New List<User>()
		ChatLog = New StringDeque()

		LocalInfo = Null

		State = STATE_IDLE
		NextUserID = USER_ID_ORIGIN

		UsernameInputComplete = False

		' Return the default response.
		Return 0
	End

	' Methods:
	Method InitNetwork:Void()
		Network = New NetworkEngine()

		Network.SetCoreCallback(Self)
		Network.SetMetaCallback(Self)
		Network.SetClientCallback(Self)

		Return
	End

	Method Host:Void(Port:Int=PORT)
		InitNetwork()

		Network.Host(Port, True, PROTOCOL)

		Print("Hosting server on port " + Port + " using " + NetworkEngine.ProtocolToString(PROTOCOL) + "...")

		Return
	End

	Method Connect:Void(Hostname:String, Port:Int=PORT)
		InitNetwork()

		Network.Connect(Hostname, Port, True, PROTOCOL)

		Print("Connecting to " + Hostname + ":" + Port + " using " + NetworkEngine.ProtocolToString(PROTOCOL) + "...")

		Return
	End

	Method OnRender:Int()
		Select State
			Case STATE_CHAT
				Render_Chat()
			Default ' STATE_IDLE
				Render_Idle()
		End Select

		' Return the default response.
		Return 0
	End

	Method OnUpdate:Int()
		UpdateAsyncEvents()

		Select State
			Case STATE_IDLE
				If (Update_Idle()) Then
					State = STATE_CHAT
				Endif
			Case STATE_CHAT
				Update_Chat()
		End Select

		' Return the default response.
		Return 0
	End

	Method OnResize:Int()
		MaintainChatLog()

		Return Super.OnResize()
	End

	Method Render_Idle:Void()
		Cls(205.0, 205.0, 205.0)

		Return
	End

	Method Render_Chat:Void()
		Const PREVIEW_USER_COUNT:= 4

		Cls(127.5, Cos(Float(Millisecs() / 10)) * 255.0, 200.0)

		If (Not Network.IsClient) Then
			Local UserCount:= Users.Count()

			If (UserCount > 0) Then
				If (UserCount > PREVIEW_USER_COUNT) Then
					DrawText("Users connected: " + UserCount, 8.0, 8.0)
				Else
					Local Names:String

					Local Users_Node:= Users.FirstNode()

					Local Iterations:= Min(PREVIEW_USER_COUNT, UserCount)

					For Local I:= 1 To Iterations
						Local U:= Users_Node.Value()

						Names += U.Name

						If (I < Iterations) Then
							Names += ", "
						Endif

						Users_Node = Users_Node.NextNode()
					Next

					DrawText("Users connected: " + Names, 8.0, 8.0)
				Endif
			Endif

			DrawText("Clients connected overall: " + Network.ClientCount, 8.0, 24.0)
		Endif

		DrawChatLog(8.0, ChatOffset)
		DrawUserInput()

		Return
	End

	Method DrawChatLog:Void(X:Float=8.0, Y:Float=8.0)
		PushMatrix()

		Translate(X, Y)

		For Local Line:= Eachin ChatLog
			DrawText(Line, 0.0, 0.0)

			Translate(0.0, 16.0)
		Next

		PopMatrix()

		Return
	End

	Method DrawUserInput:Void()
		Local X:= 8.0
		Local Y:= (Float(DeviceHeight()) - 24.0)

		If (LocalInfo = Null) Then ' Network.IsClient
			If (Not UsernameInputComplete) Then
				DrawText("Please input a username: " + UserInput, X, Y)
			Else
				If (Network.IsClient) Then
					DrawText("Waiting for authentication...", X, Y)
				Endif
			Endif
		Else
			DrawText(LocalInfo.Name + ": " + UserInput, X, Y)
		Endif

		Return
	End

	Method Update_Idle:Bool()
		If (KeyHit(KEY_1)) Then
			Host()

			ChatOffset = 64.0

			Return True
		Elseif (KeyHit(KEY_0)) Then
			Connect("127.0.0.1") ' "localhost"

			ChatOffset = 16.0

			Return True
		Endif

		' Return the default response.
		Return False
	End

	Method Update_Chat:Void()
		Network.Update()

		#Rem
			If (KeyHit(KEY_W)) Then
				SendText(String(Millisecs()))
			Endif
		#End

		UpdateUserInput()

		Return
	End

	Method UpdateUserInput:Void()
		Repeat
			Local KeyCode:= GetChar()

			If (Not KeyCode) Then
				Exit
			Elseif (KeyCode = 13) Then ' Enter
				If (UserInput.Length > 0) Then
					If (UsernameInputComplete) Then
						If (Network.HasClient) Then
							SendText(UserInput)
						Endif
					Else
						If (Network.IsClient) Then
							If (LocalInfo = Null) Then
								DisplayLine("Asking the server to authenticate us...")

								' Tell the host our username.
								AskForUser(UserInput)

								UsernameInputComplete = True
							Endif
						Else
							LocalInfo = New User(UserInput, GetNextUserID())

							BringClientUpToSpeed(Null)

							UsernameInputComplete = True
						Endif
					Endif

					UserInput = ""
				Endif
			Elseif (KeyCode = 8) Then
				UserInput = UserInput[..UserInput.Length-1]
			Elseif (KeyCode >= 32) Then
				UserInput += String.FromChar(KeyCode)
			Endif
		Forever

		Return
	End

	Method MaintainChatLog:Void()
		' Calculate the number of entries allowed in the chat-log:
		MaxChatLogEntries = ((DeviceHeight() / 16) - (4 + (Int(ChatOffset) / 16)))

		' Reduce the chat-log size.
		While (ChatLog.Length >= MaxChatLogEntries)
			ChatLog.PopFirst()
		Wend

		Return
	End

	Method DisplayLine:Void(RawText:String)
		' Output to the console.
		Print("{CHAT}: " + RawText)

		MaintainChatLog()

		' Add 'RawText' to the chat-log.
		ChatLog.PushLast(RawText)

		Return
	End

	Method DisplayLine:Void(U:User, Data:String)
		DisplayLine(ProcessLine(U, Data))

		Return
	End

	Method ProcessLine:String(U:User, Data:String)
		Return (U.Name + ": " + Data)
	End

	Method GetUser:User(C:Client)
		If (C = Null) Then
			Return Null
		Endif

		For Local U:= Eachin Users
			If (U.Handle = C) Then
				Return U
			Endif
		Next

		' Return the default response.
		Return Null
	End

	Method GetUser:User(Name:String)
		For Local U:= Eachin Users
			If (U.Name = Name) Then
				Return U
			Endif
		Next

		' Return the default response.
		Return Null
	End

	Method GetUser:User(ID:Int)
		For Local U:= Eachin Users
			If (U.ID = ID) Then
				Return U
			Endif
		Next

		' Return the default response.
		Return Null
	End

	' This repeats text to every 'Client', excluding 'Origin'.
	Method RepeatText:Void(RawText:String, Origin:Client)
		Local P:= Network.AllocatePacket()

		P.WriteLine(RawText)

		For Local C:= Eachin Network
			If (C = Origin) Then
				Continue
			Endif

			Network.Send(P, C, MSG_TYPE_RAW_LINES)
		Next

		Network.ReleasePacket(P)

		Return
	End

	Method GetNextUserID:Int() ' UShort
		Local ID:= NextUserID

		NextUserID += 1

		Return ID
	End

	Method AcceptUser:Bool(Name:String, Origin:Client)
		' Only accept the user if we don't already know them:
		If (GetUser(Origin) = Null) Then
			Local Num:Int = 0 ' Long ' Short

			For Local ExistingUser:= Eachin Users
				If (ExistingUser.Name.StartsWith(Name)) Then
					Local SuffixNum:= Int(ExistingUser.Name[Name.Length..])

					If (SuffixNum > 0) Then
						Num = (SuffixNum + 1)
					Else
						If (ExistingUser.Name = Name) Then
							Num += 1
						Endif
					Endif
				Endif
			Next

			If (Num > 0) Then
				Name += String(Num)
			Endif

			Local U:= New User(Name, GetNextUserID(), Origin)

			Users.AddLast(U)

			SendNotification(U.Name + " has connected.")

			' Tell 'Origin' what we're calling them.
			NotifyAboutUser(U, Origin, True)

			' Tell everyone else that they exist.
			For Local C:= Eachin Network
				If (C = Origin) Then
					Continue
				Endif

				NotifyAboutUser(U, C, False)
			Next

			Return True
		Endif

		' Return the default response.
		Return False
	End

	' This is used by servers to notify users about someone disconnecting.
	Method ReleaseUser:Bool(U:User)
		' Tell everyone that 'U' has disconnected.
		NotifyAboutUserDisconnection(U)

		' Remove this 'User' from our collection.
		Users.RemoveEach(U)

		' Notify everyone.
		SendNotification(U.Name + " has disconnected.")

		' Return the default response.
		Return True
	End

	Method SendText:Void(Text:String, C:Client=Null)
		Local P:= Network.AllocatePacket()

		P.WriteLine(Text)

		Network.Send(P, C, MSG_TYPE_TEXT, True)

		Network.ReleasePacket(P)

		If (C = Null And LocalInfo <> Null) Then
			DisplayLine(LocalInfo, Text)
		Endif

		Return
	End

	Method SendNotification:Void(Text:String, C:Client=Null)
		Local P:= Network.AllocatePacket()

		P.WriteLine(Text)

		Network.Send(P, C, MSG_TYPE_NOTIFICATION, True)

		Network.ReleasePacket(P)

		If (C = Null And LocalInfo <> Null) Then
			DisplayLine(NOTIFICATION_HEADER + Text)
		Endif

		Return
	End

	Method NotifyAboutUserDisconnection:Void(U:User, C:Client=Null)
		Local P:= Network.AllocatePacket()

		P.WriteShort(U.ID)

		Network.Send(P, C, MSG_TYPE_DISCONNECT, True)

		Network.ReleasePacket(P)

		Return
	End

	' These two are effectively the same, they're just not a unified codebase (Laziness):
	Method NotifyAboutUser:Void(U:User, C:Client, IsTheirInfo:Bool, IsServer:Bool=False)
		Local P:= Network.AllocatePacket()

		NetworkSerial.WriteBool(P, IsTheirInfo)
		NetworkSerial.WriteBool(P, IsServer)

		P.WriteShort(U.ID)

		P.WriteLine(U.Name)

		Network.Send(P, C, MSG_TYPE_USER, True, False)

		Network.ReleasePacket(P)

		Return
	End

	Method AskForUser:Void(Name:String, C:Client=Null, IsTheirInfo:Bool=False, IsServer:Bool=False)
		Local P:= Network.AllocatePacket()

		NetworkSerial.WriteBool(P, IsTheirInfo)
		NetworkSerial.WriteBool(P, IsServer)

		P.WriteShort(User.ID_RESERVED)

		P.WriteLine(Name)

		Network.Send(P, C, MSG_TYPE_USER, True, False)

		Network.ReleasePacket(P)

		Return
	End

	Method BringClientUpToSpeed:Void(Incoming:Client)
		For Local U:= Eachin Users
			If (U.Handle <> Incoming) Then
				NotifyAboutUser(U, Incoming, False)
			Endif
		Next

		If (LocalInfo <> Null) Then
			DebugStop()

			' Tell 'Incoming' about us.
			NotifyAboutUser(LocalInfo, Incoming, False, True)
		Endif

		Return
	End

	' Call-backs:
	Method OnReceiveMessage:Void(Network:NetworkEngine, Origin:Client, Type:MessageType, Message:Stream, MessageSize:Int)
		Print("Message received of type " + Type + ", and size " + MessageSize)
		Print("Incoming Address: " + Origin.Address)

		Local U:= GetUser(Origin)

		If (Not Network.IsClient) Then
			If (U = Null) Then
				Select Type
					Case MSG_TYPE_USER
						Local IsMyInfo:= NetworkSerial.ReadBool(Message)
						Local Reserved:= NetworkSerial.ReadBool(Message)
						Local ID:= Message.ReadShort()

						If (Not IsMyInfo And ID = User.ID_RESERVED) Then
							AcceptUser(Message.ReadLine(), Origin)
						Else
							' Nothing so far.
						Endif
				End Select

				Return
			Endif
		Else
			Local HasLocalInfo:Bool = (LocalInfo <> Null)

			Select Type
				Case MSG_TYPE_USER
					Local IsMyInfo:= NetworkSerial.ReadBool(Message)
					Local IsServerInfo:= NetworkSerial.ReadBool(Message)
					Local ID:= Message.ReadShort()
					Local Name:= Message.ReadLine()

					' Check if it's our information:
					If (IsMyInfo And Not IsServerInfo) Then
						If (Not HasLocalInfo) Then
							LocalInfo = New User(Name, ID, Origin)

							DisplayLine("User information established at: " + Origin.Address)
							DisplayLine("Obtained username: " + Name)
						Else
							' Invalid message; already have local information.

							Return
						Endif
					Else
						Local U:User

						U = GetUser(ID)

						If (U = Null) Then
							If (IsServerInfo And ID <> User.ID_RESERVED) Then
								U = New User(Name, ID, Origin)

								DisplayLine("Creating user-entry for " + Name + " at " + Origin.Address)
							Else
								U = New User(Name)

								DisplayLine("Creating user-entry for " + Name)
							Endif

							Users.AddLast(U)
						Endif
					Endif
				Case MSG_TYPE_DISCONNECT
					Local ID:= Message.ReadShort()

					Local U:= GetUser(ID)

					If (U <> Null) Then
						Users.RemoveEach(U)
					Else
						' Nothing so far.
					Endif
				Default
					' Safety net (Allows us to make assumptions later):
					If (Not HasLocalInfo) Then
						Return
					Endif
			End Select
		Endif

		Select Type
			Case MSG_TYPE_TEXT
				If (U = Null) Then
					' Unable to find a user to attach to this message.

					Return
				Endif

				Local Processed:= ProcessLine(U, Message.ReadLine())

				DisplayLine(Processed)

				If (Not Network.IsClient) Then
					' Repeat the message to everyone else.
					RepeatText(Processed, Origin)
				Endif
			Case MSG_TYPE_NOTIFICATION
				If (Not Network.IsClient) Then
					Return
				Endif

				While (Not Message.Eof)
					DisplayLine(NOTIFICATION_HEADER + Message.ReadLine())
				Wend
			Case MSG_TYPE_RAW_LINES
				If (Not Network.IsClient) Then
					Return
				Endif

				While (Not Message.Eof)
					DisplayLine(Message.ReadLine())
				Wend
			Default
				' Nothing so far.
		End Select

		Return
	End

	Method OnDisconnected:Void(Network:NetworkEngine)
		Print("Disconnected.")

		Return
	End

	Method OnClientConnect:Bool(Network:NetworkEngine, Address:NetworkAddress)
		Print("Accepting client at: " + Address)

		' Return the default response. (Accept the new connection)
		Return True
	End

	Method OnClientAccepted:Void(Network:NetworkEngine, C:Client)
		Print("Client accepted: " + C.Address)

		BringClientUpToSpeed(C)

		Return
	End

	Method OnClientDisconnected:Void(Network:NetworkEngine, C:Client)
		Local U:= GetUser(C)

		If (U <> Null) Then
			ReleaseUser(U)
		Endif

		Print("Client disconnected: " + C.Address)

		Return
	End

	Method OnNetworkBind:Void(Network:NetworkEngine, Successful:Bool)
		If (Not Successful) Then
			Print("Unable to bind socket.")

			Network.Close()

			Return
		Endif

		Print("Socket bound.")

		Return
	End

	Method OnSendComplete:Void(Network:NetworkEngine, P:Packet, Address:NetworkAddress, BytesSent:Int)
		'Print("Sending operation complete.")

		Return
	End

	' Properties:
	Method LocalInfo:User() Property
		Return Self._LocalInfo
	End

	Method LocalInfo:Void(Input:User) Property
		Local HasCollection:Bool = (Users <> Null)

		If (Input = Null And HasCollection) Then
			Users.RemoveEach(Self._LocalInfo)
		Endif

		Self._LocalInfo = Input

		If (Input <> Null And HasCollection) Then
			Users.AddLast(Input)
		Endif

		Return
	End

	' Fields (Public):

	' The active network, used for communication.
	Field Network:NetworkEngine

	' This contains other connected users. (Only the host for clients; could be changed easily)
	Field Users:List<User>

	' A log containing a limited number of messages output to the screen.
	Field ChatLog:StringDeque

	' Fields (Protected):
	Protected

	' This holds information about us. (Name, etc; backed by the 'LocalInfo' property)
	Field _LocalInfo:User

	' The user's current input.
	Field UserInput:String

	' This is actively updated with the number of log entries possible.
	' (Used to reduce log sizes, and keep things on screen)
	Field MaxChatLogEntries:Int

	' This is used to offset the chat, so we can render debug text.
	Field ChatOffset:Float

	' The state of the application.
	Field State:Int

	' This is used to keep track of user identifiers.
	Field NextUserID:Int ' UShort

	' Booleans / Flags:

	' This is used to request a username.
	Field UsernameInputComplete:Bool

	Public
End

Class User Final
	' Constant variable(s):
	Const ID_RESERVED:= 0 ' 65536

	' Constructor(s):
	Method New(Name:String, ID:Int=ID_RESERVED, Handle:Client=Null)
		Self.Name = Name
		Self.ID = ID
		Self.Handle = Handle
	End

	Method New(Name:String, Handle:Client)
		Self.Name = Name
		Self.Handle = Handle
	End

	' Fields:
	Field Handle:Client

	Field Name:String
	Field ID:Int = ID_RESERVED ' UShort
End

' Functions:
Function Main:Int()
	' Start the application.
	New Application()

	' Return the default response.
	Return 0
End


Enabled 'VIRTUALOS_EXTENSION_ENCODING_INFO' for JavaScript targets.

Strict

Public

' Preprocessor related:

' Target checks:
#If LANG = "js" ' TARGET = "html5"
	#VIRTUALOS_JS_TARGET = True
#End

' Redirection behavior:

#Rem
	This is used to control native implementation lookups.

	If enabled, this module will only look for the source
	code found in "native", instead of using external modules.

	In the case of 'os' compatibility, disabling this
	would allow C++ targets to use BRL's 'os' module.
#End

#VIRTUALOS_LOCAL_ONLY = True

' Check the build environemnt:
#If VIRTUALOS_JS_TARGET
	' Set the implementation-flag to 'True'.
	#VIRTUALOS_IMPLEMENTED = True

	' JavaScript-specific configuration:

	' Environment:

	' For JS, we need the default file for integral code paths.
	#VIRTUALOS_DEFAULT_FILE = True

	' In addition, we are modular, so we need to stay "non-traditional".
	#VIRTUALOS_TRADITIONAL = False

	' Implementation details:

	' This states that environment variables are mapped from Monkey.
	#VIRTUALOS_MAP_ENV = True
	'#VIRTUALOS_MAP_FILETIMES = True

	' Extensions:

	' This enables "download extensions".
	#VIRTUALOS_EXTENSION_DL = True

	' This enables various virtual file-system extensions.
	#VIRTUALOS_EXTENSION_VFILE = True

	' This is an extension related to "VFILE", essentially,
	' it allows you to check for available storage types.
	#VIRTUALOS_EXTENSION_STORAGE_CAPABILITIES = True

	' This enables extensions that give Monkey programmers the
	' ability to convert from "real paths" to "remote paths".
	#VIRTUALOS_EXTENSION_REMOTEPATH = True

	' This extension allows the native implementation
	' to handle recursive behavior as it sees fit.
	#VIRTUALOS_EXTENSION_NATIVE_RECURSION = True

	' This extension allows the use of unmanaged array passage.
	' (Unsafe; required for some applications)
	#VIRTUALOS_EXTENSION_UNSAFE_LOADARRAY = True

	#VIRTUALOS_EXTENSION_LOADBUFFER = True
	#VIRTUALOS_EXTENSION_LOADBUFFER_SAFE = True

	#VIRTUALOS_EXTENSION_ENCODING_INFO = True

	#If CONFIG = "debug"
		'#VIRTUALOS_DEBUG = True
	#End
#Elseif (LANG = "cpp" And Not VIRTUALOS_LOCAL_ONLY) And TARGET <> "win8" And TARGET <> "ios"
	#VIRTUALOS_REAL = True

	#VIRTUALOS_DEFAULT_FILE = False
	#VIRTUALOS_TRADITIONAL = False
#Else
	' This is a 'good-faith' situation.
	' In other words, this may or may not work:

	' Set the implementation-flag to 'True'.
	#VIRTUALOS_IMPLEMENTED = True

	#VIRTUALOS_DEFAULT_FILE = True
	#VIRTUALOS_TRADITIONAL = True
#End

' Please keep the 'VIRTUALOS_LOCAL_ONLY' and 'VIRTUALOS_TRADITIONAL' flags in mind when using this:
#If VIRTUALOS_REAL
	' Set this to 'True' in order to use 'brl' instead of 'os' as a fallback.
	'#VIRTUALOS_REAL_USE_BRL = True

	' These are used to toggle usage of individual 'brl' modules.
	' These are required to use 'VIRTUALOS_REAL_USE_BRL' effectively:

	'#VIRTUALOS_REAL_PROCESS = True
	'#VIRTUALOS_REAL_FILESYSTEM = True

	' For maximum compatibility, this is enabled by default.
	' If disabled, an internal implementation may be used.
	' In some situations, such a version may cause conflicts.
	#VIRTUALOS_REAL_FILEPATH = True
#End

' Global configuration for implemented 'virtualos' code:
#If VIRTUALOS_IMPLEMENTED
	#VIRTUALOS_STANDALONE = True

	' Enable this for 'os' compatibility with the preprocessor.
	'#VIRTUALOS_FLAG_OS = True

	' This is used by native implementations if necessary.
	' Disable this if you are sure the exact value
	' of 'FileSize' doesn't matter (Excluding 0).
	#VIRTUALOS_CARE_ABOUT_SIZES = True
#End

' If asked to do so, mark 'BRL_OS_IMPLEMENTED' (Compatibility):
#If VIRTUALOS_FLAG_OS
	#BRL_OS_IMPLEMENTED = True
#End

Strict

Public

' Preprocessor related:

' Target checks:
#If LANG = "js" ' TARGET = "html5"
	#VIRTUALOS_JS_TARGET = True
#End

' Redirection behavior:

#Rem
	This is used to control native implementation lookups.

	If enabled, this module will only look for the source
	code found in "native", instead of using external modules.

	In the case of 'os' compatibility, disabling this
	would allow C++ targets to use BRL's 'os' module.
#End

#VIRTUALOS_LOCAL_ONLY = True

' Check the build environemnt:
#If VIRTUALOS_JS_TARGET
	' Set the implementation-flag to 'True'.
	#VIRTUALOS_IMPLEMENTED = True

	' JavaScript-specific configuration:

	' Environment:

	' For JS, we need the default file for integral code paths.
	#VIRTUALOS_DEFAULT_FILE = True

	' In addition, we are modular, so we need to stay "non-traditional".
	#VIRTUALOS_TRADITIONAL = False

	' Implementation details:

	' This states that environment variables are mapped from Monkey.
	#VIRTUALOS_MAP_ENV = True
	'#VIRTUALOS_MAP_FILETIMES = True

	' Extensions:

	' This enables "download extensions".
	#VIRTUALOS_EXTENSION_DL = True

	' This enables various virtual file-system extensions.
	#VIRTUALOS_EXTENSION_VFILE = True

	' This is an extension related to "VFILE", essentially,
	' it allows you to check for available storage types.
	#VIRTUALOS_EXTENSION_STORAGE_CAPABILITIES = True

	' This enables extensions that give Monkey programmers the
	' ability to convert from "real paths" to "remote paths".
	#VIRTUALOS_EXTENSION_REMOTEPATH = True

	' This extension allows the native implementation
	' to handle recursive behavior as it sees fit.
	#VIRTUALOS_EXTENSION_NATIVE_RECURSION = True

	' This extension allows the use of unmanaged array passage.
	' (Unsafe; required for some applications)
	#VIRTUALOS_EXTENSION_UNSAFE_LOADARRAY = True

	#VIRTUALOS_EXTENSION_LOADBUFFER = True
	#VIRTUALOS_EXTENSION_LOADBUFFER_SAFE = True

	#If CONFIG = "debug"
		'#VIRTUALOS_DEBUG = True
	#End
#Elseif (LANG = "cpp" And Not VIRTUALOS_LOCAL_ONLY) And TARGET <> "win8" And TARGET <> "ios"
	#VIRTUALOS_REAL = True

	#VIRTUALOS_DEFAULT_FILE = False
	#VIRTUALOS_TRADITIONAL = False
#Else
	' This is a 'good-faith' situation.
	' In other words, this may or may not work:

	' Set the implementation-flag to 'True'.
	#VIRTUALOS_IMPLEMENTED = True

	#VIRTUALOS_DEFAULT_FILE = True
	#VIRTUALOS_TRADITIONAL = True
#End

' Please keep the 'VIRTUALOS_LOCAL_ONLY' and 'VIRTUALOS_TRADITIONAL' flags in mind when using this:
#If VIRTUALOS_REAL
	' Set this to 'True' in order to use 'brl' instead of 'os' as a fallback.
	'#VIRTUALOS_REAL_USE_BRL = True

	' These are used to toggle usage of individual 'brl' modules.
	' These are required to use 'VIRTUALOS_REAL_USE_BRL' effectively:

	'#VIRTUALOS_REAL_PROCESS = True
	'#VIRTUALOS_REAL_FILESYSTEM = True

	' For maximum compatibility, this is enabled by default.
	' If disabled, an internal implementation may be used.
	' In some situations, such a version may cause conflicts.
	#VIRTUALOS_REAL_FILEPATH = True
#End

' Global configuration for implemented 'virtualos' code:
#If VIRTUALOS_IMPLEMENTED
	#VIRTUALOS_STANDALONE = True

	' Enable this for 'os' compatibility with the preprocessor.
	'#VIRTUALOS_FLAG_OS = True

	' This is used by native implementations if necessary.
	' Disable this if you are sure the exact value
	' of 'FileSize' doesn't matter (Excluding 0).
	#VIRTUALOS_CARE_ABOUT_SIZES = True
#End

' If asked to do so, mark 'BRL_OS_IMPLEMENTED' (Compatibility):
#If VIRTUALOS_FLAG_OS
	#BRL_OS_IMPLEMENTED = True
#End


Fixed "Adding a null object to FlxGroup" bug #56

#rem
	header:This module contains the FlxGroup class
#end
Strict

Import reflection

Import flxextern
Import flxbasic
Import flxobject
Import flxu
Import system.flxarray

Private
Import brl.pool

Public

#Rem
summary:This is an organizational class that can update and render a bunch of FlxBasics.
[i][b]NOTE:[/b] Although FlxGroup extends [a flxbasic.monkey.html]FlxBasic[/a], it will not automatically add itself to the global collisions quad tree, it will only add its members.[/i]
#End
Class FlxGroup Extends FlxBasic

	Global __CLASS__:Object

	#Rem
	summary:See detail.
	Use with [a #Sort]Sort()[/a] to sort in ascending order.
	#End
	Const ASCENDING:Bool = False

	#Rem
	summary:See detail.
	Use with [a #Sort]Sort()[/a] to sort in descending order.
	#End
	Const DESCENDING:Bool = True

Private

	Field _maxSize:Int

	Field _marker:Int

	Field _length:Int

	Field _members:FlxBasic[]

	Field _comparators:StringMap<Comparator>

	Field _comparator:Comparator

	Field _slowIndex:String

	Field _sortDescending:Bool

	Field _dirty:Bool

	Field _defrag:Bool

Public
	#Rem
	summary:Constructor
	Params:
	[list]
	[*]maxSize:Int - the maximum capacity of this group.
	[/list]
	#End
	Method New(maxSize:Int = 0)
		Super.New()
		_maxSize = maxSize
		_marker = 0
		_length = 0
		_cameras = Null
		_comparators = New StringMap<Comparator>()
		_dirty = False
		_defrag = False
	End Method

	#Rem
	summary:The number of entries in the members array.
	For performance and safety you should check this variable instead of members.length unless you really know what you're doing!
	#End
	Method Length:Int() Property
		Return _length
	End Method

	#Rem
	summary:See detail.
	Array of all the [a flxbasic.monkey.html]FlxBasic[/a]s that exist in this group.
	#End
	Method Members:FlxBasic[]() Property
		Return _members
	End Method

	#Rem
	summary:Override this function to handle any deleting or "shutdown" type operations you might need,such as removing traditional Monkey/Mojo children like Images objects.
	#End
	Method Destroy:Void()
		Local basic:FlxBasic
		Local i:Int = 0

		While(i < _length)
			basic = _members[i]
			If (basic <> Null) basic.Destroy()
			i+=1
		Wend

		_length = 0
		_members = _members.Resize(_length)
		_cameras = Null

		If (_comparator <> Null) Then
			_comparator.Clear()
			_comparator = Null
		End If

		If (_comparators <> Null) Then
			_comparators.Clear()
			_comparators = Null
		End If

		Super.Destroy()
	End Method

	#Rem
	summary:Just making sure we don't increment the active objects count.
	#End
	Method PreUpdate:Void()

	End Method

	#Rem
	summary:Automatically goes through and calls update on everything you added.
	#End
	Method Update:Void()
		Local basic:FlxBasic
		Local i:Int = 0

		While(i < _length)
			basic = _members[i]
			If (basic <> Null And basic.exists And basic.active) Then
				basic.PreUpdate()
				basic.Update()
				If (basic.HasTween) basic.UpdateTweens()
				basic.PostUpdate()
			End If
			i+=1
		Wend

		If (HasTween) UpdateTweens()
	End Method

	#Rem
	summary:Automatically goes through and calls render on everything you added.
	#End
	Method Draw:Void()
		If (_cameras <> Null And Not _cameras.Contains(FlxG._CurrentCamera)) Return

		Local basic:FlxBasic
		Local i:Int = 0

		While(i < _length)
			basic = _members[i]
			If (basic <> Null And basic.exists And basic.visible) basic.Draw()
			i+=1
		Wend
	End Method

	#Rem
	summary:The maximum capacity of this group.  Default is 0, meaning no max capacity, and the group can just grow.
	#End
	Method MaxSize:Int() Property
		Return _maxSize
	End Method

	Method MaxSize:Void(size:Int) Property
		_maxSize = size

		If (_marker >= _maxSize) _marker = 0
		If (_maxSize = 0 Or _maxSize >= _length) Return

		Local basic:FlxBasic
		Local i:Int = _maxSize
		Local l:Int = _length

		While(i < l)
			basic = _members[i]
			If (basic <> Null) basic.Destroy()
			i+=1
		Wend

		_members = _members.Resize(_maxSize)
		_length = _maxSize
	End Method

	#Rem
	summary:See details.
	Adds a new [a flxbasic.monkey.html]FlxBasic[/a] subclass ([a flxbasic.monkey.html]FlxBasic[/a], [a flxsprite.monkey.html]FlxSprite[/a], Enemy, etc) to the group. FlxGroup will try to replace a null member of the array first. Failing that, FlxGroup will add it to the end of the member array, assuming there is room for it, and doubling the size of the array if necessary.

	[b]WARNING[/b]: If the group has a maxSize that has already been met, the object will NOT be added to the group!

	Params:
	[list]
	[*][a flxbasic.monkey.html]object:FlxBasic[/a] - the object you want to add to the group.
	[/list]
	Return the same [a flxbasic.monkey.html]FlxBasic[/a] object that was passed in.
	#End
	Method Add:FlxBasic(object:FlxBasic)
		If (_IndexOf(object) >= 0) Return object

		If (Not object) Then
			FlxG.Log("WARNING: Cannot add a `null` object to a FlxGroup.")
			Return Null
		End If

		_dirty = True

		Local i:Int = 0

		While (i < _length)
			If (_members[i] = Null) Then
				_members[i] = object
				If (i >= _length) _length = i+1
				Return object
			End If
			i+=1
		Wend

		If (_maxSize > 0) Then
			If (_length = _members.Length()) Then
				If (_length >= _maxSize) Then
					Return object
				ElseIf (_length*2 + 10 <= _maxSize)
					_members = _members.Resize(_length*2 + 10)
				Else
					_members = _members.Resize(_maxSize)
				End If
			End if
		Else
			If (_length = _members.Length()) Then
				_members = _members.Resize(_length*2 + 10)
			End If
		End If

		_members[i] = object
		_length+=1

		Return object
	End Method

	#Rem
	summary:Recycling is designed to help you reuse game objects without always re-allocating or "newing" them.
	If you specified a maximum size for this group (like in [a flxemitter.monkey.html]FlxEmitter[/a],then recycle will employ what we're calling "rotating" recycling. Recycle() will first check to see if the group is at capacity yet. If group is not yet at capacity, Recycle() returns a new object. If the group IS at capacity, then Recycle() just returns the next object in line

	If you did NOT specify a maximum size for this group, then Recycle() will employ what we're calling "grow-style" recycling. Recycle() will return either the first object with exists = false, or, finding none, add a new object to the array, doubling the size of the array if necessary.

	[b]WARNING:[/b] If this function needs to create a new object, and no object class was provided, it will return null instead of a valid object!

	Params:
	[list]
	[*]objectClass:FlxClass - the type objectClass implements FlxClass you want to recycle (e.g. FlxSpriteClass, EvilRobotClass, etc). Do NOT "new" the class in the parameter!
	[/list]
	Return a reference to the object that was created. Don't forget to cast it back to the objectClass you want (e.g. myObject = myObjectClass(myGroup.recycle(myObjectClassClass))).
	#End
	Method Recycle:FlxBasic(objectClass:ClassInfo = Null)
		'note: TODO: check objectClass first
		If (_maxSize > 0) Then
			If (_length < _maxSize) Then
				If (objectClass = Null) Return Null
				_dirty = True
				Return Add(FlxBasic(objectClass.NewInstance()))
			Else
				Local basic:FlxBasic = _members[_marker]
				_marker+=1
				If (_marker >= _maxSize) _marker = 0
				Return basic
			End If
		Else
			Local basic:FlxBasic = GetFirstAvailable(objectClass)
			If (basic <> Null) Return basic
			If (objectClass = Null) Return Null
			_dirty = True
			Return Add(FlxBasic(objectClass.NewInstance()))
		End If
	End Method

	Method Recycle:FlxBasic(objectOrClass:Object)
		If (ClassInfo(objectOrClass) <> Null) Then
			Return Recycle(ClassInfo(objectOrClass))
		Else
			Return Recycle(GetClass(objectOrClass))
		End If
	End Method

	#Rem
	summary:Removes an object from the group.
	Params:
	[list]
	[*][a flxbasic.monkey.html]object:FlxBasic[/a] - The [a flxbasic.monkey.html]FlxBasic[/a] you want to remove.
	[*]splice:Bool - whether the object should be cut from the array entirely or not.
	[/list]
	Return the removed object.
	#End
	Method Remove:FlxBasic(object:FlxBasic, splice:Bool = False)
		Local index:Int = _IndexOf(object)

		If (index < 0) Return Null

		If (splice) Then
			Local i:Int = index
			Local l:Int = _length

			While(i < _length - 1)
				_members[i] = _members[i+1]
				i+=1
			Wend

			_length-=1
		Else
			_members[index] = Null
		End If

		_dirty = True
		Return object
	End Method

	#Rem
	summary:Replaces an existing [a flxbasic.monkey.html]FlxBasic[/a] with a new one.
	Params:
	[list]
	[*][a flxbasic.monkey.html]oldObject:FlxBasic[/a] - the object you want to replace.
	[*][a flxbasic.monkey.html]newObject:FlxBasic[/a] - the new object you want to use instead.
	[/list]
	Return the new object.
	#End
	Method Replace:FlxBasic(oldObject:FlxBasic, newObject:FlxBasic)
		Local index:Int  = _IndexOf(oldObject)
		If (index < 0) Return Null

		_members[index] = newObject
		_dirty = True
		Return newObject
	End Method

	#Rem
	summary:Call this function to sort the group according to a particular value and order.
	For example, to sort game objects for Zelda-style overlaps you might call myGroup.Sort(FlxObject.YComparator, FlxGroup.ASCENDING) at the bottom of your [a flxstate.monkey.html]FlxState.Update()[/a] override. To sort all existing objects after a big explosion or bomb attack, you might call myGroup.Sort(FlxBasic.ExistsComparator, FlxGroup.DESCENDING)
	Params:
	[list]
	[*]comparator:FlxBasicComparator - The FlxBasicComparator you want to sort on. Default value is FlxObject.YComparator.
	[*]order:Bool - A FlxGroup constant that defines the sort order. Possible values are ASCENDING and DESCENDING. Default value is ASCENDING.
	[/list]
	Return the new object.
	#End
	Method Sort:Void(index:String = "y", order:Bool = ASCENDING)
		If (_dirty) _Validate()

		If ( Not _defrag) Then
			Local b:FlxBasic = GetFirstNotNull()
			If (b = Null) Return

			_comparator = _GetComparator(GetFirstNotNull().GetClassInfo().GetField(index))
			If (_comparator = Null) Return

			_sortDescending = order
			_FastQSort(0, _length - 1)
		Else
			_slowIndex = index
			_sortDescending = order
			_SlowQSort(0, _length - 1)
		End If
	End Method

	Method SetAll:Void(variableName:String, value:Object, recurse:Bool = True)
		If (_dirty) _Validate()

		If ( Not _defrag) Then
			_FastSetAll(variableName, value, recurse)
		Else
			_SlowSetAll(variableName, value, recurse)
		End If
	End Method

	Method CallAll:Void(functionName:String, recurse:Bool = True)
		If (_dirty) _Validate()

		If ( Not _defrag) Then
			_FastCallAll(functionName, recurse)
		Else
			_SlowCallAll(functionName, recurse)
		End If
	End Method

	Method GetFirstAvailable:FlxBasic(objectClass:ClassInfo = null)
		Local basic:FlxBasic
		Local i:Int = 0

		While(i < _length)
			basic = _members[i]
			If (basic <> Null And Not basic.exists And
					(objectClass = Null Or basic.GetClassInfo().ExtendsClass(objectClass))) Return basic
			i+=1
		Wend

		Return Null
	End Method

	Method GetFirstNull:Int()
		Local i:Int = 0

		While(i < _length)
			If (_members[i] = Null) Return i
			i+=1
		Wend

		Return -1
	End Method

	Method GetFirstNotNull:FlxBasic()
		Local i:Int = 0
		Local basic:FlxBasic

		While(i < _length)
			basic = _members[i]
			If (basic <> Null) Return basic
			i+=1
		Wend

		Return Null
	End Method

	Method GetFirstExtant:FlxBasic()
		Local i:Int = 0
		Local basic:FlxBasic

		While(i < _length)
			basic = _members[i]
			If (basic <> Null And basic.exists) Return basic
			i+=1
		Wend

		Return Null
	End Method

	Method GetFirstAlive:FlxBasic()
		Local i:Int = 0
		Local basic:FlxBasic

		While(i < _length)
			basic = _members[i]
			If (basic <> Null And basic.exists And basic.alive) Return basic
			i+=1
		Wend

		Return Null
	End Method

	Method GetFirstDead:FlxBasic()
		Local i:Int = 0
		Local basic:FlxBasic

		While(i < _length)
			basic = _members[i]
			If (basic <> Null And Not basic.alive) Return basic
			i+=1
		Wend

		Return Null
	End Method

	Method CountLiving:Int()
		Local count:Int = -1
		Local i:Int = 0
		Local basic:FlxBasic

		While(i < _length)
			basic = _members[i]
			If (basic <> Null) Then
				If (count < 0) count = 0
				If (basic.exists And basic.alive) count+=1
			End If
			i+=1
		Wend

		Return count
	End Method

	Method CountDead:Int()
		Local count:Int = -1
		Local i:Int = 0
		Local basic:FlxBasic

		While(i < _length)
			basic = _members[i]
			If (basic <> Null) Then
				If (count < 0) count = 0
				If (Not basic.alive) count+=1
			End If
			i+=1
		Wend

		Return count
	End Method

	Method GetRandom:FlxBasic(startIndex:Int = 0, length:Int = 0)
		If (length = 0) length = _length
		Return FlxArray<FlxBasic>.GetSafeRandom(_members, startIndex, length)
	End Method

	Method Clear:Void()
		_length = 0
	End Method

	Method ReviveAll:Void()
		Local i:Int = 0
		Local basic:FlxBasic

		While(i < _length)
			basic = _members[i]
			If (basic <> Null And Not basic.exists) Then
				If (FlxGroup(basic) <> Null) Then
					FlxGroup(basic).ReviveAll()
				Else
					basic.Revive()
				End If
			End If
			i+=1
		Wend

		Super.Revive()
	End Method

	Method Kill:Void()
		Local i:Int = 0
		Local basic:FlxBasic

		While(i < _length)
			basic = _members[i]
			If (basic <> Null And basic.exists) basic.Kill()
			i+=1
		Wend

		Super.Kill()
	End Method

	Method ObjectEnumerator:Enumerator()
		Return _Enumerators.Allocate()._Reset(Self)
	End

Private
	Method _FastSetAll:Void(variableName:String, value:Object, recurse:Bool = True)
		Local basic:FlxBasic = GetFirstNotNull()
		If (basic = Null) Return

		Local f:FieldInfo = basic.GetClassInfo().GetField(variableName)

		If (f = Null) Then
			Local prop:MethodInfo = basic.GetClassInfo().GetMethod(variableName,[GetClass(value)])
			If (prop <> Null) _FastCallAll(prop,[value], recurse)
			Return
		End If

		Local i:Int = 0

		While(i < _length)
			basic = _members[i]

			If (basic <> Null) Then
				If (FlxGroup(basic) <> Null) Then
					If (recurse) FlxGroup(basic).SetAll(variableName, value, recurse)
					Local groupField:FieldInfo = basic.GetClassInfo().GetField(variableName)

					If (groupField = Null) Then
						Local groupProp:MethodInfo = basic.GetClassInfo().GetMethod(variableName,[GetClass(value)])
						If (groupProp <> Null) _FastCallAll(groupProp,[value], recurse)
					Else
						groupField.SetValue(basic, value)
					End If
				Else
					f.SetValue(basic, value)
				End If
			End If

			i+=1
		Wend
	End Method

	Method _SlowSetAll:Void(variableName:String, value:Object, recurse:Bool = True)
		Local basic:FlxBasic, f:FieldInfo, p:MethodInfo
		Local valClass:ClassInfo[] =[GetClass(value)]
		Local valArray:Object[] =[value]

		Local i:Int = 0

		While (i < _length)
			basic = _members[i]

			If (basic <> Null) Then
				If (recurse And FlxGroup(basic) <> Null) FlxGroup(basic)._SlowSetAll(variableName, value, recurse)
				f = basic.GetClassInfo().GetField(variableName)

				If (f = Null) Then
					p = basic.GetClassInfo().GetMethod(variableName, valClass)
					If (p <> Null) p.Invoke(basic, valArray)
				Else
					f.SetValue(basic, value)
				End If
			End If

			i += 1
		Wend
	End Method

	Method _FastCallAll:Void(methodName:String, recurse:Bool = True)
		Local basic:FlxBasic = GetFirstNotNull()
		If (basic = Null) Return

		Local m:MethodInfo = basic.GetClassInfo().GetMethod(methodName,[])
		If (m <> Null) Then
			_FastCallAll(m,[], recurse)
		End If
	End Method

	Method _FastCallAll:Void(methodObject:MethodInfo, values:Object[], recurse:Bool = True)
		Local basic:FlxBasic
		Local i:Int = 0

		While(i < _length)
			basic = _members[i]

			If (basic <> Null) Then
				If (FlxGroup(basic) <> Null) Then
					If (recurse) FlxGroup(basic)._FastCallAll(methodObject, values, recurse)
					Local groupMethod:MethodInfo

					If (values.Length() > 0) Then
						groupMethod = basic.GetClassInfo().GetMethod(methodObject.Name,[GetClass(values[0])])
					Else
						groupMethod = basic.GetClassInfo().GetMethod(methodObject.Name,[])
					End If

					If (groupMethod <> Null) groupMethod.Invoke(basic, values)
				Else
					methodObject.Invoke(basic, values)
				End If
			End If

			i += 1
		Wend
	End Method

	Method _SlowCallAll:Void(methodName:String, recurse:Bool = True)
		Local basic:FlxBasic, m:MethodInfo
		Local i:Int = 0

		While (i < _length)
			basic = _members[i]

			If (basic <> Null) Then
				If (recurse And FlxGroup(basic) <> Null) FlxGroup(basic)._SlowCallAll(methodName, recurse)
				m = basic.GetClassInfo().GetMethod(methodName,[])

				If (m <> Null) Then
					m.Invoke(basic,[])
				End If
			End If

			i += 1
		Wend
	End Method

	Method _Validate:Void()
		_dirty = False
		_defrag = False

		Local basic:FlxBasic = GetFirstNotNull()
		If (basic = Null) Then Return

		Local i:Int = 0
		Local first:ClassInfo = basic.GetClassInfo()

		While (i < _length)
			If (_members[i] <> Null And Not first.ExtendsClass(_members[i].GetClassInfo()) And FlxGroup(_members[i]) = Null) Then
				_defrag = True
				Return
			End If

			i+=1
		Wend
	End Method

	Method _IndexOf:Int(object:FlxBasic)
		Local i:Int = 0

		While(i < _length)
			If (_members[i] = object) Return i
			i+=1
		Wend

		Return -1
	End Method

	Method _FastQSort:Void(left:Int, right:Int)
		If (right > left) Then
			Local pivot:Int = left + (right-left)/2
			Local newPivot:Int = _FastDoSort(left, right, pivot)

			_FastQSort(left, newPivot - 1)
			_FastQSort(newPivot + 1, right)
		End If
	End Method

	Method _FastDoSort:Int(left:Int, right:Int, pivot:Int)
		Local basic:FlxBasic = _members[pivot]

		_members[pivot] = _members[right]
		_members[right] = basic

		Local store:Int = left
		Local basicToCompare:FlxBasic
		Local i:Int = left

		While (i < right)
			basicToCompare = _members[i]

			If (basicToCompare = Null Or basic = Null) Then
				If (basicToCompare <> Null And basic = Null) Then
					_members[i] = _members[store]
					_members[store] = basicToCompare
					store+=1
				End If

				i += 1
				Continue
			End If

			If (_sortDescending) Then
				If (_comparator.Compare(basicToCompare, basic) >= 0) Then
					_members[i] = _members[store]
					_members[store] = basicToCompare
					store+=1
				End If
			Else
				If (_comparator.Compare(basicToCompare, basic) <= 0) Then
					_members[i] = _members[store]
					_members[store] = basicToCompare
					store+=1
				End If
			End If

			i+=1
		Wend

		basic = _members[store]
		_members[store] = _members[right]
		_members[right] = basic

		Return store
	End Method

	Method _SlowQSort:Void(left:Int, right:Int)
		If (right > left) Then
			Local pivot:Int = left + (right-left)/2
			Local newPivot:Int = _SlowDoSort(left, right, pivot)

			_SlowQSort(left, newPivot - 1)
			_SlowQSort(newPivot + 1, right)
		End If
	End Method

	Method _SlowDoSort:Int(left:Int, right:Int, pivot:Int)
		Local basic:FlxBasic = _members[pivot]

		_members[pivot] = _members[right]
		_members[right] = basic

		Local store:Int = left
		Local basicToCompare:FlxBasic
		Local i:Int = left

		While (i < right)
			basicToCompare = _members[i]

			If (basicToCompare = Null Or basic = Null) Then
				If (basicToCompare <> Null And basic = Null) Then
					_members[i] = _members[store]
					_members[store] = basicToCompare
					store+=1
				End If

				i += 1
				Continue
			End If

			_comparator = _GetComparator(basicToCompare.GetClassInfo().GetField(_slowIndex))

			If (_comparator <> Null) Then
				If (_sortDescending) Then
					If (_comparator.Compare(basicToCompare, basic) >= 0) Then
						_members[i] = _members[store]
						_members[store] = basicToCompare
						store+=1
					End If
				Else
					If (_comparator.Compare(basicToCompare, basic) <= 0) Then
						_members[i] = _members[store]
						_members[store] = basicToCompare
						store+=1
					End If
				End If
			End If

			i+=1
		Wend

		basic = _members[store]
		_members[store] = _members[right]
		_members[right] = basic

		Return store
	End Method
	Method _GetComparator:Comparator(sortIndex:FieldInfo)
		If (sortIndex = Null) Return Null

		Local key:String = sortIndex.Type.Name
		Local comparator:Comparator = _comparators.Get(key)

		If (comparator = Null) Then
			Select key
				Case FloatClass().Name
					comparator = New FloatComparator()

				Case IntClass().Name
					comparator = New IntComparator()

				Case BoolClass().Name
					comparator = New BoolComparator()

				Case StringClass().Name
					comparator = New StringComparator()

				Default
					key = FlxU.GetObjectClass().Name
					comparator = _comparators.Get(key)

					If (comparator = Null) comparator = New ObjectComparator()
			End Select

			_comparators.Set(key, comparator)
		End If

		comparator.Clear()

		If (key <> FlxU.GetObjectClass().Name) Then
			comparator.Reset(sortIndex)
		Else
			Local sortMethod:MethodInfo = sortIndex.Type.GetMethod("Compare",[sortIndex.Type])
			If (sortMethod = Null) sortMethod = sortIndex.Type.GetMethod("Compare",[FlxU.GetObjectClass()])

			If (sortMethod <> Null) Then
				comparator.Reset(sortIndex, sortMethod)
			Else
				comparator = Null
			End If
		End If

		Return comparator
	End Method

End Class

Class Enumerator

	Method HasNext:Bool()
		If (_dirty) Then
			_dirty = False
			_hasNext = False

			Repeat
				If (_nextIndex = _group._length) Then
					_Enumerators.Free(Self)
					Return _hasNext
				End If

				_basic = _group._members[_nextIndex]

				_nextIndex += 1
			Until (_basic <> Null)

			_hasNext = True
		End If

		If ( Not _hasNext) _Enumerators.Free(Self)
		Return _hasNext
	End

	Method NextObject:FlxBasic()
		If (_dirty And Not HasNext()) Return Null

		_dirty = True
		Return _basic
	End

	Method Destroy:Void()
		_Reset(Null)
		_basic = Null
	End Method

Private

	Field _group:FlxGroup

	Field _index:Int

	Field _nextIndex:Int

	Field _hasNext:Bool

	Field _dirty:Bool

	Field _basic:FlxBasic

	Method _Reset:Enumerator(group:FlxGroup)
		_group = group
		_nextIndex = 0
		_hasNext = False
		_dirty = True

		Return Self
	End Method

End

Private

Global _Enumerators:Pool<Enumerator> = New Pool<Enumerator>()

Class Comparator

	Method Compare:Int(basic1:FlxBasic, basic2:FlxBasic) Abstract

	Method Reset:Comparator(sortIndex:FieldInfo, sortMethod:MethodInfo = Null)
		_sortIndex = sortIndex
		_sortMethod = sortMethod

		Return Self
	End Method

	Method Clear:Void()
		_sortIndex = Null
		_sortMethod = Null
	End Method

Private

	Field _sortIndex:FieldInfo

	Field _sortMethod:MethodInfo

End Class

Class BoolComparator Extends Comparator

	Method Compare:Int(basic1:FlxBasic, basic2:FlxBasic)
		Return Int(UnboxBool(_sortIndex.GetValue(basic1))) - Int(UnboxBool(_sortIndex.GetValue(basic2)))
	End Method

End Class

Class IntComparator Extends Comparator

	Method Compare:Int(basic1:FlxBasic, basic2:FlxBasic)
		Return UnboxInt(_sortIndex.GetValue(basic1)) - UnboxInt(_sortIndex.GetValue(basic2))
	End Method

End Class

Class FloatComparator Extends Comparator

	Method Compare:Int(basic1:FlxBasic, basic2:FlxBasic)
		Return UnboxFloat(_sortIndex.GetValue(basic1)) - UnboxFloat(_sortIndex.GetValue(basic2))
	End Method

End Class

Class StringComparator Extends Comparator

	Method Compare:Int(basic1:FlxBasic, basic2:FlxBasic)
		Return UnboxString(_sortIndex.GetValue(basic1)).Compare(UnboxString(_sortIndex.GetValue(basic2)))
	End Method

End Class

Class ObjectComparator Extends Comparator

	Method Compare:Int(basic1:FlxBasic, basic2:FlxBasic)
		Return UnboxInt(_sortMethod.Invoke(_sortIndex.GetValue(basic1),[_sortIndex.GetValue(basic2)]))
	End Method

End Class

#Rem
footer:Flixel is an open source game-making library that is completely free for personal or commercial use.
[quote]Copyright: Flixel - 2009-2011 Adam 'Atomic' Saltsman
Copyright: Monkey port - 2011 Arthur 'devolonter' Bikmullin

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

NOTE FROM THE AUTHOR: As far as I know, you only have to include
this license if you are redistributing source code that includes
the Flixel library.  There is no need (or way, afaik) to include
it in your compiled flash games and apps![/quote]
#End

#rem
	header:This module contains the FlxGroup class
#end
Strict

Import reflection

Import flxextern
Import flxbasic
Import flxobject
Import flxu
Import system.flxarray

Private
Import brl.pool

Public

#Rem
summary:This is an organizational class that can update and render a bunch of FlxBasics.
[i][b]NOTE:[/b] Although FlxGroup extends [a flxbasic.monkey.html]FlxBasic[/a], it will not automatically add itself to the global collisions quad tree, it will only add its members.[/i]
#End
Class FlxGroup Extends FlxBasic

	Global __CLASS__:Object

	#Rem
	summary:See detail.
	Use with [a #Sort]Sort()[/a] to sort in ascending order.
	#End
	Const ASCENDING:Bool = False

	#Rem
	summary:See detail.
	Use with [a #Sort]Sort()[/a] to sort in descending order.
	#End
	Const DESCENDING:Bool = True

Private

	Field _maxSize:Int

	Field _marker:Int

	Field _length:Int

	Field _members:FlxBasic[]

	Field _comparators:StringMap<Comparator>

	Field _comparator:Comparator

	Field _slowIndex:String

	Field _sortDescending:Bool

	Field _dirty:Bool

	Field _defrag:Bool

Public
	#Rem
	summary:Constructor
	Params:
	[list]
	[*]maxSize:Int - the maximum capacity of this group.
	[/list]
	#End
	Method New(maxSize:Int = 0)
		Super.New()
		_maxSize = maxSize
		_marker = 0
		_length = 0
		_cameras = Null
		_comparators = New StringMap<Comparator>()
		_dirty = False
		_defrag = False
	End Method

	#Rem
	summary:The number of entries in the members array.
	For performance and safety you should check this variable instead of members.length unless you really know what you're doing!
	#End
	Method Length:Int() Property
		Return _length
	End Method

	#Rem
	summary:See detail.
	Array of all the [a flxbasic.monkey.html]FlxBasic[/a]s that exist in this group.
	#End
	Method Members:FlxBasic[]() Property
		Return _members
	End Method

	#Rem
	summary:Override this function to handle any deleting or "shutdown" type operations you might need,such as removing traditional Monkey/Mojo children like Images objects.
	#End
	Method Destroy:Void()
		Local basic:FlxBasic
		Local i:Int = 0

		While(i < _length)
			basic = _members[i]
			If (basic <> Null) basic.Destroy()
			i+=1
		Wend

		_length = 0
		_members = _members.Resize(_length)
		_cameras = Null

		If (_comparator <> Null) Then
			_comparator.Clear()
			_comparator = Null
		End If

		If (_comparators <> Null) Then
			_comparators.Clear()
			_comparators = Null
		End If

		Super.Destroy()
	End Method

	#Rem
	summary:Just making sure we don't increment the active objects count.
	#End
	Method PreUpdate:Void()

	End Method

	#Rem
	summary:Automatically goes through and calls update on everything you added.
	#End
	Method Update:Void()
		Local basic:FlxBasic
		Local i:Int = 0

		While(i < _length)
			basic = _members[i]
			If (basic <> Null And basic.exists And basic.active) Then
				basic.PreUpdate()
				basic.Update()
				If (basic.HasTween) basic.UpdateTweens()
				basic.PostUpdate()
			End If
			i+=1
		Wend

		If (HasTween) UpdateTweens()
	End Method

	#Rem
	summary:Automatically goes through and calls render on everything you added.
	#End
	Method Draw:Void()
		If (_cameras <> Null And Not _cameras.Contains(FlxG._CurrentCamera)) Return

		Local basic:FlxBasic
		Local i:Int = 0

		While(i < _length)
			basic = _members[i]
			If (basic <> Null And basic.exists And basic.visible) basic.Draw()
			i+=1
		Wend
	End Method

	#Rem
	summary:The maximum capacity of this group.  Default is 0, meaning no max capacity, and the group can just grow.
	#End
	Method MaxSize:Int() Property
		Return _maxSize
	End Method

	Method MaxSize:Void(size:Int) Property
		_maxSize = size

		If (_marker >= _maxSize) _marker = 0
		If (_maxSize = 0 Or _maxSize >= _length) Return

		Local basic:FlxBasic
		Local i:Int = _maxSize
		Local l:Int = _length

		While(i < l)
			basic = _members[i]
			If (basic <> Null) basic.Destroy()
			i+=1
		Wend

		_members = _members.Resize(_maxSize)
		_length = _maxSize
	End Method

	#Rem
	summary:See details.
	Adds a new [a flxbasic.monkey.html]FlxBasic[/a] subclass ([a flxbasic.monkey.html]FlxBasic[/a], [a flxsprite.monkey.html]FlxSprite[/a], Enemy, etc) to the group. FlxGroup will try to replace a null member of the array first. Failing that, FlxGroup will add it to the end of the member array, assuming there is room for it, and doubling the size of the array if necessary.

	[b]WARNING[/b]: If the group has a maxSize that has already been met, the object will NOT be added to the group!

	Params:
	[list]
	[*][a flxbasic.monkey.html]object:FlxBasic[/a] - the object you want to add to the group.
	[/list]
	Return the same [a flxbasic.monkey.html]FlxBasic[/a] object that was passed in.
	#End
	Method Add:FlxBasic(object:FlxBasic)
		If (_IndexOf(object) >= 0) Return object
		_dirty = True

		Local i:Int = 0

		While (i < _length)
			If (_members[i] = Null) Then
				_members[i] = object
				If (i >= _length) _length = i+1
				Return object
			End If
			i+=1
		Wend

		If (_maxSize > 0) Then
			If (_length = _members.Length()) Then
				If (_length >= _maxSize) Then
					Return object
				ElseIf (_length*2 + 10 <= _maxSize)
					_members = _members.Resize(_length*2 + 10)
				Else
					_members = _members.Resize(_maxSize)
				End If
			End if
		Else
			If (_length = _members.Length()) Then
				_members = _members.Resize(_length*2 + 10)
			End If
		End If

		_members[i] = object
		_length+=1

		Return object
	End Method

	#Rem
	summary:Recycling is designed to help you reuse game objects without always re-allocating or "newing" them.
	If you specified a maximum size for this group (like in [a flxemitter.monkey.html]FlxEmitter[/a],then recycle will employ what we're calling "rotating" recycling. Recycle() will first check to see if the group is at capacity yet. If group is not yet at capacity, Recycle() returns a new object. If the group IS at capacity, then Recycle() just returns the next object in line

	If you did NOT specify a maximum size for this group, then Recycle() will employ what we're calling "grow-style" recycling. Recycle() will return either the first object with exists = false, or, finding none, add a new object to the array, doubling the size of the array if necessary.

	[b]WARNING:[/b] If this function needs to create a new object, and no object class was provided, it will return null instead of a valid object!

	Params:
	[list]
	[*]objectClass:FlxClass - the type objectClass implements FlxClass you want to recycle (e.g. FlxSpriteClass, EvilRobotClass, etc). Do NOT "new" the class in the parameter!
	[/list]
	Return a reference to the object that was created. Don't forget to cast it back to the objectClass you want (e.g. myObject = myObjectClass(myGroup.recycle(myObjectClassClass))).
	#End
	Method Recycle:FlxBasic(objectClass:ClassInfo = Null)
		'note: TODO: check objectClass first
		If (_maxSize > 0) Then
			If (_length < _maxSize) Then
				If (objectClass = Null) Return Null
				_dirty = True
				Return Add(FlxBasic(objectClass.NewInstance()))
			Else
				Local basic:FlxBasic = _members[_marker]
				_marker+=1
				If (_marker >= _maxSize) _marker = 0
				Return basic
			End If
		Else
			Local basic:FlxBasic = GetFirstAvailable(objectClass)
			If (basic <> Null) Return basic
			If (objectClass = Null) Return Null
			_dirty = True
			Return Add(FlxBasic(objectClass.NewInstance()))
		End If
	End Method

	Method Recycle:FlxBasic(objectOrClass:Object)
		If (ClassInfo(objectOrClass) <> Null) Then
			Return Recycle(ClassInfo(objectOrClass))
		Else
			Return Recycle(GetClass(objectOrClass))
		End If
	End Method

	#Rem
	summary:Removes an object from the group.
	Params:
	[list]
	[*][a flxbasic.monkey.html]object:FlxBasic[/a] - The [a flxbasic.monkey.html]FlxBasic[/a] you want to remove.
	[*]splice:Bool - whether the object should be cut from the array entirely or not.
	[/list]
	Return the removed object.
	#End
	Method Remove:FlxBasic(object:FlxBasic, splice:Bool = False)
		Local index:Int = _IndexOf(object)

		If (index < 0) Return Null

		If (splice) Then
			Local i:Int = index
			Local l:Int = _length

			While(i < _length - 1)
				_members[i] = _members[i+1]
				i+=1
			Wend

			_length-=1
		Else
			_members[index] = Null
		End If

		_dirty = True
		Return object
	End Method

	#Rem
	summary:Replaces an existing [a flxbasic.monkey.html]FlxBasic[/a] with a new one.
	Params:
	[list]
	[*][a flxbasic.monkey.html]oldObject:FlxBasic[/a] - the object you want to replace.
	[*][a flxbasic.monkey.html]newObject:FlxBasic[/a] - the new object you want to use instead.
	[/list]
	Return the new object.
	#End
	Method Replace:FlxBasic(oldObject:FlxBasic, newObject:FlxBasic)
		Local index:Int  = _IndexOf(oldObject)
		If (index < 0) Return Null

		_members[index] = newObject
		_dirty = True
		Return newObject
	End Method

	#Rem
	summary:Call this function to sort the group according to a particular value and order.
	For example, to sort game objects for Zelda-style overlaps you might call myGroup.Sort(FlxObject.YComparator, FlxGroup.ASCENDING) at the bottom of your [a flxstate.monkey.html]FlxState.Update()[/a] override. To sort all existing objects after a big explosion or bomb attack, you might call myGroup.Sort(FlxBasic.ExistsComparator, FlxGroup.DESCENDING)
	Params:
	[list]
	[*]comparator:FlxBasicComparator - The FlxBasicComparator you want to sort on. Default value is FlxObject.YComparator.
	[*]order:Bool - A FlxGroup constant that defines the sort order. Possible values are ASCENDING and DESCENDING. Default value is ASCENDING.
	[/list]
	Return the new object.
	#End
	Method Sort:Void(index:String = "y", order:Bool = ASCENDING)
		If (_dirty) _Validate()

		If ( Not _defrag) Then
			Local b:FlxBasic = GetFirstNotNull()
			If (b = Null) Return

			_comparator = _GetComparator(GetFirstNotNull().GetClassInfo().GetField(index))
			If (_comparator = Null) Return

			_sortDescending = order
			_FastQSort(0, _length - 1)
		Else
			_slowIndex = index
			_sortDescending = order
			_SlowQSort(0, _length - 1)
		End If
	End Method

	Method SetAll:Void(variableName:String, value:Object, recurse:Bool = True)
		If (_dirty) _Validate()

		If ( Not _defrag) Then
			_FastSetAll(variableName, value, recurse)
		Else
			_SlowSetAll(variableName, value, recurse)
		End If
	End Method

	Method CallAll:Void(functionName:String, recurse:Bool = True)
		If (_dirty) _Validate()

		If ( Not _defrag) Then
			_FastCallAll(functionName, recurse)
		Else
			_SlowCallAll(functionName, recurse)
		End If
	End Method

	Method GetFirstAvailable:FlxBasic(objectClass:ClassInfo = null)
		Local basic:FlxBasic
		Local i:Int = 0

		While(i < _length)
			basic = _members[i]
			If (basic <> Null And Not basic.exists And
					(objectClass = Null Or basic.GetClassInfo().ExtendsClass(objectClass))) Return basic
			i+=1
		Wend

		Return Null
	End Method

	Method GetFirstNull:Int()
		Local i:Int = 0

		While(i < _length)
			If (_members[i] = Null) Return i
			i+=1
		Wend

		Return -1
	End Method

	Method GetFirstNotNull:FlxBasic()
		Local i:Int = 0
		Local basic:FlxBasic

		While(i < _length)
			basic = _members[i]
			If (basic <> Null) Return basic
			i+=1
		Wend

		Return Null
	End Method

	Method GetFirstExtant:FlxBasic()
		Local i:Int = 0
		Local basic:FlxBasic

		While(i < _length)
			basic = _members[i]
			If (basic <> Null And basic.exists) Return basic
			i+=1
		Wend

		Return Null
	End Method

	Method GetFirstAlive:FlxBasic()
		Local i:Int = 0
		Local basic:FlxBasic

		While(i < _length)
			basic = _members[i]
			If (basic <> Null And basic.exists And basic.alive) Return basic
			i+=1
		Wend

		Return Null
	End Method

	Method GetFirstDead:FlxBasic()
		Local i:Int = 0
		Local basic:FlxBasic

		While(i < _length)
			basic = _members[i]
			If (basic <> Null And Not basic.alive) Return basic
			i+=1
		Wend

		Return Null
	End Method

	Method CountLiving:Int()
		Local count:Int = -1
		Local i:Int = 0
		Local basic:FlxBasic

		While(i < _length)
			basic = _members[i]
			If (basic <> Null) Then
				If (count < 0) count = 0
				If (basic.exists And basic.alive) count+=1
			End If
			i+=1
		Wend

		Return count
	End Method

	Method CountDead:Int()
		Local count:Int = -1
		Local i:Int = 0
		Local basic:FlxBasic

		While(i < _length)
			basic = _members[i]
			If (basic <> Null) Then
				If (count < 0) count = 0
				If (Not basic.alive) count+=1
			End If
			i+=1
		Wend

		Return count
	End Method

	Method GetRandom:FlxBasic(startIndex:Int = 0, length:Int = 0)
		If (length = 0) length = _length
		Return FlxArray<FlxBasic>.GetSafeRandom(_members, startIndex, length)
	End Method

	Method Clear:Void()
		_length = 0
	End Method

	Method ReviveAll:Void()
		Local i:Int = 0
		Local basic:FlxBasic

		While(i < _length)
			basic = _members[i]
			If (basic <> Null And Not basic.exists) Then
				If (FlxGroup(basic) <> Null) Then
					FlxGroup(basic).ReviveAll()
				Else
					basic.Revive()
				End If
			End If
			i+=1
		Wend

		Super.Revive()
	End Method

	Method Kill:Void()
		Local i:Int = 0
		Local basic:FlxBasic

		While(i < _length)
			basic = _members[i]
			If (basic <> Null And basic.exists) basic.Kill()
			i+=1
		Wend

		Super.Kill()
	End Method

	Method ObjectEnumerator:Enumerator()
		Return _Enumerators.Allocate()._Reset(Self)
	End

Private
	Method _FastSetAll:Void(variableName:String, value:Object, recurse:Bool = True)
		Local basic:FlxBasic = GetFirstNotNull()
		If (basic = Null) Return

		Local f:FieldInfo = basic.GetClassInfo().GetField(variableName)

		If (f = Null) Then
			Local prop:MethodInfo = basic.GetClassInfo().GetMethod(variableName,[GetClass(value)])
			If (prop <> Null) _FastCallAll(prop,[value], recurse)
			Return
		End If

		Local i:Int = 0

		While(i < _length)
			basic = _members[i]

			If (basic <> Null) Then
				If (FlxGroup(basic) <> Null) Then
					If (recurse) FlxGroup(basic).SetAll(variableName, value, recurse)
					Local groupField:FieldInfo = basic.GetClassInfo().GetField(variableName)

					If (groupField = Null) Then
						Local groupProp:MethodInfo = basic.GetClassInfo().GetMethod(variableName,[GetClass(value)])
						If (groupProp <> Null) _FastCallAll(groupProp,[value], recurse)
					Else
						groupField.SetValue(basic, value)
					End If
				Else
					f.SetValue(basic, value)
				End If
			End If

			i+=1
		Wend
	End Method

	Method _SlowSetAll:Void(variableName:String, value:Object, recurse:Bool = True)
		Local basic:FlxBasic, f:FieldInfo, p:MethodInfo
		Local valClass:ClassInfo[] =[GetClass(value)]
		Local valArray:Object[] =[value]

		Local i:Int = 0

		While (i < _length)
			basic = _members[i]

			If (basic <> Null) Then
				If (recurse And FlxGroup(basic) <> Null) FlxGroup(basic)._SlowSetAll(variableName, value, recurse)
				f = basic.GetClassInfo().GetField(variableName)

				If (f = Null) Then
					p = basic.GetClassInfo().GetMethod(variableName, valClass)
					If (p <> Null) p.Invoke(basic, valArray)
				Else
					f.SetValue(basic, value)
				End If
			End If

			i += 1
		Wend
	End Method

	Method _FastCallAll:Void(methodName:String, recurse:Bool = True)
		Local basic:FlxBasic = GetFirstNotNull()
		If (basic = Null) Return

		Local m:MethodInfo = basic.GetClassInfo().GetMethod(methodName,[])
		If (m <> Null) Then
			_FastCallAll(m,[], recurse)
		End If
	End Method

	Method _FastCallAll:Void(methodObject:MethodInfo, values:Object[], recurse:Bool = True)
		Local basic:FlxBasic
		Local i:Int = 0

		While(i < _length)
			basic = _members[i]

			If (basic <> Null) Then
				If (FlxGroup(basic) <> Null) Then
					If (recurse) FlxGroup(basic)._FastCallAll(methodObject, values, recurse)
					Local groupMethod:MethodInfo

					If (values.Length() > 0) Then
						groupMethod = basic.GetClassInfo().GetMethod(methodObject.Name,[GetClass(values[0])])
					Else
						groupMethod = basic.GetClassInfo().GetMethod(methodObject.Name,[])
					End If

					If (groupMethod <> Null) groupMethod.Invoke(basic, values)
				Else
					methodObject.Invoke(basic, values)
				End If
			End If

			i += 1
		Wend
	End Method

	Method _SlowCallAll:Void(methodName:String, recurse:Bool = True)
		Local basic:FlxBasic, m:MethodInfo
		Local i:Int = 0

		While (i < _length)
			basic = _members[i]

			If (basic <> Null) Then
				If (recurse And FlxGroup(basic) <> Null) FlxGroup(basic)._SlowCallAll(methodName, recurse)
				m = basic.GetClassInfo().GetMethod(methodName,[])

				If (m <> Null) Then
					m.Invoke(basic,[])
				End If
			End If

			i += 1
		Wend
	End Method

	Method _Validate:Void()
		_dirty = False
		_defrag = False

		Local basic:FlxBasic = GetFirstNotNull()
		If (basic = Null) Then Return

		Local i:Int = 0
		Local first:ClassInfo = basic.GetClassInfo()

		While (i < _length)
			If (_members[i] <> Null And Not first.ExtendsClass(_members[i].GetClassInfo()) And FlxGroup(_members[i]) = Null) Then
				_defrag = True
				Return
			End If

			i+=1
		Wend
	End Method

	Method _IndexOf:Int(object:FlxBasic)
		Local i:Int = 0

		While(i < _length)
			If (_members[i] = object) Return i
			i+=1
		Wend

		Return -1
	End Method

	Method _FastQSort:Void(left:Int, right:Int)
		If (right > left) Then
			Local pivot:Int = left + (right-left)/2
			Local newPivot:Int = _FastDoSort(left, right, pivot)

			_FastQSort(left, newPivot - 1)
			_FastQSort(newPivot + 1, right)
		End If
	End Method

	Method _FastDoSort:Int(left:Int, right:Int, pivot:Int)
		Local basic:FlxBasic = _members[pivot]

		_members[pivot] = _members[right]
		_members[right] = basic

		Local store:Int = left
		Local basicToCompare:FlxBasic
		Local i:Int = left

		While (i < right)
			basicToCompare = _members[i]

			If (basicToCompare = Null Or basic = Null) Then
				If (basicToCompare <> Null And basic = Null) Then
					_members[i] = _members[store]
					_members[store] = basicToCompare
					store+=1
				End If

				i += 1
				Continue
			End If

			If (_sortDescending) Then
				If (_comparator.Compare(basicToCompare, basic) >= 0) Then
					_members[i] = _members[store]
					_members[store] = basicToCompare
					store+=1
				End If
			Else
				If (_comparator.Compare(basicToCompare, basic) <= 0) Then
					_members[i] = _members[store]
					_members[store] = basicToCompare
					store+=1
				End If
			End If

			i+=1
		Wend

		basic = _members[store]
		_members[store] = _members[right]
		_members[right] = basic

		Return store
	End Method

	Method _SlowQSort:Void(left:Int, right:Int)
		If (right > left) Then
			Local pivot:Int = left + (right-left)/2
			Local newPivot:Int = _SlowDoSort(left, right, pivot)

			_SlowQSort(left, newPivot - 1)
			_SlowQSort(newPivot + 1, right)
		End If
	End Method

	Method _SlowDoSort:Int(left:Int, right:Int, pivot:Int)
		Local basic:FlxBasic = _members[pivot]

		_members[pivot] = _members[right]
		_members[right] = basic

		Local store:Int = left
		Local basicToCompare:FlxBasic
		Local i:Int = left

		While (i < right)
			basicToCompare = _members[i]

			If (basicToCompare = Null Or basic = Null) Then
				If (basicToCompare <> Null And basic = Null) Then
					_members[i] = _members[store]
					_members[store] = basicToCompare
					store+=1
				End If

				i += 1
				Continue
			End If

			_comparator = _GetComparator(basicToCompare.GetClassInfo().GetField(_slowIndex))

			If (_comparator <> Null) Then
				If (_sortDescending) Then
					If (_comparator.Compare(basicToCompare, basic) >= 0) Then
						_members[i] = _members[store]
						_members[store] = basicToCompare
						store+=1
					End If
				Else
					If (_comparator.Compare(basicToCompare, basic) <= 0) Then
						_members[i] = _members[store]
						_members[store] = basicToCompare
						store+=1
					End If
				End If
			End If

			i+=1
		Wend

		basic = _members[store]
		_members[store] = _members[right]
		_members[right] = basic

		Return store
	End Method
	Method _GetComparator:Comparator(sortIndex:FieldInfo)
		If (sortIndex = Null) Return Null

		Local key:String = sortIndex.Type.Name
		Local comparator:Comparator = _comparators.Get(key)

		If (comparator = Null) Then
			Select key
				Case FloatClass().Name
					comparator = New FloatComparator()

				Case IntClass().Name
					comparator = New IntComparator()

				Case BoolClass().Name
					comparator = New BoolComparator()

				Case StringClass().Name
					comparator = New StringComparator()

				Default
					key = FlxU.GetObjectClass().Name
					comparator = _comparators.Get(key)

					If (comparator = Null) comparator = New ObjectComparator()
			End Select

			_comparators.Set(key, comparator)
		End If

		comparator.Clear()

		If (key <> FlxU.GetObjectClass().Name) Then
			comparator.Reset(sortIndex)
		Else
			Local sortMethod:MethodInfo = sortIndex.Type.GetMethod("Compare",[sortIndex.Type])
			If (sortMethod = Null) sortMethod = sortIndex.Type.GetMethod("Compare",[FlxU.GetObjectClass()])

			If (sortMethod <> Null) Then
				comparator.Reset(sortIndex, sortMethod)
			Else
				comparator = Null
			End If
		End If

		Return comparator
	End Method

End Class

Class Enumerator

	Method HasNext:Bool()
		If (_dirty) Then
			_dirty = False
			_hasNext = False

			Repeat
				If (_nextIndex = _group._length) Then
					_Enumerators.Free(Self)
					Return _hasNext
				End If

				_basic = _group._members[_nextIndex]

				_nextIndex += 1
			Until (_basic <> Null)

			_hasNext = True
		End If

		If ( Not _hasNext) _Enumerators.Free(Self)
		Return _hasNext
	End

	Method NextObject:FlxBasic()
		If (_dirty And Not HasNext()) Return Null

		_dirty = True
		Return _basic
	End

	Method Destroy:Void()
		_Reset(Null)
		_basic = Null
	End Method

Private

	Field _group:FlxGroup

	Field _index:Int

	Field _nextIndex:Int

	Field _hasNext:Bool

	Field _dirty:Bool

	Field _basic:FlxBasic

	Method _Reset:Enumerator(group:FlxGroup)
		_group = group
		_nextIndex = 0
		_hasNext = False
		_dirty = True

		Return Self
	End Method

End

Private

Global _Enumerators:Pool<Enumerator> = New Pool<Enumerator>()

Class Comparator

	Method Compare:Int(basic1:FlxBasic, basic2:FlxBasic) Abstract

	Method Reset:Comparator(sortIndex:FieldInfo, sortMethod:MethodInfo = Null)
		_sortIndex = sortIndex
		_sortMethod = sortMethod

		Return Self
	End Method

	Method Clear:Void()
		_sortIndex = Null
		_sortMethod = Null
	End Method

Private

	Field _sortIndex:FieldInfo

	Field _sortMethod:MethodInfo

End Class

Class BoolComparator Extends Comparator

	Method Compare:Int(basic1:FlxBasic, basic2:FlxBasic)
		Return Int(UnboxBool(_sortIndex.GetValue(basic1))) - Int(UnboxBool(_sortIndex.GetValue(basic2)))
	End Method

End Class

Class IntComparator Extends Comparator

	Method Compare:Int(basic1:FlxBasic, basic2:FlxBasic)
		Return UnboxInt(_sortIndex.GetValue(basic1)) - UnboxInt(_sortIndex.GetValue(basic2))
	End Method

End Class

Class FloatComparator Extends Comparator

	Method Compare:Int(basic1:FlxBasic, basic2:FlxBasic)
		Return UnboxFloat(_sortIndex.GetValue(basic1)) - UnboxFloat(_sortIndex.GetValue(basic2))
	End Method

End Class

Class StringComparator Extends Comparator

	Method Compare:Int(basic1:FlxBasic, basic2:FlxBasic)
		Return UnboxString(_sortIndex.GetValue(basic1)).Compare(UnboxString(_sortIndex.GetValue(basic2)))
	End Method

End Class

Class ObjectComparator Extends Comparator

	Method Compare:Int(basic1:FlxBasic, basic2:FlxBasic)
		Return UnboxInt(_sortMethod.Invoke(_sortIndex.GetValue(basic1),[_sortIndex.GetValue(basic2)]))
	End Method

End Class

#Rem
footer:Flixel is an open source game-making library that is completely free for personal or commercial use.
[quote]Copyright: Flixel - 2009-2011 Adam 'Atomic' Saltsman
Copyright: Monkey port - 2011 Arthur 'devolonter' Bikmullin

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

NOTE FROM THE AUTHOR: As far as I know, you only have to include
this license if you are redistributing source code that includes
the Flixel library.  There is no need (or way, afaik) to include
it in your compiled flash games and apps![/quote]
#End


add missing import statement to Vector2D

Strict

Private

Import bono

Public

Class Vector2D
    Field x:Float
    Field y:Float

    Method New(x:Float=0, y:Float=0)
        Self.x = x
        Self.y = y
    End

    Method IsZero:Bool()
        Return (x = 0 And y = 0)
    End

    Method Length:Float()
        Return Sqrt(x * x + y * y)
    End

    Method DotProduct:Float(v2:Vector2D)
        Return (x * v2.x) + (y * v2.y)
    End

    Method CrossProduct:Float(v2:Vector2D)
        Return (x * v2.y) - (y * v2.x)
    End

    Method Distance:Float(v2:Vector2D)
        Return Sqrt(Pow(v2.x - x, 2.0) + Pow(v2.y - y, 2.0))
    End

    Method Equal:Bool(v2:Vector2D)
        Return (x = v2.x And y = v2.y)
    End

    Method Copy:Vector2D()
        Return New Vector2D(x, y)
    End

    Method Round:Vector2D()
        x = MathHelper.Round(x)
        y = MathHelper.Round(y)

        Return Self
    End

    Method Ceil:Vector2D()
        x = monkey.Ceil(x)
        y = monkey.Ceil(y)

        Return Self
    End

    Method Floor:Vector2D()
        x = monkey.Floor(x)
        y = monkey.Floor(y)

        Return Self
    End

    Method Set:Vector2D(v2:Vector2D)
        x = v2.x
        y = v2.y

        Return Self
    End

    Method Normalize:Vector2D()
        Local length:Float = Length()
        If length = 0 Then Return Self

        x /= length
        y /= length

        Return Self
    End

    Method Rotate:Vector2D(angle:Float)
        Local tmpX:Float = (x * Cos(angle)) - (y * Sin(angle))
        Local tmpY:Float = (y * Cos(angle)) - (x * Sin(angle))

        x = tmpX
        y = tmpY

        Return Self
    End

    Method Revert:Vector2D()
        x *= -1
        y *= -1

        Return Self
    End

    Method Add:Vector2D(v2:Vector2D)
        x += v2.x
        y += v2.y
        Return Self
    End

    Method Add:Vector2D(factor:Float)
        x += factor
        y += factor
        Return Self
    End

    Method Sub:Vector2D(v2:Vector2D)
        x -= v2.x
        y -= v2.y
        Return Self
    End

    Method Sub:Vector2D(factor:Float)
        x -= factor
        y -= factor
        Return Self
    End

    Method Mul:Vector2D(v2:Vector2D)
        x *= v2.x
        y *= v2.y
        Return Self
    End

    Method Mul:Vector2D(factor:Float)
        x *= factor
        y *= factor
        Return Self
    End

    Method Div:Vector2D(v2:Vector2D)
        x /= v2.x
        y /= v2.y
        Return Self
    End

    Method Div:Vector2D(factor:Float)
        y /= factor
        x /= factor
        Return Self
    End

    Method ToString:String()
        Return "(" + x + ", " + y + ")"
    End
End

Strict

Public

Class Vector2D
    Field x:Float
    Field y:Float

    Method New(x:Float=0, y:Float=0)
        Self.x = x
        Self.y = y
    End

    Method IsZero:Bool()
        Return (x = 0 And y = 0)
    End

    Method Length:Float()
        Return Sqrt(x * x + y * y)
    End

    Method DotProduct:Float(v2:Vector2D)
        Return (x * v2.x) + (y * v2.y)
    End

    Method CrossProduct:Float(v2:Vector2D)
        Return (x * v2.y) - (y * v2.x)
    End

    Method Distance:Float(v2:Vector2D)
        Return Sqrt(Pow(v2.x - x, 2.0) + Pow(v2.y - y, 2.0))
    End

    Method Equal:Bool(v2:Vector2D)
        Return (x = v2.x And y = v2.y)
    End

    Method Copy:Vector2D()
        Return New Vector2D(x, y)
    End

    Method Round:Vector2D()
        x = MathHelper.Round(x)
        y = MathHelper.Round(y)

        Return Self
    End

    Method Ceil:Vector2D()
        x = monkey.Ceil(x)
        y = monkey.Ceil(y)

        Return Self
    End

    Method Floor:Vector2D()
        x = monkey.Floor(x)
        y = monkey.Floor(y)

        Return Self
    End

    Method Set:Vector2D(v2:Vector2D)
        x = v2.x
        y = v2.y

        Return Self
    End

    Method Normalize:Vector2D()
        Local length:Float = Length()
        If length = 0 Then Return Self

        x /= length
        y /= length

        Return Self
    End

    Method Rotate:Vector2D(angle:Float)
        Local tmpX:Float = (x * Cos(angle)) - (y * Sin(angle))
        Local tmpY:Float = (y * Cos(angle)) - (x * Sin(angle))

        x = tmpX
        y = tmpY

        Return Self
    End

    Method Revert:Vector2D()
        x *= -1
        y *= -1

        Return Self
    End

    Method Add:Vector2D(v2:Vector2D)
        x += v2.x
        y += v2.y
        Return Self
    End

    Method Add:Vector2D(factor:Float)
        x += factor
        y += factor
        Return Self
    End

    Method Sub:Vector2D(v2:Vector2D)
        x -= v2.x
        y -= v2.y
        Return Self
    End

    Method Sub:Vector2D(factor:Float)
        x -= factor
        y -= factor
        Return Self
    End

    Method Mul:Vector2D(v2:Vector2D)
        x *= v2.x
        y *= v2.y
        Return Self
    End

    Method Mul:Vector2D(factor:Float)
        x *= factor
        y *= factor
        Return Self
    End

    Method Div:Vector2D(v2:Vector2D)
        x /= v2.x
        y /= v2.y
        Return Self
    End

    Method Div:Vector2D(factor:Float)
        y /= factor
        x /= factor
        Return Self
    End

    Method ToString:String()
        Return "(" + x + ", " + y + ")"
    End
End