Shadow and border false by default

Shadow and border false by default

Strict

Import flxextern
Import flxsprite
Import flxcamera
Import flxg
Import system.flxcolor
Import system.flxassetsmanager

Private

#If FLX_TEXT_DRIVER = "angelfont"
	Import vendor.angelfont
#ElseIf FLX_TEXT_DRIVER = "fontmachine"
	Import vendor.fontmachine
#End

Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_8_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_8_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_9_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_9_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_10_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_10_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_11_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_11_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_12_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_12_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_13_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_13_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_14_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_14_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_15_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_15_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_16_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_16_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_17_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_17_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_18_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_18_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_19_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_19_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_20_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_20_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_21_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_21_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_22_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_22_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_23_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_23_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_24_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_24_flx.${FLX_FONT_EXTENSION}"

Public

Class FlxText Extends FlxSprite Implements FlxSpriteRenderer

	Global ClassObject:Object

	Const ALIGN_LEFT:Float = 0
	Const ALIGN_CENTER:Float = 0.5
	Const ALIGN_RIGHT:Float = 1

	Const SYSTEM_FONT:String = "system"

Private
	Global _FontLoader:FlxFontLoader = New FlxFontLoader()

	Global _FontsManager:FlxResourcesManager<FlxBitmapFont> = New FlxResourcesManager<FlxBitmapFont>()

	Field _shadow:FlxColor

	Field _value:String

	Field _lines:FlxTextLine[]

	Field _countLines:Int

	Field _alignment:Float

	Field _fontFamily:String

	Field _fontSize:Int

	Field _fontHeight:Int

	Field _fontObject:FlxBitmapFont

Public
	Method New(x:Float, y:Float, width:Int = 0, text:String = "")
		Super.New(x, y)

		SetRenderer(Self)
		_shadow = New FlxColor(0)

		Self.width = width
		frameWidth = Self.width
		moves = False

		SetFormat(SYSTEM_FONT)
		Text = text
	End Method

	Method Destroy:Void()
		Local i:Int = 0, l:Int = _lines.Length()

		While (i < l)
			_lines[i] = Null
			i += 1
		Wend

		_fontObject = Null
		_shadow = Null

		Super.Destroy()
	End Method

	Method SetFormat:FlxText(font:String = "", size:Int = 0, color:Int = FlxG.WHITE, alignment:Float = ALIGN_LEFT, shadowColor:Int = 0)
		_alignment = alignment
		_fontFamily = font
		_fontSize = FlxAssetsManager.GetFont(font).GetValidSize(size)

		Self.Color = color
		Shadow = shadowColor

		_ResetFont()
		_ResetHelpers()

		Return Self
	End Method

	Method SetWidth:Void(width:Float)
		Self.width = width
		frameWidth = Self.width
		_ResetHelpers()
	End Method

	Method Text:String() Property
		Return _value
	End Method

	Method Text:Void(text:String) Property
		_value = text
		_ResetHelpers()
	End Method

	Method Size:Void(size:Int) Property
		_fontSize = FlxAssetsManager.GetFont(_fontFamily).GetValidSize(size)
		_ResetFont()
		_ResetHelpers()
	End Method

	Method Size:Int() Property
		Return _fontSize
	End Method

	Method Font:String() Property
		Return _fontFamily
	End Method

	Method Font:Void(font:String) Property
		_fontFamily = font
		_ResetFont()
		_ResetHelpers()
	End Method

	Method Alignment:Float() Property
		Return _alignment
	End Method

	Method Alignment:Void(alignment:Float) Property
		If (_alignment = alignment) Return
		_alignment = alignment
		_ResetAlignment()
	End Method

	Method Shadow:Int() Property
		Return _shadow.argb
	End Method

	Method Shadow:Void(color:Int) Property
		_shadow.SetARGB(color)
	End Method

	Method OnSpriteRender:Void(x:Float, y:Float)
		If (_shadow.argb <> 0) Then
			Local oldColor:Int = FlxG._LastDrawingColor
			Local oldAlpha:Int = FlxG._LastDrawingAlpha

			If (_camera.Color <> FlxG.WHITE) Then
				_mixedColor.MixRGB(_shadow, _camera._color)

				If (FlxG._LastDrawingColor <> _mixedColor.argb) Then
					SetColor(_mixedColor.r, _mixedColor.g, _mixedColor.b)
				End If
			Else
				If (FlxG._LastDrawingColor <> _shadow.argb) Then
					SetColor(_shadow.r, _shadow.g, _shadow.b)
				End If
			End If

			If (_camera.Alpha < 1) Then
				Local _mixedAlpha:Float = _camera.Alpha * _shadow.a

				If (FlxG._LastDrawingAlpha <> _mixedAlpha) Then
					SetAlpha(_mixedAlpha)
				End If
			Else
				If (FlxG._LastDrawingAlpha <> _shadow.a) Then
					SetAlpha(_shadow.a)
				End If
			End If

			_fontObject.DrawText(Self, x + 1, y + 1)

			_mixedColor.SetRGB(oldColor)
			SetColor(_mixedColor.r, _mixedColor.g, _mixedColor.b)
			SetAlpha(oldAlpha)
		End If

		_fontObject.DrawText(Self, x, y)
	End Method

Private
	Method _ResetHelpers:Void()
		If (_value.Length() > 0) Then
			If (width <> 0) Then
				_ParseText()
			Else
				_countLines = 0
				_AddLine(0, _value.Length(), _fontObject.GetTextWidth(Self, 0, _value.Length()))
				_ResetAlignment()
			End If

			Self.height = _GetHeight()
			frameHeight = Self.height
		Else
			Self.height = 0
			frameHeight = 0
		End If

		Super._ResetHelpers()
	End Method

	Method _ResetFont:Void()
		_FontLoader.fontFamily = _fontFamily
		_FontLoader.fontSize = _fontSize

		_fontObject = _FontsManager.GetResource(_fontFamily + _fontSize, _FontLoader)
		_fontHeight = _fontObject.GetFontHeight(Self)
	End Method

	Method _ResetAlignment:Void()
		If (_value.Length() = 0) Return

		For Local line:Int = 0 Until _countLines
			_lines[line].xOffset = (width - _lines[line].width) * _alignment
		Next
	End Method

	Method _GetHeight:Int()
		If (width = 0) Then
			Return _fontHeight
		Else
			Return _fontObject.GetTextHeight(Self)
		End If
	End Method

	Method _ParseText:Void()
		_countLines = 0

		Local prevOffset:Int = 0
		Local offsetN:Int = _value.Find("~n", 0)
		Local offsetR:Int = _value.Find("~r", 0)
		Local offset:Int = 0

		If (offsetN >= 0 And offsetR >= 0) Then
			offset = Min(offsetN, offsetR)
		Else
			offset = Max(offsetN, offsetR)
		End If

		If (offset >= 0) Then
			While (offset >= 0)
				_BuildLines(prevOffset, offset)

				prevOffset = offset + 1

				offsetN = _value.Find("~n", prevOffset)
				offsetR = _value.Find("~r", prevOffset)

				If (offsetN >= 0 And offsetR >= 0) Then
					offset = Min(offsetN, offsetR)
				Else
					offset = Max(offsetN, offsetR)
				End if
			Wend

			_BuildLines(prevOffset, _value.Length())
		Else
			_BuildLines(0, _value.Length())
		End If

		_ResetAlignment()
	End Method

	Method _BuildLines:Void(startPos:Int, endPos:Int)
		Local textWidth:Int = _fontObject.GetTextWidth(Self, startPos, endPos)

		If (width < textWidth) Then
			Local textLength:Int = endPos - startPos

			Local range:Int = Ceil(textLength / Float(Floor(textWidth / Float(width)) + 1))

			Repeat
				range += 1
				textWidth = _fontObject.GetTextWidth(Self, 0, range)
			Until (textWidth >= width)

			Local maxOffset:Int = range
			Local minOffset:Int = 0
			Local offset:Int = maxOffset
			Local tmpOffset:Int = 0
			Local dirty:Bool = False
			Local finalTextWidth:Int = 0
			Local success:Bool

			Repeat
				Repeat
					offset -= 1
					If (offset - minOffset <= 1) Then
						offset = minOffset + 1
						Exit
					End if

					tmpOffset = -1
					For Local i:Int = offset - 1 To minOffset Step - 1
						success = False

						If (_value[i] = KEY_SPACE Or _value[i] = KEY_TAB) Then
							tmpOffset = i
							success = True
							Exit
						End If
					Next

					If (tmpOffset < 0) Then
						tmpOffset = _GetMinOffset(minOffset, offset)
					Else
						If (_value[minOffset] = KEY_SPACE Or _value[minOffset] = KEY_TAB) Then
							For Local i:Int = minOffset To offset
								If (_value[i] <> KEY_SPACE And _value[i] <> KEY_TAB) Then
									minOffset = i
									Exit
								End If
							Next
						End If
					End If

					offset = tmpOffset
					textWidth = _fontObject.GetTextWidth(Self, minOffset, offset)
				Until (textWidth <= width)

				dirty = False
				finalTextWidth = _fontObject.GetTextWidth(Self, minOffset)

				If (finalTextWidth > width And textLength - minOffset > 1) Then
					For Local i:Int = minOffset Until offset
						If (_value[i] = KEY_SPACE Or _value[i] = KEY_TAB) Then
							minOffset += 1
							dirty = True
						Else
							Exit
						End If
					Next

					For Local i:Int = offset - 1 To minOffset Step - 1
						If (_value[i] = KEY_SPACE Or _value[i] = KEY_TAB) Then
							offset -= 1
							dirty = True
						Else
							Exit
						End If
					Next

					If (offset - minOffset = 0) Continue

					If ( Not dirty) Then
						_AddLine(minOffset, offset, textWidth)
					Else
						_AddLine(minOffset, offset, _fontObject.GetTextWidth(Self, minOffset, offset))
					End If

					minOffset = offset
					If (success) minOffset += 1

					maxOffset = minOffset + range
					offset = maxOffset
				Else
					Local l:Int = _value.Length()

					For Local i:Int = minOffset Until l
						If (_value[i] = KEY_SPACE Or _value[i] = KEY_TAB) Then
							minOffset += 1
							dirty = True
						Else
							Exit
						End If
					Next

					For Local i:Int = l - 1 To minOffset Step - 1
						If (_value[i] = KEY_SPACE Or _value[i] = KEY_TAB) Then
							l -= 1
							dirty = True
						Else
							Exit
						End If
					Next

					If (l - minOffset <= 0) Exit

					If ( Not dirty) Then
						_AddLine(minOffset, l, finalTextWidth)
					Else
						_AddLine(minOffset, offset, _fontObject.GetTextWidth(Self, minOffset, l))
					End If

					Exit
				End If
			Forever
		Else
			_AddLine(startPos, endPos, textWidth)
		End If
	End Method

	Method _GetMinOffset:Int(startPos:Int, endPos:Int)
		Local offset:Int = endPos - startPos

		While (_fontObject.GetTextWidth(Self, startPos, startPos + offset) > width)
			offset -= 1
			If (offset = 1) Return offset
		Wend

		Return startPos + offset
	End Method

	Method _AddLine:FlxTextLine(startPos:Int, endPos:Int, width:Int)
		Local line:FlxTextLine

		If (_countLines = _lines.Length()) Then
			_lines = _lines.Resize(_countLines * 2 + 10)
			line = New FlxTextLine(startPos, endPos, width)
			_lines[_countLines] = line
		Else
			line = _lines[_countLines]
			If (line = Null) Then
				line = New FlxTextLine(startPos, endPos, width)
				_lines[_countLines] = line
			Else
				line.Reset(startPos, endPos, width)
			End If
		End If

		_countLines += 1
		Return line
	End Method

End Class

Private
Class FlxTextLine

	Field xOffset:Float

	Field startPos:Int

	Field endPos:Int

	Field width:Int

	Method New(startPos:Int, endPos:Int, width:Int)
		Reset(startPos, endPos, width)
	End Method

	Method Reset:Void(startPos:Int, endPos:Int, width:Int)
		Self.startPos = startPos
		Self.endPos = endPos
		Self.width = width
	End Method

End Class

Class FlxFontLoader Extends FlxResourceLoader<FlxBitmapFont>

	Field fontFamily:String = FlxText.SYSTEM_FONT
	Field fontSize:Int

	Method Load:FlxBitmapFont(name:String)
		Return New FlxBitmapFont(FlxAssetsManager.GetFont(fontFamily).GetPath(fontSize))
	End Method

End Class

