Added an 'ObjectEnumerator' method to 'SpecializedChainStream'.

#Rem
	THIS MODULE IS CURRENTLY EXPERIMENTAL; USE AT YOUR OWN RISK.
#End

Strict

Public

' Imports (Public):
Import brl.stream

' Imports (Private):
Private

Import byteorder

Import brl.databuffer

Public

' Classes:
Class ChainStream Extends SpecializedChainStream<Stream> Final
	' Constructor(s):
	Method New(BigEndian:Bool=Default_BigEndian, CloseRights:Bool=True, Link:Int=0)
		Super.New(BigEndian, CloseRights, Link)
	End

	Method New(Streams:Stream[], BigEndian:Bool=Default_BigEndian, CloseRights:Bool=True, Link:Int=0)
		Super.New(Streams, BigEndian, CloseRights, Link)
	End

	Method New(Streams:Stack<StreamType>, BigEndian:Bool=Default_BigEndian, CloseRights:Bool=True, Link:Int=0)
		Super.New(Streams, BigEndian, CloseRights, Link)
	End
End

Class SpecializedChainStream<StreamType> Extends Stream
	' Constant variable(s):

	' Defaults:

	' Booleans / Flags:
	Const Default_BigEndian:Bool = False

	' Constructor(s) (Public):
	Method New(BigEndian:Bool=Default_BigEndian, CloseRights:Bool=True, Link:Int=0)
		Construct(New Stack<StreamType>(), BigEndian, CloseRights, Link)
	End

	' All containers passed to these constructors should be assumed to be under this object's control:
	Method New(Streams:StreamType[], BigEndian:Bool=Default_BigEndian, CloseRights:Bool=True, Link:Int=0)
		Construct(New Stack<StreamType>(Streams), BigEndian, CloseRights, Link)
	End

	Method New(Streams:Stack<StreamType>, BigEndian:Bool=Default_BigEndian, CloseRights:Bool=True, Link:Int=0)
		Construct(Streams, BigEndian, CloseRights, Link)
	End

	' Constructor(s) (Protected):
	Protected

	Method Construct:Void(Streams:Stack<StreamType>, BigEndian:Bool=Default_BigEndian, CloseRights:Bool=True, Link:Int=0)
		Self.Chain = Streams
		Self.Link = Link

		Self.BigEndian = BigEndian
		Self.CanCloseStreams = CloseRights

		Return
	End

	Public

	' Destructor(s):
	Method Close:Void()
		If (CanCloseStreams) Then
			For Local Node:= Eachin Chain
				Node.Close()
			Next
		Endif

		' Empty the stream-chain.
		Chain.Clear()

		Return
	End

	' Methods (Public):
	Method ObjectEnumerator:stack.Enumerator<StreamType>()
		Return Chain.ObjectEnumerator()
	End

	' This is pretty bloated, but it works:
	Method Seek:Int(Position:Int)
		Local Target:Int = -1
		Local BackwardOffset:Int = 0

		For Local I:= FinalChainIndex To 0 Step -1
			Local CL:= ChainLength(I)

			If (Position >= CL) Then
				Target = I
				BackwardOffset = CL
			Else
				Chain.Get(I).Seek(0)
			Endif
		Next

		If (Target = -1) Then
			Return Self.Position
		Endif

		Link = Target
		CurrentLink.Seek(Position-BackwardOffset)

		Return Position
	End

	Method ReadShort:Int()
		If (BigEndian) Then
			Return NToHS(Super.ReadShort())
		Endif

		Return Super.ReadShort()
	End

	Method ReadInt:Int()
		If (BigEndian) Then
			Return NToHL(Super.ReadInt())
		Endif

		Return Super.ReadInt()
	End

	Method ReadFloat:Float()
		If (BigEndian) Then
			Return NToHF(Super.ReadInt())
		Endif

		Return Super.ReadFloat()
	End

	Method WriteShort:Void(Value:Int)
		If (BigEndian) Then
			Super.WriteShort(HToNS(Value))
		Else
			Super.WriteShort(Value)
		Endif

		Return
	End

	Method WriteInt:Void(Value:Int)
		If (BigEndian) Then
			Super.WriteInt(HToNL(Value))
		Else
			Super.WriteInt(Value)
		Endif

		Return
	End

	Method WriteFloat:Void(Value:Float)
		If (BigEndian) Then
			Super.WriteInt(HToNF(Value))
		Else
			Super.WriteFloat(Value)
		Endif

		Return
	End

	Method Read:Int(Buffer:DataBuffer, Offset:Int, Count:Int)
		Local BytesRead:= CurrentLink.Read(Buffer, Offset, Count)

		If (BytesRead < Count) Then
			If (OnFinalLink) Then
				Return 0 ' -1
			Endif

			Link += 1

			Return (BytesRead + Read(Buffer, Offset+BytesRead, (Count-BytesRead)))
		Endif

		Return BytesRead
	End

	Method Write:Int(Buffer:DataBuffer, Offset:Int, Count:Int)
		Local BytesWritten:= CurrentLink.Write(Buffer, Offset, Count)

		If (BytesWritten < Count) Then
			If (OnFinalLink) Then
				Return 0 ' -1
			Endif

			Link += 1

			Return (BytesWritten + Write(Buffer, Offset+BytesWritten, (Count-BytesWritten)))
		Endif

		Return BytesWritten
	End

	' Methods (Protected):
	Protected

	' The length of the chain as-of the link specified.
	Method ChainLength:Int(LinkPosition:Int)
		If (LinkPosition < 0 Or LinkPosition > LinkCount) Then
			Return 0
		Endif

		Local P:Int = 0

		For Local I:= 0 Until LinkPosition
			' Add the length of the previous link in the chain.
			P += Chain.Get(I).Length
		Next

		Return (P + Chain.Get(LinkPosition).Position)
	End

	Public

	' Properties (Public):

	' This specifies if we are at the end of the stream.
	' This uses the final chain as a reference, as normal operations
	' will automatically move through each link in the chain.
	Method Eof:Int() Property
		'Return CurrentLink.Eof
		Return FinalLink.Eof
	End

	' The overall length of this stream. (Bytes contained)
	Method Length:Int() Property
		Local L:Int = 0

		For Local Node:= Eachin Chain
			L += Node.Length
		Next

		Return L
	End

	' The current "virtual" position in the stream;
	' functions like a normal stream, use as you would normally.
	Method Position:Int() Property
		' Add the previous links' lengths to the current link's position.
		Return (ChainLength(Link))
	End

	' A public "accessor" for the internal "chain".
	' Mutation may result in undefined behavior; use at your own risk.
	Method Links:Stack<StreamType>() Property
		Return Self.Chain
	End

	' The number of links in the internal "chain".
	Method LinkCount:Int() Property
		Return Chain.Length
	End

	' This specifies if we are currently
	' using the final link in the chain.
	Method OnFinalLink:Bool() Property
		Return (Link = FinalChainIndex)
	End

	' Properties (Protected):
	Protected

	' The final chain-index (Link) in the chain.
	Method FinalChainIndex:Int() Property
		Return Max(LinkCount-1, 0)
	End

	' The current stream-link in the chain. (Use at your own risk)
	Method CurrentLink:StreamType() Property
		Return Chain.Get(Link)
	End

	' The final stream in the "chain". (Use at your own risk)
	Method FinalLink:StreamType() Property
		Return Chain.Get(FinalChainIndex)
	End

	Public

	' Fields (Protected):
	Protected

	' A "chain" of streams representing
	' the data this stream delegates.
	Field Chain:Stack<StreamType>

	' The active element of 'Chain';
	' the stream currently used for I/O.
	Field Link:Int

	' Booleans / Flags:

	' This specifies if big-endian storage should be used/expected.
	Field BigEndian:Bool

	' This specifies if this object has the right to
	' close the elements of the internal "chain".
	Field CanCloseStreams:Bool = True

	Public
End

#Rem
	THIS MODULE IS CURRENTLY EXPERIMENTAL; USE AT YOUR OWN RISK.
#End

Strict

Public

' Imports (Public):
Import brl.stream

' Imports (Private):
Private

Import byteorder

Import brl.databuffer

Public

' Classes:
Class ChainStream Extends SpecializedChainStream<Stream> Final
	' Constructor(s):
	Method New(BigEndian:Bool=Default_BigEndian, CloseRights:Bool=True, Link:Int=0)
		Super.New(BigEndian, CloseRights, Link)
	End

	Method New(Streams:Stream[], BigEndian:Bool=Default_BigEndian, CloseRights:Bool=True, Link:Int=0)
		Super.New(Streams, BigEndian, CloseRights, Link)
	End

	Method New(Streams:Stack<StreamType>, BigEndian:Bool=Default_BigEndian, CloseRights:Bool=True, Link:Int=0)
		Super.New(Streams, BigEndian, CloseRights, Link)
	End
End

