Fixed angelfont loader

Fixed angelfont loader

#rem
[March 11 2011]
 - added support for xml standard single and double quotes. Single quote must end with single and double with double
 - added support for comments <!-- comment -->
 - added destructor .Free() to clean up xml data (needs to be called manually)
 - tweaked parser to remove some unnecessary steps
#end

Class ConfigError
    Field message:String
    Field context:String
    Field line:Int
    Field column:Int
    Field data:String

    Method New(message:String,context:String="",data:String="",line:Int=0,column:Int=0)
        Self.message = message
        Self.context = context
        Self.data = data
        Self.line = line
        Self.column = column
    End

    Method ToString:String()
        Local error := ""
        If context.Length() > 0
            error += "Error In '"+context+"': "+message
        Else
            error += "Error: "+message
        Endif
        If line > 0
            error += " (line:"+line+")"
            If column > 0 error += " (column: "+column+")"
        Endif
        If data.Length() > 0 error += "~nData: "+data
        Return error
    End
End

Class ConfigNode
	Field name:String
	Field value:String
	Field parent:ConfigNode
	Field attributes:List<ConfigAttribute>
	Field children:List<ConfigNode>
	Field line:Int
	Field column:Int
	Field scriptOffset:Int

	Method New(name:String)
		Self.name = name
	End

	Method Free:Void()
		' --- free resources ---
		name = ""
		value = ""
		parent = Null
		line = 0
		column = 0
		scriptOffset = 0

		'free attributes
		If attributes
			For Local attribute := EachIn attributes
				attribute.Free()
			Next
			attributes.Clear()
		EndIf

		'free children
		If children
			For Local node := EachIn children
				node.Free()
			Next
			children.Clear()
		EndIf
	End

	Method ToString:String()
		' --- create debuggable print output ---
		Local build := "<"+name
		If attributes
			For Local attribute := Eachin attributes
				build += " "+attribute.name+"=~q"+attribute.value+"~q"
			Next
		Endif
		build += ">"
		Return build
	End

	Method FindValue:String(path:String,defaultValue:String="")
		Local configNode := FindNodeByPath(path)
		If configNode Return configNode.GetValue()
	End

	Method FindNodeByPath:ConfigNode(path:String)
		' --- returns first node with given path ---
		'return self if no path is given
		If path.Length() = 0 Return Self

		'skip if no children
		If children = Null Return Null

		'split the path into bits
		Local pathParts := path.ToLower().Split("/")
		Local nodeFound := False
		Local pointerNode:ConfigNode = Self

		'scan the path
		For Local pathIndex := 0 Until pathParts.Length()
			'validate all bits of the path
			If pathParts[pathIndex].Length() = 0 Return Null

			'check children of this pointer
			nodeFound = False
			If pointerNode.children
				For Local childNode := Eachin pointerNode.children
					If childNode.name = pathParts[pathIndex]
						pointerNode = childNode
						nodeFound = True
						Exit
					Endif
				Next
			Endif

			'check for node not found
			If nodeFound = False Return Null
		Next

		'return
		Return pointerNode
	End

	Method FindNodesByPath:List<ConfigNode>(path:String)
		' --- this will find a path within the children ---
		Local pointerNodes:List<ConfigNode>

		'return self if no path is given
		If path.Length() = 0
			pointerNodes = New List<ConfigNode>
			pointerNodes.AddLast(Self)
			Return pointerNodes
		Endif

		'skip if no children
		If children = Null Return Null

		'split the path into bits
		Local pathParts := path.ToLower().Split("/")
		pointerNodes = New List<ConfigNode>
		Local newPointerNodes := New List<ConfigNode>

		'add self as root
		pointerNodes.AddLast(Self)

		'scan the path
		For Local pathIndex := 0 Until pathParts.Length()
			'validate all bits of the path
			If pathParts[pathIndex].Length() = 0 Return Null

			'scan children of each pointer
			For Local pointerNode := Eachin pointerNodes
				'check children of this pointer
				If pointerNode.children
					For Local childNode := Eachin pointerNode.children
						If childNode.name = pathParts[pathIndex] newPointerNodes.AddLast(childNode)
					Next
				Endif
			Next
			pointerNodes.Clear()

			'add new pointers to scan
			For Local pointerNode := Eachin newPointerNodes
				pointerNodes.AddLast(pointerNode)
			Next
			newPointerNodes.Clear()
		Next

		'return
		If pointerNodes.IsEmpty() Return Null
		Return pointerNodes
	End

	Method FindNodesByName:List<ConfigNode>(name:String)
		' --- find all nodes of a given type ---
		If children = Null Or children.IsEmpty() Return Null

		Local returnNodes := New List<ConfigNode>
		Local stackNodes := New List<ConfigNode>
		Local stackNode:ConfigNode

		stackNodes.AddLast(Self)

		While stackNodes.IsEmpty() = False
			stackNode = stackNodes.RemoveLast()
			For Local scanNode := Eachin stackNode.children
				If scanNode.name = name returnNodes.AddLast(scanNode)
				If scanNode.children And scanNode.children.IsEmpty() = False stackNodes.AddLast(scanNode)
			Next
		Wend

		If returnNodes.IsEmpty() Return Null
		Return returnNodes
	End

	Method AddChild(childNode:ConfigNode)
		' --- add child to this node ---
		'set the parent of the child being added
		childNode.parent = Self

		'create the children list
		If children = Null children = New List<ConfigNode>

		'add the child
		children.AddLast(childNode)
	End

	Method GetAttribute:String(name:String,defaultValue:String="")
		' --- get an attribute value ---
		Local attribute := GetConfigAttribute(name)
		If attribute = Null Return defaultValue
		Return attribute.value
	End

	Method GetAttributes:List<ConfigAttribute>()
		' --- return all of thr attributes ---
		Return attributes
	End

	Method GetValue:String()
		Return value
	End

	Method GetName:String()
		Return name
	End

	Method GetChildren:List<ConfigNode>()
		Return children
	End

	Method GetConfigAttribute:ConfigAttribute(name:String)
		' --- find a property ---
		'make sure there are attributes
		If attributes = Null Return Null

		'fix casing of attribute
		name = name.ToLower()

		'search for matching attribute
		For Local attribute := Eachin attributes
			If name = attribute.name Return attribute
		Next

		Return Null
	End

	Method SetAttribute(name:String,value:String)
		' --- add a new property to this node ---
		'create properties list for first time
		If attributes = Null attributes = New List<ConfigAttribute>

		'find existing property
		Local attribute := GetConfigAttribute(name)

		'add / update attribute
		If attribute
			'update (overwrite)
			attribute.SetValue(value)
		Else
			'add
			attributes.AddLast(New ConfigAttribute(name,value))
		Endif
	End

	Method Remove()
		If parent parent.children.RemoveEach(Self)
	End
End

Class ConfigAttribute
	Field name:String
	Field value:String
	Field segments:String[]

	Method New(name:String,value:String)
		' --- setup new attribute ---
		SetName(name.ToLower())
		SetValue(value)
	End

	Method Free:Void()
		' --- free resources ---
		name = ""
		value = ""
		segments = []
	End

	Private
	Method UpdateValue()
		Local length = segments.Length()

		If length = 0
			value = ""
			Return
		Endif

		If length = 1
			value = segments[0]
			Return
		Endif

		Local build := ""
		value = ""
		For Local index := 0 Until length
			value += segments[index]
		Next
	End
	Public

	Method IsSegmented:Bool()
		Return segments.Length() > 1
	End

	Method SetName(name:String)
		Self.name = name
	End

	Method GetSegments:String[]()
		Return segments
	End

	Method GetName:String()
		Return name
	End

	Method SetValue(value:String)
		segments = [value]
		UpdateValue()
	End

	Method AddSegment(segment:String)
		Local index := segments.Length()
		segments = segments.Resize(index+1)
		segments[index] = segment
		UpdateValue()
	End

	Method GetBoolString:String()
		If value = "1" Or value = "true"
			Return "1"
		Else
			Return "0"
		Endif
	End

	Method GetIntString:String()
		Return Int(value)
	End

	Method GetFloatString:String()
		Return Float(value)
	End

	Method GetInt:Int()
		Return Int(value)
	End

	Method GetFloat:Float()
		Return Float(value)
	End

	Method GetBool:Bool()
		If value = "1" Or value = "true" Return True
	End

	Method GetString:String()
		Return value
	End

	Method IsBool:Bool()
		'returns true if the value is a valid true/false definition
		Return value = "1" Or value = "0" Or value = "true" Or value = "false"
	End

	Method IsInt:Bool()
		' --- make sure this is a valid int ---
		Local asc:Int
		If value = "-" Return False
		For Local index := 0 Until value.Length()
			asc = value[index]
			If asc <> 45
				If asc < 48 Or value[index] > 57 Return False
			Else
				If index > 0 Return False
			Endif
		Next
		Return True
	End

	Method IsFloat:Bool()
		' --- make sure this is a valid float ---
		Local asc:Int
		Local countPeriod := 0
		If value = "-" Return False
		For Local index := 0 Until value.Length()
			asc = value[index]
			If asc <> 45
				If asc = 46
					If countPeriod > 0 Return False
					countPeriod += 1
				Else
					If (asc < 48 Or value[index] > 57) Return False
				Endif
			Else
				If index > 0 Return False
			Endif
		Next
		Return True
	End
End