#If FLX_TEXT_DRIVER = "angelfont"
	Class FlxBitmapFont
	Private
		Field image:Image[] = New Image[1]
		Field chars:Char[256]

		Field kernPairs:IntMap<IntMap<KernPair>> = New IntMap<IntMap<KernPair>>
		Global firstKp:IntMap<KernPair>
		Global secondKp:KernPair

		Field iniText:String

		Field xOffset:Int
		Field yOffset:Int

	Public
		Field useKerning:Bool = True

		Field height:Int = 0
		Field heightOffset:Int = 9999

		Field lineGap:Int = 5

		Method New(url:String)
			iniText = LoadString(url+".fnt")
			Local lines:String[] = iniText.Split(String.FromChar(10))
			Local firstLine:String = lines[0]
			If firstLine.Contains("<?xml")
				Local lineList:List<String> = New List<String>(lines)
				lineList.RemoveFirst()
				lines = lineList.ToArray()
				iniText = "~n".Join(lines)
			End

			Local pageCount:Int = 0

			Local config:= LoadConfig(iniText)

			Local nodes:= config.FindNodesByPath("font/chars/char")

			For Local node:= EachIn nodes
				Local id:Int = Int(node.GetAttribute("id"))
				Local page:Int = Int(node.GetAttribute("page"))
				If pageCount < page pageCount = page
				chars[id] = New Char(Int(node.GetAttribute("x")), Int(node.GetAttribute("y")), Int(node.GetAttribute("width")), Int(node.GetAttribute("height")),  Int(node.GetAttribute("xoffset")),  Int(node.GetAttribute("yoffset")),  Int(node.GetAttribute("xadvance")), page)
				Local ch := chars[id]
				If ch.height > Self.height Self.height = ch.height
				If ch.yOffset < Self.heightOffset Self.heightOffset = ch.yOffset
			Next

			nodes = config.FindNodesByPath("font/kernings/kerning")

			If (nodes <> Null) Then
				For Local node:= EachIn nodes
					Local first:Int = Int(node.GetAttribute("first"))
					firstKp = kernPairs.Get(first)
					If firstKp = Null
						kernPairs.Add(first, New IntMap<KernPair>)
						firstKp = kernPairs.Get(first)
					End

					Local second:Int = Int(node.GetAttribute("second"))
					firstKp.Add(second, New KernPair(first, second, Int(node.GetAttribute("amount"))))
				End
			Else
				useKerning = False
			End If

			If pageCount = 0
				image[0] = LoadImage(url+".png")
				If image[0] = Null image[0] = LoadImage(url + "_0.png")
			Else
				For Local page:= 0 To pageCount
					If image.Length < page+1 image = image.Resize(page+1)
					image[page] = LoadImage(url+"_"+page+".png")
				End
			End
		End

		Method GetTextWidth:Int(txt:FlxText, startPos:Int = 0, endPos:Int = -1)
			Local prevChar:Int = 0
			Local width:Int = 0

			If (endPos < 0) endPos = txt._value.Length()
			Local asc:Int, ac:Char

			For Local i:= startPos Until endPos
				asc = txt._value[i]
				ac = chars[asc]

				If ac <> Null
					If useKerning
						Local firstKp:= kernPairs.Get(prevChar)
						If firstKp <> Null
							Local secondKp:= firstKp.Get(asc)
							If secondKp <> Null
								xOffset += secondKp.amount
							End
						Endif
					EndIf

					width += ac.xAdvance
					prevChar = asc
				EndIf
			Next

			Return width
		End Method

		Method GetFontHeight:Int(txt:FlxText)
			Local h:Int = 0

			If (txt._value.Length() <> 0) Then
				Local l:Int = txt._value.Length()

				For Local i:= 0 Until l
					Local asc:Int = txt._value[i]
					Local ac:Char = chars[asc]
					If ac.height+ac.yOffset > h h = ac.height+ac.yOffset
				Next
			Else
				h = chars[KEY_SPACE].height + chars[KEY_SPACE].yOffset
			End If

			Return h
		End

		Method GetTextHeight:Int(txt:FlxText)
			Return ( (txt._fontHeight + lineGap) * txt._countLines) - lineGap
		End Method

		Method DrawText:Void(txt:FlxText, x:Int, y:Int)
			Local lineIndex:Int = 0, countLines:Int = txt._countLines, line:FlxTextLine

			Local prevChar:Int = 0, xOffset:Int = 0, yOffset:Int = 0
			Local i:Int = 0, l:Int
			Local asc:Int, ac:Char
			Local lineHeight:Int = txt._fontHeight + lineGap

			While (lineIndex < countLines)
				line = txt._lines[lineIndex]
				i = line.startPos
				l = line.endPos
				xOffset = 0

				While (i < l)
					asc = txt._value[i]
					ac = chars[asc]

					If ac <> Null
						If useKerning
							firstKp = kernPairs.Get(prevChar)
							If firstKp <> Null
								secondKp = firstKp.Get(asc)
								If secondKp <> Null
									xOffset += secondKp.amount
								End
							Endif
						Endif

						ac.Draw(image[ac.page], x + xOffset + line.xOffset, y + yOffset)
						xOffset += ac.xAdvance
						prevChar = asc
					Endif

					i += 1
				Wend

				lineIndex += 1
				yOffset += lineHeight
			Wend
		End Method

	End Class

#ElseIf FLX_TEXT_DRIVER = "fontmachine"

	Class FlxBitmapFont Implements Font

		Method New(fontDescriptionFilePath:String)
			Local text:String = LoadString(fontDescriptionFilePath + ".txt")
			if text = "" Then Print "FONT " + fontDescriptionFilePath + " WAS NOT FOUND!!!"
			LoadFontData(text, fontDescriptionFilePath, False)
		End

		Method GetTextWidth:Float(text:FlxText, fromChar:Int = 0, toChar:Int = -1)
			Local twidth:Float
			Local char:Int
			Local lastchar:Int = 0

			If (toChar < 0) toChar = text._value.Length()

			For Local i:Int = fromChar Until toChar
				char = text._value[i]

				If faceChars[char] <> Null Then
					lastchar = char
					twidth = twidth + faceChars[char].drawingMetrics.drawingWidth + Kerning.x
				End If
			Next

			Return twidth - faceChars[lastchar].drawingMetrics.drawingWidth + faceChars[lastchar].drawingMetrics.drawingSize.x
		End Method

		Method GetTextHeight:Float(text:FlxText)
			Return ( (text._fontHeight + Kerning.y) * text._countLines) - Kerning.y
		End

		Method GetFontHeight:Int(txt:FlxText)
			If faceChars[32] = Null Then Return 0
			Return faceChars[32].drawingMetrics.drawingSize.y
		End Method

		Method DrawText:Void(text:FlxText, x:Float, y:Float)
			If (_drawShadow) DrawChars(text, x, y, shadowChars)
			If (_drawBorder) DrawChars(text, x, y, borderChars)
			DrawChars(text, x, y, faceChars)
		End

		Method Kerning:drawingpoint.DrawingPoint() property
			if _kerning = null Then _kerning = New drawingpoint.DrawingPoint
			Return _kerning
		End

		Method Kerning:void(value:drawingpoint.DrawingPoint) property
			_kerning = value
		End

	Private

		Field _drawShadow:Bool = False
		Field _drawBorder:Bool = False
		Field borderChars:BitMapChar[]
		Field faceChars:BitMapChar[]
		Field shadowChars:BitMapChar[]

		Method LoadFontData:Void(Info:String, fontName:String, dynamicLoad:bool)
			if Info.StartsWith("P1") Then
				LoadPacked(Info,fontName,dynamicLoad)
				return
			EndIf
			Local tokenStream:String[] = Info.Split(",")
			local index:Int = 0
			borderChars = New BitMapChar[65536]
			faceChars = New BitMapChar[65536]
			shadowChars = New BitMapChar[65536]

			Local prefixName:String = fontName
			if prefixName.ToLower().EndsWith(".txt") Then prefixName = prefixName[..-4]

			Local char:Int = 0
			while index<tokenStream.Length
				'We get char to load:
				Local strChar:String = tokenStream[index]
				if strChar.Trim() = "" Then
					'Print "This is going to fail..."
					index+=1
					Exit
				endif
				char = int(strChar)
				'Print "Loading char: " + char + " at index: " + index
				index+=1

				Local kind:String = tokenStream[index]
				'Print "Found kind= " + kind
				index +=1

				Select kind
					Case "{BR"
						index+=3 '3 control point for future use
						borderChars[char] = New BitMapChar
						borderChars[char].drawingMetrics.drawingOffset.x = Int(tokenStream[index])
						borderChars[char].drawingMetrics.drawingOffset.y = Int(tokenStream[index+1])
						borderChars[char].drawingMetrics.drawingSize.x = Int(tokenStream[index+2])
						borderChars[char].drawingMetrics.drawingSize.y = Int(tokenStream[index+3])
						borderChars[char].drawingMetrics.drawingWidth = Int(tokenStream[index+4])
						if dynamicLoad  = False then
							borderChars[char].image = LoadImage(prefixName + "_BORDER_" + char + ".png")
							borderChars[char].image.SetHandle(-borderChars[char].drawingMetrics.drawingOffset.x,-borderChars[char].drawingMetrics.drawingOffset.y)
						Else
							borderChars[char].SetImageResourceName  prefixName + "_BORDER_" + char + ".png"
						endif
						index+=5
						index += 1 ' control point for future use

						_drawBorder = True

					Case "{SH"
						index+=3 '3 control point for future use
						shadowChars[char] = New BitMapChar
						shadowChars[char].drawingMetrics.drawingOffset.x = Int(tokenStream[index])
						shadowChars[char].drawingMetrics.drawingOffset.y = Int(tokenStream[index+1])
						shadowChars[char].drawingMetrics.drawingSize.x = Int(tokenStream[index+2])
						shadowChars[char].drawingMetrics.drawingSize.y = Int(tokenStream[index+3])
						shadowChars[char].drawingMetrics.drawingWidth = Int(tokenStream[index+4])
						Local filename:String = prefixName + "_SHADOW_" + char + ".png"
						if dynamicLoad  = False then
							shadowChars[char].image = LoadImage(filename)
							shadowChars[char].image.SetHandle(-shadowChars[char].drawingMetrics.drawingOffset.x,-shadowChars[char].drawingMetrics.drawingOffset.y)
						Else
							shadowChars[char].SetImageResourceName  filename
						endif

						'shadowChars[char].image = LoadImage(filename)
						'shadowChars[char].image.SetHandle(-shadowChars[char].drawingMetrics.drawingOffset.x,-shadowChars[char].drawingMetrics.drawingOffset.y)

						index+=5
						index += 1 ' control point for future use

						_drawShadow = True

					Case "{FC"
						index+=3 '3 control point for future use
						faceChars[char] = New BitMapChar
						faceChars[char].drawingMetrics.drawingOffset.x = Int(tokenStream[index])
						faceChars[char].drawingMetrics.drawingOffset.y = Int(tokenStream[index+1])
						faceChars[char].drawingMetrics.drawingSize.x = Int(tokenStream[index+2])
						faceChars[char].drawingMetrics.drawingSize.y = Int(tokenStream[index+3])
						faceChars[char].drawingMetrics.drawingWidth = Int(tokenStream[index+4])
						if dynamicLoad = False then
							faceChars[char].image = LoadImage(prefixName + "_" + char + ".png")
							faceChars[char].image.SetHandle(-faceChars[char].drawingMetrics.drawingOffset.x,-faceChars[char].drawingMetrics.drawingOffset.y)
						Else
							faceChars[char].SetImageResourceName prefixName + "_" + char + ".png"
						endif
						index+=5
						index+=1 ' control point for future use

					Default
					Print "Error loading font! Char = " + char

				End
			Wend
			borderChars = borderChars[..char+1]
			faceChars = faceChars[..char+1]
			shadowChars = shadowChars[..char+1]
		End

		Field packedImages:Image[]

		Method LoadPacked:Void(info:String, fontName:String, dynamicLoad:bool)

			Local header:String = info[.. info.Find(",")]

			Local separator:String
			Select header
				Case "P1"
					separator = "."
				Case "P1.01"
					separator = "_P_"
			End Select
			info = info[info.Find(",")+1..]
			borderChars = New BitMapChar[65536]
			faceChars = New BitMapChar[65536]
			shadowChars = New BitMapChar[65536]
			packedImages = New Image[256]
			Local maxPacked:Int = 0
			Local maxChar:Int = 0

			Local prefixName:String = fontName
			if prefixName.ToLower().EndsWith(".txt") Then prefixName = prefixName[..-4]

			Local charList:string[] = info.Split(";")
			For local chr:String = EachIn charList

				Local chrdata:string[] = chr.Split(",")
				if chrdata.Length() <2 Then Exit
				Local char:bitmapchar.BitMapChar
				Local charIndex:Int = int(chrdata[0])
				if maxChar<charIndex Then maxChar = charIndex

				select chrdata[1]
					Case "B"
						borderChars[charIndex] = New BitMapChar
						char = borderChars[charIndex]
						_drawBorder = True
					Case "F"
						faceChars [charIndex] = New BitMapChar
						char = faceChars[charIndex]
					Case "S"
						shadowChars [charIndex] = New BitMapChar
						char = shadowChars[charIndex]
						_drawShadow = True
				End Select
				char.packedFontIndex = Int(chrdata[2])
				if packedImages[char.packedFontIndex] = null Then
					packedImages[char.packedFontIndex] = LoadImage(prefixName + separator + char.packedFontIndex +  ".png")
					if maxPacked<char.packedFontIndex Then maxPacked = char.packedFontIndex
				endif
				char.packedPosition.x = Int(chrdata[3])
				char.packedPosition.y = Int(chrdata[4])
				char.packedSize.x = Int(chrdata[5])
				char.packedSize.y = Int(chrdata[6])
				char.drawingMetrics.drawingOffset.x = Int(chrdata[8])
				char.drawingMetrics.drawingOffset.y = Int(chrdata[9])
				char.drawingMetrics.drawingSize.x = Int(chrdata[10])
				char.drawingMetrics.drawingSize.y = Int(chrdata[11])
				char.drawingMetrics.drawingWidth = Int(chrdata[12])

			Next

			borderChars = borderChars[..maxChar+1]
			faceChars = faceChars[..maxChar+1]
			shadowChars = shadowChars[..maxChar+1]
			packedImages = packedImages[..maxPacked+1]

		End

		Method DrawChars:Void(text:FlxText, x:Float, y:Float, target:BitMapChar[])
			Local lineIndex:Int = 0, countLines:Int = text._countLines, line:FlxTextLine

			Local drx:Int = x, dry:Int = y
			Local i:Int = 0, l:Int
			Local char:Int

			Local lineHeight:Int = text._fontHeight + Kerning.y
			Local targetLength:Int = target.Length()

			While (lineIndex < countLines)
				line = text._lines[lineIndex]
				i = line.startPos
				l = line.endPos
				drx = x + line.xOffset

				While (i < l)
					char = text._value[i]

					If char >= 0 And char <= targetLength And target[char] <> Null Then
						If target[char].packedFontIndex > 0 Then
								DrawImageRect(packedImages[target[char].packedFontIndex], drx + target[char].drawingMetrics.drawingOffset.x, dry + target[char].drawingMetrics.drawingOffset.y, target[char].packedPosition.x, target[char].packedPosition.y, target[char].packedSize.x, target[char].packedSize.y)
						ElseIf target[char].image <> Null Then
							DrawImage(target[char].image, drx, dry)
						EndIf

						drx += faceChars[char].drawingMetrics.drawingWidth + Kerning.x
					End If

					i += 1
				Wend

				lineIndex += 1
				dry += lineHeight
			Wend
		End Method

		Field _kerning:drawingpoint.DrawingPoint
	End