Class SpecializedChainStream<StreamType> Extends Stream
	' Constant variable(s):

	' Defaults:

	' Booleans / Flags:
	Const Default_BigEndian:Bool = False

	' Constructor(s) (Public):
	Method New(BigEndian:Bool=Default_BigEndian, CloseRights:Bool=True, Link:Int=0)
		Construct(New Stack<StreamType>(), BigEndian, CloseRights, Link)
	End

	' All containers passed to these constructors should be assumed to be under this object's control:
	Method New(Streams:StreamType[], BigEndian:Bool=Default_BigEndian, CloseRights:Bool=True, Link:Int=0)
		Construct(New Stack<StreamType>(Streams), BigEndian, CloseRights, Link)
	End

	Method New(Streams:Stack<StreamType>, BigEndian:Bool=Default_BigEndian, CloseRights:Bool=True, Link:Int=0)
		Construct(Streams, BigEndian, CloseRights, Link)
	End

	' Constructor(s) (Protected):
	Protected

	Method Construct:Void(Streams:Stack<StreamType>, BigEndian:Bool=Default_BigEndian, CloseRights:Bool=True, Link:Int=0)
		Self.Chain = Streams
		Self.Link = Link

		Self.BigEndian = BigEndian
		Self.CanCloseStreams = CloseRights

		Return
	End

	Public

	' Destructor(s):
	Method Close:Void()
		If (CanCloseStreams) Then
			For Local Node:= Eachin Chain
				Node.Close()
			Next
		Endif

		' Empty the stream-chain.
		Chain.Clear()

		Return
	End

	' Methods (Public):

	' This is pretty bloated, but it works:
	Method Seek:Int(Position:Int)
		Local Target:Int = -1
		Local BackwardOffset:Int = 0

		For Local I:= FinalChainIndex To 0 Step -1
			Local CL:= ChainLength(I)

			If (Position >= CL) Then
				Target = I
				BackwardOffset = CL
			Else
				Chain.Get(I).Seek(0)
			Endif
		Next

		If (Target = -1) Then
			Return Self.Position
		Endif

		Link = Target
		CurrentLink.Seek(Position-BackwardOffset)

		Return Position
	End

	Method ReadShort:Int()
		If (BigEndian) Then
			Return NToHS(Super.ReadShort())
		Endif

		Return Super.ReadShort()
	End

	Method ReadInt:Int()
		If (BigEndian) Then
			Return NToHL(Super.ReadInt())
		Endif

		Return Super.ReadInt()
	End

	Method ReadFloat:Float()
		If (BigEndian) Then
			Return NToHF(Super.ReadInt())
		Endif

		Return Super.ReadFloat()
	End

	Method WriteShort:Void(Value:Int)
		If (BigEndian) Then
			Super.WriteShort(HToNS(Value))
		Else
			Super.WriteShort(Value)
		Endif

		Return
	End

	Method WriteInt:Void(Value:Int)
		If (BigEndian) Then
			Super.WriteInt(HToNL(Value))
		Else
			Super.WriteInt(Value)
		Endif

		Return
	End

	Method WriteFloat:Void(Value:Float)
		If (BigEndian) Then
			Super.WriteInt(HToNF(Value))
		Else
			Super.WriteFloat(Value)
		Endif

		Return
	End

	Method Read:Int(Buffer:DataBuffer, Offset:Int, Count:Int)
		Local BytesRead:= CurrentLink.Read(Buffer, Offset, Count)

		If (BytesRead < Count) Then
			If (OnFinalLink) Then
				Return 0 ' -1
			Endif

			Link += 1

			Return (BytesRead + Read(Buffer, Offset+BytesRead, (Count-BytesRead)))
		Endif

		Return BytesRead
	End

	Method Write:Int(Buffer:DataBuffer, Offset:Int, Count:Int)
		Local BytesWritten:= CurrentLink.Write(Buffer, Offset, Count)

		If (BytesWritten < Count) Then
			If (OnFinalLink) Then
				Return 0 ' -1
			Endif

			Link += 1

			Return (BytesWritten + Write(Buffer, Offset+BytesWritten, (Count-BytesWritten)))
		Endif

		Return BytesWritten
	End

	' Methods (Protected):
	Protected

	' The length of the chain as-of the link specified.
	Method ChainLength:Int(LinkPosition:Int)
		If (LinkPosition < 0 Or LinkPosition > LinkCount) Then
			Return 0
		Endif

		Local P:Int = 0

		For Local I:= 0 Until LinkPosition
			' Add the length of the previous link in the chain.
			P += Chain.Get(I).Length
		Next

		Return (P + Chain.Get(LinkPosition).Position)
	End

	Public

	' Properties (Public):

	' This specifies if we are at the end of the stream.
	' This uses the final chain as a reference, as normal operations
	' will automatically move through each link in the chain.
	Method Eof:Int() Property
		'Return CurrentLink.Eof
		Return FinalLink.Eof
	End

	' The overall length of this stream. (Bytes contained)
	Method Length:Int() Property
		Local L:Int = 0

		For Local Node:= Eachin Chain
			L += Node.Length
		Next

		Return L
	End

	' The current "virtual" position in the stream;
	' functions like a normal stream, use as you would normally.
	Method Position:Int() Property
		' Add the previous links' lengths to the current link's position.
		Return (ChainLength(Link))
	End

	' A public "accessor" for the internal "chain".
	' Mutation may result in undefined behavior; use at your own risk.
	Method Links:Stack<StreamType>() Property
		Return Self.Chain
	End

	' The number of links in the internal "chain".
	Method LinkCount:Int() Property
		Return Chain.Length
	End

	' This specifies if we are currently
	' using the final link in the chain.
	Method OnFinalLink:Bool() Property
		Return (Link = FinalChainIndex)
	End

	' Properties (Protected):
	Protected

	' The final chain-index (Link) in the chain.
	Method FinalChainIndex:Int() Property
		Return Max(LinkCount-1, 0)
	End

	' The current stream-link in the chain. (Use at your own risk)
	Method CurrentLink:StreamType() Property
		Return Chain.Get(Link)
	End

	' The final stream in the "chain". (Use at your own risk)
	Method FinalLink:StreamType() Property
		Return Chain.Get(FinalChainIndex)
	End

	Public

	' Fields (Protected):
	Protected

	' A "chain" of streams representing
	' the data this stream delegates.
	Field Chain:Stack<StreamType>

	' The active element of 'Chain';
	' the stream currently used for I/O.
	Field Link:Int

	' Booleans / Flags:

	' This specifies if big-endian storage should be used/expected.
	Field BigEndian:Bool

	' This specifies if this object has the right to
	' close the elements of the internal "chain".
	Field CanCloseStreams:Bool = True

	Public
End


Fixed CircleOverlap (thanks Gus)

Fixed CircleOverlap (thanks Gus)

#Rem
Copyright (c) 2011 Steve Revill and Shane Woolcock
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#End

Import mojo
Import framework
Import assert
Import vector2d
Import externfunctions
Import constants

#If TARGET<>"html5" Then
	Function GetBrowserName:String()
		Return ""
	End
	Function GetBrowserVersion:String()
		Return ""
	End
	Function GetBrowserOS:String()
		Return ""
	End
	#If TARGET<>"flash" Then
		Function GetCurrentURL:String()
			Return ""
		End
	#End
#End

#If LANG <> "java" Then
	Function BuildString:String(arr:Int[], offset:Int, length:Int)
		If offset<0 Or length<=0 Or offset+length > arr.Length Then Return ""
		Local rv:String = String.FromChar(arr[offset])
		For Local i:Int = offset+1 Until offset+length
			rv += String.FromChar(arr[i])
		Next
		Return rv
	End
#End

#If TARGET="xna" Or TARGET="psm" Then
	Private
	Global seekMusicCalled:Bool = False
	Public
	Function SeekMusic:Int(timeMillis:Int)
		If Not seekMusicCalled Then
			seekMusicCalled = True
			Print "Warning: SeekMusic is not implemented in XNA and PSM!"
		End
		Return 1
	End
#End

Function FlushKeys:Void()
	ResetInput()
End

Function GetUpdateRate:Int()
	Return UpdateRate()
End

Function LaunchBrowser(address:String, openNewWindow:Bool = True)
	Local windowName:String = "_self"
	If openNewWindow
		windowName = "_blank"
	End
	LaunchNativeBrowser(address, windowName)
End

Function SetGraphics:Void(w:Int, h:Int)
	SetNativeGraphicsSize(w, h)
	DEVICE_WIDTH = w
	DEVICE_HEIGHT = h
	SCREEN_HEIGHT = h
	SCREEN_WIDTH = w
	SCREEN_WIDTH2 = SCREEN_WIDTH / 2
	SCREEN_HEIGHT2 = SCREEN_HEIGHT / 2
End

Function ExitApp:Void()
	#If TARGET="win8"
		Print "Cant exit a Win8 application"
	#Else
		EndApp()
	#End
End

Function GetDayOfMonth:Int()
	 Local date:=GetDate()
	 Local day:=date[2]
	 Return day
End

Function GetDayOfWeek:Int()
	Print "GetDayOfWeek is not supported returning -1"
	Print "GetDayOfWeek will be removed in future versions!!!"
	Return -1
End

Function GetMonth:Int()
	Local date:=GetDate()
	Local month:=date[1]
	Return month
End

Function GetYear:Int()
	Local date:=GetDate()
	Local year:=date[0]
	Return year
End

Function GetHours:Int()
	Local date:=GetDate()
	Local hour:=date[3]
	Return hour
End

Function GetMinutes:Int()
	Local date:=GetDate()
	Local min:=date[4]
	Return min
End

Function GetSeconds:Int()
	Local date:=GetDate()
	Local sec:=date[5]
	Return sec
End

Function GetMilliSeconds:Int()
	Local date:=GetDate()
	Local msec:=date[6]
	Return msec
End

Function RectsOverlap:Int(x0:Float, y0:Float, w0:Float, h0:Float, x2:Float, y2:Float, w2:Float, h2:Float)
	If x0 > (x2 + w2) Or (x0 + w0) < x2 Then Return False
	If y0 > (y2 + h2) Or (y0 + h0) < y2 Then Return False
	Return True
End

Function DrawRectOutline:Void(x:Int, y:Int, w:Int, h:Int)
	w -= 1
	h -= 1
	DrawLine(x, y, x + w, y)
	DrawLine(x + w, y, x + w, y + h)
	DrawLine(x + w, y + h, x, y + h)
	DrawLine(x, y + h, x, y)
End

Function CircleOverlap:Bool(x1:Float, y1:Float, r1:Float, x2:Float, y2:Float, r2:Float)
	Local dx:Float = x1 - x2
	Local dy:Float = y1 - y2
	Local r:Float = r1 + r2
	If dx * dx + dy * dy <= r * r Then ' collided
		Return True
	End
	Return False
End

Function CircleRectsOverlap:Bool(x1:Float, y1:Float, w1:Float, h1:Float, cx:Float, cy:Float, r:Float)
	Local testX:Float = cx
	Local testY:Float = cy
	If testX < x1 Then testX = x1
	If testX > (x1 + w1) Then testX = (x1 + w1)
	If testY < y1 Then testY = y1
	If testY > (y1 + h1) Then testY = (y1 + h1)
	Return ( (cx - testX) * (cx - testX) + (cy - testY) * (cy - testY)) < r * r
End

Function LoadBitmap:Image(path$, flags%=0)
	Local pointer:Image = LoadImage(path, 1, flags)

	AssertNotNull(pointer, "Error loading bitmap "+path)

   	Return pointer
End

Function LoadAnimBitmap:Image(path$, w%, h%, count%, tmpImage:Image)
	'tmpImage = loadBitmap(path) <-- This creates another image, decided to just copy the code here
	tmpImage = LoadImage(path)

	AssertNotNull(tmpImage, "Error loading bitmap "+path)

	Local pointer:Image = tmpImage.GrabImage( 0, 0, w, h, count, Image.MidHandle)

   	Return pointer
End

Function LoadSoundSample:Sound(path$)
	Local pointer:Sound = LoadSound(path)
	AssertNotNull(pointer, "Error loading sound "+path)
	Return pointer
End

Function FormatNumeric:String(value:Float)
	Local i:Int,s:String,ns:String,k:Int
	Local os:String
	s=String(value)
	os=s
	Local pos:Int=s.Length()
	If s.Find(".")>0 pos=s.Find(".") Else os=""
	For i=pos To 1 Step -1
		If k>2 ns+="." k=0
		k+=1
		ns=ns+Mid(s,i,1)
	Next
	s=""
	For i= ns.Length() To 1 Step -1
		s+=Mid(ns,i,1)
	Next
	If os<>"" s=s+","+os[pos+1..]
	Return s
End

Function Left$( str$,n:Int )
	If n>str.Length() n=str.Length()
	Return str[..n]
End

Function Right$( str$,n:Int )
	If n>str.Length() n=str.Length()
	Return str[str.Length()-n..]
End

Function LSet$( str$,n:Int,char:String=" " )
	Local rep:String
	For Local i:Int=1 To n
		rep=rep+char
	Next
	str=str+rep
	Return str[..n]
End