Class Config Extends ConfigNode
	Field path:String
	Field script:String
	Field error:ConfigError

	Private
	Method Parse:Bool(scriptRaw:String)
		' --- parse the string and fill the data objects ---
		'setup root item
		name = "root"

		'fix the raw script
		script = scriptRaw.Replace("~r~n","~n")

		'setup the stack
		Local stack := New List<ConfigNode>
		stack.AddLast(Self)

		'setup some parsing values (quite a few)
		Local stackTag:ConfigNode

		Local scriptLength = script.Length()
		Local scriptIndex := 0

		Local findTagQuote := -1
		Local findTagStart := -1
		Local findTagStartSize := 0
		Local findTagTemp := -1
		Local findTagEnd := -1
		Local findTagEndSize := 0
		Local findTag:ConfigNode
		Local findAttribute:ConfigAttribute
		Local findQuoteCharacter:Int
		Local findTagLine := 0
		Local findTagColumn := 0
		Local findName := ""
		Local findOperator := ""
		Local findSegment := ""
		Local findWhitespace := ""
		Local findLine := 1
		Local findLineStart := 0
		Local findHasPair := False
		Local findHasClose := False
		Local findHasFirst := False
		Local findHasStart := False
		Local findHasName := False
		Local findHasNameEnd := False
		Local findHasEquals := False
		Local findHasValue := False
		Local findHasComment := False
		Local findHasQuotes := False
		Local findHasEnd := False
		Local findHasSegment := False
		Local findHasSegmentEnd := False
		Local findHasOperator := False

		'parse the script
		While scriptIndex < scriptLength-1
			'parse looking for a complete bracket! (also skip comments)
			For Local findIndex := scriptIndex Until scriptLength
				'test for new line
				If script[findIndex] = 10
					findLine += 1
					findLineStart = findIndex + 1
				EndIf

				'not in comment
				If findHasStart = False
					'check to see if in comment
					If findHasComment = False
						'looking for <
						If script[findIndex] = 60
							'bracket starting
							'check if comment
							If findIndex < scriptLength-4 And script[findIndex..findIndex+4] = "<!--"
								'comment
								findHasComment = True
								findIndex += 3
							Else
								'process looking for start
								If findIndex < scriptLength-1 And script[findIndex+1] = 47
									findHasClose = True
									findTagStartSize = 2
								Else
									findTagStartSize = 1
								EndIf
								findHasStart = True
								findTagStart = findIndex
								findTagLine = findLine
								findTagColumn = (findIndex-findLineStart)+1
							EndIf
						Else
							findWhitespace += String.FromChar(script[findIndex])
						EndIf
					Else
						'in comment look for end of comment
						If script[findIndex] = 45 And findIndex + 3 < scriptLength And script[findIndex..findIndex+3] = "-->"
							findHasComment = False
							findIndex += 2
						EndIf
					EndIf
				Else
					'looking for >
					If findHasQuotes = False
						'not in quotes
						If script[findIndex] = 34
							'quote (double)
							findHasQuotes = True
							findQuoteCharacter = 34
						Else If script[findIndex] = 39
							'quote (single)
							findHasQuotes = True
							findQuoteCharacter = 39
						Else If script[findIndex] = 60
							'illegal open bracket
							Return SetError("unexpected opening bracket",findLine,(findIndex-findLineStart)+1)
						Else If script[findIndex] = 62
							'bracket ending
							If findIndex > 0 And script[findIndex-1] = 47
								findTagEndSize = 2
								findHasClose = True
								findTagEnd = findIndex - 1
							Else
								findHasPair = True
								findTagEndSize = 1
								findTagEnd = findIndex
							EndIf
							findHasEnd = True
							Exit
						EndIf
					Else
						'in quotes, can we get out please?
						If script[findIndex] = findQuoteCharacter findHasQuotes = False
					EndIf
				EndIf
			Next

			'check for errors
			If findHasQuotes Return SetError("expecting ending quote",findLine,scriptLength-findLineStart)
			If findHasStart And findHasEnd = False Return SetError("expecting ending bracket",findLine,scriptLength-findLineStart)

			'perform further parse on tag if found
			If findHasEnd = False
				'there are no more tags to retrieve from the string
				'check to see if any items waiting on the stack (other than root)
				If stack.Count() > 1 Return SetError("unexpected end of file",findLine,scriptLength-findLineStart)

				'dont add white space to root
				'Self.value += script[scriptIndex..scriptLength]

				Exit
			Else
				'reset
				findName = ""
				findOperator = ""
				findSegment = ""
				findHasEnd = False
				findHasName = False
				findHasNameEnd = False
				findHasEquals = False
				findHasValue = False
				findHasQuotes = False
				findHasSegment = False
				findHasSegmentEnd = False
				findHasOperator = False

				'add the white space
				stackTag = stack.Last()
				stackTag.value += findWhitespace'script[scriptIndex..findTagStart]

				'move the script index
				scriptIndex = findTagEnd+findTagEndSize

				'we now have an opening node so parse the node data!
				findTag = New ConfigNode()
				findTag.line = findTagLine
				findTag.column = findTagColumn

				For Local findIndex := findTagStart+findTagStartSize Until findTagEnd
					If findHasName = False
						'scanning for name
						If script[findIndex] = 32
							'space
							If findName findHasNameEnd = True

						Else If script[findIndex] = 10
							'new line
							If findName findHasNameEnd = True
							findLine += 1
							findLineStart = findIndex + 1

						Else If script[findIndex] = 61
							'=
							If findName
								findIndex -= 1
								findHasNameEnd = True
							Else
								Return SetError("expecting tag or attribute name",findLine,(findIndex-findLineStart)+1)
							EndIf

						Else If script[findIndex] = 95 or (script[findIndex] >= 48 and script[findIndex] <= 57) or (script[findIndex] >= 65 and script[findIndex] <= 90) or (script[findIndex] >= 97 and script[findIndex] <= 122)
							'_ a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8 9
							findName += String.FromChar(script[findIndex])

						Else
							'illegal character
							Return SetError("illegal character asc='"+script[findIndex]+"' chr='"+script[findIndex..findIndex+1]+"'",findLine,(findIndex-findLineStart)+1)

						EndIf
					Else
						'check for what to scan for next
						If findHasEquals = False
							'searching for equals so can start the value!
							If script[findIndex] = 32
								'space

							Else If script[findIndex] = 10
								'new line
								findLine += 1
								findLineStart = findIndex + 1

							Else If script[findIndex] = 61
								'=
								findHasEquals = True

							Else If script[findIndex] = 95 or (script[findIndex] >= 48 and script[findIndex] <= 57) or (script[findIndex] >= 65 and script[findIndex] <= 90) or (script[findIndex] >= 97 and script[findIndex] <= 122)
								'_ a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8 9
								'alpha numerics
								findHasEnd = True
								findIndex -= 1

							Else
								'illegal character
								Return SetError("illegal character asc='"+script[findIndex]+"' chr='"+script[findIndex..findIndex+1]+"'",findLine,(findIndex-findLineStart)+1)

							EndIf
						Else
							'scan through the value until an end is found!
							If findHasQuotes = False
								'look to see if there is a waiting segment to continue processing
								If findHasSegment = False
									'value hasn't completed a segment yet
									If script[findIndex] = 32
										'space
										If findSegment
											findHasSegment = True
											findHasSegmentEnd = True
										EndIf

									Else If script[findIndex] = 10
										'new line
										If findSegment
											findHasSegment = True
											findHasSegmentEnd = True
										EndIf

										findLine += 1
										findLineStart = findIndex + 1

									Else If script[findIndex] = 34 Or script[findIndex] = 39
										'"
										'looking for start quote
										If findSegment
											'unexepcted quote
											Return SetError("unexpected opening quote",findLine,(findIndex-findLineStart)+1)
										Else
											'start quotes
											findHasQuotes = True
											findHasValue = True
											findQuoteCharacter = script[findIndex]
										EndIf

									Else If script[findIndex] = 45
										'-
										'looking for negative value or operator
										If findSegment
											'operator
											findHasSegment = True
											findHasSegmentEnd = True
											findHasOperator = True
											findOperator = String.FromChar(script[findIndex])
										Else
											'negative value
											findSegment += String.FromChar(script[findIndex])
											findHasValue = True
										EndIf

									Else If script[findIndex] = 42 or script[findIndex] = 43 or script[findIndex] = 47
										'+
										'looking for (other non -) operators
										If findHasValue = False
											'unexpected oeprator
											Return SetError("unexpected operator asc='"+script[findIndex]+"' chr='"+script[findIndex..findIndex+1]+"'",findLine,(findIndex-findLineStart)+1)
										Else
											'end of value
											findHasSegmentEnd = True
											findHasOperator = True
											findOperator = String.FromChar(script[findIndex])
										EndIf

									Else If script[findIndex] = 36 or script[findIndex] = 46 or script[findIndex] = 95 or (script[findIndex] >= 48 and script[findIndex] <= 57) or (script[findIndex] >= 65 and script[findIndex] <= 90) or (script[findIndex] >= 97 and script[findIndex] <= 122)
										'$ . _ a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8 9
										'looking for unquoted attribute value
										findSegment += String.FromChar(script[findIndex])
										findHasValue = True

									Else
										'ilegal character
										Return SetError("illegal character asc='"+script[findIndex]+"' chr='"+script[findIndex..findIndex+1]+"'",findLine,(findIndex-findLineStart)+1)
									EndIf

								Else
									'value already has segment so need to look for end or operator
									If script[findIndex] = 32
										'space

									Else If script[findIndex] = 10
										'new line
										findLine += 1
										findLineStart = findIndex + 1

									Else If script[findIndex] = 95 or (script[findIndex] >= 48 and script[findIndex] <= 57) or (script[findIndex] >= 65 and script[findIndex] <= 90) or (script[findIndex] >= 97 and script[findIndex] <= 122)
										'_ a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8 9
										'looking for new attribute so must rescan this character
										findHasSegment = False
										findIndex -= 1
										findHasEnd = True

									Else If script[findIndex] = 42 Or script[findIndex] = 43 or script[findIndex] = 45 or script[findIndex] = 47
										'+ -
										'looking for operators
										findHasSegment = False
										findHasOperator = True
										findOperator = String.FromChar(script[findIndex])

									Else
										Return SetError("illegal character asc='"+script[findIndex]+"' chr='"+script[findIndex..findIndex+1]+"'",findLine,(findIndex-findLineStart)+1)
									EndIf
								EndIf
							Else
								'quotes mode (anything goes) (possibly need to do escape quote character at some point)
								If script[findIndex] = 10
									'new line
									findLine += 1
									findLineStart = findIndex + 1

								Else If script[findIndex] = findQuoteCharacter
									'looking for end of quotes
									findHasSegment = True
									findHasSegmentEnd = True
									findHasQuotes = False
									findQuoteCharacter = 0
								Else
									'any character add to value
									findSegment += String.FromChar(script[findIndex])

								EndIf
							EndIf
						EndIf
					EndIf

					'look for forced end (eol)
					If findIndex = findTagEnd-1
						findHasEnd = True

						If findHasQuotes
							'unclosed "
							Return SetError("exepcting closing quote",findLine,(findIndex-findLineStart)+1)
						Else
							If findHasName = False
								If findName findHasNameEnd = True
							Else
								If findHasEquals = True
									If findHasValue = False And findSegment = ""
										'nothing after =
										Return SetError("expecting attribute value",findLine,(findIndex-findLineStart)+1)
									Else
										If findHasOperator
											'nothing after operator
											Return SetError("unexcpected end of attribute",findLine,(findIndex-findLineStart)+1)
										Else
											If findSegment <> "" findHasSegmentEnd = True
										EndIf
									EndIf
								EndIf
							EndIf
						EndIf
					EndIf

					'look at setting up tag name
					If findHasNameEnd
						'tag name
						If findHasFirst = False
							findHasFirst = True
							findTag.name = findName.ToLower()
						Else
							findHasStart = True
						EndIf

						'say that name is found
						findHasName = True

						'reset
						findHasNameEnd = False
					EndIf

					If findHasName
						'look at adding segment to value
						If findHasSegmentEnd = True
							'look for appending existing attribute
							findAttribute = findTag.GetConfigAttribute(findName)
							If findAttribute = Null
								findTag.SetAttribute(findName,findSegment)
							Else
								findAttribute.AddSegment(findSegment)
							EndIf

							'reset
							findHasSegmentEnd = False
							findSegment = ""
						EndIf

						'look for adding operator to value
						If findHasOperator = True
							'look for appending existing attribute
							findAttribute = findTag.GetConfigAttribute(findName)
							If findAttribute = Null
								findTag.SetAttribute(findName,findOperator)
							Else
								findAttribute.AddSegment(findOperator)
							EndIf

							'reset
							findHasOperator = False
							findOperator = ""
						EndIf
					EndIf

					If findHasEnd
						'reset
						findName = ""
						findOperator = ""
						findSegment = ""
						findHasEnd = False
						findHasName = False
						findHasNameEnd = False
						findHasEquals = False
						findHasValue = False
						findHasQuotes = False
						findHasSegment = False
						findHasSegmentEnd = False
						findHasOperator = False
					EndIf
				Next

				'node is complete - what next
				If findHasClose
					If findHasPair
						'close the node from the stack
						stackTag = stack.RemoveLast()
						If stackTag <> Null
							If stackTag.name = findTag.name
								'close the node and add to parent
								stackTag.parent.AddChild(stackTag)
							Else
								'mis matched node
								Return SetError("open tag name '"+stackTag.name+"' doesn't match closing tag name '"+findTag.name+"'",findTag.line,findTag.column)
							EndIf
						Else
							'error - closing unopened node
							Return SetError("unexpected closing tag '"+findTag.name+"'",findTag.line,findTag.column)
						EndIf
					Else
						'close the node by itself and add to parent!
						findTag.parent = stack.Last()
						findTag.parent.AddChild(findTag)
					EndIf
				Else
					'add the node to the stack!
					'set parent first
					findTag.parent = stack.Last()
					stack.AddLast(findTag)
				EndIf

				'reset flags/values
				findHasStart = False
				findHasClose = False
				findHasPair = False
				findHasFirst = False
				findWhitespace = ""
			EndIf
		Wend

		'return parse ok
		Return True
	End

	Method SetError:Bool(message:String,line:Int=0,column:Int=0,data:String="")
		' --- error in parse ---
		error = New ConfigError(message,path,data,line,column)
		If attributes attributes.Clear()
		If children children.Clear()
		Return False
	End
	Public

	Method Free:Void()
		' --- free resources ---
		'call chain
		Super.Free()

		'free own resources
		path = ""
		script = ""
		error = Null
	End

	Method HasError:Bool()
		Return error <> Null
	End
End

Function LoadConfig:Config(script:String)
	'create the config
	Local config := New Config
	config.path = ""

	'parse the script
	config.Parse(script)
	Return config
End

#rem
[March 11 2011]
 - added support for xml standard single and double quotes. Single quote must end with single and double with double
 - added support for comments <!-- comment -->
 - added destructor .Free() to clean up xml data (needs to be called manually)
 - tweaked parser to remove some unnecessary steps
#end

Class ConfigError
    Field message:String
    Field context:String
    Field line:Int
    Field column:Int
    Field data:String

    Method New(message:String,context:String="",data:String="",line:Int=0,column:Int=0)
        Self.message = message
        Self.context = context
        Self.data = data
        Self.line = line
        Self.column = column
    End

    Method ToString:String()
        Local error := ""
        If context.Length() > 0
            error += "Error In '"+context+"': "+message
        Else
            error += "Error: "+message
        Endif
        If line > 0
            error += " (line:"+line+")"
            If column > 0 error += " (column: "+column+")"
        Endif
        If data.Length() > 0 error += "~nData: "+data
        Return error
    End