#End

Strict

Import flxextern
Import flxsprite
Import flxcamera
Import flxg
Import system.flxcolor
Import system.flxassetsmanager

Private

#If FLX_TEXT_DRIVER = "angelfont"
	Import vendor.angelfont
#ElseIf FLX_TEXT_DRIVER = "fontmachine"
	Import vendor.fontmachine
#End

Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_8_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_8_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_9_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_9_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_10_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_10_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_11_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_11_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_12_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_12_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_13_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_13_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_14_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_14_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_15_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_15_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_16_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_16_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_17_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_17_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_18_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_18_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_19_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_19_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_20_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_20_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_21_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_21_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_22_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_22_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_23_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_23_flx.${FLX_FONT_EXTENSION}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_24_flx_${FLX_FONT_IMAGE_MASK}"
Import "data/fonts/${FLX_TEXT_DRIVER}/system_font_24_flx.${FLX_FONT_EXTENSION}"

Public

Class FlxText Extends FlxSprite Implements FlxSpriteRenderer

	Global ClassObject:Object

	Const ALIGN_LEFT:Float = 0
	Const ALIGN_CENTER:Float = 0.5
	Const ALIGN_RIGHT:Float = 1

	Const SYSTEM_FONT:String = "system"

Private
	Global _FontLoader:FlxFontLoader = New FlxFontLoader()

	Global _FontsManager:FlxResourcesManager<FlxBitmapFont> = New FlxResourcesManager<FlxBitmapFont>()

	Field _shadow:FlxColor

	Field _value:String

	Field _lines:FlxTextLine[]

	Field _countLines:Int

	Field _alignment:Float

	Field _fontFamily:String

	Field _fontSize:Int

	Field _fontHeight:Int

	Field _fontObject:FlxBitmapFont

Public
	Method New(x:Float, y:Float, width:Int = 0, text:String = "")
		Super.New(x, y)

		SetRenderer(Self)
		_shadow = New FlxColor(0)

		Self.width = width
		frameWidth = Self.width
		moves = False

		SetFormat(SYSTEM_FONT)
		Text = text
	End Method

	Method Destroy:Void()
		Local i:Int = 0, l:Int = _lines.Length()

		While (i < l)
			_lines[i] = Null
			i += 1
		Wend

		_fontObject = Null
		_shadow = Null

		Super.Destroy()
	End Method

	Method SetFormat:FlxText(font:String = "", size:Int = 0, color:Int = FlxG.WHITE, alignment:Float = ALIGN_LEFT, shadowColor:Int = 0)
		_alignment = alignment
		_fontFamily = font
		_fontSize = FlxAssetsManager.GetFont(font).GetValidSize(size)

		Self.Color = color
		Shadow = shadowColor

		_ResetFont()
		_ResetHelpers()

		Return Self
	End Method

	Method SetWidth:Void(width:Float)
		Self.width = width
		frameWidth = Self.width
		_ResetHelpers()
	End Method

	Method Text:String() Property
		Return _value
	End Method

	Method Text:Void(text:String) Property
		_value = text
		_ResetHelpers()
	End Method

	Method Size:Void(size:Int) Property
		_fontSize = FlxAssetsManager.GetFont(_fontFamily).GetValidSize(size)
		_ResetFont()
		_ResetHelpers()
	End Method

	Method Size:Int() Property
		Return _fontSize
	End Method

	Method Font:String() Property
		Return _fontFamily
	End Method

	Method Font:Void(font:String) Property
		_fontFamily = font
		_ResetFont()
		_ResetHelpers()
	End Method

	Method Alignment:Float() Property
		Return _alignment
	End Method

	Method Alignment:Void(alignment:Float) Property
		If (_alignment = alignment) Return
		_alignment = alignment
		_ResetAlignment()
	End Method

	Method Shadow:Int() Property
		Return _shadow.argb
	End Method

	Method Shadow:Void(color:Int) Property
		_shadow.SetARGB(color)
	End Method

	Method OnSpriteRender:Void(x:Float, y:Float)
		If (_shadow.argb <> 0) Then
			Local oldColor:Int = FlxG._LastDrawingColor
			Local oldAlpha:Int = FlxG._LastDrawingAlpha

			If (_camera.Color <> FlxG.WHITE) Then
				_mixedColor.MixRGB(_shadow, _camera._color)

				If (FlxG._LastDrawingColor <> _mixedColor.argb) Then
					SetColor(_mixedColor.r, _mixedColor.g, _mixedColor.b)
				End If
			Else
				If (FlxG._LastDrawingColor <> _shadow.argb) Then
					SetColor(_shadow.r, _shadow.g, _shadow.b)
				End If
			End If

			If (_camera.Alpha < 1) Then
				Local _mixedAlpha:Float = _camera.Alpha * _shadow.a

				If (FlxG._LastDrawingAlpha <> _mixedAlpha) Then
					SetAlpha(_mixedAlpha)
				End If
			Else
				If (FlxG._LastDrawingAlpha <> _shadow.a) Then
					SetAlpha(_shadow.a)
				End If
			End If

			_fontObject.DrawText(Self, x + 1, y + 1)

			_mixedColor.SetRGB(oldColor)
			SetColor(_mixedColor.r, _mixedColor.g, _mixedColor.b)
			SetAlpha(oldAlpha)
		End If

		_fontObject.DrawText(Self, x, y)
	End Method

Private
	Method _ResetHelpers:Void()
		If (_value.Length() > 0) Then
			If (width <> 0) Then
				_ParseText()
			Else
				_countLines = 0
				_AddLine(0, _value.Length(), _fontObject.GetTextWidth(Self, 0, _value.Length()))
				_ResetAlignment()
			End If

			Self.height = _GetHeight()
			frameHeight = Self.height
		Else
			Self.height = 0
			frameHeight = 0
		End If

		Super._ResetHelpers()
	End Method

	Method _ResetFont:Void()
		_FontLoader.fontFamily = _fontFamily
		_FontLoader.fontSize = _fontSize

		_fontObject = _FontsManager.GetResource(_fontFamily + _fontSize, _FontLoader)
		_fontHeight = _fontObject.GetFontHeight(Self)
	End Method

	Method _ResetAlignment:Void()
		If (_value.Length() = 0) Return

		For Local line:Int = 0 Until _countLines
			_lines[line].xOffset = (width - _lines[line].width) * _alignment
		Next
	End Method

	Method _GetHeight:Int()
		If (width = 0) Then
			Return _fontHeight
		Else
			Return _fontObject.GetTextHeight(Self)
		End If
	End Method

	Method _ParseText:Void()
		_countLines = 0

		Local prevOffset:Int = 0
		Local offsetN:Int = _value.Find("~n", 0)
		Local offsetR:Int = _value.Find("~r", 0)
		Local offset:Int = 0

		If (offsetN >= 0 And offsetR >= 0) Then
			offset = Min(offsetN, offsetR)
		Else
			offset = Max(offsetN, offsetR)
		End If

		If (offset >= 0) Then
			While (offset >= 0)
				_BuildLines(prevOffset, offset)

				prevOffset = offset + 1

				offsetN = _value.Find("~n", prevOffset)
				offsetR = _value.Find("~r", prevOffset)

				If (offsetN >= 0 And offsetR >= 0) Then
					offset = Min(offsetN, offsetR)
				Else
					offset = Max(offsetN, offsetR)
				End if
			Wend

			_BuildLines(prevOffset, _value.Length())
		Else
			_BuildLines(0, _value.Length())
		End If

		_ResetAlignment()
	End Method

	Method _BuildLines:Void(startPos:Int, endPos:Int)
		Local textWidth:Int = _fontObject.GetTextWidth(Self, startPos, endPos)

		If (width < textWidth) Then
			Local textLength:Int = endPos - startPos

			Local range:Int = Ceil(textLength / Float(Floor(textWidth / Float(width)) + 1))

			Repeat
				range += 1
				textWidth = _fontObject.GetTextWidth(Self, 0, range)
			Until (textWidth >= width)

			Local maxOffset:Int = range
			Local minOffset:Int = 0
			Local offset:Int = maxOffset
			Local tmpOffset:Int = 0
			Local dirty:Bool = False
			Local finalTextWidth:Int = 0
			Local success:Bool

			Repeat
				Repeat
					offset -= 1
					If (offset - minOffset <= 1) Then
						offset = minOffset + 1
						Exit
					End if

					tmpOffset = -1
					For Local i:Int = offset - 1 To minOffset Step - 1
						success = False

						If (_value[i] = KEY_SPACE Or _value[i] = KEY_TAB) Then
							tmpOffset = i
							success = True
							Exit
						End If
					Next

					If (tmpOffset < 0) Then
						tmpOffset = _GetMinOffset(minOffset, offset)
					Else
						If (_value[minOffset] = KEY_SPACE Or _value[minOffset] = KEY_TAB) Then
							For Local i:Int = minOffset To offset
								If (_value[i] <> KEY_SPACE And _value[i] <> KEY_TAB) Then
									minOffset = i
									Exit
								End If
							Next
						End If
					End If

					offset = tmpOffset
					textWidth = _fontObject.GetTextWidth(Self, minOffset, offset)
				Until (textWidth <= width)

				dirty = False
				finalTextWidth = _fontObject.GetTextWidth(Self, minOffset)

				If (finalTextWidth > width And textLength - minOffset > 1) Then
					For Local i:Int = minOffset Until offset
						If (_value[i] = KEY_SPACE Or _value[i] = KEY_TAB) Then
							minOffset += 1
							dirty = True
						Else
							Exit
						End If
					Next

					For Local i:Int = offset - 1 To minOffset Step - 1
						If (_value[i] = KEY_SPACE Or _value[i] = KEY_TAB) Then
							offset -= 1
							dirty = True
						Else
							Exit
						End If
					Next

					If (offset - minOffset = 0) Continue

					If ( Not dirty) Then
						_AddLine(minOffset, offset, textWidth)
					Else
						_AddLine(minOffset, offset, _fontObject.GetTextWidth(Self, minOffset, offset))
					End If

					minOffset = offset
					If (success) minOffset += 1

					maxOffset = minOffset + range
					offset = maxOffset
				Else
					Local l:Int = _value.Length()

					For Local i:Int = minOffset Until l
						If (_value[i] = KEY_SPACE Or _value[i] = KEY_TAB) Then
							minOffset += 1
							dirty = True
						Else
							Exit
						End If
					Next

					For Local i:Int = l - 1 To minOffset Step - 1
						If (_value[i] = KEY_SPACE Or _value[i] = KEY_TAB) Then
							l -= 1
							dirty = True
						Else
							Exit
						End If
					Next

					If (l - minOffset <= 0) Exit

					If ( Not dirty) Then
						_AddLine(minOffset, l, finalTextWidth)
					Else
						_AddLine(minOffset, offset, _fontObject.GetTextWidth(Self, minOffset, l))
					End If

					Exit
				End If
			Forever
		Else
			_AddLine(startPos, endPos, textWidth)
		End If
	End Method

	Method _GetMinOffset:Int(startPos:Int, endPos:Int)
		Local offset:Int = endPos - startPos

		While (_fontObject.GetTextWidth(Self, startPos, startPos + offset) > width)
			offset -= 1
			If (offset = 1) Return offset
		Wend

		Return startPos + offset
	End Method

	Method _AddLine:FlxTextLine(startPos:Int, endPos:Int, width:Int)
		Local line:FlxTextLine

		If (_countLines = _lines.Length()) Then
			_lines = _lines.Resize(_countLines * 2 + 10)
			line = New FlxTextLine(startPos, endPos, width)
			_lines[_countLines] = line
		Else
			line = _lines[_countLines]
			If (line = Null) Then
				line = New FlxTextLine(startPos, endPos, width)
				_lines[_countLines] = line
			Else
				line.Reset(startPos, endPos, width)
			End If
		End If

		_countLines += 1
		Return line
	End Method

End Class

Private
Class FlxTextLine

	Field xOffset:Float

	Field startPos:Int

	Field endPos:Int

	Field width:Int

	Method New(startPos:Int, endPos:Int, width:Int)
		Reset(startPos, endPos, width)
	End Method

	Method Reset:Void(startPos:Int, endPos:Int, width:Int)
		Self.startPos = startPos
		Self.endPos = endPos
		Self.width = width
	End Method

End Class

Class FlxFontLoader Extends FlxResourceLoader<FlxBitmapFont>

	Field fontFamily:String = FlxText.SYSTEM_FONT
	Field fontSize:Int

	Method Load:FlxBitmapFont(name:String)
		Return New FlxBitmapFont(FlxAssetsManager.GetFont(fontFamily).GetPath(fontSize))
	End Method

End Class