Function RSet$( str$,n:Int,char:String=" " )
	Local rep:String
	For Local i:Int=1 To n
		rep=rep+char
	Next
	str=rep+str
	Return str[str.Length()-n..]
End

Function Mid$( str$,pos:Int,size:Int=-1 )
	If pos>str.Length() Return ""
	pos-=1
	If( size<0 ) Return str[pos..]
	If pos<0 size=size+pos pos=0
	If pos+size>str.Length() size=str.Length()-pos
	Return str[pos..pos+size]
End

Function StripDir$( path$ )
	Local i:=path.FindLast( "/" )
	If i<>-1 Return path[i+1..]
	Return path
End

Function StripExt$( path$ )
	Local i:=path.FindLast( "." )
	If i<>-1 And path.Find( "/",i+1 )=-1 Return path[..i]
	Return path
End

Function StripAll$( path$ )
	Return StripDir( StripExt( path ) )
End

Function Round%(flot#)
	Return Floor(flot+0.5)
End

'summary: returns an angle between two points
Function CalcAngle:Float(x1:Float, y1:Float, x2:Float, y2:Float)
	Local dx:Float = x2 - x1
	Local dy:Float = y2 - y1
	Return (ATan2(dy, dx) + 360) Mod 360
End

'summary: Calculate distance between two points - overload using x,y
Function CalcDistance:Float(x1:Float, y1:Float, x2:Float, y2:Float)
	Local dx:Float = x2 - x1
	Local dy:Float = y2 - y1
	Return Sqrt(dx * dx + dy * dy)
End

Function PointInSpot:Int(x1:Float, y1:Float, x2:Float, y2:Float, radius:Float)
	Local dx:Float = x2 - x1
	Local dy:Float = y2 - y1
	Return dx * dx + dy * dy <= radius * radius
End

Function AnyInputPressed:Bool()
	For Local i:Int = 0 To 511
		If KeyHit(i) Then Return True
	Next
	Return False
End

Function FormatNumber:String(number:Float, decimal:Int=4, comma:Int=0, padleft:Int=0 )
	Assert(decimal > -1 And comma > -1 And padleft > -1, "Negative numbers not allowed in FormatNumber()")

	Local str:String = number
	Local dl:Int = str.Find(".")
	If decimal = 0 Then decimal = -1
	str = str[..dl+decimal+1]

	If comma
		While dl>comma
			str = str[..dl-comma] + "," + str[dl-comma..]
			dl -= comma
		Wend
	End

	If padleft
		Local paddedLength:Int = padleft+decimal+1
		If paddedLength < str.Length Then str = "Error"
		str = RSet(str,paddedLength)
	End
	Return str
End

Function IsWhitespace:Bool(str:String)
	Return str = "~t" Or str = "~n" Or str = "~r" Or str = " "
End

Function IsWhitespace:Bool(val:Int)
	Return val = ASC_TAB Or val = ASC_LF Or val = ASC_CR Or val = ASC_SPACE
End

Function Interpolate:Float(type:Int, startValue:Float, endValue:Float, alpha:Float)
	Local range:Float = endValue-startValue
	Local rv:Float = 0
	Select type
		Case INTERPOLATION_LINEAR
			rv = startValue + range*alpha

		Case INTERPOLATION_INVERSE_LINEAR
			rv = startValue + range - range*alpha

		Case INTERPOLATION_HALF_SINE
			rv = startValue + range * Sinr(alpha*PI)

		Case INTERPOLATION_HALF_COSINE
			rv = startValue + range * Cosr(alpha*PI)
	End
	' clip to start/end
	If startValue < endValue And rv < startValue Or startValue > endValue And rv > startValue Then
		rv = startValue
	Elseif startValue < endValue And rv > endValue Or startValue > endValue And rv < endValue Then
		rv = endValue
	End
	Return rv
End

' arghhhh monkey needs enums!!!
Function InterpolationFromString:Int(interp:String)
	If interp = "" Or interp = "none" Then Return INTERPOLATION_NONE
	If interp = "linear" Then Return INTERPOLATION_LINEAR
	If interp = "inverselinear" Then Return INTERPOLATION_INVERSE_LINEAR
	If interp = "halfsine" Then Return INTERPOLATION_HALF_SINE
	If interp = "halfcosine" Then Return INTERPOLATION_HALF_COSINE
End

' colour conversions (hsl is range 0-1, return is RGB as a single int)
' Monkey conversion of http://www.geekymonkey.com/Programming/CSharp/RGB2HSL_HSL2RGB.htm
Function HSLtoRGB:Int(hue:Float, saturation:Float, luminance:Float, rgbArray:Int[] = [])
	Local r:Float = luminance, g:Float = luminance, b:Float = luminance
	Local v:Float = 0
	If luminance <= 0.5 Then
		v = luminance * (1.0 + saturation)
	Else
		v = luminance + saturation - luminance * saturation
	End
	If v > 0 Then
		Local m:Float = luminance + luminance - v
		Local sv:Float = (v - m) / v
		hue *= 6
		Local sextant:Int = Int(hue)
		Local fract:Float = hue - sextant
		Local vsf:Float = v * sv * fract
		Local mid1:Float = m + vsf
		Local mid2:Float = v - vsf

		Select sextant
			Case 0
				r = v
				g = mid1
				b = m

			Case 1
				r = mid2
				g = v
				b = m

			Case 2
				r = m
				g = v
				b = mid1

			Case 3
				r = m
				g = mid2
				b = v

			Case 4
				r = mid1
				g = m
				b = v

			Case 5
				r = v
				g = m
				b = mid2
		End
	End
	If rgbArray.Length = 3 Then
		rgbArray[0] = Int(r*255)
		rgbArray[1] = Int(g*255)
		rgbArray[2] = Int(b*255)
	End
	Return $ff000000 | (Int(r*255) Shl 16) | (Int(g*255) Shl 8) | (Int(b*255) Shl 0)
End

' colour conversions (rgb is 0-255, return is a float array, reusing the hslvals array if it was big enough)
' Monkey conversion of http://www.geekymonkey.com/Programming/CSharp/RGB2HSL_HSL2RGB.htm
Function RGBtoHSL:Float[](red:Int, green:Int, blue:Int, hslvals:Float[] = [])
	If hslvals.Length <> 3 Then hslvals = New Float[3]
	Local r:Float = red/255.0, g:Float = green/255.0, b:Float = blue/255.0
	hslvals[0] = 0
	hslvals[1] = 0
	hslvals[2] = 0

	' calculate luminance
	Local v:Float = Max(Max(r,g),b)
	Local m:Float = Min(Min(r,g),b)
	hslvals[2] = (m + v) / 2.0
	' die if it's black
	If hslvals[2] <= 0 Then Return hslvals

	' precalculate saturation
	Local vm:Float = v - m
	hslvals[1] = vm
	' die if it's grey
	If hslvals[1] <= 0 Then Return hslvals

	' finish saturation
	If hslvals[2] <= 0.5 Then
		hslvals[1] /= v + m
	Else
		hslvals[1] /= 2 - v - m
	End

	Local r2:Float = (v - r) / vm
	Local g2:Float = (v - g) / vm
	Local b2:Float = (v - b) / vm
	If r = v Then
		If g = m Then hslvals[0] = 5 + b2 Else hslvals[0] = 1 - g2
	Elseif g = v Then
		If b = m Then hslvals[0] = 1 + r2 Else hslvals[0] = 3 - b2
	Else
		If r = m Then hslvals[0] = 3 + g2 Else hslvals[0] = 5 - r2
	End
	hslvals[0] /= 6.0

	Return hslvals
End

Function DecToHex:String(dec:Int)
	Local rv:String = ""
	If dec <= 0 Then Return "0"
	While dec > 0
		If dec Mod 16 < 10 Then
			rv += dec Mod 16
		Else
			rv += String.FromChar(ASC_LOWER_A+(dec Mod 16)-10)
		End
		dec = dec / 16
	End
	Return rv
End

Function HexToDec:Int(hx:String)
	Local rv:Int = 0
	Local lookup:String = "0123456789abcdef"
	hx = hx.ToLower()
	For Local i:Int = 0 Until hx.Length()
		rv *= 16
		Local idx:Int = lookup.Find(hx[i..i+1])
		If idx < 0 Then Error("Error parsing Hex string!")
		rv += idx
	Next
	Return rv
End

Function DrawLineThick(x1:Int, y1:Int, x2:Int, y2:Int, thinkness:Int = 2)
	Local steep:Bool = Abs(y2 - y1) > Abs(x2 - x1)
	Local swapped:Bool = False

	If steep
		Local tmp:Int = x1
		x1 = y1
		y1 = tmp

		tmp = x2
		x2 = y2
		y2 = tmp
	End

	If x1 > x2
		Local t:Float = x1
		x1 = x2
		x2 = t
		t = y1
		y1 = y2
		y2 = t
		swapped = True
	End

	Local deltax:Float = x2 - x1
	Local deltay:Float = Abs(y2 - y1)
	Local error:Float = deltax / 2
	Local ystep:Float
	Local y:Float = y1

	If y1 < y2 Then
		ystep = 1
	Else
		ystep = -1
	End
	Local offset:Int = thinkness / 2
	For Local x:Int = x1 Until x2
		If steep
			DrawRect(y - offset, x - offset, thinkness, thinkness)
		Else
			DrawRect(x - offset, y - offset, thinkness, thinkness)
		End

		error -= deltay
		If error < 0
			y += ystep
			error += deltax
		End
	Next
End

Function BresenhamLine:Vector2D[](startPos:Vector2D, endPos:Vector2D)
	Local points:Stack<Vector2D> = New Stack<Vector2D>
	Local steep:Bool = Abs(endPos.y - startPos.y) > Abs(endPos.x - startPos.x)
	Local swapped:Bool = False

	If steep
		startPos.SwapXY(startPos.x, startPos.y)
		endPos.SwapXY(endPos.x, endPos.y)
	End

	If startPos.x > endPos.x
		Local t:Float = startPos.x
		startPos.x = endPos.x
		endPos.x = t
		t = startPos.y
		startPos.y = endPos.y
		endPos.y = t
		swapped = True
	End

	Local deltax:Float = endPos.x - startPos.x
	Local deltay:Float = Abs(endPos.y - startPos.y)
	Local error:Float = deltax / 2
	Local ystep:Float
	Local y:Float = startPos.y

	If startPos.y < endPos.y Then
		ystep = 1
	Else
		ystep = -1
	End

	For Local x:Int = startPos.x Until endPos.x
		If steep
			points.Push(New Vector2D(y, x))
		Else
			points.Push(New Vector2D(x, y))
		End

		error -= deltay
		If error < 0
			y += ystep
			error += deltax
		End
	Next
	Local arr:Vector2D[] = points.ToArray()
	If swapped
		Arrays < Vector2D >.Reverse(arr)
	End
	Return arr
End

Private
Global screenshotPixels:Int[] ' this is globally cached for performance
Public
' summary: Takes a screenshot of a certain rectangle on the screen.  Defaults to the entire screen.
Function CreateScreenshot:Image(x%=0, y%=0, width%=0, height%=0)
	' if no width passed in, assume the whole width
	If width <= 0 Then
		width = DeviceWidth()
		x = 0
	End

	' if no height passed in, assume the whole height
	If height <= 0 Then
		height = DeviceHeight()
		y = 0
	End

	' check that the rectangle is entirely within the screen bounds
	If x < 0 Or y < 0 Or x + width > DeviceWidth() Or y + height > DeviceHeight() Then Return Null

	' create an image
	Local image:Image = CreateImage(width, height)

	' read the screen
	If screenshotPixels.Length <> width*height Then screenshotPixels = New Int[width*height]
	ReadPixels(screenshotPixels, x, y, width, height)
	image.WritePixels(screenshotPixels, 0, 0, width, height)

	Return image
End

'summary: Returns a random int in the range low (inclusive) to high (exclusive).
Function Rand:Int (low:Int, high:Int)
	Local v:Float = Rnd(low, high)
	if (v < 0) Then v -= 1.0
	Local vi:Int = Int(v)
	if vi = (low - 1) Then vi = Min (-1, high)
	Return vi
End

'summary: Converts Mask Pixel Color to Transparent Pixel
Function PixelArrayMask:Void(pixels:Int[], maskRed:Int = 0, maskGreen:Int = 0, maskBlue:Int = 0)
	For Local i:Int = 0 Until pixels.Length
		Local argb:Int = pixels[i]
		Local a:Int = (argb Shr 24) & $ff
		Local r:Int = (argb Shr 16) & $ff
		Local g:Int = (argb Shr 8) & $ff
		Local b:Int = argb & $ff

		If a = 255 And r = maskRed And g = maskGreen And b = maskBlue
			a = 0
			argb = (a Shl 24) | (r Shl 16) | (g Shl 8) | b
			pixels[i] = argb
		End
	Next
End

Function Lerp:Float(startValue:Float, endValue:Float, progress:Float)
	Return startValue + (endValue-startValue) * progress
End

Function InterpolateWithEase:Float(startValue:Float, endValue:Float, progress:Float, ease:Int)
	If progress <= 0 Then Return startValue
	If progress >= 1 Then Return endValue
	Select ease
		Case EASE_IN_DOUBLE
			Return Lerp(endValue, startValue, (1-progress)*(1-progress)*(1-progress)*(1-progress))
		Case EASE_IN
			Return Lerp(endValue, startValue, (1-progress)*(1-progress))
		Case EASE_IN_HALF
			Return Lerp(endValue, startValue, Pow(1-progress, 1.5))
		Case EASE_OUT
			Return Lerp(startValue, endValue, progress * progress)
		Case EASE_OUT_HALF
			Return Lerp(startValue, endValue, Pow(progress, 1.5))
		Case EASE_OUT_DOUBLE
			Return Lerp(startValue, endValue, progress*progress*progress*progress)
		Case EASE_IN_OUT
			Return startValue + (-2*(progress*progress*progress) + 3*(progress*progress)) * (endValue - startValue)
		Default
			Return Lerp(startValue, endValue, progress);
	End
End

#Rem
Copyright (c) 2011 Steve Revill and Shane Woolcock
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#End

Import mojo
Import framework
Import assert
Import vector2d
Import externfunctions
Import constants

#If TARGET<>"html5" Then
	Function GetBrowserName:String()
		Return ""
	End
	Function GetBrowserVersion:String()
		Return ""
	End
	Function GetBrowserOS:String()
		Return ""
	End
	#If TARGET<>"flash" Then
		Function GetCurrentURL:String()
			Return ""
		End
	#End
#End

#If LANG <> "java" Then
	Function BuildString:String(arr:Int[], offset:Int, length:Int)
		If offset<0 Or length<=0 Or offset+length > arr.Length Then Return ""
		Local rv:String = String.FromChar(arr[offset])
		For Local i:Int = offset+1 Until offset+length
			rv += String.FromChar(arr[i])
		Next
		Return rv
	End
#End

#If TARGET="xna" Or TARGET="psm" Then
	Private
	Global seekMusicCalled:Bool = False
	Public
	Function SeekMusic:Int(timeMillis:Int)
		If Not seekMusicCalled Then
			seekMusicCalled = True
			Print "Warning: SeekMusic is not implemented in XNA and PSM!"
		End
		Return 1
	End
#End

Function FlushKeys:Void()
	ResetInput()
End

Function GetUpdateRate:Int()
	Return UpdateRate()
End

Function LaunchBrowser(address:String, openNewWindow:Bool = True)
	Local windowName:String = "_self"
	If openNewWindow
		windowName = "_blank"
	End
	LaunchNativeBrowser(address, windowName)
End

Function SetGraphics:Void(w:Int, h:Int)
	SetNativeGraphicsSize(w, h)
	DEVICE_WIDTH = w
	DEVICE_HEIGHT = h
	SCREEN_HEIGHT = h
	SCREEN_WIDTH = w
	SCREEN_WIDTH2 = SCREEN_WIDTH / 2
	SCREEN_HEIGHT2 = SCREEN_HEIGHT / 2
End

Function ExitApp:Void()
	#If TARGET="win8"
		Print "Cant exit a Win8 application"
	#Else
		EndApp()
	#End
End

Function GetDayOfMonth:Int()
	 Local date:=GetDate()
	 Local day:=date[2]
	 Return day
End

Function GetDayOfWeek:Int()
	Print "GetDayOfWeek is not supported returning -1"
	Print "GetDayOfWeek will be removed in future versions!!!"
	Return -1
End

Function GetMonth:Int()
	Local date:=GetDate()
	Local month:=date[1]
	Return month
End

Function GetYear:Int()
	Local date:=GetDate()
	Local year:=date[0]
	Return year
End

Function GetHours:Int()
	Local date:=GetDate()
	Local hour:=date[3]
	Return hour
End

Function GetMinutes:Int()
	Local date:=GetDate()
	Local min:=date[4]
	Return min
End

Function GetSeconds:Int()
	Local date:=GetDate()
	Local sec:=date[5]
	Return sec
End

Function GetMilliSeconds:Int()
	Local date:=GetDate()
	Local msec:=date[6]
	Return msec
End

Function RectsOverlap:Int(x0:Float, y0:Float, w0:Float, h0:Float, x2:Float, y2:Float, w2:Float, h2:Float)
	If x0 > (x2 + w2) Or (x0 + w0) < x2 Then Return False
	If y0 > (y2 + h2) Or (y0 + h0) < y2 Then Return False
	Return True
End

Function DrawRectOutline:Void(x:Int, y:Int, w:Int, h:Int)
	w -= 1
	h -= 1
	DrawLine(x, y, x + w, y)
	DrawLine(x + w, y, x + w, y + h)
	DrawLine(x + w, y + h, x, y + h)
	DrawLine(x, y + h, x, y)
End

Function CircleOverlap:Bool(x1:Float, y1:Float, r1:Float, x2:Float, y2:Float, r2:Float)
	dx = x1 - x2
	dy = y1 - y2
	r = r1 + r2
	If dx * dx + dy * dy <= r * r Then ' collided
		Return True
	End
	Return False
End

Function CircleRectsOverlap:Bool(x1:Float, y1:Float, w1:Float, h1:Float, cx:Float, cy:Float, r:Float)
	Local testX:Float = cx
	Local testY:Float = cy
	If testX < x1 Then testX = x1
	If testX > (x1 + w1) Then testX = (x1 + w1)
	If testY < y1 Then testY = y1
	If testY > (y1 + h1) Then testY = (y1 + h1)
	Return ( (cx - testX) * (cx - testX) + (cy - testY) * (cy - testY)) < r * r
End

Function LoadBitmap:Image(path$, flags%=0)
	Local pointer:Image = LoadImage(path, 1, flags)

	AssertNotNull(pointer, "Error loading bitmap "+path)

   	Return pointer
End

Function LoadAnimBitmap:Image(path$, w%, h%, count%, tmpImage:Image)
	'tmpImage = loadBitmap(path) <-- This creates another image, decided to just copy the code here
	tmpImage = LoadImage(path)

	AssertNotNull(tmpImage, "Error loading bitmap "+path)

	Local pointer:Image = tmpImage.GrabImage( 0, 0, w, h, count, Image.MidHandle)

   	Return pointer
End

Function LoadSoundSample:Sound(path$)
	Local pointer:Sound = LoadSound(path)
	AssertNotNull(pointer, "Error loading sound "+path)
	Return pointer
End

Function FormatNumeric:String(value:Float)
	Local i:Int,s:String,ns:String,k:Int
	Local os:String
	s=String(value)
	os=s
	Local pos:Int=s.Length()
	If s.Find(".")>0 pos=s.Find(".") Else os=""
	For i=pos To 1 Step -1
		If k>2 ns+="." k=0
		k+=1
		ns=ns+Mid(s,i,1)
	Next
	s=""
	For i= ns.Length() To 1 Step -1
		s+=Mid(ns,i,1)
	Next
	If os<>"" s=s+","+os[pos+1..]
	Return s
End

Function Left$( str$,n:Int )
	If n>str.Length() n=str.Length()
	Return str[..n]
End

Function Right$( str$,n:Int )
	If n>str.Length() n=str.Length()
	Return str[str.Length()-n..]
End

Function LSet$( str$,n:Int,char:String=" " )
	Local rep:String
	For Local i:Int=1 To n
		rep=rep+char
	Next
	str=str+rep
	Return str[..n]
End

Function RSet$( str$,n:Int,char:String=" " )
	Local rep:String
	For Local i:Int=1 To n
		rep=rep+char
	Next
	str=rep+str
	Return str[str.Length()-n..]
End

Function Mid$( str$,pos:Int,size:Int=-1 )
	If pos>str.Length() Return ""
	pos-=1
	If( size<0 ) Return str[pos..]
	If pos<0 size=size+pos pos=0
	If pos+size>str.Length() size=str.Length()-pos
	Return str[pos..pos+size]
End

Function StripDir$( path$ )
	Local i:=path.FindLast( "/" )
	If i<>-1 Return path[i+1..]
	Return path
End

Function StripExt$( path$ )
	Local i:=path.FindLast( "." )
	If i<>-1 And path.Find( "/",i+1 )=-1 Return path[..i]
	Return path
End

Function StripAll$( path$ )
	Return StripDir( StripExt( path ) )
End

Function Round%(flot#)
	Return Floor(flot+0.5)
End

'summary: returns an angle between two points
Function CalcAngle:Float(x1:Float, y1:Float, x2:Float, y2:Float)
	Local dx:Float = x2 - x1
	Local dy:Float = y2 - y1
	Return (ATan2(dy, dx) + 360) Mod 360
End

'summary: Calculate distance between two points - overload using x,y
Function CalcDistance:Float(x1:Float, y1:Float, x2:Float, y2:Float)
	Local dx:Float = x2 - x1
	Local dy:Float = y2 - y1
	Return Sqrt(dx * dx + dy * dy)
End

Function PointInSpot:Int(x1:Float, y1:Float, x2:Float, y2:Float, radius:Float)
	Local dx:Float = x2 - x1
	Local dy:Float = y2 - y1
	Return dx * dx + dy * dy <= radius * radius
End

Function AnyInputPressed:Bool()
	For Local i:Int = 0 To 511
		If KeyHit(i) Then Return True
	Next
	Return False
End

Function FormatNumber:String(number:Float, decimal:Int=4, comma:Int=0, padleft:Int=0 )
	Assert(decimal > -1 And comma > -1 And padleft > -1, "Negative numbers not allowed in FormatNumber()")

	Local str:String = number
	Local dl:Int = str.Find(".")
	If decimal = 0 Then decimal = -1
	str = str[..dl+decimal+1]

	If comma
		While dl>comma
			str = str[..dl-comma] + "," + str[dl-comma..]
			dl -= comma
		Wend
	End

	If padleft
		Local paddedLength:Int = padleft+decimal+1
		If paddedLength < str.Length Then str = "Error"
		str = RSet(str,paddedLength)
	End
	Return str
End

Function IsWhitespace:Bool(str:String)
	Return str = "~t" Or str = "~n" Or str = "~r" Or str = " "
End

Function IsWhitespace:Bool(val:Int)
	Return val = ASC_TAB Or val = ASC_LF Or val = ASC_CR Or val = ASC_SPACE
End

Function Interpolate:Float(type:Int, startValue:Float, endValue:Float, alpha:Float)
	Local range:Float = endValue-startValue
	Local rv:Float = 0
	Select type
		Case INTERPOLATION_LINEAR
			rv = startValue + range*alpha

		Case INTERPOLATION_INVERSE_LINEAR
			rv = startValue + range - range*alpha

		Case INTERPOLATION_HALF_SINE
			rv = startValue + range * Sinr(alpha*PI)

		Case INTERPOLATION_HALF_COSINE
			rv = startValue + range * Cosr(alpha*PI)
	End
	' clip to start/end
	If startValue < endValue And rv < startValue Or startValue > endValue And rv > startValue Then
		rv = startValue
	Elseif startValue < endValue And rv > endValue Or startValue > endValue And rv < endValue Then
		rv = endValue
	End
	Return rv
End

' arghhhh monkey needs enums!!!
Function InterpolationFromString:Int(interp:String)
	If interp = "" Or interp = "none" Then Return INTERPOLATION_NONE
	If interp = "linear" Then Return INTERPOLATION_LINEAR
	If interp = "inverselinear" Then Return INTERPOLATION_INVERSE_LINEAR
	If interp = "halfsine" Then Return INTERPOLATION_HALF_SINE
	If interp = "halfcosine" Then Return INTERPOLATION_HALF_COSINE
End

' colour conversions (hsl is range 0-1, return is RGB as a single int)
' Monkey conversion of http://www.geekymonkey.com/Programming/CSharp/RGB2HSL_HSL2RGB.htm
Function HSLtoRGB:Int(hue:Float, saturation:Float, luminance:Float, rgbArray:Int[] = [])
	Local r:Float = luminance, g:Float = luminance, b:Float = luminance
	Local v:Float = 0
	If luminance <= 0.5 Then
		v = luminance * (1.0 + saturation)
	Else
		v = luminance + saturation - luminance * saturation
	End
	If v > 0 Then
		Local m:Float = luminance + luminance - v
		Local sv:Float = (v - m) / v
		hue *= 6
		Local sextant:Int = Int(hue)
		Local fract:Float = hue - sextant
		Local vsf:Float = v * sv * fract
		Local mid1:Float = m + vsf
		Local mid2:Float = v - vsf

		Select sextant
			Case 0
				r = v
				g = mid1
				b = m

			Case 1
				r = mid2
				g = v
				b = m

			Case 2
				r = m
				g = v
				b = mid1

			Case 3
				r = m
				g = mid2
				b = v

			Case 4
				r = mid1
				g = m
				b = v

			Case 5
				r = v
				g = m
				b = mid2
		End
	End
	If rgbArray.Length = 3 Then
		rgbArray[0] = Int(r*255)
		rgbArray[1] = Int(g*255)
		rgbArray[2] = Int(b*255)
	End
	Return $ff000000 | (Int(r*255) Shl 16) | (Int(g*255) Shl 8) | (Int(b*255) Shl 0)
End

' colour conversions (rgb is 0-255, return is a float array, reusing the hslvals array if it was big enough)
' Monkey conversion of http://www.geekymonkey.com/Programming/CSharp/RGB2HSL_HSL2RGB.htm
Function RGBtoHSL:Float[](red:Int, green:Int, blue:Int, hslvals:Float[] = [])
	If hslvals.Length <> 3 Then hslvals = New Float[3]
	Local r:Float = red/255.0, g:Float = green/255.0, b:Float = blue/255.0
	hslvals[0] = 0
	hslvals[1] = 0
	hslvals[2] = 0

	' calculate luminance
	Local v:Float = Max(Max(r,g),b)
	Local m:Float = Min(Min(r,g),b)
	hslvals[2] = (m + v) / 2.0
	' die if it's black
	If hslvals[2] <= 0 Then Return hslvals

	' precalculate saturation
	Local vm:Float = v - m
	hslvals[1] = vm
	' die if it's grey
	If hslvals[1] <= 0 Then Return hslvals

	' finish saturation
	If hslvals[2] <= 0.5 Then
		hslvals[1] /= v + m
	Else
		hslvals[1] /= 2 - v - m
	End

	Local r2:Float = (v - r) / vm
	Local g2:Float = (v - g) / vm
	Local b2:Float = (v - b) / vm
	If r = v Then
		If g = m Then hslvals[0] = 5 + b2 Else hslvals[0] = 1 - g2
	Elseif g = v Then
		If b = m Then hslvals[0] = 1 + r2 Else hslvals[0] = 3 - b2
	Else
		If r = m Then hslvals[0] = 3 + g2 Else hslvals[0] = 5 - r2
	End
	hslvals[0] /= 6.0

	Return hslvals
End

Function DecToHex:String(dec:Int)
	Local rv:String = ""
	If dec <= 0 Then Return "0"
	While dec > 0
		If dec Mod 16 < 10 Then
			rv += dec Mod 16
		Else
			rv += String.FromChar(ASC_LOWER_A+(dec Mod 16)-10)
		End
		dec = dec / 16
	End
	Return rv
End

Function HexToDec:Int(hx:String)
	Local rv:Int = 0
	Local lookup:String = "0123456789abcdef"
	hx = hx.ToLower()
	For Local i:Int = 0 Until hx.Length()
		rv *= 16
		Local idx:Int = lookup.Find(hx[i..i+1])
		If idx < 0 Then Error("Error parsing Hex string!")
		rv += idx
	Next
	Return rv
End

Function DrawLineThick(x1:Int, y1:Int, x2:Int, y2:Int, thinkness:Int = 2)
	Local steep:Bool = Abs(y2 - y1) > Abs(x2 - x1)
	Local swapped:Bool = False

	If steep
		Local tmp:Int = x1
		x1 = y1
		y1 = tmp

		tmp = x2
		x2 = y2
		y2 = tmp
	End

	If x1 > x2
		Local t:Float = x1
		x1 = x2
		x2 = t
		t = y1
		y1 = y2
		y2 = t
		swapped = True
	End

	Local deltax:Float = x2 - x1
	Local deltay:Float = Abs(y2 - y1)
	Local error:Float = deltax / 2
	Local ystep:Float
	Local y:Float = y1

	If y1 < y2 Then
		ystep = 1
	Else
		ystep = -1
	End
	Local offset:Int = thinkness / 2
	For Local x:Int = x1 Until x2
		If steep
			DrawRect(y - offset, x - offset, thinkness, thinkness)
		Else
			DrawRect(x - offset, y - offset, thinkness, thinkness)
		End

		error -= deltay
		If error < 0
			y += ystep
			error += deltax
		End
	Next
End

Function BresenhamLine:Vector2D[](startPos:Vector2D, endPos:Vector2D)
	Local points:Stack<Vector2D> = New Stack<Vector2D>
	Local steep:Bool = Abs(endPos.y - startPos.y) > Abs(endPos.x - startPos.x)
	Local swapped:Bool = False

	If steep
		startPos.SwapXY(startPos.x, startPos.y)
		endPos.SwapXY(endPos.x, endPos.y)
	End

	If startPos.x > endPos.x
		Local t:Float = startPos.x
		startPos.x = endPos.x
		endPos.x = t
		t = startPos.y
		startPos.y = endPos.y
		endPos.y = t
		swapped = True
	End

	Local deltax:Float = endPos.x - startPos.x
	Local deltay:Float = Abs(endPos.y - startPos.y)
	Local error:Float = deltax / 2
	Local ystep:Float
	Local y:Float = startPos.y

	If startPos.y < endPos.y Then
		ystep = 1
	Else
		ystep = -1
	End

	For Local x:Int = startPos.x Until endPos.x
		If steep
			points.Push(New Vector2D(y, x))
		Else
			points.Push(New Vector2D(x, y))
		End

		error -= deltay
		If error < 0
			y += ystep
			error += deltax
		End
	Next
	Local arr:Vector2D[] = points.ToArray()
	If swapped
		Arrays < Vector2D >.Reverse(arr)
	End
	Return arr
End

Private
Global screenshotPixels:Int[] ' this is globally cached for performance
Public
' summary: Takes a screenshot of a certain rectangle on the screen.  Defaults to the entire screen.
Function CreateScreenshot:Image(x%=0, y%=0, width%=0, height%=0)
	' if no width passed in, assume the whole width
	If width <= 0 Then
		width = DeviceWidth()
		x = 0
	End

	' if no height passed in, assume the whole height
	If height <= 0 Then
		height = DeviceHeight()
		y = 0
	End

	' check that the rectangle is entirely within the screen bounds
	If x < 0 Or y < 0 Or x + width > DeviceWidth() Or y + height > DeviceHeight() Then Return Null

	' create an image
	Local image:Image = CreateImage(width, height)

	' read the screen
	If screenshotPixels.Length <> width*height Then screenshotPixels = New Int[width*height]
	ReadPixels(screenshotPixels, x, y, width, height)
	image.WritePixels(screenshotPixels, 0, 0, width, height)

	Return image
End

'summary: Returns a random int in the range low (inclusive) to high (exclusive).
Function Rand:Int (low:Int, high:Int)
	Local v:Float = Rnd(low, high)
	if (v < 0) Then v -= 1.0
	Local vi:Int = Int(v)
	if vi = (low - 1) Then vi = Min (-1, high)
	Return vi
End

'summary: Converts Mask Pixel Color to Transparent Pixel
Function PixelArrayMask:Void(pixels:Int[], maskRed:Int = 0, maskGreen:Int = 0, maskBlue:Int = 0)
	For Local i:Int = 0 Until pixels.Length
		Local argb:Int = pixels[i]
		Local a:Int = (argb Shr 24) & $ff
		Local r:Int = (argb Shr 16) & $ff
		Local g:Int = (argb Shr 8) & $ff
		Local b:Int = argb & $ff

		If a = 255 And r = maskRed And g = maskGreen And b = maskBlue
			a = 0
			argb = (a Shl 24) | (r Shl 16) | (g Shl 8) | b
			pixels[i] = argb
		End
	Next
End

Function Lerp:Float(startValue:Float, endValue:Float, progress:Float)
	Return startValue + (endValue-startValue) * progress
End

Function InterpolateWithEase:Float(startValue:Float, endValue:Float, progress:Float, ease:Int)
	If progress <= 0 Then Return startValue
	If progress >= 1 Then Return endValue
	Select ease
		Case EASE_IN_DOUBLE
			Return Lerp(endValue, startValue, (1-progress)*(1-progress)*(1-progress)*(1-progress))
		Case EASE_IN
			Return Lerp(endValue, startValue, (1-progress)*(1-progress))
		Case EASE_IN_HALF
			Return Lerp(endValue, startValue, Pow(1-progress, 1.5))
		Case EASE_OUT
			Return Lerp(startValue, endValue, progress * progress)
		Case EASE_OUT_HALF
			Return Lerp(startValue, endValue, Pow(progress, 1.5))
		Case EASE_OUT_DOUBLE
			Return Lerp(startValue, endValue, progress*progress*progress*progress)
		Case EASE_IN_OUT
			Return startValue + (-2*(progress*progress*progress) + 3*(progress*progress)) * (endValue - startValue)
		Default
			Return Lerp(startValue, endValue, progress);
	End
End


OnContentInit trigger function #50

OnContentInit trigger function #50

Strict

Import mojo
Import reflection

Import flxextern
Import flxbasic
Import flxstate
Import flxcamera
Import flxtimer
Import flxtext
Import flxg
Import flxu
Import flxtilemap
Import system.flxassetsmanager
Import system.flxfont
Import system.flxdebugger
Import system.flxreplay
Import plugin.timermanager

#If TARGET = "xna" Or TARGET = "psm"
	Import "data/beep_flx.wav"
#ElseIf TARGET = "html5"
	Import "data/beep_flx.ogg"
	Import "data/beep_flx.mp3"
#ElseIf TARGET = "glfw"
	Import "data/beep_flx.ogg"
#Else
	Import "data/beep_flx.mp3"
#End

Class FlxGame extends App

	Field useSoundHotKeys:Bool

	Field useSystemCursor:Bool

	Field forceDebugger:Bool

	Field _state:FlxState

	Field _requestedState:FlxState

	Field _requestedReset:Bool

	Field _debugger:FlxDebugger

	Field _debuggerUp:Bool

	Field _replay:FlxReplay

	Field _replayRequested:Bool

	Field _recordingRequested:Bool

	Field _replaying:Bool

	Field _recording:Bool

	Field _replayCancelKeys:Int[]

	Field _replayTimer:Float

	Field _replayCallback:FlxReplayListener

Private
	Field _iState:ClassInfo

	Field _created:Bool

	Field _total:Int

	Field _accumulator:Float

	Field _step:Float

	Field _maxAccumulation:Float

	Field _soundTrayTimer:Float

	Field _soundTrayWidth:Float

	Field _soundTrayHeight:Float

	Field _soundTrayX:Float

	Field _soundTrayY:Float

	Field _soundTrayVisible:Bool

	Field _soundTrayLabel:FlxText

	Field _updaterate:Int

	Field _framerate:Int

Public
	Method New(gameSizeX:Int, gameSizeY:Int, initialState:ClassInfo, zoom:Float = 1, updaterate:Int = 60, framerate:Int = 30, useSystemCursor:Bool = False)
		Local classObject:GlobalInfo

		For Local classInfo:ClassInfo = EachIn GetClasses()
			classObject = classInfo.GetGlobal("ClassObject", False)
			If (classObject = Null) classObject = classInfo.GetGlobal("ClassInfo", False)

			If (classObject <> Null) Then
				classObject.SetValue(classInfo)
			End If
		Next

		_soundTrayTimer = 0
		_soundTrayWidth = 160
		_soundTrayHeight = 45
		_soundTrayX = 0
		_soundTrayY	= -_soundTrayHeight
		_soundTrayVisible = False

		FlxG.Init(Self, gameSizeX, gameSizeY, zoom)
		FlxG.Framerate = framerate
		FlxG.Updaterate = updaterate
		_total = 0

		_state = Null

		useSoundHotKeys = Not IsMobile()
		Self.useSystemCursor = useSystemCursor

		If (Not useSystemCursor) HideMouse()

		forceDebugger = False
		_debuggerUp = False

		_replay = New FlxReplay()
		_replayRequested = False
		_recordingRequested = False
		_replaying = False
		_recording = False

		_iState = initialState
		_requestedState = Null
		_requestedReset = True
		_created = False
	End Method

	Method OnCreate:Int()
		_InitData()
		_Step()

		#Rem
		_soundTrayLabel = New FlxText(10, 32, _soundTrayWidth, "VOLUME")
		_soundTrayLabel.SetFormat(FlxText.SYSTEM_FONT, 16, FlxG.WHITE, FlxText.ALIGN_CENTER)
		#End
		Return 0
	End Method

	Method OnUpdate:Int()
		FlxG.UpdateDevice()

	#If TARGET <> "ios" And TARGET <> "android" And TARGET <> "psm"
		If (useSoundHotKeys) Then
			If (KeyHit(KEY_0)) Then
				FlxG.Mute = Not FlxG.Mute

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

				_ShowSoundTray()
			End If

			If (KeyHit(KEY_MINUS)) Then
				FlxG.Mute = False
				FlxG.Volume(FlxG.Volume() - .1)
				_ShowSoundTray()
			End If

			If (KeyHit(KEY_EQUALS)) Then
				FlxG.Mute = False
				FlxG.Volume(FlxG.Volume() + .1)
				_ShowSoundTray()
			End If
		End If

		_UpdateSoundTray(_step)
	#End

		Return 0
	End Method

	Method OnRender:Int()
		If (FlxG.Framerate <> _framerate Or FlxG.Updaterate <> _updaterate) Then
			_ResetFramerate()
		End If

		Local mark:Int = Millisecs()
		Local elapsedMS:Int = mark - _total
		_total = mark

		If (_debugger <> Null) Then
			'TODO!
		Else
			_accumulator += elapsedMS

			if (_accumulator > _maxAccumulation) Then
				_accumulator = _maxAccumulation
			End If

			While (_accumulator >= _step)
				_Step()
				_accumulator -= _step
			Wend
		End If

		FlxBasic._VisibleCount = 0

		_Draw()

		Return 0
	End Method

	Method OnSuspend:Int()
		ShowMouse()
		FlxG.PauseSounds()
		Return 0
	End Method

	Method OnResume:Int()
		If (Not _debuggerUp And Not useSystemCursor) Then
			HideMouse()
		End If

		FlxG.ResetInput()
		FlxG.ResumeSounds()
		Return 0
	End Method

	Method OnContentInit:Void()
	End Method

Private
	Method _ShowSoundTray:Void(silent:Bool = False)
		If (Not silent) Then
			FlxG.Play("beep" + FlxG.DATA_SUFFIX)
		End If

		_soundTrayTimer = 1
		_soundTrayY = 0
		_soundTrayVisible = True
	End Method

	Method _SwitchState:Void()
		FlxG.ResetCameras()
		FlxG.ResetInput()
		FlxG.DestroySounds()

		If (_debugger <> Null) Then
			'TODO!
		End If

		Local timeManager:TimerManager = FlxTimer.Manager()
		If (timeManager <> Null) timeManager.Clear()

		If (_state <> Null) _state.Destroy()

		_state = _requestedState
		_state.Create()

		If (FlxG.Framerate <> _framerate Or FlxG.Updaterate <> _updaterate) Then
			_ResetFramerate()
		End If
	End Method

	Method _Step:Void()
		If (_requestedReset) Then
			_requestedReset = False
			_requestedState = FlxState(_iState.NewInstance())
			_replayTimer = 0
			_replayCancelKeys = []
			_Reset()
			FlxG.Reset()
		End If

		If (_recordingRequested) Then
			_recordingRequested = False
			_replay.Create(FlxG.GlobalSeed)
			_recording = True

			If (_debugger <> Null) Then
				'TODO
				FlxG.Log("FLIXEL: starting new flixel gameplay record.")
			End If

		ElseIf (_replayRequested)
			_replayRequested = False
			_replay.Rewind()
			FlxG.GlobalSeed = _replay.seed

			If (_debugger <> Null) Then
				'TODO
			End If

			_replaying = True
		End If

		If (_state <> _requestedState) _SwitchState()

		FlxBasic._ActiveCount = 0

		If (_replaying) Then
			If (_replayCancelKeys.Length() > 0 And Not KeyDown(192) And Not KeyDown(220)) Then
				Local i:Int = 0
				Local l:Int = _replayCancelKeys.Length()

				While (i < l)
					If (KeyDown(_replayCancelKeys[i])) Then
						If (_replayCallback <> Null) Then
							_replayCallback.OnReplayComplete()
							_replayCallback = Null
						Else
							FlxG.StopReplay()
						End If

						Exit
					End If

					i += 1
				Wend
			End If

			_replay.PlayNextFrame()

			If (_replayTimer > 0) Then
				_replayTimer -= _step

				If (_replayTimer <= 0) Then
					If (_replayCallback <> Null) Then
						_replayCallback.OnReplayComplete()
						_replayCallback = Null
					Else
						FlxG.StopReplay()
					End If
				End If
			End If

			If (_replaying And _replay.finished) Then
				FlxG.StopReplay()

				If (_replayCallback <> Null) Then
					_replayCallback.OnReplayComplete()
					_replayCallback = Null
				End If
			End If

			If (_debugger <> Null) Then
				'TODO
			End If
		Else
			FlxG.UpdateInput()
		End If

		If (_recording) Then
			_replay.RecordFrame()

			If (_debugger <> Null) Then
				'TODO
			End If
		End If

		_Update()

		If (_debugger <> Null) Then
			'TODO
		End If
	End Method

	Method _Update:Void()
		FlxG.Elapsed = FlxG.TimeScale * (_step / 1000.0)

		FlxG.UpdateSounds()
		FlxG.UpdatePlugins()
		_state.Update()

		If (FlxG.Tweener.active And FlxG.Tweener.HasTween) Then
			FlxG.Tweener.UpdateTweens()
		End If

		FlxG.UpdateCameras()

		If (_debuggerUp) Then
			'TODO!
		End If
	End Method

	Method _UpdateSoundTray:Void(ms:Int)
		If (Not _soundTrayVisible) Return

		If (_soundTrayTimer > 0) Then
			_soundTrayTimer -= ms / 1000.0

		ElseIf (_soundTrayY > -_soundTrayHeight)
			_soundTrayY =_soundTrayY - (ms / 1000.0 * FlxG.Height * 2)

			If (_soundTrayY <= -_soundTrayHeight) Then
				_soundTrayVisible = False

				'TODO Save sound settings
			End If
		End If
	End Method

	Method _Draw:Void()
		Cls(FlxG._BgColor.r, FlxG._BgColor.g, FlxG._BgColor.b)

	#If TARGET <> "ios" And TARGET <> "android" And TARGET <> "psm"
		If( Not FlxG.Mobile) Then
			PushMatrix()
		End If
	#End

		Translate(FlxG._DeviceOffsetX, FlxG._DeviceOffsetY)
		Scale(FlxG._DeviceScaleFactorX, FlxG._DeviceScaleFactorY)

		FlxG._LastDrawingColor = FlxG.WHITE
		FlxG._LastDrawingBlend = GetBlend()
		FlxG._LastDrawingAlpha = GetAlpha()
		FlxG._CurrentCamera = Null

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

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

			If (Not FlxG._CurrentCamera.active) Then
				i+=1
				Continue
			End If

			If (FlxG._CurrentCamera = Null Or Not FlxG._CurrentCamera.exists Or Not FlxG._CurrentCamera.visible) Continue

			FlxG._CurrentCamera.DrawFX() 'not realy draw. Only calculation
			FlxG._CurrentCamera.Lock()
			_state.Draw()
			FlxG.DrawPlugins()
			FlxG._CurrentCamera.Unlock()

			i+=1
		Wend

	#If TARGET <> "ios" And TARGET <> "android" And TARGET <> "psm"
		If( Not FlxG.Mobile) Then
			PopMatrix()
			_DrawSoundTray()
			FlxG.Mouse.Draw()
		End If
	#End
	End Method

	Method _DrawSoundTray:Void()
		If (_soundTrayVisible) Then
			Local globalVolume:Int = FlxU.Round(FlxG.Volume() * 10)
			If (FlxG.Mute) globalVolume = 0

			PushMatrix()

			Transform(FlxG._DeviceScaleFactorX, 0, 0, FlxG._DeviceScaleFactorY, ( (FlxG.DeviceWidth - _soundTrayWidth * FlxG._DeviceScaleFactorX) * 0.5), _soundTrayY)

			If (FlxG._LastDrawingAlpha <> .5) Then
				SetAlpha(.5)
				FlxG._LastDrawingAlpha = .5
			End If

			If (FlxG._LastDrawingColor <> FlxG.BLACK) Then
				SetColor(0, 0, 0)
			End If

			DrawRect(0, 0, _soundTrayWidth, _soundTrayHeight)

			SetColor(255, 255, 255)
			FlxG._LastDrawingColor = FlxG.WHITE

			Local bx:Int = 20
			Local by:Int = 28

			Local i:Int = 0
			While (i < 10)
				If (i < globalVolume) Then
					If (FlxG._LastDrawingAlpha <> 1) Then
						SetAlpha(1)
						FlxG._LastDrawingAlpha = 1
					End If

				Else
					If (FlxG._LastDrawingAlpha <> .5) Then
						SetAlpha(.5)
						FlxG._LastDrawingAlpha = .5
					End If
				End If

				DrawRect(bx, by, 8, i * 2)

				bx += 12
				by -= 2
				i += 1
			Wend

			'_soundTrayLabel.Draw()

			PopMatrix()
		End If
	End Method

	Method _Reset:Void()
		If (FlxG.Framerate <> _framerate Or FlxG.Updaterate <> _updaterate) Then
			_ResetFramerate()
		End If

		Seed = SystemMillisecs()
		FlxG.UpdateDevice()
	End Method

	Method _ResetFramerate:Void()
		SetUpdateRate(FlxG.Framerate)

		_step = 1000.0 / FlxG.Updaterate
		_maxAccumulation = 2000.0 / FlxG.Framerate - 1

		If (_maxAccumulation < _step) Then
			_maxAccumulation = _step
		End If

		_updaterate = FlxG.Updaterate
		_framerate = FlxG.Framerate
	End Method

	Method _InitData:Void()
		FlxAssetsManager.Init()

		Local minSystemFontSize:Int = 8
		Local maxSystemFontSize:Int = 24
		Local fontPathPrefix:String = FlxText.SYSTEM_FONT + "_font_"

		Local system:FlxFont = 	FlxAssetsManager.AddFont(FlxText.SYSTEM_FONT)

		For Local size:Int = minSystemFontSize To maxSystemFontSize
			system.SetPath(size, fontPathPrefix + Min(size, 17) +  FlxG.DATA_SUFFIX + ".png")
		Next

		FlxAssetsManager.AddImage("default" + FlxG.DATA_SUFFIX, "default" +  FlxG.DATA_SUFFIX + ".png")
		FlxAssetsManager.AddImage("button" + FlxG.DATA_SUFFIX, "button" + FlxG.DATA_SUFFIX + ".png")
		FlxAssetsManager.AddImage(FlxTilemap.AUTOTILES, FlxTilemap.AUTOTILES + ".png")
		FlxAssetsManager.AddImage(FlxTilemap.AUTOTILES_ALT, FlxTilemap.AUTOTILES_ALT + ".png")
		FlxAssetsManager.AddCursor("cursor" + FlxG.DATA_SUFFIX, "cursor" + FlxG.DATA_SUFFIX + ".png")

		FlxAssetsManager.AddSound("beep" + FlxG.DATA_SUFFIX, "beep" + FlxG.DATA_SUFFIX + "." + FlxSound.GetValidExt())

		Local contentInitTrigger:FunctionInfo

		For Local classInfo:ClassInfo = EachIn GetClasses()
			contentInitTrigger = classInfo.GetFunction("OnContentInit",[], False)
			If (contentInitTrigger <> Null) contentInitTrigger.Invoke([])
		Next

		Self.OnContentInit()
	End Method
End Class

Private
Class FlxFPSCounter

	Field FPS:Int

Private
	Field _FPSCounter:Int

	Field _lastUpdateTime:Int

Public
	Method New()
		Reset()
	End Method

	Method Update:Void()
		If (_lastUpdateTime = 0) Then
			_lastUpdateTime = Millisecs()
		ElseIf (Millisecs() - _lastUpdateTime >= 1000) Then
			FPS = _FPSCounter
			_lastUpdateTime = Millisecs()
			_FPSCounter = 0
		End If

		_FPSCounter += 1
	End Method

	Method Reset:Void()
		FPS = FlxG.Framerate
		_FPSCounter = 0
		_lastUpdateTime = 0
	End Method

End Class

Strict

Import mojo
Import reflection

Import flxextern
Import flxbasic
Import flxstate
Import flxcamera
Import flxtimer
Import flxtext
Import flxg
Import flxu
Import flxtilemap
Import system.flxassetsmanager
Import system.flxfont
Import system.flxdebugger
Import system.flxreplay
Import plugin.timermanager

#If TARGET = "xna" Or TARGET = "psm"
	Import "data/beep_flx.wav"
#ElseIf TARGET = "html5"
	Import "data/beep_flx.ogg"
	Import "data/beep_flx.mp3"
#ElseIf TARGET = "glfw"
	Import "data/beep_flx.ogg"
#Else
	Import "data/beep_flx.mp3"
#End

Class FlxGame extends App

	Field useSoundHotKeys:Bool

	Field useSystemCursor:Bool

	Field forceDebugger:Bool

	Field _state:FlxState

	Field _requestedState:FlxState

	Field _requestedReset:Bool

	Field _debugger:FlxDebugger

	Field _debuggerUp:Bool

	Field _replay:FlxReplay

	Field _replayRequested:Bool

	Field _recordingRequested:Bool

	Field _replaying:Bool

	Field _recording:Bool

	Field _replayCancelKeys:Int[]

	Field _replayTimer:Float

	Field _replayCallback:FlxReplayListener

Private
	Field _iState:ClassInfo

	Field _created:Bool

	Field _total:Int

	Field _accumulator:Float

	Field _step:Float

	Field _maxAccumulation:Float

	Field _soundTrayTimer:Float

	Field _soundTrayWidth:Float

	Field _soundTrayHeight:Float

	Field _soundTrayX:Float

	Field _soundTrayY:Float

	Field _soundTrayVisible:Bool

	Field _soundTrayLabel:FlxText

	Field _updaterate:Int

	Field _framerate:Int

Public
	Method New(gameSizeX:Int, gameSizeY:Int, initialState:ClassInfo, zoom:Float = 1, updaterate:Int = 60, framerate:Int = 30, useSystemCursor:Bool = False)
		Local classObject:GlobalInfo

		For Local classInfo:ClassInfo = EachIn GetClasses()
			classObject = classInfo.GetGlobal("ClassObject", False)
			If (classObject = Null) classObject = classInfo.GetGlobal("ClassInfo", False)

			If (classObject <> Null) Then
				classObject.SetValue(classInfo)
			End If
		Next

		_soundTrayTimer = 0
		_soundTrayWidth = 160
		_soundTrayHeight = 45
		_soundTrayX = 0
		_soundTrayY	= -_soundTrayHeight
		_soundTrayVisible = False

		FlxG.Init(Self, gameSizeX, gameSizeY, zoom)
		FlxG.Framerate = framerate
		FlxG.Updaterate = updaterate
		_total = 0

		_state = Null

		useSoundHotKeys = Not IsMobile()
		Self.useSystemCursor = useSystemCursor

		If (Not useSystemCursor) HideMouse()

		forceDebugger = False
		_debuggerUp = False

		_replay = New FlxReplay()
		_replayRequested = False
		_recordingRequested = False
		_replaying = False
		_recording = False

		_iState = initialState
		_requestedState = Null
		_requestedReset = True
		_created = False
	End Method

	Method OnCreate:Int()
		_InitData()
		_Step()

		#Rem
		_soundTrayLabel = New FlxText(10, 32, _soundTrayWidth, "VOLUME")
		_soundTrayLabel.SetFormat(FlxText.SYSTEM_FONT, 16, FlxG.WHITE, FlxText.ALIGN_CENTER)
		#End
		Return 0
	End Method

	Method OnUpdate:Int()
		FlxG.UpdateDevice()

	#If TARGET <> "ios" And TARGET <> "android" And TARGET <> "psm"
		If (useSoundHotKeys) Then
			If (KeyHit(KEY_0)) Then
				FlxG.Mute = Not FlxG.Mute

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

				_ShowSoundTray()
			End If

			If (KeyHit(KEY_MINUS)) Then
				FlxG.Mute = False
				FlxG.Volume(FlxG.Volume() - .1)
				_ShowSoundTray()
			End If

			If (KeyHit(KEY_EQUALS)) Then
				FlxG.Mute = False
				FlxG.Volume(FlxG.Volume() + .1)
				_ShowSoundTray()
			End If
		End If

		_UpdateSoundTray(_step)
	#End

		Return 0
	End Method

	Method OnRender:Int()
		If (FlxG.Framerate <> _framerate Or FlxG.Updaterate <> _updaterate) Then
			_ResetFramerate()
		End If

		Local mark:Int = Millisecs()
		Local elapsedMS:Int = mark - _total
		_total = mark

		If (_debugger <> Null) Then
			'TODO!
		Else
			_accumulator += elapsedMS

			if (_accumulator > _maxAccumulation) Then
				_accumulator = _maxAccumulation
			End If

			While (_accumulator >= _step)
				_Step()
				_accumulator -= _step
			Wend
		End If

		FlxBasic._VisibleCount = 0

		_Draw()

		Return 0
	End Method

	Method OnSuspend:Int()
		ShowMouse()
		FlxG.PauseSounds()
		Return 0
	End Method

	Method OnResume:Int()
		If (Not _debuggerUp And Not useSystemCursor) Then
			HideMouse()
		End If

		FlxG.ResetInput()
		FlxG.ResumeSounds()
		Return 0
	End Method

	Method OnContentInit:Void()
	End Method

Private
	Method _ShowSoundTray:Void(silent:Bool = False)
		If (Not silent) Then
			FlxG.Play("beep" + FlxG.DATA_SUFFIX)
		End If

		_soundTrayTimer = 1
		_soundTrayY = 0
		_soundTrayVisible = True
	End Method

	Method _SwitchState:Void()
		FlxG.ResetCameras()
		FlxG.ResetInput()
		FlxG.DestroySounds()

		If (_debugger <> Null) Then
			'TODO!
		End If

		Local timeManager:TimerManager = FlxTimer.Manager()
		If (timeManager <> Null) timeManager.Clear()

		If (_state <> Null) _state.Destroy()

		_state = _requestedState
		_state.Create()

		If (FlxG.Framerate <> _framerate Or FlxG.Updaterate <> _updaterate) Then
			_ResetFramerate()
		End If
	End Method

	Method _Step:Void()
		If (_requestedReset) Then
			_requestedReset = False
			_requestedState = FlxState(_iState.NewInstance())
			_replayTimer = 0
			_replayCancelKeys = []
			_Reset()
			FlxG.Reset()
		End If

		If (_recordingRequested) Then
			_recordingRequested = False
			_replay.Create(FlxG.GlobalSeed)
			_recording = True

			If (_debugger <> Null) Then
				'TODO
				FlxG.Log("FLIXEL: starting new flixel gameplay record.")
			End If

		ElseIf (_replayRequested)
			_replayRequested = False
			_replay.Rewind()
			FlxG.GlobalSeed = _replay.seed

			If (_debugger <> Null) Then
				'TODO
			End If

			_replaying = True
		End If

		If (_state <> _requestedState) _SwitchState()

		FlxBasic._ActiveCount = 0

		If (_replaying) Then
			If (_replayCancelKeys.Length() > 0 And Not KeyDown(192) And Not KeyDown(220)) Then
				Local i:Int = 0
				Local l:Int = _replayCancelKeys.Length()

				While (i < l)
					If (KeyDown(_replayCancelKeys[i])) Then
						If (_replayCallback <> Null) Then
							_replayCallback.OnReplayComplete()
							_replayCallback = Null
						Else
							FlxG.StopReplay()
						End If

						Exit
					End If

					i += 1
				Wend
			End If

			_replay.PlayNextFrame()

			If (_replayTimer > 0) Then
				_replayTimer -= _step

				If (_replayTimer <= 0) Then
					If (_replayCallback <> Null) Then
						_replayCallback.OnReplayComplete()
						_replayCallback = Null
					Else
						FlxG.StopReplay()
					End If
				End If
			End If

			If (_replaying And _replay.finished) Then
				FlxG.StopReplay()

				If (_replayCallback <> Null) Then
					_replayCallback.OnReplayComplete()
					_replayCallback = Null
				End If
			End If

			If (_debugger <> Null) Then
				'TODO
			End If
		Else
			FlxG.UpdateInput()
		End If

		If (_recording) Then
			_replay.RecordFrame()

			If (_debugger <> Null) Then
				'TODO
			End If
		End If

		_Update()

		If (_debugger <> Null) Then
			'TODO
		End If
	End Method

	Method _Update:Void()
		FlxG.Elapsed = FlxG.TimeScale * (_step / 1000.0)

		FlxG.UpdateSounds()
		FlxG.UpdatePlugins()
		_state.Update()

		If (FlxG.Tweener.active And FlxG.Tweener.HasTween) Then
			FlxG.Tweener.UpdateTweens()
		End If

		FlxG.UpdateCameras()

		If (_debuggerUp) Then
			'TODO!
		End If
	End Method

	Method _UpdateSoundTray:Void(ms:Int)
		If (Not _soundTrayVisible) Return

		If (_soundTrayTimer > 0) Then
			_soundTrayTimer -= ms / 1000.0

		ElseIf (_soundTrayY > -_soundTrayHeight)
			_soundTrayY =_soundTrayY - (ms / 1000.0 * FlxG.Height * 2)

			If (_soundTrayY <= -_soundTrayHeight) Then
				_soundTrayVisible = False

				'TODO Save sound settings
			End If
		End If
	End Method

	Method _Draw:Void()
		Cls(FlxG._BgColor.r, FlxG._BgColor.g, FlxG._BgColor.b)

	#If TARGET <> "ios" And TARGET <> "android" And TARGET <> "psm"
		If( Not FlxG.Mobile) Then
			PushMatrix()
		End If
	#End

		Translate(FlxG._DeviceOffsetX, FlxG._DeviceOffsetY)
		Scale(FlxG._DeviceScaleFactorX, FlxG._DeviceScaleFactorY)

		FlxG._LastDrawingColor = FlxG.WHITE
		FlxG._LastDrawingBlend = GetBlend()
		FlxG._LastDrawingAlpha = GetAlpha()
		FlxG._CurrentCamera = Null

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

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

			If (Not FlxG._CurrentCamera.active) Then
				i+=1
				Continue
			End If

			If (FlxG._CurrentCamera = Null Or Not FlxG._CurrentCamera.exists Or Not FlxG._CurrentCamera.visible) Continue

			FlxG._CurrentCamera.DrawFX() 'not realy draw. Only calculation
			FlxG._CurrentCamera.Lock()
			_state.Draw()
			FlxG.DrawPlugins()
			FlxG._CurrentCamera.Unlock()

			i+=1
		Wend

	#If TARGET <> "ios" And TARGET <> "android" And TARGET <> "psm"
		If( Not FlxG.Mobile) Then
			PopMatrix()
			_DrawSoundTray()
			FlxG.Mouse.Draw()
		End If
	#End
	End Method

	Method _DrawSoundTray:Void()
		If (_soundTrayVisible) Then
			Local globalVolume:Int = FlxU.Round(FlxG.Volume() * 10)
			If (FlxG.Mute) globalVolume = 0

			PushMatrix()

			Transform(FlxG._DeviceScaleFactorX, 0, 0, FlxG._DeviceScaleFactorY, ( (FlxG.DeviceWidth - _soundTrayWidth * FlxG._DeviceScaleFactorX) * 0.5), _soundTrayY)

			If (FlxG._LastDrawingAlpha <> .5) Then
				SetAlpha(.5)
				FlxG._LastDrawingAlpha = .5
			End If

			If (FlxG._LastDrawingColor <> FlxG.BLACK) Then
				SetColor(0, 0, 0)
			End If

			DrawRect(0, 0, _soundTrayWidth, _soundTrayHeight)

			SetColor(255, 255, 255)
			FlxG._LastDrawingColor = FlxG.WHITE

			Local bx:Int = 20
			Local by:Int = 28

			Local i:Int = 0
			While (i < 10)
				If (i < globalVolume) Then
					If (FlxG._LastDrawingAlpha <> 1) Then
						SetAlpha(1)
						FlxG._LastDrawingAlpha = 1
					End If

				Else
					If (FlxG._LastDrawingAlpha <> .5) Then
						SetAlpha(.5)
						FlxG._LastDrawingAlpha = .5
					End If
				End If

				DrawRect(bx, by, 8, i * 2)

				bx += 12
				by -= 2
				i += 1
			Wend

			'_soundTrayLabel.Draw()

			PopMatrix()
		End If
	End Method

	Method _Reset:Void()
		If (FlxG.Framerate <> _framerate Or FlxG.Updaterate <> _updaterate) Then
			_ResetFramerate()
		End If

		Seed = SystemMillisecs()
		FlxG.UpdateDevice()
	End Method

	Method _ResetFramerate:Void()
		SetUpdateRate(FlxG.Framerate)

		_step = 1000.0 / FlxG.Updaterate
		_maxAccumulation = 2000.0 / FlxG.Framerate - 1

		If (_maxAccumulation < _step) Then
			_maxAccumulation = _step
		End If

		_updaterate = FlxG.Updaterate
		_framerate = FlxG.Framerate
	End Method

	Method _InitData:Void()
		FlxAssetsManager.Init()

		Local minSystemFontSize:Int = 8
		Local maxSystemFontSize:Int = 24
		Local fontPathPrefix:String = FlxText.SYSTEM_FONT + "_font_"

		Local system:FlxFont = 	FlxAssetsManager.AddFont(FlxText.SYSTEM_FONT)

		For Local size:Int = minSystemFontSize To maxSystemFontSize
			system.SetPath(size, fontPathPrefix + Min(size, 17) +  FlxG.DATA_SUFFIX + ".png")
		Next

		FlxAssetsManager.AddImage("default" + FlxG.DATA_SUFFIX, "default" +  FlxG.DATA_SUFFIX + ".png")
		FlxAssetsManager.AddImage("button" + FlxG.DATA_SUFFIX, "button" + FlxG.DATA_SUFFIX + ".png")
		FlxAssetsManager.AddImage(FlxTilemap.AUTOTILES, FlxTilemap.AUTOTILES + ".png")
		FlxAssetsManager.AddImage(FlxTilemap.AUTOTILES_ALT, FlxTilemap.AUTOTILES_ALT + ".png")
		FlxAssetsManager.AddCursor("cursor" + FlxG.DATA_SUFFIX, "cursor" + FlxG.DATA_SUFFIX + ".png")

		FlxAssetsManager.AddSound("beep" + FlxG.DATA_SUFFIX, "beep" + FlxG.DATA_SUFFIX + "." + FlxSound.GetValidExt())

		Self.OnContentInit()
	End Method
End Class

Private
Class FlxFPSCounter

	Field FPS:Int

Private
	Field _FPSCounter:Int

	Field _lastUpdateTime:Int

Public
	Method New()
		Reset()
	End Method

	Method Update:Void()
		If (_lastUpdateTime = 0) Then
			_lastUpdateTime = Millisecs()
		ElseIf (Millisecs() - _lastUpdateTime >= 1000) Then
			FPS = _FPSCounter
			_lastUpdateTime = Millisecs()
			_FPSCounter = 0
		End If

		_FPSCounter += 1
	End Method

	Method Reset:Void()
		FPS = FlxG.Framerate
		_FPSCounter = 0
		_lastUpdateTime = 0
	End Method

End Class