End

Class ConfigNode
	Field name:String
	Field value:String
	Field parent:ConfigNode
	Field attributes:List<ConfigAttribute>
	Field children:List<ConfigNode>
	Field line:Int
	Field column:Int
	Field scriptOffset:Int

	Method New(name:String)
		Self.name = name
	End

	Method Free:Void()
		' --- free resources ---
		name = ""
		value = ""
		parent = Null
		line = 0
		column = 0
		scriptOffset = 0

		'free attributes
		If attributes
			For Local attribute := EachIn attributes
				attribute.Free()
			Next
			attributes.Clear()
		EndIf

		'free children
		If children
			For Local node := EachIn children
				node.Free()
			Next
			children.Clear()
		EndIf
	End

	Method ToString:String()
		' --- create debuggable print output ---
		Local build := "<"+name
		If attributes
			For Local attribute := Eachin attributes
				build += " "+attribute.name+"=~q"+attribute.value+"~q"
			Next
		Endif
		build += ">"
		Return build
	End

	Method FindValue:String(path:String,defaultValue:String="")
		Local configNode := FindNodeByPath(path)
		If configNode Return configNode.GetValue()
	End

	Method FindNodeByPath:ConfigNode(path:String)
		' --- returns first node with given path ---
		'return self if no path is given
		If path.Length() = 0 Return Self

		'skip if no children
		If children = Null Return Null

		'split the path into bits
		Local pathParts := path.ToLower().Split("/")
		Local nodeFound := False
		Local pointerNode:ConfigNode = Self

		'scan the path
		For Local pathIndex := 0 Until pathParts.Length()
			'validate all bits of the path
			If pathParts[pathIndex].Length() = 0 Return Null

			'check children of this pointer
			nodeFound = False
			If pointerNode.children
				For Local childNode := Eachin pointerNode.children
					If childNode.name = pathParts[pathIndex]
						pointerNode = childNode
						nodeFound = True
						Exit
					Endif
				Next
			Endif

			'check for node not found
			If nodeFound = False Return Null
		Next

		'return
		Return pointerNode
	End

	Method FindNodesByPath:List<ConfigNode>(path:String)
		' --- this will find a path within the children ---
		Local pointerNodes:List<ConfigNode>

		'return self if no path is given
		If path.Length() = 0
			pointerNodes = New List<ConfigNode>
			pointerNodes.AddLast(Self)
			Return pointerNodes
		Endif

		'skip if no children
		If children = Null Return Null

		'split the path into bits
		Local pathParts := path.ToLower().Split("/")
		pointerNodes = New List<ConfigNode>
		Local newPointerNodes := New List<ConfigNode>

		'add self as root
		pointerNodes.AddLast(Self)

		'scan the path
		For Local pathIndex := 0 Until pathParts.Length()
			'validate all bits of the path
			If pathParts[pathIndex].Length() = 0 Return Null

			'scan children of each pointer
			For Local pointerNode := Eachin pointerNodes
				'check children of this pointer
				If pointerNode.children
					For Local childNode := Eachin pointerNode.children
						If childNode.name = pathParts[pathIndex] newPointerNodes.AddLast(childNode)
					Next
				Endif
			Next
			pointerNodes.Clear()

			'add new pointers to scan
			For Local pointerNode := Eachin newPointerNodes
				pointerNodes.AddLast(pointerNode)
			Next
			newPointerNodes.Clear()
		Next

		'return
		If pointerNodes.IsEmpty() Return Null
		Return pointerNodes
	End

	Method FindNodesByName:List<ConfigNode>(name:String)
		' --- find all nodes of a given type ---
		If children = Null Or children.IsEmpty() Return Null

		Local returnNodes := New List<ConfigNode>
		Local stackNodes := New List<ConfigNode>
		Local stackNode:ConfigNode

		stackNodes.AddLast(Self)

		While stackNodes.IsEmpty() = False
			stackNode = stackNodes.RemoveLast()
			For Local scanNode := Eachin stackNode.children
				If scanNode.name = name returnNodes.AddLast(scanNode)
				If scanNode.children And scanNode.children.IsEmpty() = False stackNodes.AddLast(scanNode)
			Next
		Wend

		If returnNodes.IsEmpty() Return Null
		Return returnNodes
	End

	Method AddChild(childNode:ConfigNode)
		' --- add child to this node ---
		'set the parent of the child being added
		childNode.parent = Self

		'create the children list
		If children = Null children = New List<ConfigNode>

		'add the child
		children.AddLast(childNode)
	End

	Method GetAttribute:String(name:String,defaultValue:String="")
		' --- get an attribute value ---
		Local attribute := GetConfigAttribute(name)
		If attribute = Null Return defaultValue
		Return attribute.value
	End

	Method GetAttributes:List<ConfigAttribute>()
		' --- return all of thr attributes ---
		Return attributes
	End

	Method GetValue:String()
		Return value
	End

	Method GetName:String()
		Return name
	End

	Method GetChildren:List<ConfigNode>()
		Return children
	End

	Method GetConfigAttribute:ConfigAttribute(name:String)
		' --- find a property ---
		'make sure there are attributes
		If attributes = Null Return Null

		'fix casing of attribute
		name = name.ToLower()

		'search for matching attribute
		For Local attribute := Eachin attributes
			If name = attribute.name Return attribute
		Next

		Return Null
	End

	Method SetAttribute(name:String,value:String)
		' --- add a new property to this node ---
		'create properties list for first time
		If attributes = Null attributes = New List<ConfigAttribute>

		'find existing property
		Local attribute := GetConfigAttribute(name)

		'add / update attribute
		If attribute
			'update (overwrite)
			attribute.SetValue(value)
		Else
			'add
			attributes.AddLast(New ConfigAttribute(name,value))
		Endif
	End

	Method Remove()
		If parent parent.children.Remove(Self)
	End
End

Class ConfigAttribute
	Field name:String
	Field value:String
	Field segments:String[]

	Method New(name:String,value:String)
		' --- setup new attribute ---
		SetName(name.ToLower())
		SetValue(value)
	End

	Method Free:Void()
		' --- free resources ---
		name = ""
		value = ""
		segments = []
	End

	Private
	Method UpdateValue()
		Local length = segments.Length()

		If length = 0
			value = ""
			Return
		Endif

		If length = 1
			value = segments[0]
			Return
		Endif

		Local build := ""
		value = ""
		For Local index := 0 Until length
			value += segments[index]
		Next
	End
	Public

	Method IsSegmented:Bool()
		Return segments.Length() > 1
	End

	Method SetName(name:String)
		Self.name = name
	End

	Method GetSegments:String[]()
		Return segments
	End

	Method GetName:String()
		Return name
	End

	Method SetValue(value:String)
		segments = [value]
		UpdateValue()
	End

	Method AddSegment(segment:String)
		Local index := segments.Length()
		segments = segments.Resize(index+1)
		segments[index] = segment
		UpdateValue()
	End

	Method GetBoolString:String()
		If value = "1" Or value = "true"
			Return "1"
		Else
			Return "0"
		Endif
	End

	Method GetIntString:String()
		Return Int(value)
	End

	Method GetFloatString:String()
		Return Float(value)
	End

	Method GetInt:Int()
		Return Int(value)
	End

	Method GetFloat:Float()
		Return Float(value)
	End

	Method GetBool:Bool()
		If value = "1" Or value = "true" Return True
	End

	Method GetString:String()
		Return value
	End

	Method IsBool:Bool()
		'returns true if the value is a valid true/false definition
		Return value = "1" Or value = "0" Or value = "true" Or value = "false"
	End

	Method IsInt:Bool()
		' --- make sure this is a valid int ---
		Local asc:Int
		If value = "-" Return False
		For Local index := 0 Until value.Length()
			asc = value[index]
			If asc <> 45
				If asc < 48 Or value[index] > 57 Return False
			Else
				If index > 0 Return False
			Endif
		Next
		Return True
	End

	Method IsFloat:Bool()
		' --- make sure this is a valid float ---
		Local asc:Int
		Local countPeriod := 0
		If value = "-" Return False
		For Local index := 0 Until value.Length()
			asc = value[index]
			If asc <> 45
				If asc = 46
					If countPeriod > 0 Return False
					countPeriod += 1
				Else
					If (asc < 48 Or value[index] > 57) Return False
				Endif
			Else
				If index > 0 Return False
			Endif
		Next
		Return True
	End
End