#If FLX_TEXT_DRIVER = "angelfont"
	Class FlxBitmapFont
	Private
		Field image:Image[] = New Image[1]
		Field chars:Char[256]

		Field kernPairs:IntMap<IntMap<KernPair>> = New IntMap<IntMap<KernPair>>
		Global firstKp:IntMap<KernPair>
		Global secondKp:KernPair

		Field iniText:String

		Field xOffset:Int
		Field yOffset:Int

	Public
		Field useKerning:Bool = True

		Field height:Int = 0
		Field heightOffset:Int = 9999

		Field lineGap:Int = 5

		Method New(url:String)
			iniText = LoadString(url+".fnt")
			Local lines:String[] = iniText.Split(String.FromChar(10))
			Local firstLine:String = lines[0]
			If firstLine.Contains("<?xml")
				Local lineList:List<String> = New List<String>(lines)
				lineList.RemoveFirst()
				lines = lineList.ToArray()
				iniText = "~n".Join(lines)
			End

			Local pageCount:Int = 0

			Local config:= LoadConfig(iniText)

			Local nodes:= config.FindNodesByPath("font/chars/char")

			For Local node:= EachIn nodes
				Local id:Int = Int(node.GetAttribute("id"))
				Local page:Int = Int(node.GetAttribute("page"))
				If pageCount < page pageCount = page
				chars[id] = New Char(Int(node.GetAttribute("x")), Int(node.GetAttribute("y")), Int(node.GetAttribute("width")), Int(node.GetAttribute("height")),  Int(node.GetAttribute("xoffset")),  Int(node.GetAttribute("yoffset")),  Int(node.GetAttribute("xadvance")), page)
				Local ch := chars[id]
				If ch.height > Self.height Self.height = ch.height
				If ch.yOffset < Self.heightOffset Self.heightOffset = ch.yOffset
			Next

			nodes = config.FindNodesByPath("font/kernings/kerning")

			If (nodes <> Null) Then
				For Local node:= EachIn nodes
					Local first:Int = Int(node.GetAttribute("first"))
					firstKp = kernPairs.Get(first)
					If firstKp = Null
						kernPairs.Add(first, New IntMap<KernPair>)
						firstKp = kernPairs.Get(first)
					End

					Local second:Int = Int(node.GetAttribute("second"))
					firstKp.Add(second, New KernPair(first, second, Int(node.GetAttribute("amount"))))
				End
			Else
				useKerning = False
			End If

			If pageCount = 0
				image[0] = LoadImage(url+".png")
				If image[0] = Null image[0] = LoadImage(url + "_0.png")
			Else
				For Local page:= 0 To pageCount
					If image.Length < page+1 image = image.Resize(page+1)
					image[page] = LoadImage(url+"_"+page+".png")
				End
			End
		End

		Method GetTextWidth:Int(txt:FlxText, startPos:Int = 0, endPos:Int = -1)
			Local prevChar:Int = 0
			Local width:Int = 0

			If (endPos < 0) endPos = txt._value.Length()
			Local asc:Int, ac:Char

			For Local i:= startPos Until endPos
				asc = txt._value[i]
				ac = chars[asc]

				If ac <> Null
					If useKerning
						Local firstKp:= kernPairs.Get(prevChar)
						If firstKp <> Null
							Local secondKp:= firstKp.Get(asc)
							If secondKp <> Null
								xOffset += secondKp.amount
							End
						Endif
					EndIf

					width += ac.xAdvance
					prevChar = asc
				EndIf
			Next

			Return width
		End Method

		Method GetFontHeight:Int(txt:FlxText)
			Local h:Int = 0

			If (txt._value.Length() <> 0) Then
				Local l:Int = txt._value.Length()

				For Local i:= 0 Until l
					Local asc:Int = txt._value[i]
					Local ac:Char = chars[asc]
					If ac.height+ac.yOffset > h h = ac.height+ac.yOffset
				Next
			Else
				h = chars[KEY_SPACE].height + chars[KEY_SPACE].yOffset
			End If

			Return h
		End

		Method GetTextHeight:Int(txt:FlxText)
			Return ( (txt._fontHeight + lineGap) * txt._countLines) - lineGap
		End Method

		Method DrawText:Void(txt:FlxText, x:Int, y:Int)
			Local lineIndex:Int = 0, countLines:Int = txt._countLines, line:FlxTextLine

			Local prevChar:Int = 0, xOffset:Int = 0, yOffset:Int = 0
			Local i:Int = 0, l:Int
			Local asc:Int, ac:Char
			Local lineHeight:Int = txt._fontHeight + lineGap

			While (lineIndex < countLines)
				line = txt._lines[lineIndex]
				i = line.startPos
				l = line.endPos
				xOffset = 0

				While (i < l)
					asc = txt._value[i]
					ac = chars[asc]

					If ac <> Null
						If useKerning
							firstKp = kernPairs.Get(prevChar)
							If firstKp <> Null
								secondKp = firstKp.Get(asc)
								If secondKp <> Null
									xOffset += secondKp.amount
								End
							Endif
						Endif

						ac.Draw(image[ac.page], x + xOffset + line.xOffset, y + yOffset)
						xOffset += ac.xAdvance
						prevChar = asc
					Endif

					i += 1
				Wend

				lineIndex += 1
				yOffset += lineHeight
			Wend
		End Method

	End Class

#ElseIf FLX_TEXT_DRIVER = "fontmachine"

	Class FlxBitmapFont Implements Font

		Method New(fontDescriptionFilePath:String)
			Local text:String = LoadString(fontDescriptionFilePath + ".txt")
			if text = "" Then Print "FONT " + fontDescriptionFilePath + " WAS NOT FOUND!!!"
			LoadFontData(text, fontDescriptionFilePath, False)
		End

		Method GetTextWidth:Float(text:FlxText, fromChar:Int = 0, toChar:Int = -1)
			Local twidth:Float
			Local char:Int
			Local lastchar:Int = 0

			If (toChar < 0) toChar = text._value.Length()

			For Local i:Int = fromChar Until toChar
				char = text._value[i]

				If faceChars[char] <> Null Then
					lastchar = char
					twidth = twidth + faceChars[char].drawingMetrics.drawingWidth + Kerning.x
				End If
			Next

			Return twidth - faceChars[lastchar].drawingMetrics.drawingWidth + faceChars[lastchar].drawingMetrics.drawingSize.x
		End Method

		Method GetTextHeight:Float(text:FlxText)
			Return ( (text._fontHeight + Kerning.y) * text._countLines) - Kerning.y
		End

		Method GetFontHeight:Int(txt:FlxText)
			If faceChars[32] = Null Then Return 0
			Return faceChars[32].drawingMetrics.drawingSize.y
		End Method

		Method DrawText:Void(text:FlxText, x:Float, y:Float)
			If (_drawShadow) DrawChars(text, x, y, shadowChars)
			If (_drawBorder) DrawChars(text, x, y, borderChars)
			DrawChars(text, x, y, faceChars)
		End

		Method Kerning:drawingpoint.DrawingPoint() property
			if _kerning = null Then _kerning = New drawingpoint.DrawingPoint
			Return _kerning
		End

		Method Kerning:void(value:drawingpoint.DrawingPoint) property
			_kerning = value
		End

	Private

		Field _drawShadow:Bool = true
		Field _drawBorder:Bool = true
		Field borderChars:BitMapChar[]
		Field faceChars:BitMapChar[]
		Field shadowChars:BitMapChar[]

		Method LoadFontData:Void(Info:String, fontName:String, dynamicLoad:bool)
			if Info.StartsWith("P1") Then
				LoadPacked(Info,fontName,dynamicLoad)
				return
			EndIf
			Local tokenStream:String[] = Info.Split(",")
			local index:Int = 0
			borderChars = New BitMapChar[65536]
			faceChars = New BitMapChar[65536]
			shadowChars = New BitMapChar[65536]

			Local prefixName:String = fontName
			if prefixName.ToLower().EndsWith(".txt") Then prefixName = prefixName[..-4]

			Local char:Int = 0
			while index<tokenStream.Length
				'We get char to load:
				Local strChar:String = tokenStream[index]
				if strChar.Trim() = "" Then
					'Print "This is going to fail..."
					index+=1
					Exit
				endif
				char = int(strChar)
				'Print "Loading char: " + char + " at index: " + index
				index+=1

				Local kind:String = tokenStream[index]
				'Print "Found kind= " + kind
				index +=1

				Select kind
					Case "{BR"
						index+=3 '3 control point for future use
						borderChars[char] = New BitMapChar
						borderChars[char].drawingMetrics.drawingOffset.x = Int(tokenStream[index])
						borderChars[char].drawingMetrics.drawingOffset.y = Int(tokenStream[index+1])
						borderChars[char].drawingMetrics.drawingSize.x = Int(tokenStream[index+2])
						borderChars[char].drawingMetrics.drawingSize.y = Int(tokenStream[index+3])
						borderChars[char].drawingMetrics.drawingWidth = Int(tokenStream[index+4])
						if dynamicLoad  = False then
							borderChars[char].image = LoadImage(prefixName + "_BORDER_" + char + ".png")
							borderChars[char].image.SetHandle(-borderChars[char].drawingMetrics.drawingOffset.x,-borderChars[char].drawingMetrics.drawingOffset.y)
						Else
							borderChars[char].SetImageResourceName  prefixName + "_BORDER_" + char + ".png"
						endif
						index+=5
						index+=1 ' control point for future use

					Case "{SH"
						index+=3 '3 control point for future use
						shadowChars[char] = New BitMapChar
						shadowChars[char].drawingMetrics.drawingOffset.x = Int(tokenStream[index])
						shadowChars[char].drawingMetrics.drawingOffset.y = Int(tokenStream[index+1])
						shadowChars[char].drawingMetrics.drawingSize.x = Int(tokenStream[index+2])
						shadowChars[char].drawingMetrics.drawingSize.y = Int(tokenStream[index+3])
						shadowChars[char].drawingMetrics.drawingWidth = Int(tokenStream[index+4])
						Local filename:String = prefixName + "_SHADOW_" + char + ".png"
						if dynamicLoad  = False then
							shadowChars[char].image = LoadImage(filename)
							shadowChars[char].image.SetHandle(-shadowChars[char].drawingMetrics.drawingOffset.x,-shadowChars[char].drawingMetrics.drawingOffset.y)
						Else
							shadowChars[char].SetImageResourceName  filename
						endif

						'shadowChars[char].image = LoadImage(filename)
						'shadowChars[char].image.SetHandle(-shadowChars[char].drawingMetrics.drawingOffset.x,-shadowChars[char].drawingMetrics.drawingOffset.y)

						index+=5
						index+=1 ' control point for future use

					Case "{FC"
						index+=3 '3 control point for future use
						faceChars[char] = New BitMapChar
						faceChars[char].drawingMetrics.drawingOffset.x = Int(tokenStream[index])
						faceChars[char].drawingMetrics.drawingOffset.y = Int(tokenStream[index+1])
						faceChars[char].drawingMetrics.drawingSize.x = Int(tokenStream[index+2])
						faceChars[char].drawingMetrics.drawingSize.y = Int(tokenStream[index+3])
						faceChars[char].drawingMetrics.drawingWidth = Int(tokenStream[index+4])
						if dynamicLoad = False then
							faceChars[char].image = LoadImage(prefixName + "_" + char + ".png")
							faceChars[char].image.SetHandle(-faceChars[char].drawingMetrics.drawingOffset.x,-faceChars[char].drawingMetrics.drawingOffset.y)
						Else
							faceChars[char].SetImageResourceName prefixName + "_" + char + ".png"
						endif
						index+=5
						index+=1 ' control point for future use

					Default
					Print "Error loading font! Char = " + char

				End
			Wend
			borderChars = borderChars[..char+1]
			faceChars = faceChars[..char+1]
			shadowChars = shadowChars[..char+1]
		End

		Field packedImages:Image[]

		Method LoadPacked:Void(info:String, fontName:String, dynamicLoad:bool)

			Local header:String = info[.. info.Find(",")]

			Local separator:String
			Select header
				Case "P1"
					separator = "."
				Case "P1.01"
					separator = "_P_"
			End Select
			info = info[info.Find(",")+1..]
			borderChars = New BitMapChar[65536]
			faceChars = New BitMapChar[65536]
			shadowChars = New BitMapChar[65536]
			packedImages = New Image[256]
			Local maxPacked:Int = 0
			Local maxChar:Int = 0

			Local prefixName:String = fontName
			if prefixName.ToLower().EndsWith(".txt") Then prefixName = prefixName[..-4]

			Local charList:string[] = info.Split(";")
			For local chr:String = EachIn charList

				Local chrdata:string[] = chr.Split(",")
				if chrdata.Length() <2 Then Exit
				Local char:bitmapchar.BitMapChar
				Local charIndex:Int = int(chrdata[0])
				if maxChar<charIndex Then maxChar = charIndex

				select chrdata[1]
					Case "B"
						borderChars[charIndex] = New BitMapChar
						char = borderChars[charIndex]
					Case "F"
						faceChars [charIndex] = New BitMapChar
						char = faceChars[charIndex]
					Case "S"
						shadowChars [charIndex] = New BitMapChar
						char = shadowChars[charIndex]
				End Select
				char.packedFontIndex = Int(chrdata[2])
				if packedImages[char.packedFontIndex] = null Then
					packedImages[char.packedFontIndex] = LoadImage(prefixName + separator + char.packedFontIndex +  ".png")
					if maxPacked<char.packedFontIndex Then maxPacked = char.packedFontIndex
				endif
				char.packedPosition.x = Int(chrdata[3])
				char.packedPosition.y = Int(chrdata[4])
				char.packedSize.x = Int(chrdata[5])
				char.packedSize.y = Int(chrdata[6])
				char.drawingMetrics.drawingOffset.x = Int(chrdata[8])
				char.drawingMetrics.drawingOffset.y = Int(chrdata[9])
				char.drawingMetrics.drawingSize.x = Int(chrdata[10])
				char.drawingMetrics.drawingSize.y = Int(chrdata[11])
				char.drawingMetrics.drawingWidth = Int(chrdata[12])

			Next

			borderChars = borderChars[..maxChar+1]
			faceChars = faceChars[..maxChar+1]
			shadowChars = shadowChars[..maxChar+1]
			packedImages = packedImages[..maxPacked+1]

		End

		Method DrawChars:Void(text:FlxText, x:Float, y:Float, target:BitMapChar[])
			Local lineIndex:Int = 0, countLines:Int = text._countLines, line:FlxTextLine

			Local drx:Int = x, dry:Int = y
			Local i:Int = 0, l:Int
			Local char:Int

			Local lineHeight:Int = text._fontHeight + Kerning.y
			Local targetLength:Int = target.Length()

			While (lineIndex < countLines)
				line = text._lines[lineIndex]
				i = line.startPos
				l = line.endPos
				drx = x + line.xOffset

				While (i < l)
					char = text._value[i]

					If char >= 0 And char <= targetLength And target[char] <> Null Then
						If target[char].packedFontIndex > 0 Then
								DrawImageRect(packedImages[target[char].packedFontIndex], drx + target[char].drawingMetrics.drawingOffset.x, dry + target[char].drawingMetrics.drawingOffset.y, target[char].packedPosition.x, target[char].packedPosition.y, target[char].packedSize.x, target[char].packedSize.y)
						ElseIf target[char].image <> Null Then
							DrawImage(target[char].image, drx, dry)
						EndIf

						drx += faceChars[char].drawingMetrics.drawingWidth + Kerning.x
					End If

					i += 1
				Wend

				lineIndex += 1
				dry += lineHeight
			Wend

			#Rem
			For Local i:Int = startPos to endPos 'text.Length
				Local char:Int = text[i-1]
				if char>=0 And char<=target.Length Then
					if char = 10 Then
						dry += Int(faceChars[32].drawingMetrics.drawingSize.y) + Kerning.y
						Self.DrawCharsText(text, oldX, dry, target, align, i + 1, endPos)
						return
					ElseIf target[char] <> null Then
						if target[char].CharImageLoaded() = false Then
							target[char].LoadCharImage()
						End
						if target[char].image <> null Then
							DrawImage(target[char].image,drx-xOffset,dry)
						ElseIf target[char].packedFontIndex > 0 Then
							DrawImageRect(packedImages[target[char].packedFontIndex],-xOffset+drx+target[char].drawingMetrics.drawingOffset.x,dry+target[char].drawingMetrics.drawingOffset.y,target[char].packedPosition.x,target[char].packedPosition.y,target[char].packedSize.x,target[char].packedSize.y)
						Endif
						drx+=faceChars[char].drawingMetrics.drawingWidth  + Kerning.x
					endif
				Else
				'	Print "Char " + char + " out of scope."
				EndIf
			Next
			#End
		End Method

		Field _kerning:drawingpoint.DrawingPoint
	End