Class Config Extends ConfigNode
	Field path:String
	Field script:String
	Field error:ConfigError

	Private
	Method Parse:Bool(scriptRaw:String)
		' --- parse the string and fill the data objects ---
		'setup root item
		name = "root"

		'fix the raw script
		script = scriptRaw.Replace("~r~n","~n")

		'setup the stack
		Local stack := New List<ConfigNode>
		stack.AddLast(Self)

		'setup some parsing values (quite a few)
		Local stackTag:ConfigNode

		Local scriptLength = script.Length()
		Local scriptIndex := 0

		Local findTagQuote := -1
		Local findTagStart := -1
		Local findTagStartSize := 0
		Local findTagTemp := -1
		Local findTagEnd := -1
		Local findTagEndSize := 0
		Local findTag:ConfigNode
		Local findAttribute:ConfigAttribute
		Local findQuoteCharacter:Int
		Local findTagLine := 0
		Local findTagColumn := 0
		Local findName := ""
		Local findOperator := ""
		Local findSegment := ""
		Local findWhitespace := ""
		Local findLine := 1
		Local findLineStart := 0
		Local findHasPair := False
		Local findHasClose := False
		Local findHasFirst := False
		Local findHasStart := False
		Local findHasName := False
		Local findHasNameEnd := False
		Local findHasEquals := False
		Local findHasValue := False
		Local findHasComment := False
		Local findHasQuotes := False
		Local findHasEnd := False
		Local findHasSegment := False
		Local findHasSegmentEnd := False
		Local findHasOperator := False

		'parse the script
		While scriptIndex < scriptLength-1
			'parse looking for a complete bracket! (also skip comments)
			For Local findIndex := scriptIndex Until scriptLength
				'test for new line
				If script[findIndex] = 10
					findLine += 1
					findLineStart = findIndex + 1
				EndIf

				'not in comment
				If findHasStart = False
					'check to see if in comment
					If findHasComment = False
						'looking for <
						If script[findIndex] = 60
							'bracket starting
							'check if comment
							If findIndex < scriptLength-4 And script[findIndex..findIndex+4] = "<!--"
								'comment
								findHasComment = True
								findIndex += 3
							Else
								'process looking for start
								If findIndex < scriptLength-1 And script[findIndex+1] = 47
									findHasClose = True
									findTagStartSize = 2
								Else
									findTagStartSize = 1
								EndIf
								findHasStart = True
								findTagStart = findIndex
								findTagLine = findLine
								findTagColumn = (findIndex-findLineStart)+1
							EndIf
						Else
							findWhitespace += String.FromChar(script[findIndex])
						EndIf
					Else
						'in comment look for end of comment
						If script[findIndex] = 45 And findIndex + 3 < scriptLength And script[findIndex..findIndex+3] = "-->"
							findHasComment = False
							findIndex += 2
						EndIf
					EndIf
				Else
					'looking for >
					If findHasQuotes = False
						'not in quotes
						If script[findIndex] = 34
							'quote (double)
							findHasQuotes = True
							findQuoteCharacter = 34
						Else If script[findIndex] = 39
							'quote (single)
							findHasQuotes = True
							findQuoteCharacter = 39
						Else If script[findIndex] = 60
							'illegal open bracket
							Return SetError("unexpected opening bracket",findLine,(findIndex-findLineStart)+1)
						Else If script[findIndex] = 62
							'bracket ending
							If findIndex > 0 And script[findIndex-1] = 47
								findTagEndSize = 2
								findHasClose = True
								findTagEnd = findIndex - 1
							Else
								findHasPair = True
								findTagEndSize = 1
								findTagEnd = findIndex
							EndIf
							findHasEnd = True
							Exit
						EndIf
					Else
						'in quotes, can we get out please?
						If script[findIndex] = findQuoteCharacter findHasQuotes = False
					EndIf
				EndIf
			Next

			'check for errors
			If findHasQuotes Return SetError("expecting ending quote",findLine,scriptLength-findLineStart)
			If findHasStart And findHasEnd = False Return SetError("expecting ending bracket",findLine,scriptLength-findLineStart)

			'perform further parse on tag if found
			If findHasEnd = False
				'there are no more tags to retrieve from the string
				'check to see if any items waiting on the stack (other than root)
				If stack.Count() > 1 Return SetError("unexpected end of file",findLine,scriptLength-findLineStart)

				'dont add white space to root
				'Self.value += script[scriptIndex..scriptLength]

				Exit
			Else
				'reset
				findName = ""
				findOperator = ""
				findSegment = ""
				findHasEnd = False
				findHasName = False
				findHasNameEnd = False
				findHasEquals = False
				findHasValue = False
				findHasQuotes = False
				findHasSegment = False
				findHasSegmentEnd = False
				findHasOperator = False

				'add the white space
				stackTag = stack.Last()
				stackTag.value += findWhitespace'script[scriptIndex..findTagStart]

				'move the script index
				scriptIndex = findTagEnd+findTagEndSize

				'we now have an opening node so parse the node data!
				findTag = New ConfigNode()
				findTag.line = findTagLine
				findTag.column = findTagColumn

				For Local findIndex := findTagStart+findTagStartSize Until findTagEnd
					If findHasName = False
						'scanning for name
						If script[findIndex] = 32
							'space
							If findName findHasNameEnd = True

						Else If script[findIndex] = 10
							'new line
							If findName findHasNameEnd = True
							findLine += 1
							findLineStart = findIndex + 1

						Else If script[findIndex] = 61
							'=
							If findName
								findIndex -= 1
								findHasNameEnd = True
							Else
								Return SetError("expecting tag or attribute name",findLine,(findIndex-findLineStart)+1)
							EndIf

						Else If script[findIndex] = 95 or (script[findIndex] >= 48 and script[findIndex] <= 57) or (script[findIndex] >= 65 and script[findIndex] <= 90) or (script[findIndex] >= 97 and script[findIndex] <= 122)
							'_ a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8 9
							findName += String.FromChar(script[findIndex])

						Else
							'illegal character
							Return SetError("illegal character asc='"+script[findIndex]+"' chr='"+script[findIndex..findIndex+1]+"'",findLine,(findIndex-findLineStart)+1)

						EndIf
					Else
						'check for what to scan for next
						If findHasEquals = False
							'searching for equals so can start the value!
							If script[findIndex] = 32
								'space

							Else If script[findIndex] = 10
								'new line
								findLine += 1
								findLineStart = findIndex + 1

							Else If script[findIndex] = 61
								'=
								findHasEquals = True

							Else If script[findIndex] = 95 or (script[findIndex] >= 48 and script[findIndex] <= 57) or (script[findIndex] >= 65 and script[findIndex] <= 90) or (script[findIndex] >= 97 and script[findIndex] <= 122)
								'_ a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8 9
								'alpha numerics
								findHasEnd = True
								findIndex -= 1

							Else
								'illegal character
								Return SetError("illegal character asc='"+script[findIndex]+"' chr='"+script[findIndex..findIndex+1]+"'",findLine,(findIndex-findLineStart)+1)

							EndIf
						Else
							'scan through the value until an end is found!
							If findHasQuotes = False
								'look to see if there is a waiting segment to continue processing
								If findHasSegment = False
									'value hasn't completed a segment yet
									If script[findIndex] = 32
										'space
										If findSegment
											findHasSegment = True
											findHasSegmentEnd = True
										EndIf

									Else If script[findIndex] = 10
										'new line
										If findSegment
											findHasSegment = True
											findHasSegmentEnd = True
										EndIf

										findLine += 1
										findLineStart = findIndex + 1

									Else If script[findIndex] = 34 Or script[findIndex] = 39
										'"
										'looking for start quote
										If findSegment
											'unexepcted quote
											Return SetError("unexpected opening quote",findLine,(findIndex-findLineStart)+1)
										Else
											'start quotes
											findHasQuotes = True
											findHasValue = True
											findQuoteCharacter = script[findIndex]
										EndIf

									Else If script[findIndex] = 45
										'-
										'looking for negative value or operator
										If findSegment
											'operator
											findHasSegment = True
											findHasSegmentEnd = True
											findHasOperator = True
											findOperator = String.FromChar(script[findIndex])
										Else
											'negative value
											findSegment += String.FromChar(script[findIndex])
											findHasValue = True
										EndIf

									Else If script[findIndex] = 42 or script[findIndex] = 43 or script[findIndex] = 47
										'+
										'looking for (other non -) operators
										If findHasValue = False
											'unexpected oeprator
											Return SetError("unexpected operator asc='"+script[findIndex]+"' chr='"+script[findIndex..findIndex+1]+"'",findLine,(findIndex-findLineStart)+1)
										Else
											'end of value
											findHasSegmentEnd = True
											findHasOperator = True
											findOperator = String.FromChar(script[findIndex])
										EndIf

									Else If script[findIndex] = 36 or script[findIndex] = 46 or script[findIndex] = 95 or (script[findIndex] >= 48 and script[findIndex] <= 57) or (script[findIndex] >= 65 and script[findIndex] <= 90) or (script[findIndex] >= 97 and script[findIndex] <= 122)
										'$ . _ a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8 9
										'looking for unquoted attribute value
										findSegment += String.FromChar(script[findIndex])
										findHasValue = True

									Else
										'ilegal character
										Return SetError("illegal character asc='"+script[findIndex]+"' chr='"+script[findIndex..findIndex+1]+"'",findLine,(findIndex-findLineStart)+1)
									EndIf

								Else
									'value already has segment so need to look for end or operator
									If script[findIndex] = 32
										'space

									Else If script[findIndex] = 10
										'new line
										findLine += 1
										findLineStart = findIndex + 1

									Else If script[findIndex] = 95 or (script[findIndex] >= 48 and script[findIndex] <= 57) or (script[findIndex] >= 65 and script[findIndex] <= 90) or (script[findIndex] >= 97 and script[findIndex] <= 122)
										'_ a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8 9
										'looking for new attribute so must rescan this character
										findHasSegment = False
										findIndex -= 1
										findHasEnd = True

									Else If script[findIndex] = 42 Or script[findIndex] = 43 or script[findIndex] = 45 or script[findIndex] = 47
										'+ -
										'looking for operators
										findHasSegment = False
										findHasOperator = True
										findOperator = String.FromChar(script[findIndex])

									Else
										Return SetError("illegal character asc='"+script[findIndex]+"' chr='"+script[findIndex..findIndex+1]+"'",findLine,(findIndex-findLineStart)+1)
									EndIf
								EndIf
							Else
								'quotes mode (anything goes) (possibly need to do escape quote character at some point)
								If script[findIndex] = 10
									'new line
									findLine += 1
									findLineStart = findIndex + 1

								Else If script[findIndex] = findQuoteCharacter
									'looking for end of quotes
									findHasSegment = True
									findHasSegmentEnd = True
									findHasQuotes = False
									findQuoteCharacter = 0
								Else
									'any character add to value
									findSegment += String.FromChar(script[findIndex])

								EndIf
							EndIf
						EndIf
					EndIf

					'look for forced end (eol)
					If findIndex = findTagEnd-1
						findHasEnd = True

						If findHasQuotes
							'unclosed "
							Return SetError("exepcting closing quote",findLine,(findIndex-findLineStart)+1)
						Else
							If findHasName = False
								If findName findHasNameEnd = True
							Else
								If findHasEquals = True
									If findHasValue = False And findSegment = ""
										'nothing after =
										Return SetError("expecting attribute value",findLine,(findIndex-findLineStart)+1)
									Else
										If findHasOperator
											'nothing after operator
											Return SetError("unexcpected end of attribute",findLine,(findIndex-findLineStart)+1)
										Else
											If findSegment <> "" findHasSegmentEnd = True
										EndIf
									EndIf
								EndIf
							EndIf
						EndIf
					EndIf

					'look at setting up tag name
					If findHasNameEnd
						'tag name
						If findHasFirst = False
							findHasFirst = True
							findTag.name = findName.ToLower()
						Else
							findHasStart = True
						EndIf

						'say that name is found
						findHasName = True

						'reset
						findHasNameEnd = False
					EndIf

					If findHasName
						'look at adding segment to value
						If findHasSegmentEnd = True
							'look for appending existing attribute
							findAttribute = findTag.GetConfigAttribute(findName)
							If findAttribute = Null
								findTag.SetAttribute(findName,findSegment)
							Else
								findAttribute.AddSegment(findSegment)
							EndIf

							'reset
							findHasSegmentEnd = False
							findSegment = ""
						EndIf

						'look for adding operator to value
						If findHasOperator = True
							'look for appending existing attribute
							findAttribute = findTag.GetConfigAttribute(findName)
							If findAttribute = Null
								findTag.SetAttribute(findName,findOperator)
							Else
								findAttribute.AddSegment(findOperator)
							EndIf

							'reset
							findHasOperator = False
							findOperator = ""
						EndIf
					EndIf

					If findHasEnd
						'reset
						findName = ""
						findOperator = ""
						findSegment = ""
						findHasEnd = False
						findHasName = False
						findHasNameEnd = False
						findHasEquals = False
						findHasValue = False
						findHasQuotes = False
						findHasSegment = False
						findHasSegmentEnd = False
						findHasOperator = False
					EndIf
				Next

				'node is complete - what next
				If findHasClose
					If findHasPair
						'close the node from the stack
						stackTag = stack.RemoveLast()
						If stackTag <> Null
							If stackTag.name = findTag.name
								'close the node and add to parent
								stackTag.parent.AddChild(stackTag)
							Else
								'mis matched node
								Return SetError("open tag name '"+stackTag.name+"' doesn't match closing tag name '"+findTag.name+"'",findTag.line,findTag.column)
							EndIf
						Else
							'error - closing unopened node
							Return SetError("unexpected closing tag '"+findTag.name+"'",findTag.line,findTag.column)
						EndIf
					Else
						'close the node by itself and add to parent!
						findTag.parent = stack.Last()
						findTag.parent.AddChild(findTag)
					EndIf
				Else
					'add the node to the stack!
					'set parent first
					findTag.parent = stack.Last()
					stack.AddLast(findTag)
				EndIf

				'reset flags/values
				findHasStart = False
				findHasClose = False
				findHasPair = False
				findHasFirst = False
				findWhitespace = ""
			EndIf
		Wend

		'return parse ok
		Return True
	End

	Method SetError:Bool(message:String,line:Int=0,column:Int=0,data:String="")
		' --- error in parse ---
		error = New ConfigError(message,path,data,line,column)
		If attributes attributes.Clear()
		If children children.Clear()
		Return False
	End
	Public

	Method Free:Void()
		' --- free resources ---
		'call chain
		Super.Free()

		'free own resources
		path = ""
		script = ""
		error = Null
	End

	Method HasError:Bool()
		Return error <> Null
	End
End

Function LoadConfig:Config(script:String)
	'create the config
	Local config := New Config
	config.path = ""

	'parse the script
	config.Parse(script)
	Return config
End


Fixed FlxG.RemoveBitmap

Fixed FlxG.RemoveBitmap

Strict

Import mojo
Import reflection

Import flxextern
Import flxbasic
Import flxgame
Import flxcamera
Import flxobject
Import flxsignals
Import flxsound
Import flxmusic
Import flxu

Import system.input.accel
Import system.input.joystick
Import system.input.keyboard
Import system.input.mouse
Import system.input.touch
Import system.tweens.flxtween
Import system.tweens.util.ease
Import system.tweens.misc.multivartween
Import system.resolutionpolicy.flxresolutionpolicy
Import system.resolutionpolicy.fill
Import system.flxresourcesmanager
Import system.flxquadtree
Import system.flxreplay
Import system.flximagedata

Import plugin.timermanager
Import plugin.debugpathdisplay

Alias AccelInput = accel.Accel
Alias MouseInput = mouse.Mouse
Alias TouchInput = touch.Touch
Alias JoystickInput = joystick.Joystick
Alias MojoDeviceWidth = mojo.graphics.DeviceWidth
Alias MojoDeviceHeight = mojo.graphics.DeviceHeight

Class FlxG

	Const LIBRARY_NAME:String = "Flixel for Monkey"

	Const LIBRARY_MAJOR_VERSION:Int = 1

	Const LIBRARY_MINOR_VERSION:Int = 0

	Const DATA_SUFFIX:String = "_flx"

	Const RED:Int = $FFFF0012

	Const GREEN:Int = $FF00F225

	Const BLUE:Int = $FF0090E9

	Const PINK:Int = $FFF01EFF

	Const WHITE:Int = $FFFFFFFF

	Const BLACK:Int = $FF000000

	Global Paused:Bool