#End


added compiler option for NEON

added compiler option for NEON

#If LANG<>"cpp"
#Error "SIMD only supported on C++ targets"
#Endif

#CPP_MEM_ALIGNMENT = 16
#CPP_USE_ALIGNED_ALLOC = True
' Enable sse for gcc/mingw

#If TARGET="android" Or TARGET="ios"
' Use ARM's NEON
#CC_OPTS = "-mfpu=neon"
Import "native/simd.neon.cpp"
#Else
' Use SSE/AVX
#CC_OPTS = "-msse"
Import "native/simd.cpp"
#Endif

Extern

Class @Float32x4 Extends Object="SIMD_Float32x4"
End

Class SIMD
	' Float32x4 Factory
	Function MakeFloat32x4:Float32x4(x:Float, y:Float, z:Float, w:Float)
	Function MakeFloat32x4Zero:Float32x4()
	Function MakeFloat32x4Reverse:Float32x4(x:Float, y:Float, z:Float, w:Float)
	Function MakeFloat32x4One:Float32x4(x:Float)
	' Float32x4 Set Operations
	Function Set:Void(destination:Float32x4, x:Float, y:Float, z:Float, w:Float)
	Function SetZero:Void(destination:Float32x4)
	Function SetOne:Void(destination:Float32x4, x:Float)
	Function SetReverse:Void(destination:Float32x4, x:Float, y:Float, z:Float, w:Float)
	' Float32x4 Load and Store
	Function Load:Void(destination:Float32x4, source:Float[])
	Function Store:Void(destination:Float[], source:Float32x4)
	' Float32x4 Arithmetic
	Function Add:Void(destination:Float32x4, operandR:Float32x4, operandL:Float32x4)
	Function Sub:Void(destination:Float32x4, operandR:Float32x4, operandL:Float32x4)
	Function Mul:Void(destination:Float32x4, operandR:Float32x4, operandL:Float32x4)
	Function Min:Void(destination:Float32x4, operandR:Float32x4, operandL:Float32x4)
	Function Max:Void(destination:Float32x4, operandR:Float32x4, operandL:Float32x4)
	Function Sqrt:Void(destination:Float32x4, source:Float32x4)
	Function Rsqrt:Void(destination:Float32x4, source:Float32x4)
	' Float32x4 Logical Operations
	Function LogicAnd:Void(destination:Float32x4, operandR:Float32x4, operandL:Float32x4)
	Function LogicAndNot:Void(destination:Float32x4, operandR:Float32x4, operandL:Float32x4)
	Function LogicOr:Void(destination:Float32x4, operandR:Float32x4, operandL:Float32x4)
	Function LogicXor:Void(destination:Float32x4, operandR:Float32x4, operandL:Float32x4)
	' Float32x4 Compare Operations
	Function CompareEqual:Void(destination:Float32x4, operandR:Float32x4, operandL:Float32x4)
	Function CompareLowerThan:Void(destination:Float32x4, operandR:Float32x4, operandL:Float32x4)
	Function CompareLowerThanOrEqual:Void(destination:Float32x4, operandR:Float32x4, operandL:Float32x4)
	Function CompareGreaterThan:Void(destination:Float32x4, operandR:Float32x4, operandL:Float32x4)
	Function CompareGreaterThanOrEqual:Void(destination:Float32x4, operandR:Float32x4, operandL:Float32x4)
End

' Float32x4 Shuffle.
Function SIMD_Shuffle:Void(destination:Float32x4, operandR:Float32x4, operandL:Float32x4, mask:Int) = "SIMD_SHUFFLE"
Function SIMD_ShuffleHelper:Int(x:Int, y:Int, z:Int, w:Int) = "_MM_SHUFFLE"

#If LANG<>"cpp"
#Error "SIMD only supported on C++ targets"
#Endif

#CPP_MEM_ALIGNMENT = 16
#CPP_USE_ALIGNED_ALLOC = True
' Enable sse for gcc/mingw
#CC_OPTS = "-std=c++11 -msse"

#If TARGET="android" Or TARGET="ios"
' Use ARM's NEON
Import "native/simd.neon.cpp"
#Else
' Use SSE/AVX
Import "native/simd.cpp"
#Endif

Extern

Class @Float32x4 Extends Object="SIMD_Float32x4"
End

Class SIMD
	' Float32x4 Factory
	Function MakeFloat32x4:Float32x4(x:Float, y:Float, z:Float, w:Float)
	Function MakeFloat32x4Zero:Float32x4()
	Function MakeFloat32x4Reverse:Float32x4(x:Float, y:Float, z:Float, w:Float)
	Function MakeFloat32x4One:Float32x4(x:Float)
	' Float32x4 Set Operations
	Function Set:Void(destination:Float32x4, x:Float, y:Float, z:Float, w:Float)
	Function SetZero:Void(destination:Float32x4)
	Function SetOne:Void(destination:Float32x4, x:Float)
	Function SetReverse:Void(destination:Float32x4, x:Float, y:Float, z:Float, w:Float)
	' Float32x4 Load and Store
	Function Load:Void(destination:Float32x4, source:Float[])
	Function Store:Void(destination:Float[], source:Float32x4)
	' Float32x4 Arithmetic
	Function Add:Void(destination:Float32x4, operandR:Float32x4, operandL:Float32x4)
	Function Sub:Void(destination:Float32x4, operandR:Float32x4, operandL:Float32x4)
	Function Mul:Void(destination:Float32x4, operandR:Float32x4, operandL:Float32x4)
	Function Min:Void(destination:Float32x4, operandR:Float32x4, operandL:Float32x4)
	Function Max:Void(destination:Float32x4, operandR:Float32x4, operandL:Float32x4)
	Function Sqrt:Void(destination:Float32x4, source:Float32x4)
	Function Rsqrt:Void(destination:Float32x4, source:Float32x4)
	' Float32x4 Logical Operations
	Function LogicAnd:Void(destination:Float32x4, operandR:Float32x4, operandL:Float32x4)
	Function LogicAndNot:Void(destination:Float32x4, operandR:Float32x4, operandL:Float32x4)
	Function LogicOr:Void(destination:Float32x4, operandR:Float32x4, operandL:Float32x4)
	Function LogicXor:Void(destination:Float32x4, operandR:Float32x4, operandL:Float32x4)
	' Float32x4 Compare Operations
	Function CompareEqual:Void(destination:Float32x4, operandR:Float32x4, operandL:Float32x4)
	Function CompareLowerThan:Void(destination:Float32x4, operandR:Float32x4, operandL:Float32x4)
	Function CompareLowerThanOrEqual:Void(destination:Float32x4, operandR:Float32x4, operandL:Float32x4)
	Function CompareGreaterThan:Void(destination:Float32x4, operandR:Float32x4, operandL:Float32x4)
	Function CompareGreaterThanOrEqual:Void(destination:Float32x4, operandR:Float32x4, operandL:Float32x4)
End

' Float32x4 Shuffle.
Function SIMD_Shuffle:Void(destination:Float32x4, operandR:Float32x4, operandL:Float32x4, mask:Int) = "SIMD_SHUFFLE"
Function SIMD_ShuffleHelper:Int(x:Int, y:Int, z:Int, w:Int) = "_MM_SHUFFLE"


Changed to use assertions, and fixed null sorting.

--HG--
extra : convert_revision : svn%3Ae934d3cd-0d57-f34a-94d0-9e8d31ae1c29/trunk%4062

Strict

Import assert

#Rem
Monkey Collections Framework
Based loosely on the Java Collections Framework
#End

'''' Top level Collection stuff ''''

#Rem
	AbstractCollection
	Due to limitations with generics in Monkey, ToArray cannot work with an array of type E.  We must return Object[] instead.
#End
Class AbstractCollection<E> Abstract
Private
	' A custom comparator to use for sorting and comparing items.
	Field comparator:AbstractComparator = Null

Public
	' Property to read comparator
	Method Comparator:AbstractComparator() Property
		Return comparator
	End
	' Property to write comparator
	Method Comparator:Void(comparator:AbstractComparator) Property
		Self.comparator = comparator
	End

	Method Add:Bool(o:E) Abstract
	Method AddAll:Bool(c:AbstractCollection<E>) Abstract
	Method Clear:Void() Abstract
	Method Contains:Bool(o:E) Abstract
	Method ContainsAll:Bool(c:AbstractCollection<E>) Abstract
	Method Enumerator:AbstractEnumerator<E>() Abstract
	Method FillArray:Int(arr:Object[]) Abstract ' populates the passed array and returns the number of items filled. best used for android
	Method IsEmpty:Bool() Abstract
	Method Remove:Bool(o:E) Abstract
	Method RemoveAll:Bool(c:AbstractCollection<E>) Abstract
	Method RetainAll:Bool(c:AbstractCollection<E>) Abstract
	Method Size:Int() Property Abstract
	Method Sort:Void(reverse:Bool = False, comp:AbstractComparator = Null) Abstract
	Method ToArray:Object[]() Abstract ' creates a new array of the correct size and returns it

	Method ObjectEnumerator:AbstractEnumerator<E>()
		Return Enumerator()
	End
End

#Rem
	AbstractEnumerator
	Used in the ObjectEnumerator method for calls to EachIn.
	If retrieved and used manually, the HasPrevious/PreviousObject/Remove/First/Last methods can be called.
#End
Class AbstractEnumerator<E>
Public
	Method HasNext:Bool() Abstract
	Method HasPrevious:Bool() Abstract
	Method NextObject:E() Abstract
	Method PreviousObject:E() Abstract
	Method Remove:Void() Abstract
	Method First:Void() Abstract
	Method Last:Void() Abstract
End

#Rem
	AbstractComparator
	This is a way For developers to provide a custom comparison method for sorting lists.
	It's sort of like a function pointer.
#End
Class AbstractComparator Abstract
	Method Compare:Int(o1:Object, o2:Object) Abstract

	Method CompareBool:Bool(o1:Object, o2:Object)
		Return o1 = o2 Or Compare(o1, o2) = 0
	End

	Method HashCode:Int(o:Object)
		Return 0
	End
End

#Rem
	DefaultComparator
	Implements an AbstractComparator to handle primitive wrappers and strings.
#End
Global DEFAULT_COMPARATOR:DefaultComparator = New DefaultComparator
Class DefaultComparator Extends AbstractComparator
	Method Compare:Int(o1:Object, o2:Object)
		If IntObject(o1) <> Null And IntObject(o2) <> Null Then
			If IntObject(o1).value < IntObject(o2).value Then Return -1
			If IntObject(o1).value > IntObject(o2).value Then Return 1
			Return 0
		ElseIf FloatObject(o1) <> Null And FloatObject(o2) <> Null Then
			If FloatObject(o1).value < FloatObject(o2).value Then Return -1
			If FloatObject(o1).value > FloatObject(o2).value Then Return 1
			Return 0
		ElseIf StringObject(o1) <> Null And StringObject(o2) <> Null Then
			' TODO: string comparison
			'If StringObject(o1).value < StringObject(o2).value Then Return -1
			'If StringObject(o1).value > StringObject(o2).value Then Return 1
			Return 0
		End
		If o1 = o2 Then Return 0
		If o1 = Null Then Return -1
		If o2 = Null Then Return 1
		Return 0 ' don't know what to do!
	End

	Method CompareBool:Bool(o1:Object, o2:Object)
		If IntObject(o1) <> Null And IntObject(o2) <> Null Then
			Return IntObject(o1).value = IntObject(o2).value
		ElseIf FloatObject(o1) <> Null And FloatObject(o2) <> Null Then
			Return FloatObject(o1).value = FloatObject(o2).value
		ElseIf StringObject(o1) <> Null And StringObject(o2) <> Null Then
			Return StringObject(o1).value = StringObject(o2).value
		End
		Return o1 = o2
	End
End

'''' List stuff ''''

#Rem
	AbstractList
	Extends AbstractCollection to implement some of the abstract methods that apply to any kind of list.
#End
Class AbstractList<E> Extends AbstractCollection<E> Abstract
Private
	' If true, bounds checking should be performed.
	Field rangeChecking:Bool = True

	' A counter for modifications to the list.  Used for concurrency checks.
	Field modCount:Int = 0

	' Performs a range check.
	Method RangeCheck:Void(index:Int)
		Local size:Int = Self.Size()
		AssertRange(index, 0, size, "AbstractList.RangeCheck: Index out of bounds: Index: " + index + ", Size: " + size)
	End

Public
	' Property to read rangeChecking
	Method RangeChecking:Bool() Property
		Return rangeChecking
	End
	' Property to write rangeChecking
	Method RangeChecking:Void(rangeChecking:Bool) Property
		Self.rangeChecking = rangeChecking
	End

	Method Enumerator:AbstractEnumerator<E>()
		Return New ListEnumerator<E>(Self)
	End

	Method Get:E(index:Int) Abstract
	Method Insert:Void(index:Int, o:E) Abstract
	Method InsertAll:Bool(index:Int, c:AbstractCollection<E>) Abstract
	Method IndexOf:Int(o:E) Abstract
	Method LastIndexOf:Int(o:E) Abstract
	Method RemoveAt:E(index:Int) Abstract ' Can't overload Remove since it's an inherited method, and Monkey doesn't support that.
	Method RemoveRange:Void(fromIndex:Int, toIndex:Int) Abstract
	Method Set:E(index:Int, o:E) Abstract
End

#Rem
	ListEnumerator
	Extends AbstractEnumerator to provide support for EachIn.  Blocks concurrent modification, but allows elements to be removed on the fly.
#End
Class ListEnumerator<E> Extends AbstractEnumerator<E>
Private
	Field lst:AbstractList<E>
	Field lastIndex:Int = 0
	Field index:Int = 0
	Field expectedModCount:Int = 0
Public
	Method New(lst:AbstractList<E>)
		Self.lst = lst
		expectedModCount = lst.modCount
	End

	Method CheckConcurrency:Void()
		AssertEquals(lst.modCount, expectedModCount, "ListEnumerator.CheckConcurrency: Concurrent list modification")
	End

	Method HasNext:Bool()
		CheckConcurrency()
		Return index < lst.Size
	End

	Method HasPrevious:Bool()
		CheckConcurrency()
		Return index > 0
	End

	Method NextObject:E()
		CheckConcurrency()
		lastIndex = index
		index += 1
		Return lst.Get(lastIndex)
	End

	Method PreviousObject:E()
		CheckConcurrency()
		index -= 1
		lastIndex = index
		Return lst.Get(lastIndex)
	End

	Method Remove:Void()
		CheckConcurrency()
		lst.RemoveAt(lastIndex)
		If lastIndex < index Then index -= 1
		lastIndex = -1
		expectedModCount = lst.modCount
	End

	Method First:Void()
		CheckConcurrency()
		index = 0
	End

	Method Last:Void()
		CheckConcurrency()
		index = lst.Size
	End
End

Class IntListEnumerator Extends ListEnumerator<IntObject>
Public
	Method New(lst:AbstractList<IntObject>)
		Super.New(lst)
	End

	Method NextInt:Int()
		CheckConcurrency()
		lastIndex = index
		index += 1
		Return lst.Get(lastIndex).value
	End

	Method PreviousInt:Int()
		CheckConcurrency()
		index -= 1
		lastIndex = index
		Return lst.Get(lastIndex).value
	End
End

Class FloatListEnumerator Extends ListEnumerator<FloatObject>
Public
	Method New(lst:AbstractList<FloatObject>)
		Super.New(lst)
	End

	Method NextFloat:Float()
		CheckConcurrency()
		lastIndex = index
		index += 1
		Return lst.Get(lastIndex).value
	End

	Method PreviousFloat:Float()
		CheckConcurrency()
		index -= 1
		lastIndex = index
		Return lst.Get(lastIndex).value
	End
End

Class StringListEnumerator Extends ListEnumerator<StringObject>
Public
	Method New(lst:AbstractList<IntObject>)
		Super.New(lst)
	End

	Method NextString:String()
		CheckConcurrency()
		lastIndex = index
		index += 1
		Return lst.Get(lastIndex).value
	End

	Method PreviousString:String()
		CheckConcurrency()
		index -= 1
		lastIndex = index
		Return lst.Get(lastIndex).value
	End
End

#Rem
	ArrayList
	Concrete implementation of AbstractList that uses a dynamically sized array to store elements.
	Has best performance when it is initialised with a capacity large enough to hold the expected number of elements.
#End
Class ArrayList<E> Extends AbstractList<E>
Private
	' fields
	Field elements:Object[]
	Field size:Int = 0

	' resizes the elements array if necessary to ensure it can fit minCapacity elements
	Method EnsureCapacity:Void(minCapacity:Int)
		Local oldCapacity:Int = elements.Length
		If minCapacity > oldCapacity Then
			Local newCapacity:Int = (oldCapacity * 3) / 2 + 1
			If newCapacity < minCapacity Then newCapacity = minCapacity
			elements = elements.Resize(newCapacity)
			modCount += 1
		End
	End

	Method RangeCheck:Void(index:Int)
		AssertRange(index, 0, size, "ArrayList.RangeCheck: Index out of bounds: Index: " + index + ", Size: " + size)
	End

	Field tempArr:Object[] = New Object[128] ' temp array used for internal call to ToArray (so we don't create an object)

Public
	' constructors
	Method New()
		Self.elements = New Object[10]
	End

	Method New(initialCapacity:Int)
		AssertGreaterThanOrEqual(initialCapacity, 0, "ArrayList.New: Illegal Capacity: "+initialCapacity)
		Self.elements = New Object[initialCapacity]
	End

	Method New(c:AbstractCollection<E>)
		elements = c.ToArray()
		size = elements.Length
	End

	'implements AbstractCollection

	Method Add:Bool(o:E)
		If size+1 > elements.Length Then EnsureCapacity(size+1)
		elements[size] = o
		size+=1
		modCount += 1
		Return True
	End

	Method AddAll:Bool(c:AbstractCollection<E>)
		If c.IsEmpty() Then Return False
		Local newItemCount:Int = c.Size
		If size + newItemCount > elements.Length Then EnsureCapacity(size+newItemCount)
		If tempArr.Length < newItemCount Then tempArr = tempArr.Resize(newItemCount)
		Local len:Int = c.FillArray(tempArr)
		For Local i:Int = 0 Until len
			elements[size] = tempArr[i]
			size+=1
		End
		modCount += 1
		Return newItemCount <> 0
	End

	Method Clear:Void()
		For Local i:Int = 0 Until size
			elements[i] = Null
		Next
		modCount += 1
		size = 0
	End

	Method Contains:Bool(o:E)
		For Local i:Int = 0 Until size
			If elements[i] = o Then Return True
		Next
		Return False
	End

	Method ContainsAll:Bool(c:AbstractCollection<E>)
		If c.IsEmpty() Then Return True
		If tempArr.Length < c.Size Then tempArr = tempArr.Resize(c.Size)
		Local len:Int = c.FillArray(tempArr)
		For Local i:Int = 0 Until len
			If Not Self.Contains(E(tempArr[i])) Then Return False
		Next
		Return True
	End

	Method FillArray:Int(arr:Object[])
		AssertGreaterThanOrEqual(arr.Length, size, "ArrayList.FillArray: Array too small")
		For Local i:Int = 0 Until size
			arr[i] = elements[i]
		Next
		Return size
	End

	Method IsEmpty:Bool()
		Return size = 0
	End

	Method Remove:Bool(o:E)
		For Local i:Int = 0 Until size
			If elements[i] = o Then
				RemoveAt(i)
				modCount += 1
				Return True
			End
		Next
		Return False
	End

	Method RemoveAll:Bool(c:AbstractCollection<E>)
		If c.IsEmpty() Then Return False
		Local modified:Bool = False
		If tempArr.Length < c.Size Then tempArr = tempArr.Resize(c.Size)
		Local len:Int = c.FillArray(tempArr)
		For Local i:Int = 0 Until len
			If Self.Contains(E(tempArr[i])) Then
				Self.Remove(E(tempArr[i]))
			End
		Next
		If modified Then modCount += 1
		Return modified
	End

	Method RetainAll:Bool(c:AbstractCollection<E>)
		Local modified:Bool = False
		If tempArr.Length < c.Size Then tempArr = tempArr.Resize(c.Size)
		Local len:Int = c.FillArray(tempArr)
		For Local i:Int = 0 Until len
			If Not Self.Contains(E(tempArr[i])) Then
				Self.Remove(E(tempArr[i]))
				modified = True
			End
		Next
		If modified Then modCount += 1
		Return modified
	End

	Method Size:Int() Property
		Return size
	End

	Method Sort:Void(reverse:Bool = False, comp:AbstractComparator = Null)
		If size <= 1 Then Return ' can't sort 0 or 1 elements
		If comp = Null Then comp = Self.Comparator
		If comp = Null Then comp = DEFAULT_COMPARATOR
		QuickSort(elements, 0, size-1, comp, reverse)
		modCount += 1
	End

	Method ToArray:Object[]()
		Local arr:Object[] = New Object[size]
		For Local i:Int = 0 Until size
			arr[i] = elements[i]
		Next
		Return arr
	End

	' implements AbstractList

	Method Get:E(index:Int)
		If rangeChecking Then RangeCheck(index)
		Return E(elements[index])
	End

	Method Insert:Void(index:Int, o:E)
		If rangeChecking Then RangeCheck(index)
		If size+1 > elements.Length Then EnsureCapacity(size+1)
		For Local i:Int = size Until index Step -1
			elements[i] = elements[i-1]
		Next
		elements[index] = o
		size+=1
		modCount += 1
	End

	Method InsertAll:Bool(index:Int, c:AbstractCollection<E>)
		Local newItemCount:Int = c.Size
		If newItemCount = 0 Then Return False
		If size + newItemCount > elements.Length Then EnsureCapacity(size+newItemCount)
		If tempArr.Length < newItemCount Then tempArr = tempArr.Resize(newItemCount)
		Local len:Int = c.FillArray(tempArr)
		For Local i:Int = size - 1 To index Step -1
			elements[i+newItemCount] = elements[i]
		Next
		For Local i:Int = 0 Until newItemCount
			elements[index+i] = tempArr[i]
		End
		size += newItemCount
		modCount += 1
		Return newItemCount <> 0
	End

	Method IndexOf:Int(o:E)
		For Local i:Int = 0 Until size
			If elements[i] = o Then Return i
		Next
		Return -1
	End

	Method LastIndexOf:Int(o:E)
		For Local i:Int = size-1 To 0 Step -1
			If elements[i] = o Then Return i
		Next
		Return -1
	End

	Method RemoveAt:E(index:Int)
		If rangeChecking Then RangeCheck(index)
		Local oldValue:E = E(elements[index])
		For Local i:Int = index Until size-1
			elements[i] = elements[i+1]
		Next
		elements[size-1] = Null
		size-=1
		modCount += 1
		Return oldValue
	End

	Method RemoveRange:Void(fromIndex:Int, toIndex:Int)
		AssertLessThanOrEqual(fromIndex, toIndex, "ArrayList.RemoveRange: fromIndex > toIndex")
		If rangeChecking Then
			RangeCheck(fromIndex)
			RangeCheck(toIndex)
		End
		For Local i:Int = 0 Until toIndex - fromIndex
			RemoveAt(fromIndex)
		Next
	End

	Method Set:E(index:Int, o:E)
		If rangeChecking Then RangeCheck(index)
		Local oldValue:E = E(elements[index])
		elements[index] = o
		modCount += 1
		Return oldValue
	End
End

' ArrayList wrapper classes

Class IntArrayList Extends ArrayList<IntObject>
Public
	Method AddInt:Bool(o:Int)
		If size+1 > elements.Length Then EnsureCapacity(size+1)
		elements[size] = New IntObject(o)
		size+=1
		modCount += 1
		Return True
	End

	Method ContainsInt:Bool(o:Int)
		For Local i:Int = 0 Until size
			If IntObject(elements[i]).value = o Then Return True
		Next
		Return False
	End

	Method Enumerator:AbstractEnumerator<IntObject>()
		Return New IntArrayListEnumerator(Self)
	End

	Method RemoveInt:Bool(o:Int)
		For Local i:Int = 0 Until size
			If IntObject(elements[i]).value = o Then
				RemoveAt(i)
				modCount += 1
				Return True
			End
		Next
		Return False
	End

	Method ToIntArray:Int[]()
		Local arr:Int[] = New Int[size]
		For Local i:Int = 0 Until size
			arr[i] = IntObject(elements[i]).value
		Next
		Return arr
	End

	Method GetInt:Int(index:Int)
		If rangeChecking Then RangeCheck(index)
		Return IntObject(elements[index]).value
	End

	Method InsertInt:Void(index:Int, o:Int)
		If rangeChecking Then RangeCheck(index)
		If size+1 > elements.Length Then EnsureCapacity(size+1)
		For Local i:Int = size Until index Step -1
			elements[i] = elements[i-1]
		Next
		elements[index] = New IntObject(o)
		size+=1
		modCount += 1
	End

	Method IndexOfInt:Int(o:Int)
		For Local i:Int = 0 Until size
			If IntObject(elements[i]).value = o Then Return i
		Next
		Return -1
	End

	Method LastIndexOfInt:Int(o:E)
		For Local i:Int = size-1 To 0 Step -1
			If IntObject(elements[i]).value = o Then Return i
		Next
		Return -1
	End

	Method SetInt:Int(index:Int, o:Int)
		If rangeChecking Then RangeCheck(index)
		Local oldValue:Int = IntObject(elements[index]).value
		If elements[index] <> Null Then
			IntObject(elements[index]).value = o ' XXX: not sure whether we can change value on the fly
		End
		modCount += 1
		Return oldValue
	End

	Method FillIntArray:Int(arr:Int[])
		AssertLessThan(arr.Length, size, "IntArrayList.FillIntArray: Array too small")
		For Local i:Int = 0 Until size
			arr[i] = IntObject(elements[i]).value
		Next
		Return size
	End