#If FLX_DEBUG_ENABLED = "1"
	Global Debug:Bool = True
#Else
	Global Debug:Bool = False
#End

	Global Elapsed:Float

	Global TimeScale:Float

	Global DeviceWidth:Int

	Global DeviceHeight:Int

	Global Width:Int

	Global Height:Int

	Global WorldBounds:FlxRect

	Global WorldDivisions:Int

	Global VisualDebug:Bool

	Global Mobile:Bool

	Global GlobalSeed:Int

	Global Scores:Stack<Int>

	Global Score:Int

	Global Levels:Stack<Int>

	Global Level:Int

	Global Accel:AccelInput

	Global Keys:Keyboard

	Global Mouse:MouseInput

	Global Music:FlxMusic

	Global Sounds:FlxGroup

	Global Mute:Bool

	Global Cameras:Stack<FlxCamera>

	Global Camera:FlxCamera

	Global Plugins:Stack<FlxBasic>

	Global VolumeHandler:FlxVolumeChangeListener

	Global Updaterate:Int

	Global Tweener:FlxBasic

	Global Signals:FlxSignals

	Global _DeviceScaleFactorX:Float = 1

	Global _DeviceScaleFactorY:Float = 1

	Global _DeviceOffsetX:Int

	Global _DeviceOffsetY:Int

	Global _BgColor:FlxColor = FlxColor.ARGB(FlxG.BLACK)

	Global _Game:FlxGame

	Global _Volume:Float

	Global _BitmapCache:FlxResourcesManager<Image>

	Global _SoundCache:FlxResourcesManager<Sound>

	Global _LastDrawingColor:Int

	Global _LastDrawingAlpha:Float

	Global _LastDrawingBlend:Int

	Global _CurrentCamera:FlxCamera

Private
#If TARGET = "psm"
	Const _JOY_UNITS_COUNT:Int = 1
#Else
	Const _JOY_UNITS_COUNT:Int = 4
#End
	Const _TOUCH_COUNT:Int = 32

	Global _Joystick:JoystickInput[_JOY_UNITS_COUNT]

	Global _Touch:TouchInput[_TOUCH_COUNT]

	Global _CollideListener:FlxCollideProcessListener = New FlxCollideProcessListener()

	Global _ResolutionPolicy:FlxResolutionPolicy = New FillResolutionPolicy()

	Global _Point:FlxPoint = New FlxPoint()

Public
	Function GetLibraryName:String()
		Return FlxG.LIBRARY_NAME + " v" + FlxG.LIBRARY_MAJOR_VERSION + "." + FlxG.LIBRARY_MINOR_VERSION
	End Function

	Function Log:Void(data:String)
	#If FLX_DEBUG_ENABLED = "1"
		'note: TODO: to add log objects
		Print data
	#End
	End Function

	Function Random:Float()
		FlxG.GlobalSeed = (FlxG.GlobalSeed * 1664525 + 1013904223) | 0
		Return FlxU.Srand(FlxG.GlobalSeed)
	End Function

	Function Random:Float(range:Float)
		Return FlxG.Random() * range
	End Function

	Function Random:Float(low:Float, high:Float)
		Return FlxG.Random(high - low) + low
	End Function

	Function Random:Int(range:Int)
		Return Int(FlxG.Random() * (1 + range))
	End Function

	Function Random:Int(low:Int, high:Int)
		Return FlxG.Random(high - low) + low
	End Function

	#Rem
	Function GetRandom:Object(objects:Object[], startIndex:Int = 0, length:Int = 0)
		GetRandom is not currently supported in Monkey. Use FlxArray.GetSafeRandom method
	End Function
	#End

	Function LoadReplay:Void(data:String, state:FlxState = Null, cancelKeys:Int[] = [], timeout:Float = 0, callback:FlxReplayListener = Null)
		_Game._replay.Load(data)
		If (state = Null) Then
			FlxG.ResetGame()
		Else
			FlxG.SwitchState(state)
		End If

		_Game._replayCancelKeys = cancelKeys
		_Game._replayTimer = timeout * 1000
		_Game._replayCallback = callback
		_Game._replayRequested = True
	End Function

	Function ReloadReplay:Void(standardMode:Bool = True)
		If (standardMode) Then
			FlxG.ResetGame()
		Else
			FlxG.ResetState()
		End If

		If (_Game._replay.frameCount > 0) _Game._replayRequested = True
	End Function

	Function StopReplay:Void()
		_Game._replaying = False

	#If FLX_DEBUG_ENABLED = "1"
		If (_Game._debugger <> Null) Then
			'note: TODO:
		End If
	#End

		ResetInput()
	End Function

	Function RecordReplay:Void(standardMode:Bool = True)
		If (standardMode) Then
			FlxG.ResetGame()
		Else
			FlxG.ResetState()
		End If

		_Game._recordingRequested = True
	End Function

	Function StopRecording:String()
		_Game._recording = False

	#If FLX_DEBUG_ENABLED = "1"
		If (_Game._debugger <> Null) Then
			'note: TODO:
		End If
	#End

		Return _Game._replay.Save()
	End Function

	Function ResetState:Void()
		_Game._requestedState = FlxState(_Game._state.GetClassInfo().NewInstance())
	End Function

	Function ResetGame:Void()
		_Game._requestedReset = True
	End Function

	Function ResetInput:Void()
		For Local i:Int = 0 Until _JOY_UNITS_COUNT
			_Joystick[i].Reset()
		Next

		For Local i:Int = 0 Until _TOUCH_COUNT
			_Touch[i].Reset()
		Next

		Accel.Reset()
		Mouse.Reset()
		Keys.Reset()
	End Function

	Function PlayMusic:Void(music:String, volume:Float = 1.0)
		If (FlxG.Music = Null) Then
			FlxG.Music = New FlxMusic()

		ElseIf (FlxG.Music.active) Then
			FlxG.Music.Stop()
		End If

		FlxG.Music.Load(music, True)
		FlxG.Music.Volume = volume
		FlxG.Music.survive = True

		FlxG.Music.Play()
	End Function

	Function LoadSound:FlxSound(sound:String, volume:Float = 1.0, looped:Bool = False, autoDestroy:Bool = False, autoPlay:Bool = False, stopPrevious:Bool = True)
		Local s:FlxSound = FlxSound(Sounds.Recycle(FlxSound.__CLASS__))

		s.Load(sound, looped, autoDestroy, stopPrevious)
		s.Volume = volume
		If (autoPlay) s.Play()

		Return s
	End Function

	Function Play:FlxSound(sound:String, volume:Float = 1.0, looped:Bool = False, autoDestroy:Bool = True)
		Local s:FlxSound = FlxG.LoadSound(sound, volume, looped, autoDestroy, True, False)
		If (Not looped) s.exists = False
		Return s
	End Function

	Function Volume:Float()
		Return _Volume
	End Function

	Function Volume:Void(volume:Float)
		_Volume = volume

		If (_Volume < 0) Then
			_Volume = 0
		ElseIf (_Volume > 1) Then
			_Volume = 1
		End If

		If (VolumeHandler <> Null) Then
			If (FlxG.Mute) Then
				VolumeHandler.OnVolumeChange(0)
			Else
				VolumeHandler.OnVolumeChange(_Volume)
			End If
		End If

		If (Music <> Null And Music.active) Then
			Music._UpdateTransform()
		End If

		For Local basic:FlxBasic = EachIn Sounds
			FlxSound(basic)._UpdateTransform()
		Next
	End Function

	Function DestroySounds:Void(forceDestroy:Bool = False)
		If (Music <> Null And (forceDestroy Or Not Music.survive)) Then
			Music.Destroy()
			Music = Null
		End If

		Local sound:FlxSound

		For Local basic:FlxBasic = EachIn Sounds
			sound = FlxSound(basic)

			If (sound <> Null And (forceDestroy Or Not sound.survive)) Then
				sound.Destroy()
			End If
		Next
	End Function

	Function UpdateSounds:Void()
		If (Music <> Null And Music.active) Then
			Music.Update()
		End If

		If (Sounds <> Null And Sounds.active) Then
			Sounds.Update()
		End If
	End Function

	Function PauseSounds:Void()
		If (Music <> Null And Music.exists And Music.active) Then
			Music.Pause()
		End If

		Local sound:FlxSound

		For Local basic:FlxBasic = EachIn Sounds
			sound = FlxSound(basic)

			If (sound <> Null And sound.exists And sound.active) Then
				sound.Pause()
			End If
		Next
	End Function

	Function ResumeSounds:Void()
		If (Music <> Null And Music.exists) Then
			Music.Play()
		End If

		Local sound:FlxSound

		For Local basic:FlxBasic = EachIn Sounds
			sound = FlxSound(basic)

			If (sound <> Null And sound.exists) Then
				sound.Resume()
			End If
		Next
	End Function

	Function CheckBitmapCache:Bool(key:String)
		Return _BitmapCache.CheckResource(key)
	End Function

	Function CreateBitmap:Image(width:Int, height:Int, color:Int, unigue:Bool, key:String = "")
		If (key.Length() = 0) Then
			key = width + "x" + height + ":" + color

			If (unigue And CheckBitmapCache(key)) Then
				Local inc:Int = 0
				Local ukey:String

				Repeat
					ukey = key + inc
					inc += 1
				Until (CheckBitmapCache(ukey))

				key = ukey
			End If
		End If

		Return _BitmapCache.GetResource(key, New CreateImageResource(key, width, height, color))
	End Function

	Function AddBitmap:Image(resource:FlxResource<Image>, unique:Bool = False, key:String = "")
		If (key.Length() = 0) Then
			key = resource.name

			If (unique And CheckBitmapCache(key)) Then
				Local inc:Int = 0
				Local ukey:String

				Repeat
					ukey = key + inc
					inc += 1
				Until(CheckBitmapCache(ukey))

				key = ukey
			End If
		End If

		Return _BitmapCache.GetResource(key, resource)
	End Function

	Function RemoveBitmap:Void(graphic:String)
		If (_BitmapCache <> Null) Then
			_BitmapCache.RemoveResource(graphic)
		End If
	End Function

	Function ClearBitmapCache:Void()
		If (_BitmapCache = Null) _BitmapCache = New FlxResourcesManager<Image>()
		_BitmapCache.Clear()
	End Function

	Function AddSound:Sound(resource:FlxResource<Sound>)
		Return _SoundCache.GetResource(resource.name, resource)
	End Function

	Function ClearSoundCache:Void()
		If (_SoundCache = Null) _SoundCache = New FlxResourcesManager<Sound>()
		_SoundCache.Clear()
	End Function

	Function State:FlxState()
		Return _Game._state
	End Function

	Function SwitchState:Void(state:FlxState)
		FlxG._Game._requestedState = state
	End Function

	Function SetSwitchStateListener:Void(listener:FlxSwitchStateListener)
		FlxG._Game.SetSwitchStateListener(listener)
	End Function

	Function AddCamera:FlxCamera(newCamera:FlxCamera)
		FlxG.Cameras.Push(newCamera)
		Return newCamera
	End Function

	Function RemoveCamera:Void(camera:FlxCamera, destroy:Bool = True)
		FlxG.Cameras.RemoveEach(camera)
		If (destroy) camera.Destroy()
	End Function

	Function ResetCameras:Void(newCamera:FlxCamera = Null)
		Local cam:FlxCamera
		Local i:Int = 0
		Local l:Int = FlxG.Cameras.Length()

		While(i < l)
			cam = FlxG.Cameras.Get(i)
			cam.Destroy()
			i+=1
		Wend

		FlxG.Cameras.Clear()
		FlxG.Camera = Null

		If (newCamera = Null) newCamera = New FlxCamera(0, 0, FlxG.Width, FlxG.Height)

		FlxG.Camera = FlxG.AddCamera(newCamera)
	End Function

	Function Flash:Void(color:Int = FlxG.WHITE, duration:Float = 1, onComplete:FlxCameraFlashListener = Null, force:Bool = False)
		Local i:Int = 0
		Local l:Int = FlxG.Cameras.Length()

		While(i < l)
			FlxG.Cameras.Get(i).Flash(color, duration, onComplete, force)
			i+=1
		Wend
	End Function

	Function Fade:Void(color:Int = FlxG.BLACK, duration:Float = 1, onComplete:FlxCameraFadeListener = Null, force:Bool = False)
		Local i:Int = 0
		Local l:Int = FlxG.Cameras.Length()

		While(i < l)
			FlxG.Cameras.Get(i).Fade(color, duration, onComplete, force)
			i+=1
		Wend
	End Function

	Function Shake:Void(intensity:Float = 0.05, duration:Float = 0.5, onComplete:FlxCameraShakeListener = Null, force:Bool = True, direction:Int = FlxCamera.SHAKE_BOTH_AXES)
		Local i:Int = 0
		Local l:Int = FlxG.Cameras.Length()

		While(i < l)
			FlxG.Cameras.Get(i).Shake(intensity, duration, onComplete, force, direction)
			i+=1
		Wend
	End Function

	Function BgColor:Int()
		If (FlxG.Camera = Null Or Not FlxG.Camera.alive) Return FlxG._BgColor.argb
		Return FlxG.Camera.BgColor
	End Function

	Function SetBgColor:Void(color:Int)
		Local i:Int = 0
		Local l:Int = FlxG.Cameras.Length()

		While(i < l)
			FlxG.Cameras.Get(i).BgColor = color
			i+=1
		Wend

		FlxG._BgColor.SetARGB(color)
	End Function

	Function Overlap:Bool(objectOrGroup1:FlxBasic = Null, objectOrGroup2:FlxBasic, notifyCallback:FlxOverlapNotifyListener = Null, processCallback:FlxOverlapProcessListener = Null)
		If (objectOrGroup1 = Null) objectOrGroup1 = FlxG.State()
		If (objectOrGroup2 = objectOrGroup1) objectOrGroup2 = Null

		FlxQuadTree.Divisions = FlxG.WorldDivisions

		Local quadTree:FlxQuadTree = FlxQuadTree.Recycle(FlxG.WorldBounds.x, FlxG.WorldBounds.y, FlxG.WorldBounds.width, FlxG.WorldBounds.height)
		quadTree.Load(objectOrGroup1, objectOrGroup2, notifyCallback, processCallback)

		Local result:Bool = quadTree.Execute()
		quadTree.Destroy()

		Return result
	End Function

	Function Collide:Bool(objectOrGroup1:FlxBasic = Null, objectOrGroup2:FlxBasic = Null, notifyCallback:FlxOverlapNotifyListener = Null)
		Return	Overlap(objectOrGroup1, objectOrGroup2, notifyCallback, _CollideListener)
	End Function

	Function AddPlugin:FlxBasic(plugin:FlxBasic)
		Local pluginList:Stack<FlxBasic> = FlxG.Plugins
		Local i:Int = 0
		Local l:Int = pluginList.Length()

		While(i < l)
			If (pluginList.Get(i).ToString() = plugin.ToString()) Then
				Return plugin
			End If

			i+=1
		Wend

		pluginList.Push(plugin)
		Return plugin
	End Function

	Function GetPlugin:FlxBasic(objectClass:ClassInfo)
		Local pluginList:Stack<FlxBasic> = FlxG.Plugins
		Local plugin:FlxBasic
		Local i:Int = 0
		Local l:Int = pluginList.Length()

		While(i < l)
			plugin = pluginList.Get(i)
			If (plugin.GetClassInfo().ExtendsClass(objectClass)) Return plugin

			i+=1
		Wend

		Return Null
	End Function

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

	Function RemovePlugin:FlxBasic(plugin:FlxBasic)
		Plugins.RemoveEach(plugin)
		Return plugin
	End Function

	Function RemovePluginType:Bool(objectClass:ClassInfo)
		Local results:Bool = False
		Local pluginList:Stack<FlxBasic> = FlxG.Plugins
		Local i:Int = pluginList.Length() - 1

		While(i >= 0)
			If (pluginList.Get(i).GetClassInfo().ExtendsClass(objectClass)) Then
				pluginList.Remove(i)
				results = True
			End If

			i-=1
		Wend

		Return results
	End Function

	Function Init:Void(game:FlxGame, width:Int, height:Int, zoom:Float)
		FlxG._Game = game
		FlxG.Width = width
		FlxG.Height = height

		FlxG.Tweener = New FlxBasic()
		FlxG.Signals = New FlxSignals()

		FlxG.Mute = False
		FlxG._Volume = .5
		FlxG.Sounds = New FlxGroup()
		FlxG.VolumeHandler = Null

		FlxG.ClearBitmapCache()

		FlxCamera.DefaultZoom = zoom
		FlxG.Cameras = New Stack<FlxCamera>()

		Plugins = New Stack<FlxBasic>

	#If FLX_DEBUG_ENABLED = "1"
		AddPlugin(New DebugPathDisplay())
	#End
		AddPlugin(New TimerManager())

		FlxG.Accel = New AccelInput()
		FlxG.Keys = New Keyboard()
		FlxG.Mouse = New MouseInput()

		For Local i:Int = 0 Until _JOY_UNITS_COUNT
			_Joystick[i] = New JoystickInput(i)
		Next

		For Local i:Int = 0 Until _TOUCH_COUNT
			_Touch[i] = New TouchInput(i)
		Next

		FlxG.Mobile = FlxIsMobile()

		FlxG.Scores = New Stack<Int>()
		FlxG.Levels = New Stack<Int>()
		FlxG.VisualDebug = False
	End Function

	Function Reset:Void()
		FlxG.ClearBitmapCache()
		FlxG.DestroySounds(True)
		FlxG.ClearSoundCache()
		FlxG.ResetInput()
		FlxG.Signals.Destroy()
		FlxG.Levels.Clear()
		FlxG.Scores.Clear()
		FlxG.Level = 0
		FlxG.Score = 0
		FlxG.TimeScale = 1
		FlxG.Elapsed = 0
		FlxG.GlobalSeed = Seed
		FlxG.WorldBounds = New FlxRect(-10, -10, FlxG.Width + 20, FlxG.Height + 20)
		FlxG.WorldDivisions = 6

	#If FLX_DEBUG_ENABLED = "1"
		Local debugPathDisplay:DebugPathDisplay = DebugPathDisplay(FlxG.GetPlugin(DebugPathDisplay.__CLASS__))
		If (debugPathDisplay <> Null) debugPathDisplay.Clear()
	#End
	End Function

	Function UpdateInput:Void()
	#If FLX_MOUSE_ENABLED = "1"
		#If FLX_DEBUG_ENABLED = "1"
			If (Not _Game._debuggerUp Or Not _Game._debugger.hasMouse) Then
				Mouse.Update(MouseX(), MouseY())
			End If
		#Else
			Mouse.Update(MouseX(), MouseY())
		#End

		_Touch[0].Update(TouchX(), TouchY())
	#End

	#If FLX_KEYBOARD_ENABLED = "1"
		Keys.Update()
	#End

	#If FLX_JOYSTICK_ENABLED = "1"
		For Local i:Int = 0 Until _JOY_UNITS_COUNT
			_Joystick[i].Update()
		Next
	#End

	#If FLX_MULTITOUCH_ENABLED = "1"
		For Local i:Int = 1 Until _TOUCH_COUNT
			_Touch[i].Update(TouchX(i), TouchY(i))

			If ( Not _Touch[i].Used) Exit
		Next
	#End

	#If FLX_ACCEL_ENABLED = "1"
		Accel.Update(AccelX(), AccelY(), AccelZ())
	#End
	End Function

	Function UpdateCameras:Void()
		Local cam:FlxCamera
		Local cams:Stack<FlxCamera> = FlxG.Cameras
		Local i:Int = 0
		Local l:Int = cams.Length()

		While(i < l)
			cam = cams.Get(i)
			If (cam <> Null And cam.exists And cam.active) cam.Update()
			i+=1
		Wend
	End Function

	Function UpdatePlugins:Void()
		Local plugin:FlxBasic
		Local pluginList:Stack<FlxBasic> = FlxG.Plugins
		Local i:Int = 0
		Local l:Int = pluginList.Length()

		While(i < l)
			plugin = pluginList.Get(i)
			If (plugin.exists And plugin.active) plugin.Update()
			i+=1
		Wend
	End Function

	Function DrawPlugins:Void()
		Local plugin:FlxBasic
		Local pluginList:Stack<FlxBasic> = FlxG.Plugins
		Local i:Int = 0
		Local l:Int = pluginList.Length()

		While(i < l)
			plugin = pluginList.Get(i)
			If (plugin.exists And plugin.visible) plugin.Draw()
			i+=1
		Wend
	End Function

	Function Joystick:JoystickInput(unit:Int = 0)
		Return _Joystick[unit]
	End Function

	Function Touch:TouchInput(index:Int = 0)
		Return _Touch[index]
	End Function

	Function JoystickCount:Int()
		Return _JOY_UNITS_COUNT
	End Function

	Function TouchCount:Int()
		Return _TOUCH_COUNT
	End Function

	Function SetResolutionPolicy:Void(resolutionPolicy:FlxResolutionPolicy)
		_ResolutionPolicy = resolutionPolicy
		_Measure()
	End Function

	Function UpdateDevice:Void()
		If(FlxG.DeviceWidth <> MojoDeviceWidth() Or FlxG.DeviceHeight <> MojoDeviceHeight()) Then
			_Measure()
		End If
	End Function

	Function Tween:MultiVarTween(object:Object, values:StringMap<Float>, duration:Float, type:Int = FlxTween.ONESHOT, complete:FlxTweenListener = Null, ease:FlxEaseFunction = Null, tweener:FlxBasic = Null)
		If (tweener = Null) Then
			If (FlxBasic(object) <> Null) Then
				tweener = FlxBasic(object)
			Else
				tweener = FlxG.Tweener
			End If
		End If

		Local tween:MultiVarTween = New MultiVarTween(complete, type)
		tween.Tween(object, values, duration, ease)
		tweener.AddTween(tween)

		Return tween
	End Function

Private
	Function _Measure:Void()
		FlxG.DeviceWidth = MojoDeviceWidth()
		FlxG.DeviceHeight = MojoDeviceHeight()

		_ResolutionPolicy.OnMeasure(FlxG.DeviceWidth, FlxG.DeviceHeight, _Point)

		FlxG._DeviceScaleFactorX = _Point.x / Float(FlxG.Width)
		FlxG._DeviceScaleFactorY = _Point.y / Float(FlxG.Height)

		Local zoom:Float = FlxCamera.DefaultZoom
		If(FlxG.Camera <> Null) zoom = FlxG.Camera.Zoom

		FlxG._DeviceOffsetX = Ceil( (FlxG.DeviceWidth - _Point.x * zoom) * 0.5)
		FlxG._DeviceOffsetY = Ceil( (FlxG.DeviceHeight - _Point.y * zoom) * 0.5)

		Local i:Int = 0
		Local l:Int = FlxG.Cameras.Length()
		Local cam:FlxCamera

		While(i < l)
			cam = FlxG.Cameras.Get(i)

			'recalc values
			cam.X = cam.X
			cam.Y = cam.Y
			cam.Width = cam.Width
			cam.Height = cam.Height

			i += 1
		Wend
	End Function

End Class

Interface FlxVolumeChangeListener

	Method OnVolumeChange:Void(volume:Float)

 End Interface

Private

Class FlxCollideProcessListener Implements FlxOverlapProcessListener

	Method OnOverlapProcess:Bool(object1:FlxObject, object2:FlxObject)
		Return FlxObject.Separate(object1, object2)
	End Method

End Class

Class CreateImageResource Extends FlxResource<Image>

	Field width:Float

	Field height:Float

	Field color:Int

	Field image:Image

	Method New(name:String, width:Float, height:Float, color:Int)
		Super.New(name)
		Self.width = width
		Self.height = height
		Self.color = color
	End Method

	Method Load:Image()
		image = (New FlxImageData(width, height, False, color)).Image
		Return image
	End Method

	Method Use:Image()
		Return image
	End Method

	Method Discard:Void()
		image.Discard()
		image = Null
	End Method

End Class

Strict

Import mojo
Import reflection

Import flxextern
Import flxbasic
Import flxgame
Import flxcamera
Import flxobject
Import flxsignals
Import flxsound
Import flxmusic
Import flxu

Import system.input.accel
Import system.input.joystick
Import system.input.keyboard
Import system.input.mouse
Import system.input.touch
Import system.tweens.flxtween
Import system.tweens.util.ease
Import system.tweens.misc.multivartween
Import system.resolutionpolicy.flxresolutionpolicy
Import system.resolutionpolicy.fill
Import system.flxresourcesmanager
Import system.flxquadtree
Import system.flxreplay
Import system.flximagedata

Import plugin.timermanager
Import plugin.debugpathdisplay