End

Class FloatArrayList Extends ArrayList<FloatObject>
Public
	Method AddFloat:Bool(o:Float)
		If size + 1 > elements.Length Then EnsureCapacity(size + 1)
		elements[size] = New FloatObject(o)
		size += 1
		modCount += 1
		Return True
	End

	Method ContainsFloat:Bool(o:Float)
		For Local i:Int = 0 Until size
			If FloatObject(elements[i]).value = o Then Return True
		Next
		Return False
	End

	Method Enumerator:AbstractEnumerator<FloatObject>()
		Return New FloatArrayListEnumerator(Self)
	End

	Method RemoveFloat:Bool(o:Float)
		For Local i:Int = 0 Until size
			If FloatObject(elements[i]).value = o Then
				RemoveAt(i)
				modCount += 1
				Return True
			End
		Next
		Return False
	End

	Method ToFloatArray:Float[]()
		Local arr:Float[] = New Float[size]
		For Local i:Int = 0 Until size
			arr[i] = FloatObject(elements[i]).value
		Next
		Return arr
	End

	Method GetFloat:Float(index:Int)
		If rangeChecking Then RangeCheck(index)
		Return FloatObject(elements[index]).value
	End

	Method InsertFloat:Void(index:Int, o:Float)
		If rangeChecking Then RangeCheck(index)
		If size+1 > elements.Length Then EnsureCapacity(size+1)
		For Local i:Int = size Until index Step -1
			elements[i] = elements[i-1]
		Next
		elements[index] = New FloatObject(o)
		size+=1
		modCount += 1
	End

	Method IndexOfFloat:Int(o:Float)
		For Local i:Int = 0 Until size
			If FloatObject(elements[i]).value = o Then Return i
		Next
		Return -1
	End

	Method LastIndexOfFloat:Int(o:Float)
		For Local i:Int = size-1 To 0 Step -1
			If FloatObject(elements[i]).value = o Then Return i
		Next
		Return -1
	End

	Method SetFloat:Float(index:Int, o:Float)
		If rangeChecking Then RangeCheck(index)
		Local oldValue:Float = FloatObject(elements[index]).value
		If elements[index] <> Null Then
			FloatObject(elements[index]).value = o ' XXX: not sure whether we can change value on the fly
		End
		modCount += 1
		Return oldValue
	End

	Method FillFloatArray:Int(arr:Float[])
		AssertLessThan(arr.Length, size, "FloatArrayList.FillFloatArray: Array too small")
		For Local i:Int = 0 Until size
			arr[i] = FloatObject(elements[i]).value
		Next
		Return size
	End
End

Class StringArrayList Extends ArrayList<StringObject>
Public
	Method AddString:Bool(o:String)
		If size+1 > elements.Length Then EnsureCapacity(size+1)
		elements[size] = New StringObject(o)
		size+=1
				modCount += 1
				Return True
			End

	Method ContainsString:Bool(o:String)
		For Local i:Int = 0 Until size
			If StringObject(elements[i]).value = o Then Return True
		Next
		Return False
	End

	Method Enumerator:AbstractEnumerator<StringObject>()
		Return New StringArrayListEnumerator(Self)
	End

	Method RemoveString:Bool(o:String)
		For Local i:Int = 0 Until size
			If StringObject(elements[i]).value = o Then
				RemoveAt(i)
				modCount += 1
				Return True
			End
		Next
		Return False
	End

	Method ToStringArray:String[]()
		Local arr:String[] = New String[size]
		For Local i:Int = 0 Until size
			arr[i] = StringObject(elements[i]).value
		Next
		Return arr
	End

	Method GetString:String(index:Int)
		If rangeChecking Then RangeCheck(index)
		Return StringObject(elements[index]).value
	End

	Method InsertString:Void(index:Int, o:String)
		If rangeChecking Then RangeCheck(index)
		If size+1 > elements.Length Then EnsureCapacity(size+1)
		For Local i:Int = size Until index Step -1
			elements[i] = elements[i-1]
		Next
		elements[index] = New StringObject(o)
		size+=1
		modCount += 1
	End

	Method IndexOfString:Int(o:String)
		For Local i:Int = 0 Until size
			If StringObject(elements[i]).value = o Then Return i
		Next
		Return -1
	End

	Method LastIndexOfString:Int(o:String)
		For Local i:Int = size-1 To 0 Step -1
			If StringObject(elements[i]).value = o Then Return i
		Next
		Return -1
	End

	Method SetString:String(index:Int, o:String)
		If rangeChecking Then RangeCheck(index)
		Local oldValue:String = StringObject(elements[index]).value
		If elements[index] <> Null Then
			StringObject(elements[index]).value = o ' XXX: not sure whether we can change value on the fly
		End
		modCount += 1
		Return oldValue
	End

	Method FillStringArray:Int(arr:String[])
		AssertLessThan(arr.Length, size, "StringArrayList.FillStringArray: Array too small")
		For Local i:Int = 0 Until size
			If StringObject(elements[i]).value = value Then
				Remove(elements[i])
				modCount += 1
				Return True
			End
		Next
		Return False
	End
End

Function QuickSort:Void(arr:Object[], left:Int, right:Int, comp:AbstractComparator, reverse:Bool = False)
	If right > left Then
		Local pivotIndex:Int = left + (right-left)/2
		Local pivotNewIndex:Int = QuickSortPartition(arr, left, right, pivotIndex, comp, reverse)
		QuickSort(arr, left, pivotNewIndex - 1, comp, reverse)
		QuickSort(arr, pivotNewIndex + 1, right, comp, reverse)
	End
End

Function QuickSortPartition:Int(arr:Object[], left:Int, right:Int, pivotIndex:Int, comp:AbstractComparator, reverse:Bool = False)
	Local pivotValue:Object = arr[pivotIndex]
	arr[pivotIndex] = arr[right]
	arr[right] = pivotValue
	Local storeIndex:Int = left, val:Object
	For Local i:Int = left Until right
		If Not reverse And comp.Compare(arr[i], pivotValue) <= 0 Or reverse And comp.Compare(arr[i], pivotValue) >= 0 Then
			val = arr[i]
			arr[i] = arr[storeIndex]
			arr[storeIndex] = val
			storeIndex += 1
		End
	Next
	val = arr[storeIndex]
	arr[storeIndex] = arr[right]
	arr[right] = val
	Return storeIndex
End

Strict

#Rem
Monkey Collections Framework
Based loosely on the Java Collections Framework
#End

#Rem
	AbstractCollection
	Due to limitations with generics in Monkey, ToArray cannot work with an array of type E.  We must return Object[] instead.
#End
Class AbstractCollection<E> Abstract
Private
	' A custom comparator to use for sorting and comparing items.
	Field comparator:AbstractComparator = Null

Public
	' Property to read comparator
	Method Comparator:AbstractComparator() Property
		Return comparator
	End
	' Property to write comparator
	Method Comparator:Void(comparator:AbstractComparator) Property
		Self.comparator = comparator
	End

	Method Add:Bool(o:E) Abstract
	Method AddAll:Bool(c:AbstractCollection<E>) Abstract
	Method Clear:Void() Abstract
	Method Contains:Bool(o:E) Abstract
	Method ContainsAll:Bool(c:AbstractCollection<E>) Abstract
	'Method Equals:Bool(c:AbstractCollection<E>) Abstract
	Method FillArray:Int(arr:Object[]) Abstract ' populates the passed array and returns the number of items filled. best used for android
	Method IsEmpty:Bool() Abstract
	Method ObjectEnumerator:AbstractEnumerator<E>() Abstract
	Method Remove:Bool(o:E) Abstract
	Method RemoveAll:Bool(c:AbstractCollection<E>) Abstract
	Method RetainAll:Bool(c:AbstractCollection<E>) Abstract
	Method Size:Int() Property Abstract
	Method Sort:Void(reverse:Bool = False, comp:AbstractComparator = Null) Abstract
	Method ToArray:Object[]() Abstract ' creates a new array of the correct size and returns it
End

#Rem
	AbstractEnumerator
	Used in the ObjectEnumerator method for calls to EachIn.
	If retrieved and used manually, the HasPrevious/PreviousObject/Remove/First/Last methods can be called.
#End
Class AbstractEnumerator<E>
Public
	Method HasNext:Bool() Abstract
	Method HasPrevious:Bool() Abstract
	Method NextObject:E() Abstract
	Method PreviousObject:E() Abstract
	Method Remove:Void() Abstract
	Method First:Void() Abstract
	Method Last:Void() Abstract
End

#Rem
	AbstractComparator
	This is a way For developers to provide a custom comparison method for sorting lists.
	It's sort of like a function pointer.
#End
Class AbstractComparator Abstract
	Method Compare:Int(o1:Object, o2:Object) Abstract

	Method CompareBool:Bool(o1:Object, o2:Object)
		Return o1 = o2 Or Compare(o1, o2) = 0
	End

	Method HashCode:Int(o:Object)
		Return 0
	End
End

Global DEFAULT_COMPARATOR:DefaultComparator = New DefaultComparator
Class DefaultComparator Extends AbstractComparator
	Method Compare:Int(o1:Object, o2:Object)
		If IntObject(o1) <> Null And IntObject(o2) <> Null Then
			If IntObject(o1).value < IntObject(o2).value Then Return -1
			If IntObject(o1).value > IntObject(o2).value Then Return 1
			Return 0
		ElseIf FloatObject(o1) <> Null And FloatObject(o2) <> Null Then
			If FloatObject(o1).value < FloatObject(o2).value Then Return -1
			If FloatObject(o1).value > FloatObject(o2).value Then Return 1
			Return 0
		ElseIf StringObject(o1) <> Null And StringObject(o2) <> Null Then
			' TODO: string comparison
			'If StringObject(o1).value < StringObject(o2).value Then Return -1
			'If StringObject(o1).value > StringObject(o2).value Then Return 1
			Return 0
		End
		If o1 = o2 Then Return 0
		Return 1 ' don't know what to do!
	End

	Method CompareBool:Bool(o1:Object, o2:Object)
		If IntObject(o1) <> Null And IntObject(o2) <> Null Then
			Return IntObject(o1).value = IntObject(o2).value
		ElseIf FloatObject(o1) <> Null And FloatObject(o2) <> Null Then
			Return FloatObject(o1).value = FloatObject(o2).value
		ElseIf StringObject(o1) <> Null And StringObject(o2) <> Null Then
			Return StringObject(o1).value = StringObject(o2).value
		End
		Return o1 = o2
	End
End

#rem
	AbstractList
	Extends AbstractCollection to implement some of the abstract methods that apply to any kind of list.
#End
Class AbstractList<E> Extends AbstractCollection<E> Abstract
Private
	' If true, bounds checking should be performed.
	Field rangeChecking:Bool = True

	' A counter for modifications to the list.  Used for concurrency checks.
	Field modCount:Int = 0

	' Performs a range check.
	Method RangeCheck:Void(index:Int)
		Local size:Int = Self.Size()
		If index < 0 Or index >= size Then Error("Index out of bounds: Index: " + index + ", Size: " + size)
	End

Public
	' Property to read rangeChecking
	Method RangeChecking:Bool() Property
		Return rangeChecking
	End
	' Property to write rangeChecking
	Method RangeChecking:Void(rangeChecking:Bool) Property
		Self.rangeChecking = rangeChecking
	End

	Method ObjectEnumerator:AbstractEnumerator<E>()
		Return New ListEnumerator<E>(Self)
	End

	Method Get:E(index:Int) Abstract
	Method Insert:Void(index:Int, o:E) Abstract
	Method InsertAll:Bool(index:Int, c:AbstractCollection<E>) Abstract
	Method IndexOf:Int(o:E) Abstract
	Method LastIndexOf:Int(o:E) Abstract
	Method RemoveAt:E(index:Int) Abstract ' Can't overload Remove since it's an inherited method, and Monkey doesn't support that.
	Method RemoveRange:Void(fromIndex:Int, toIndex:Int) Abstract
	Method Set:E(index:Int, o:E) Abstract
End

#Rem
	ListEnumerator
	Extends AbstractEnumerator to provide support for EachIn.  Blocks concurrent modification, but allows elements to be removed on the fly.
#End
Class ListEnumerator<E> Extends AbstractEnumerator<E>
Private
	Field lst:AbstractList<E>
	Field lastIndex:Int = 0
	Field index:Int = 0
	Field expectedModCount:Int = 0
Public
	Method New(lst:AbstractList<E>)
		Self.lst = lst
		expectedModCount = lst.modCount
	End

	Method HasNext:Bool()
		If lst.modCount <> expectedModCount Then Error("Concurrent list modification")
		Return index < lst.Size
	End

	Method HasPrevious:Bool()
		If lst.modCount <> expectedModCount Then Error("Concurrent list modification")
		Return index > 0
	End

	Method NextObject:E()
		If lst.modCount <> expectedModCount Then Error("Concurrent list modification")
		lastIndex = index
		index += 1
		Return lst.Get(lastIndex)
	End

	Method PreviousObject:E()
		If lst.modCount <> expectedModCount Then Error("Concurrent list modification")
		index -= 1
		lastIndex = index
		Return lst.Get(lastIndex)
	End

	Method Remove:Void()
		If lst.modCount <> expectedModCount Then Error("Concurrent list modification")
		lst.RemoveAt(lastIndex)
		If lastIndex < index Then index -= 1
		lastIndex = -1
		expectedModCount = lst.modCount
	End

	Method First:Void()
		If lst.modCount <> expectedModCount Then Error("Concurrent list modification")
		index = 0
	End

	Method Last:Void()
		If lst.modCount <> expectedModCount Then Error("Concurrent list modification")
		index = lst.Size
	End