Alias AccelInput = accel.Accel
Alias MouseInput = mouse.Mouse
Alias TouchInput = touch.Touch
Alias JoystickInput = joystick.Joystick
Alias MojoDeviceWidth = mojo.graphics.DeviceWidth
Alias MojoDeviceHeight = mojo.graphics.DeviceHeight

Class FlxG

	Const LIBRARY_NAME:String = "Flixel for Monkey"

	Const LIBRARY_MAJOR_VERSION:Int = 1

	Const LIBRARY_MINOR_VERSION:Int = 0

	Const DATA_SUFFIX:String = "_flx"

	Const RED:Int = $FFFF0012

	Const GREEN:Int = $FF00F225

	Const BLUE:Int = $FF0090E9

	Const PINK:Int = $FFF01EFF

	Const WHITE:Int = $FFFFFFFF

	Const BLACK:Int = $FF000000

	Global Paused:Bool

#If FLX_DEBUG_ENABLED = "1"
	Global Debug:Bool = True
#Else
	Global Debug:Bool = False
#End

	Global Elapsed:Float

	Global TimeScale:Float

	Global DeviceWidth:Int

	Global DeviceHeight:Int

	Global Width:Int

	Global Height:Int

	Global WorldBounds:FlxRect

	Global WorldDivisions:Int

	Global VisualDebug:Bool

	Global Mobile:Bool

	Global GlobalSeed:Int

	Global Scores:Stack<Int>

	Global Score:Int

	Global Levels:Stack<Int>

	Global Level:Int

	Global Accel:AccelInput

	Global Keys:Keyboard

	Global Mouse:MouseInput

	Global Music:FlxMusic

	Global Sounds:FlxGroup

	Global Mute:Bool

	Global Cameras:Stack<FlxCamera>

	Global Camera:FlxCamera

	Global Plugins:Stack<FlxBasic>

	Global VolumeHandler:FlxVolumeChangeListener

	Global Updaterate:Int

	Global Tweener:FlxBasic

	Global Signals:FlxSignals

	Global _DeviceScaleFactorX:Float = 1

	Global _DeviceScaleFactorY:Float = 1

	Global _DeviceOffsetX:Int

	Global _DeviceOffsetY:Int

	Global _BgColor:FlxColor = FlxColor.ARGB(FlxG.BLACK)

	Global _Game:FlxGame

	Global _Volume:Float

	Global _BitmapCache:FlxResourcesManager<Image>

	Global _SoundCache:FlxResourcesManager<Sound>

	Global _LastDrawingColor:Int

	Global _LastDrawingAlpha:Float

	Global _LastDrawingBlend:Int

	Global _CurrentCamera:FlxCamera

Private
#If TARGET = "psm"
	Const _JOY_UNITS_COUNT:Int = 1
#Else
	Const _JOY_UNITS_COUNT:Int = 4
#End
	Const _TOUCH_COUNT:Int = 32

	Global _Joystick:JoystickInput[_JOY_UNITS_COUNT]

	Global _Touch:TouchInput[_TOUCH_COUNT]

	Global _CollideListener:FlxCollideProcessListener = New FlxCollideProcessListener()

	Global _ResolutionPolicy:FlxResolutionPolicy = New FillResolutionPolicy()

	Global _Point:FlxPoint = New FlxPoint()

Public
	Function GetLibraryName:String()
		Return FlxG.LIBRARY_NAME + " v" + FlxG.LIBRARY_MAJOR_VERSION + "." + FlxG.LIBRARY_MINOR_VERSION
	End Function

	Function Log:Void(data:String)
	#If FLX_DEBUG_ENABLED = "1"
		'note: TODO: to add log objects
		Print data
	#End
	End Function

	Function Random:Float()
		FlxG.GlobalSeed = (FlxG.GlobalSeed * 1664525 + 1013904223) | 0
		Return FlxU.Srand(FlxG.GlobalSeed)
	End Function

	Function Random:Float(range:Float)
		Return FlxG.Random() * range
	End Function

	Function Random:Float(low:Float, high:Float)
		Return FlxG.Random(high - low) + low
	End Function

	Function Random:Int(range:Int)
		Return Int(FlxG.Random() * (1 + range))
	End Function

	Function Random:Int(low:Int, high:Int)
		Return FlxG.Random(high - low) + low
	End Function

	#Rem
	Function GetRandom:Object(objects:Object[], startIndex:Int = 0, length:Int = 0)
		GetRandom is not currently supported in Monkey. Use FlxArray.GetSafeRandom method
	End Function
	#End

	Function LoadReplay:Void(data:String, state:FlxState = Null, cancelKeys:Int[] = [], timeout:Float = 0, callback:FlxReplayListener = Null)
		_Game._replay.Load(data)
		If (state = Null) Then
			FlxG.ResetGame()
		Else
			FlxG.SwitchState(state)
		End If

		_Game._replayCancelKeys = cancelKeys
		_Game._replayTimer = timeout * 1000
		_Game._replayCallback = callback
		_Game._replayRequested = True
	End Function

	Function ReloadReplay:Void(standardMode:Bool = True)
		If (standardMode) Then
			FlxG.ResetGame()
		Else
			FlxG.ResetState()
		End If

		If (_Game._replay.frameCount > 0) _Game._replayRequested = True
	End Function

	Function StopReplay:Void()
		_Game._replaying = False

	#If FLX_DEBUG_ENABLED = "1"
		If (_Game._debugger <> Null) Then
			'note: TODO:
		End If
	#End

		ResetInput()
	End Function

	Function RecordReplay:Void(standardMode:Bool = True)
		If (standardMode) Then
			FlxG.ResetGame()
		Else
			FlxG.ResetState()
		End If

		_Game._recordingRequested = True
	End Function

	Function StopRecording:String()
		_Game._recording = False

	#If FLX_DEBUG_ENABLED = "1"
		If (_Game._debugger <> Null) Then
			'note: TODO:
		End If
	#End

		Return _Game._replay.Save()
	End Function

	Function ResetState:Void()
		_Game._requestedState = FlxState(_Game._state.GetClassInfo().NewInstance())
	End Function

	Function ResetGame:Void()
		_Game._requestedReset = True
	End Function

	Function ResetInput:Void()
		For Local i:Int = 0 Until _JOY_UNITS_COUNT
			_Joystick[i].Reset()
		Next

		For Local i:Int = 0 Until _TOUCH_COUNT
			_Touch[i].Reset()
		Next

		Accel.Reset()
		Mouse.Reset()
		Keys.Reset()
	End Function

	Function PlayMusic:Void(music:String, volume:Float = 1.0)
		If (FlxG.Music = Null) Then
			FlxG.Music = New FlxMusic()

		ElseIf (FlxG.Music.active) Then
			FlxG.Music.Stop()
		End If

		FlxG.Music.Load(music, True)
		FlxG.Music.Volume = volume
		FlxG.Music.survive = True

		FlxG.Music.Play()
	End Function

	Function LoadSound:FlxSound(sound:String, volume:Float = 1.0, looped:Bool = False, autoDestroy:Bool = False, autoPlay:Bool = False, stopPrevious:Bool = True)
		Local s:FlxSound = FlxSound(Sounds.Recycle(FlxSound.__CLASS__))

		s.Load(sound, looped, autoDestroy, stopPrevious)
		s.Volume = volume
		If (autoPlay) s.Play()

		Return s
	End Function

	Function Play:FlxSound(sound:String, volume:Float = 1.0, looped:Bool = False, autoDestroy:Bool = True)
		Local s:FlxSound = FlxG.LoadSound(sound, volume, looped, autoDestroy, True, False)
		If (Not looped) s.exists = False
		Return s
	End Function

	Function Volume:Float()
		Return _Volume
	End Function

	Function Volume:Void(volume:Float)
		_Volume = volume

		If (_Volume < 0) Then
			_Volume = 0
		ElseIf (_Volume > 1) Then
			_Volume = 1
		End If

		If (VolumeHandler <> Null) Then
			If (FlxG.Mute) Then
				VolumeHandler.OnVolumeChange(0)
			Else
				VolumeHandler.OnVolumeChange(_Volume)
			End If
		End If

		If (Music <> Null And Music.active) Then
			Music._UpdateTransform()
		End If

		For Local basic:FlxBasic = EachIn Sounds
			FlxSound(basic)._UpdateTransform()
		Next
	End Function

	Function DestroySounds:Void(forceDestroy:Bool = False)
		If (Music <> Null And (forceDestroy Or Not Music.survive)) Then
			Music.Destroy()
			Music = Null
		End If

		Local sound:FlxSound

		For Local basic:FlxBasic = EachIn Sounds
			sound = FlxSound(basic)

			If (sound <> Null And (forceDestroy Or Not sound.survive)) Then
				sound.Destroy()
			End If
		Next
	End Function

	Function UpdateSounds:Void()
		If (Music <> Null And Music.active) Then
			Music.Update()
		End If

		If (Sounds <> Null And Sounds.active) Then
			Sounds.Update()
		End If
	End Function

	Function PauseSounds:Void()
		If (Music <> Null And Music.exists And Music.active) Then
			Music.Pause()
		End If

		Local sound:FlxSound

		For Local basic:FlxBasic = EachIn Sounds
			sound = FlxSound(basic)

			If (sound <> Null And sound.exists And sound.active) Then
				sound.Pause()
			End If
		Next
	End Function

	Function ResumeSounds:Void()
		If (Music <> Null And Music.exists) Then
			Music.Play()
		End If

		Local sound:FlxSound

		For Local basic:FlxBasic = EachIn Sounds
			sound = FlxSound(basic)

			If (sound <> Null And sound.exists) Then
				sound.Resume()
			End If
		Next
	End Function

	Function CheckBitmapCache:Bool(key:String)
		Return _BitmapCache.CheckResource(key)
	End Function

	Function CreateBitmap:Image(width:Int, height:Int, color:Int, unigue:Bool, key:String = "")
		If (key.Length() = 0) Then
			key = width + "x" + height + ":" + color

			If (unigue And CheckBitmapCache(key)) Then
				Local inc:Int = 0
				Local ukey:String

				Repeat
					ukey = key + inc
					inc += 1
				Until (CheckBitmapCache(ukey))

				key = ukey
			End If
		End If

		Return _BitmapCache.GetResource(key, New CreateImageResource(key, width, height, color))
	End Function

	Function AddBitmap:Image(resource:FlxResource<Image>, unique:Bool = False, key:String = "")
		If (key.Length() = 0) Then
			key = resource.name

			If (unique And CheckBitmapCache(key)) Then
				Local inc:Int = 0
				Local ukey:String

				Repeat
					ukey = key + inc
					inc += 1
				Until(CheckBitmapCache(ukey))

				key = ukey
			End If
		End If

		Return _BitmapCache.GetResource(key, resource)
	End Function

	Function RemoveBitmap:Void(graphic:String)
		If (_BitmapCache <> Null) Then
			Local image:Image = _BitmapCache.Resources.Get(graphic)
			If (image <> Null) Then
				image.Discard()
				_BitmapCache.RemoveResource(graphic)
			End If
		End If
	End Function

	Function ClearBitmapCache:Void()
		If (_BitmapCache = Null) _BitmapCache = New FlxResourcesManager<Image>()
		_BitmapCache.Clear()
	End Function

	Function AddSound:Sound(resource:FlxResource<Sound>)
		Return _SoundCache.GetResource(resource.name, resource)
	End Function

	Function ClearSoundCache:Void()
		If (_SoundCache = Null) _SoundCache = New FlxResourcesManager<Sound>()
		_SoundCache.Clear()
	End Function

	Function State:FlxState()
		Return _Game._state
	End Function

	Function SwitchState:Void(state:FlxState)
		FlxG._Game._requestedState = state
	End Function

	Function SetSwitchStateListener:Void(listener:FlxSwitchStateListener)
		FlxG._Game.SetSwitchStateListener(listener)
	End Function

	Function AddCamera:FlxCamera(newCamera:FlxCamera)
		FlxG.Cameras.Push(newCamera)
		Return newCamera
	End Function

	Function RemoveCamera:Void(camera:FlxCamera, destroy:Bool = True)
		FlxG.Cameras.RemoveEach(camera)
		If (destroy) camera.Destroy()
	End Function

	Function ResetCameras:Void(newCamera:FlxCamera = Null)
		Local cam:FlxCamera
		Local i:Int = 0
		Local l:Int = FlxG.Cameras.Length()

		While(i < l)
			cam = FlxG.Cameras.Get(i)
			cam.Destroy()
			i+=1
		Wend

		FlxG.Cameras.Clear()
		FlxG.Camera = Null

		If (newCamera = Null) newCamera = New FlxCamera(0, 0, FlxG.Width, FlxG.Height)

		FlxG.Camera = FlxG.AddCamera(newCamera)
	End Function

	Function Flash:Void(color:Int = FlxG.WHITE, duration:Float = 1, onComplete:FlxCameraFlashListener = Null, force:Bool = False)
		Local i:Int = 0
		Local l:Int = FlxG.Cameras.Length()

		While(i < l)
			FlxG.Cameras.Get(i).Flash(color, duration, onComplete, force)
			i+=1
		Wend
	End Function

	Function Fade:Void(color:Int = FlxG.BLACK, duration:Float = 1, onComplete:FlxCameraFadeListener = Null, force:Bool = False)
		Local i:Int = 0
		Local l:Int = FlxG.Cameras.Length()

		While(i < l)
			FlxG.Cameras.Get(i).Fade(color, duration, onComplete, force)
			i+=1
		Wend
	End Function

	Function Shake:Void(intensity:Float = 0.05, duration:Float = 0.5, onComplete:FlxCameraShakeListener = Null, force:Bool = True, direction:Int = FlxCamera.SHAKE_BOTH_AXES)
		Local i:Int = 0
		Local l:Int = FlxG.Cameras.Length()

		While(i < l)
			FlxG.Cameras.Get(i).Shake(intensity, duration, onComplete, force, direction)
			i+=1
		Wend
	End Function

	Function BgColor:Int()
		If (FlxG.Camera = Null Or Not FlxG.Camera.alive) Return FlxG._BgColor.argb
		Return FlxG.Camera.BgColor
	End Function

	Function SetBgColor:Void(color:Int)
		Local i:Int = 0
		Local l:Int = FlxG.Cameras.Length()

		While(i < l)
			FlxG.Cameras.Get(i).BgColor = color
			i+=1
		Wend

		FlxG._BgColor.SetARGB(color)
	End Function

	Function Overlap:Bool(objectOrGroup1:FlxBasic = Null, objectOrGroup2:FlxBasic, notifyCallback:FlxOverlapNotifyListener = Null, processCallback:FlxOverlapProcessListener = Null)
		If (objectOrGroup1 = Null) objectOrGroup1 = FlxG.State()
		If (objectOrGroup2 = objectOrGroup1) objectOrGroup2 = Null

		FlxQuadTree.Divisions = FlxG.WorldDivisions

		Local quadTree:FlxQuadTree = FlxQuadTree.Recycle(FlxG.WorldBounds.x, FlxG.WorldBounds.y, FlxG.WorldBounds.width, FlxG.WorldBounds.height)
		quadTree.Load(objectOrGroup1, objectOrGroup2, notifyCallback, processCallback)

		Local result:Bool = quadTree.Execute()
		quadTree.Destroy()

		Return result
	End Function

	Function Collide:Bool(objectOrGroup1:FlxBasic = Null, objectOrGroup2:FlxBasic = Null, notifyCallback:FlxOverlapNotifyListener = Null)
		Return	Overlap(objectOrGroup1, objectOrGroup2, notifyCallback, _CollideListener)
	End Function

	Function AddPlugin:FlxBasic(plugin:FlxBasic)
		Local pluginList:Stack<FlxBasic> = FlxG.Plugins
		Local i:Int = 0
		Local l:Int = pluginList.Length()

		While(i < l)
			If (pluginList.Get(i).ToString() = plugin.ToString()) Then
				Return plugin
			End If

			i+=1
		Wend

		pluginList.Push(plugin)
		Return plugin
	End Function

	Function GetPlugin:FlxBasic(objectClass:ClassInfo)
		Local pluginList:Stack<FlxBasic> = FlxG.Plugins
		Local plugin:FlxBasic
		Local i:Int = 0
		Local l:Int = pluginList.Length()

		While(i < l)
			plugin = pluginList.Get(i)
			If (plugin.GetClassInfo().ExtendsClass(objectClass)) Return plugin

			i+=1
		Wend

		Return Null
	End Function

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

	Function RemovePlugin:FlxBasic(plugin:FlxBasic)
		Plugins.RemoveEach(plugin)
		Return plugin
	End Function

	Function RemovePluginType:Bool(objectClass:ClassInfo)
		Local results:Bool = False
		Local pluginList:Stack<FlxBasic> = FlxG.Plugins
		Local i:Int = pluginList.Length() - 1

		While(i >= 0)
			If (pluginList.Get(i).GetClassInfo().ExtendsClass(objectClass)) Then
				pluginList.Remove(i)
				results = True
			End If

			i-=1
		Wend

		Return results
	End Function

	Function Init:Void(game:FlxGame, width:Int, height:Int, zoom:Float)
		FlxG._Game = game
		FlxG.Width = width
		FlxG.Height = height

		FlxG.Tweener = New FlxBasic()
		FlxG.Signals = New FlxSignals()

		FlxG.Mute = False
		FlxG._Volume = .5
		FlxG.Sounds = New FlxGroup()
		FlxG.VolumeHandler = Null

		FlxG.ClearBitmapCache()

		FlxCamera.DefaultZoom = zoom
		FlxG.Cameras = New Stack<FlxCamera>()

		Plugins = New Stack<FlxBasic>

	#If FLX_DEBUG_ENABLED = "1"
		AddPlugin(New DebugPathDisplay())
	#End
		AddPlugin(New TimerManager())

		FlxG.Accel = New AccelInput()
		FlxG.Keys = New Keyboard()
		FlxG.Mouse = New MouseInput()

		For Local i:Int = 0 Until _JOY_UNITS_COUNT
			_Joystick[i] = New JoystickInput(i)
		Next

		For Local i:Int = 0 Until _TOUCH_COUNT
			_Touch[i] = New TouchInput(i)
		Next

		FlxG.Mobile = FlxIsMobile()

		FlxG.Scores = New Stack<Int>()
		FlxG.Levels = New Stack<Int>()
		FlxG.VisualDebug = False
	End Function

	Function Reset:Void()
		FlxG.ClearBitmapCache()
		FlxG.DestroySounds(True)
		FlxG.ClearSoundCache()
		FlxG.ResetInput()
		FlxG.Signals.Destroy()
		FlxG.Levels.Clear()
		FlxG.Scores.Clear()
		FlxG.Level = 0
		FlxG.Score = 0
		FlxG.TimeScale = 1
		FlxG.Elapsed = 0
		FlxG.GlobalSeed = Seed
		FlxG.WorldBounds = New FlxRect(-10, -10, FlxG.Width + 20, FlxG.Height + 20)
		FlxG.WorldDivisions = 6

	#If FLX_DEBUG_ENABLED = "1"
		Local debugPathDisplay:DebugPathDisplay = DebugPathDisplay(FlxG.GetPlugin(DebugPathDisplay.__CLASS__))
		If (debugPathDisplay <> Null) debugPathDisplay.Clear()
	#End
	End Function

	Function UpdateInput:Void()
	#If FLX_MOUSE_ENABLED = "1"
		#If FLX_DEBUG_ENABLED = "1"
			If (Not _Game._debuggerUp Or Not _Game._debugger.hasMouse) Then
				Mouse.Update(MouseX(), MouseY())
			End If
		#Else
			Mouse.Update(MouseX(), MouseY())
		#End

		_Touch[0].Update(TouchX(), TouchY())
	#End

	#If FLX_KEYBOARD_ENABLED = "1"
		Keys.Update()
	#End

	#If FLX_JOYSTICK_ENABLED = "1"
		For Local i:Int = 0 Until _JOY_UNITS_COUNT
			_Joystick[i].Update()
		Next
	#End

	#If FLX_MULTITOUCH_ENABLED = "1"
		For Local i:Int = 1 Until _TOUCH_COUNT
			_Touch[i].Update(TouchX(i), TouchY(i))

			If ( Not _Touch[i].Used) Exit
		Next
	#End

	#If FLX_ACCEL_ENABLED = "1"
		Accel.Update(AccelX(), AccelY(), AccelZ())
	#End
	End Function

	Function UpdateCameras:Void()
		Local cam:FlxCamera
		Local cams:Stack<FlxCamera> = FlxG.Cameras
		Local i:Int = 0
		Local l:Int = cams.Length()

		While(i < l)
			cam = cams.Get(i)
			If (cam <> Null And cam.exists And cam.active) cam.Update()
			i+=1
		Wend
	End Function

	Function UpdatePlugins:Void()
		Local plugin:FlxBasic
		Local pluginList:Stack<FlxBasic> = FlxG.Plugins
		Local i:Int = 0
		Local l:Int = pluginList.Length()

		While(i < l)
			plugin = pluginList.Get(i)
			If (plugin.exists And plugin.active) plugin.Update()
			i+=1
		Wend
	End Function

	Function DrawPlugins:Void()
		Local plugin:FlxBasic
		Local pluginList:Stack<FlxBasic> = FlxG.Plugins
		Local i:Int = 0
		Local l:Int = pluginList.Length()

		While(i < l)
			plugin = pluginList.Get(i)
			If (plugin.exists And plugin.visible) plugin.Draw()
			i+=1
		Wend
	End Function

	Function Joystick:JoystickInput(unit:Int = 0)
		Return _Joystick[unit]
	End Function

	Function Touch:TouchInput(index:Int = 0)
		Return _Touch[index]
	End Function

	Function JoystickCount:Int()
		Return _JOY_UNITS_COUNT
	End Function

	Function TouchCount:Int()
		Return _TOUCH_COUNT
	End Function

	Function SetResolutionPolicy:Void(resolutionPolicy:FlxResolutionPolicy)
		_ResolutionPolicy = resolutionPolicy
		_Measure()
	End Function

	Function UpdateDevice:Void()
		If(FlxG.DeviceWidth <> MojoDeviceWidth() Or FlxG.DeviceHeight <> MojoDeviceHeight()) Then
			_Measure()
		End If
	End Function

	Function Tween:MultiVarTween(object:Object, values:StringMap<Float>, duration:Float, type:Int = FlxTween.ONESHOT, complete:FlxTweenListener = Null, ease:FlxEaseFunction = Null, tweener:FlxBasic = Null)
		If (tweener = Null) Then
			If (FlxBasic(object) <> Null) Then
				tweener = FlxBasic(object)
			Else
				tweener = FlxG.Tweener
			End If
		End If

		Local tween:MultiVarTween = New MultiVarTween(complete, type)
		tween.Tween(object, values, duration, ease)
		tweener.AddTween(tween)

		Return tween
	End Function

Private
	Function _Measure:Void()
		FlxG.DeviceWidth = MojoDeviceWidth()
		FlxG.DeviceHeight = MojoDeviceHeight()

		_ResolutionPolicy.OnMeasure(FlxG.DeviceWidth, FlxG.DeviceHeight, _Point)

		FlxG._DeviceScaleFactorX = _Point.x / Float(FlxG.Width)
		FlxG._DeviceScaleFactorY = _Point.y / Float(FlxG.Height)

		Local zoom:Float = FlxCamera.DefaultZoom
		If(FlxG.Camera <> Null) zoom = FlxG.Camera.Zoom

		FlxG._DeviceOffsetX = Ceil( (FlxG.DeviceWidth - _Point.x * zoom) * 0.5)
		FlxG._DeviceOffsetY = Ceil( (FlxG.DeviceHeight - _Point.y * zoom) * 0.5)

		Local i:Int = 0
		Local l:Int = FlxG.Cameras.Length()
		Local cam:FlxCamera

		While(i < l)
			cam = FlxG.Cameras.Get(i)

			'recalc values
			cam.X = cam.X
			cam.Y = cam.Y
			cam.Width = cam.Width
			cam.Height = cam.Height

			i += 1
		Wend
	End Function

End Class

Interface FlxVolumeChangeListener

	Method OnVolumeChange:Void(volume:Float)

 End Interface

Private

Class FlxCollideProcessListener Implements FlxOverlapProcessListener

	Method OnOverlapProcess:Bool(object1:FlxObject, object2:FlxObject)
		Return FlxObject.Separate(object1, object2)
	End Method

End Class

Class CreateImageResource Extends FlxResource<Image>

	Field width:Float

	Field height:Float

	Field color:Int

	Field image:Image

	Method New(name:String, width:Float, height:Float, color:Int)
		Super.New(name)
		Self.width = width
		Self.height = height
		Self.color = color
	End Method

	Method Load:Image()
		image = (New FlxImageData(width, height, False, color)).Image
		Return image
	End Method

	Method Use:Image()
		Return image
	End Method

	Method Discard:Void()
		image.Discard()
		image = Null
	End Method

End Class