End

#rem
	ArrayList
	Concrete implementation of AbstractList that uses a dynamically sized array to store elements.
	Has best performance when it is initialised with a capacity large enough to hold the expected number of elements.
#End
Class ArrayList<E> Extends AbstractList<E>
Private
	' fields
	Field elements:Object[]
	Field size:Int = 0

	' resizes the elements array if necessary to ensure it can fit minCapacity elements
	Method EnsureCapacity:Void(minCapacity:Int)
		Local oldCapacity:Int = elements.Length
		If minCapacity > oldCapacity Then
			Local newCapacity:Int = (oldCapacity * 3) / 2 + 1
			If newCapacity < minCapacity Then newCapacity = minCapacity
			elements = elements.Resize(newCapacity)
			modCount += 1
		End
	End

	Method RangeCheck:Void(index:Int)
		If index >= size Or index < 0 Then Error("Index out of bounds: Index: "+index+", Size: "+size)
	End

	Field tempArr:Object[] = New Object[128] ' temp array used for internal call to ToArray (so we don't create an object)

Public
	' constructors
	Method New()
		Self.elements = New Object[10]
	End

	Method New(initialCapacity:Int)
		If initialCapacity < 0 Then Error("Illegal Capacity: "+initialCapacity)
		Self.elements = New Object[initialCapacity]
	End

	Method New(c:AbstractCollection<E>)
		elements = c.ToArray()
		size = elements.Length
	End

	'implements AbstractCollection

	Method Add:Bool(o:E)
		If size+1 > elements.Length Then EnsureCapacity(size+1)
		elements[size] = o
		size+=1
		modCount += 1
		Return True
	End

	Method AddAll:Bool(c:AbstractCollection<E>)
		Local newItemCount:Int = c.Size
		If size + newItemCount > elements.Length Then EnsureCapacity(size+newItemCount)
		If tempArr.Length < newItemCount Then tempArr = tempArr.Resize(newItemCount)
		Local len:Int = c.FillArray(tempArr)
		For Local i:Int = 0 Until len
			elements[size] = tempArr[i]
			size+=1
		End
		modCount += 1
		Return newItemCount <> 0
	End

	Method Clear:Void()
		For Local i:Int = 0 Until size
			elements[i] = Null
		Next
		modCount += 1
		size = 0
	End

	Method Contains:Bool(o:E)
		For Local i:Int = 0 Until size
			If elements[i] = o Then Return True
		Next
		Return False
	End

	Method ContainsAll:Bool(c:AbstractCollection<E>)
		If tempArr.Length < c.Size Then tempArr = tempArr.Resize(c.Size)
		Local len:Int = c.FillArray(tempArr)
		For Local i:Int = 0 Until len
			If Not Self.Contains(E(tempArr[i])) Then Return False
		Next
		Return True
	End

	'Method Equals:Bool(c:AbstractCollection<E>)
	'	Return c.Size = size And Self.ContainsAll(c)
	'End

	Method FillArray:Int(arr:Object[])
		If arr.Length < size Then Error("Array too small")
		For Local i:Int = 0 Until size
			arr[i] = elements[i]
		Next
		Return size
	End

	Method IsEmpty:Bool()
		Return size = 0
	End

	Method ObjectEnumerator:AbstractEnumerator<E>()
		Return New ArrayListEnumerator<E>(Self)
	End

	Method Remove:Bool(o:E)
		For Local i:Int = 0 Until size
			If elements[i] = o Then
				RemoveAt(i)
				modCount += 1
				Return True
			End
		Next
		Return False
	End

	Method RemoveAll:Bool(c:AbstractCollection<E>)
		Local modified:Bool = False
		If tempArr.Length < c.Size Then tempArr = tempArr.Resize(c.Size)
		Local len:Int = c.FillArray(tempArr)
		For Local i:Int = 0 Until len
			If Self.Contains(E(tempArr[i])) Then
				Self.Remove(E(tempArr[i]))
			End
		Next
		If modified Then modCount += 1
		Return modified
	End

	Method RetainAll:Bool(c:AbstractCollection<E>)
		Local modified:Bool = False
		If tempArr.Length < c.Size Then tempArr = tempArr.Resize(c.Size)
		Local len:Int = c.FillArray(tempArr)
		For Local i:Int = 0 Until len
			If Not Self.Contains(E(tempArr[i])) Then
				Self.Remove(E(tempArr[i]))
				modified = True
			End
		Next
		If modified Then modCount += 1
		Return modified
	End

	Method Size:Int() Property
		Return size
	End

	Method Sort:Void(reverse:Bool = False, comp:AbstractComparator = Null)
		If comp = Null Then comp = Self.Comparator
		If comp = Null Then comp = DEFAULT_COMPARATOR
		QuickSort(elements, 0, size-1, comp, reverse)
	End

	Method ToArray:Object[]()
		Local arr:Object[] = New Object[size]
		For Local i:Int = 0 Until size
			arr[i] = elements[i]
		Next
		Return arr
	End

	' implements AbstractList

	Method Get:E(index:Int)
		If rangeChecking Then RangeCheck(index)
		Return E(elements[index])
	End

	Method Insert:Void(index:Int, o:E)
		If rangeChecking Then RangeCheck(index)
		If size+1 > elements.Length Then EnsureCapacity(size+1)
		For Local i:Int = size Until index Step -1
			elements[i] = elements[i-1]
		Next
		elements[index] = o
		size+=1
		modCount += 1
	End

	Method InsertAll:Bool(index:Int, c:AbstractCollection<E>)
		Local newItemCount:Int = c.Size
		If size + newItemCount > elements.Length Then EnsureCapacity(size+newItemCount)
		If tempArr.Length < newItemCount Then tempArr = tempArr.Resize(newItemCount)
		Local len:Int = c.FillArray(tempArr)
		For Local i:Int = size - 1 To index Step -1
			elements[i+newItemCount] = elements[i]
		Next
		For Local i:Int = 0 Until newItemCount
			elements[index+i] = tempArr[i]
		End
		size += newItemCount
		modCount += 1
		Return newItemCount <> 0
	End

	Method IndexOf:Int(o:E)
		For Local i:Int = 0 Until size
			If elements[i] = o Then Return i
		Next
		Return -1
	End

	Method LastIndexOf:Int(o:E)
		For Local i:Int = size-1 To 0 Step -1
			If elements[i] = o Then Return i
		Next
		Return -1
	End

	Method RemoveAt:E(index:Int)
		If rangeChecking Then RangeCheck(index)
		Local oldValue:E = E(elements[index])
		For Local i:Int = index Until size-1
			elements[i] = elements[i+1]
		Next
		elements[size-1] = Null
		size-=1
		modCount += 1
		Return oldValue
	End

	Method RemoveRange:Void(fromIndex:Int, toIndex:Int)
		If fromIndex > toIndex Then Error("fromIndex > toIndex")
		If rangeChecking Then
			RangeCheck(fromIndex)
			RangeCheck(toIndex)
		End
		For Local i:Int = 0 Until toIndex - fromIndex
			RemoveAt(fromIndex)
		Next
	End

	Method Set:E(index:Int, o:E)
		If rangeChecking Then RangeCheck(index)
		Local oldValue:E = E(elements[index])
		elements[index] = o
		modCount += 1
		Return oldValue
	End
End

#Rem
	ListEnumerator
	Extends AbstractEnumerator to provide support for EachIn.  Blocks concurrent modification, but allows elements to be removed on the fly.
#End
Class ArrayListEnumerator<E> Extends AbstractEnumerator<E>
Private
	Field lst:ArrayList<E>
	Field lastIndex:Int = 0
	Field index:Int = 0
	Field expectedModCount:Int = 0
Public
	Method New(lst:ArrayList<E>)
		Self.lst = lst
		expectedModCount = lst.modCount
	End

	Method HasNext:Bool()
		If lst.modCount <> expectedModCount Then Error("Concurrent list modification")
		Return index < lst.size
	End

	Method HasPrevious:Bool()
		If lst.modCount <> expectedModCount Then Error("Concurrent list modification")
		Return index > 0
	End

	Method NextObject:E()
		If lst.modCount <> expectedModCount Then Error("Concurrent list modification")
		lastIndex = index
		index += 1
		Return lst.elements[lastIndex]
	End

	Method PreviousObject:E()
		If lst.modCount <> expectedModCount Then Error("Concurrent list modification")
		index -= 1
		lastIndex = index
		Return lst.elements[lastIndex]
	End

	Method Remove:Void()
		If lst.modCount <> expectedModCount Then Error("Concurrent list modification")
		lst.RemoveAt(lastIndex)
		If lastIndex < index Then index -= 1
		lastIndex = -1
		expectedModCount = lst.modCount
	End

	Method First:Void()
		If lst.modCount <> expectedModCount Then Error("Concurrent list modification")
		index = 0
	End

	Method Last:Void()
		If lst.modCount <> expectedModCount Then Error("Concurrent list modification")
		index = lst.size
	End
End

' TODO: complete wrapper classes
Class IntArrayList Extends ArrayList<IntObject>
Public
	Method ToIntArray:Int[]()
		Local arr:Int[] = New Int[size]
		For Local i:Int = 0 Until size
			arr[i] = IntObject(elements[i]).value
		Next
		Return arr
	End

	Method AddInt:Bool(val:Int)
		If size + 1 > elements.Length Then EnsureCapacity(size + 1)
		elements[size] = New IntObject(val)
		size += 1
		modCount += 1
		Return True
	End

	Method GetInt:Int(index:Int)
		If rangeChecking Then RangeCheck(index)
		Return IntObject(elements[index]).value
	End

	Method RemoveInt:Bool(value:Int)
		For Local i:Int = 0 Until size
			If IntObject(elements[i]).value = value Then
				Remove(elements[i])
				modCount += 1
				Return True
			End
		Next
		Return False
	End
End

Class FloatArrayList Extends ArrayList<FloatObject>
Public
	Method ToFloatArray:Float[]()
		Local arr:Float[] = New Float[size]
		For Local i:Float = 0 Until size
			arr[i] = FloatObject(elements[i]).value
		Next
		Return arr
	End

	Method AddFloat:Bool(val:Float)
		If size+1 > elements.Length Then EnsureCapacity(size+1)
		elements[size] = New FloatObject(val)
		size+=1
		modCount += 1
		Return True
	End

	Method GetFloat:Float(index:Int)
		If rangeChecking Then RangeCheck(index)
		Return FloatObject(elements[index]).value
	End

	Method RemoveFloat:Bool(value:Float)
		For Local i:Int = 0 Until size
			If FloatObject(elements[i]).value = value Then
				Remove(elements[i])
				modCount += 1
				Return True
			End
		Next
		Return False
	End
End

Class StringArrayList Extends ArrayList<StringObject>
Public
	Method ToStringArray:String[]()
		Local arr:String[] = New String[size]
		For Local i:Int = 0 Until size
			arr[i] = StringObject(elements[i]).value
		Next
		Return arr
	End

	Method AddString:Bool(val:String)
		If size+1 > elements.Length Then EnsureCapacity(size+1)
		elements[size] = New StringObject(val)
		size+=1
		modCount += 1
		Return True
	End

	Method GetString:String(index:Int)
		If rangeChecking Then RangeCheck(index)
		Return StringObject(elements[index]).value
	End

	Method RemoveString:Bool(value:String)
		If rangeChecking Then RangeCheck(index)
		For Local i:Int = 0 Until size
			If StringObject(elements[i]).value = value Then
				Remove(elements[i])
				modCount += 1
				Return True
			End
		Next
		Return False
	End
End

Function QuickSort:Void(arr:Object[], left:Int, right:Int, comp:AbstractComparator, reverse:Bool = False)
	If right > left Then
		Local pivotIndex:Int = left + (right-left)/2
		Local pivotNewIndex:Int = QuickSortPartition(arr, left, right, pivotIndex, comp, reverse)
		QuickSort(arr, left, pivotNewIndex - 1, comp, reverse)
		QuickSort(arr, pivotNewIndex + 1, right, comp, reverse)
	End
End

Function QuickSortPartition:Int(arr:Object[], left:Int, right:Int, pivotIndex:Int, comp:AbstractComparator, reverse:Bool = False)
	Local pivotValue:Object = arr[pivotIndex]
	arr[pivotIndex] = arr[right]
	arr[right] = pivotValue
	Local storeIndex:Int = left, val:Object
	For Local i:Int = left Until right
		If Not reverse And comp.Compare(arr[i], pivotValue) <= 0 Or reverse And comp.Compare(arr[i], pivotValue) >= 0 Then
			val = arr[i]
			arr[i] = arr[storeIndex]
			arr[storeIndex] = val
			storeIndex += 1
		End
	Next
	val = arr[storeIndex]
	arr[storeIndex] = arr[right]
	arr[right] = val
	Return storeIndex
End


catch and print exception in the testrunner

Strict

Private

Import bono
Import bono.testimport

Public

Function Main:Int()
    Local report:TestReportSimple = New TestReportSimple()
    report.verbose = False

    Local suite:TestSuite = New AppTestSuite()
    TestSuiteHelper.Autodiscover(suite)

    Try
        suite.Run(report)
    Catch ex:Exception
        Error ex
    End
    Return 0
End

Strict

Private

Import bono
Import bono.testimport

Public

Function Main:Int()
    Local report:TestReportSimple = New TestReportSimple()
    report.verbose = False

    Local suite:TestSuite = New AppTestSuite()
    TestSuiteHelper.Autodiscover(suite)
    suite.Run(report)
    Return 0
End