Added Uncle's Date functions for Android and HTML5: * GetDayOfMonth * GetDayOfWeek * GetHours * GetMilliSeconds * GetMinutes * GetMonth * GetSeconds

#if HOST="macos" And TARGET="glfw"
	Import "native/diddy.${TARGET}.mac.${LANG}"
#else
	Import "native/diddy.${TARGET}.${LANG}"
#end
Import mojo
Import framework
Import assert

Extern

	#If LANG="cpp" Then
		Function RealMillisecs:Int() = "diddy::systemMillisecs"
		Function FlushKeys:Void() = "diddy::flushKeys"
		Function HideMouse:Void() = "diddy::hideMouse"
		Function ShowMouse:Void() = "diddy::showMouse"
		Function GetUpdateRate:Int() = "diddy::getUpdateRate"
		Function SetMouse:Void(x:Int, y:Int) = "diddy::setMouse"
		Function ShowKeyboard:Void() = "diddy::showKeyboard"
		Function LaunchBrowser:Void(address:String) = "diddy::launchBrowser"
		Function LaunchEmail:Void(email:String, subject:String, text:String) = "diddy::launchEmail"
		Function SetNativeGraphicsSize:Void(w:Int, h:Int) = "diddy::setGraphics"
		Function RealMod:Float(value:Float, amount:Float) = "diddy::realMod"
		Function StartVibrate:Void(millisec:Int) = "diddy::startVibrate"
		Function StopVibrate:Void() = "diddy::stopVibrate"
		Function GetDayOfMonth:Int()="diddy::getDayOfMonth"
		Function GetDayOfWeek:Int()="diddy::getDayOfWeek"
		Function GetMonth:Int()="diddy::getMonth"
		Function GetYear:Int()="diddy::getYear"
		Function GetHours:Int()="diddy::getHours"
		Function GetMinutes:Int()="diddy::getMinutes"
		Function GetSeconds:Int()="diddy::getSeconds"
		Function GetMilliSeconds:Int()="diddy::getMilliSeconds"
	#Else
		Function RealMillisecs:Int() = "diddy.systemMillisecs"
		Function FlushKeys:Void() = "diddy.flushKeys"
		Function HideMouse:Void() = "diddy.hideMouse"
		Function ShowMouse:Void() = "diddy.showMouse"
		Function GetUpdateRate:Int() = "diddy.getUpdateRate"
		Function SetMouse:Void(x:Int, y:Int) = "diddy.setMouse"
		Function ShowKeyboard:Void() = "diddy.showKeyboard"
		Function LaunchBrowser:Void(address:String) = "diddy.launchBrowser"
		Function LaunchEmail:Void(email:String, subject:String, text:String) = "diddy.launchEmail"
		Function SetNativeGraphicsSize:Void(w:Int, h:Int) = "diddy.setGraphics"
		Function RealMod:Float(value:Float, amount:Float) = "diddy.realMod"
		Function StartVibrate:Void(millisec:Int) = "diddy.startVibrate"
		Function StopVibrate:Void() = "diddy.stopVibrate"
		Function GetDayOfMonth:Int()="diddy.getDayOfMonth"
		Function GetDayOfWeek:Int()="diddy.getDayOfWeek"
		Function GetMonth:Int()="diddy.getMonth"
		Function GetYear:Int()="diddy.getYear"
		Function GetHours:Int()="diddy.getHours"
		Function GetMinutes:Int()="diddy.getMinutes"
		Function GetSeconds:Int()="diddy.getSeconds"
		Function GetMilliSeconds:Int()="diddy.getMilliSeconds"
	#End

	#If LANG="java" Then
		Function BuildString:String(arr:Int[], offset:Int, length:Int) = "diddy.buildString"
	#End
Public

#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

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()
	Error ""
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 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

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

' TODO: allow encoding of Int[]
' TODO: enabling lineWrap will wrap the string every lineWrapWidth characters (defaults to 80)
' enabling padOutput will pad the ending of the output with equals characters (=) to next 4-byte boundary
Function EncodeBase64:String(src:String, padOutput:Bool=False, lineWrap:Bool=False, lineWrapWidth:Int=80)
	Local rv:String = ""
	Local s1:Int, s2:Int, s3:Int, a:Int, b:Int, c:Int, d:Int, i:Int
	If src.Length = 0 Then Return ""
	Repeat
		s1 = src[i]
		If i+1 < src.Length Then s2 = src[i+1] Else s2 = 0
		If i+2 < src.Length Then s3 = src[i+2] Else s3 = 0
		a = s1 Shr 2
		b = ((s1 & 3) Shl 4) | (s2 Shr 4)
		c = ((s2 & 15) Shl 2) | (s3 Shr 6)
		d = s3 & 63
		If i+1 >= src.Length Then c = 64
		If i+2 >= src.Length Then d = 64
		rv += BASE64_CHARS[a..a+1] + BASE64_CHARS[b..b+1]
		If c < 64 Or padOutput Then rv += BASE64_CHARS[c..c+1]
		If d < 64 Or padOutput Then rv += BASE64_CHARS[d..d+1]
		i += 3
	Until i >= src.Length
	Return rv
End

' TODO: convert this to use BASE64_ARRAY
' I'll do this once Monkey has a decent string builder class.
Function DecodeBase64:String(src:String)
	Local rv:String = "", src2:String = ""
	Local s1:Int, s2:Int, s3:Int, a:Int, b:Int, c:Int, d:Int, i:Int
	' remove any non-base64 characters
	For i = 0 Until src.Length
		If BASE64_CHARS.Find(src[i..i+1]) >= 0 Then src2 += src[i..i+1]
	Next
	If src2.Length = 0 Then Return ""
	i = 0
	Repeat
		a = BASE64_CHARS.Find(src2[i..i+1])
		If i+1 > src2.Length Then Exit ' This shouldn't happen with base64, so something's wrong!
		b = BASE64_CHARS.Find(src2[i+1..i+2])
		If i+2 < src2.Length Then c = BASE64_CHARS.Find(src2[i+2..i+3]) Else c = 64
		If i+3 < src2.Length Then d = BASE64_CHARS.Find(src2[i+3..i+4]) Else d = 64
		s1 = (a Shl 2) | (b Shr 4)
		s2 = ((b & 15) Shl 4) | (c Shr 2)
		s3 = ((c & 3) Shl 6) | d
		rv += String.FromChar(s1)
		If c <> 64 Then rv += String.FromChar(s2)
		If d <> 64 Then rv += String.FromChar(s3)
		i += 4
	Until i >= src2.Length
	Return rv
End

Function DecodeBase64Bytes:Int[](src:String)
	InitBase64()
	Local a:Int, b:Int, c:Int, d:Int, i:Int, j:Int
	Local src2:Int[] = New Int[src.Length]
	Local padding:Int = 0

	' find out how many base64 characters
	Local srclen:Int = 0
	For i = 0 Until src.Length
		If BASE64_ARRAY[src[i]] >= 0 Then
			src2[srclen] = src[i]
			srclen += 1
			' check if it's a padding character and increment the count
			If BASE64_ARRAY[src[i]] = 64 Then padding += 1
		End
	Next

	' die if there are no base64 chars
	If srclen = 0 Then Return []

	' get the target length and create the array
	Local len:Int = 3*(srclen/4)
	If srclen Mod 4 = 0 Then
		len -= padding
	ElseIf padding = 0 Then
		If srclen Mod 4 >= 2 Then len += 1
		If srclen Mod 4 = 3 Then len += 1
	End
	Local rv:Int[] = New Int[len]

	i = 0
	j = 0
	Repeat
		a = BASE64_ARRAY[src2[i]]
		If i+1 > srclen Then Exit ' This shouldn't happen with base64, so something's wrong!
		b = BASE64_ARRAY[src2[i+1]]
		If i+2 < srclen Then c = BASE64_ARRAY[src2[i+2]] Else c = 64
		If i+3 < srclen Then d = BASE64_ARRAY[src2[i+3]] Else d = 64
		rv[j] = (a Shl 2) | (b Shr 4)
		If j+1 < len Then rv[j+1] = ((b & 15) Shl 4) | (c Shr 2)
		If j+2 < len Then rv[j+2] = ((c & 3) Shl 6) | d
		i += 4
		j += 3
	Until i >= srclen
	Return rv
End

Function InitBase64:Void()
	If BASE64_ARRAY.Length = 0 Then
		BASE64_ARRAY = New Int[256]
		Local i% = 0
		For i = 0 Until BASE64_ARRAY.Length
			BASE64_ARRAY[i] = -1
		Next
		For i = 0 Until BASE64_CHARS.Length
			BASE64_ARRAY[BASE64_CHARS[i]] = i
		Next
	End
End

' constants

Const BASE64_CHARS:String = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
Global BASE64_ARRAY:Int[] = []

' control characters
Const ASC_NUL:Int = 0       ' Null character
Const ASC_SOH:Int = 1       ' Start of Heading
Const ASC_STX:Int = 2       ' Start of Text
Const ASC_ETX:Int = 3       ' End of Text
Const ASC_EOT:Int = 4       ' End of Transmission
Const ASC_ENQ:Int = 5       ' Enquiry
Const ASC_ACK:Int = 6       ' Acknowledgment
Const ASC_BEL:Int = 7       ' Bell
Const ASC_BACKSPACE:Int = 8 ' Backspace
Const ASC_TAB:Int = 9       ' Horizontal tab
Const ASC_LF:Int = 10       ' Linefeed
Const ASC_VTAB:Int = 11     ' Vertical tab
Const ASC_FF:Int = 12       ' Form feed
Const ASC_CR:Int = 13       ' Carriage return
Const ASC_SO:Int = 14       ' Shift Out
Const ASC_SI:Int = 15       ' Shift In
Const ASC_DLE:Int = 16      ' Data Line Escape
Const ASC_DC1:Int = 17      ' Device Control 1
Const ASC_DC2:Int = 18      ' Device Control 2
Const ASC_DC3:Int = 19      ' Device Control 3
Const ASC_DC4:Int = 20      ' Device Control 4
Const ASC_NAK:Int = 21      ' Negative Acknowledgment
Const ASC_SYN:Int = 22      ' Synchronous Idle
Const ASC_ETB:Int = 23      ' End of Transmit Block
Const ASC_CAN:Int = 24      ' Cancel
Const ASC_EM:Int = 25       ' End of Medium
Const ASC_SUB:Int = 26      ' Substitute
Const ASC_ESCAPE:Int = 27   ' Escape
Const ASC_FS:Int = 28       ' File separator
Const ASC_GS:Int = 29       ' Group separator
Const ASC_RS:Int = 30       ' Record separator
Const ASC_US:Int = 31       ' Unit separator

' visible characters
Const ASC_SPACE:Int = 32                ' '
Const ASC_EXCLAMATION:Int = 33          '!'
Const ASC_DOUBLE_QUOTE:Int = 34         '"'
Const ASC_HASH:Int = 35                 '#'
Const ASC_DOLLAR:Int = 36               '$'
Const ASC_PERCENT:Int = 37              '%'
Const ASC_AMPERSAND:Int = 38            '&'
Const ASC_SINGLE_QUOTE:Int = 39         '''
Const ASC_OPEN_PARENTHESIS:Int = 40     '('
Const ASC_CLOSE_PARENTHESIS:Int = 41    ')'
Const ASC_ASTERISK:Int = 42             '*'
Const ASC_PLUS:Int = 43                 '+'
Const ASC_COMMA:Int = 44                ','
Const ASC_HYPHEN:Int = 45               '-'
Const ASC_PERIOD:Int = 46               '.'
Const ASC_SLASH:Int = 47                '/'
Const ASC_0:Int = 48
Const ASC_1:Int = 49
Const ASC_2:Int = 50
Const ASC_3:Int = 51
Const ASC_4:Int = 52
Const ASC_5:Int = 53
Const ASC_6:Int = 54
Const ASC_7:Int = 55
Const ASC_8:Int = 56
Const ASC_9:Int = 57
Const ASC_COLON:Int = 58        ':'
Const ASC_SEMICOLON:Int = 59    ';'
Const ASC_LESS_THAN:Int = 60    '<'
Const ASC_EQUALS:Int = 61       '='
Const ASC_GREATER_THAN:Int = 62 '>'
Const ASC_QUESTION:Int = 63     '?'
Const ASC_AT:Int = 64           '@'
Const ASC_UPPER_A:Int = 65
Const ASC_UPPER_B:Int = 66
Const ASC_UPPER_C:Int = 67
Const ASC_UPPER_D:Int = 68
Const ASC_UPPER_E:Int = 69
Const ASC_UPPER_F:Int = 70
Const ASC_UPPER_G:Int = 71
Const ASC_UPPER_H:Int = 72
Const ASC_UPPER_I:Int = 73
Const ASC_UPPER_J:Int = 74
Const ASC_UPPER_K:Int = 75
Const ASC_UPPER_L:Int = 76
Const ASC_UPPER_M:Int = 77
Const ASC_UPPER_N:Int = 78
Const ASC_UPPER_O:Int = 79
Const ASC_UPPER_P:Int = 80
Const ASC_UPPER_Q:Int = 81
Const ASC_UPPER_R:Int = 82
Const ASC_UPPER_S:Int = 83
Const ASC_UPPER_T:Int = 84
Const ASC_UPPER_U:Int = 85
Const ASC_UPPER_V:Int = 86
Const ASC_UPPER_W:Int = 87
Const ASC_UPPER_X:Int = 88
Const ASC_UPPER_Y:Int = 89
Const ASC_UPPER_Z:Int = 90
Const ASC_OPEN_BRACKET:Int = 91     '['
Const ASC_BACKSLASH:Int = 92        '\'
Const ASC_CLOSE_BRACKET:Int = 93    ']'
Const ASC_CIRCUMFLEX:Int = 94       '^'
Const ASC_UNDERSCORE:Int = 95       '_'
Const ASC_BACKTICK:Int = 96         '`'
Const ASC_LOWER_A:Int = 97
Const ASC_LOWER_B:Int = 98
Const ASC_LOWER_C:Int = 99
Const ASC_LOWER_D:Int = 100
Const ASC_LOWER_E:Int = 101
Const ASC_LOWER_F:Int = 102
Const ASC_LOWER_G:Int = 103
Const ASC_LOWER_H:Int = 104
Const ASC_LOWER_I:Int = 105
Const ASC_LOWER_J:Int = 106
Const ASC_LOWER_K:Int = 107
Const ASC_LOWER_L:Int = 108
Const ASC_LOWER_M:Int = 109
Const ASC_LOWER_N:Int = 110
Const ASC_LOWER_O:Int = 111
Const ASC_LOWER_P:Int = 112
Const ASC_LOWER_Q:Int = 113
Const ASC_LOWER_R:Int = 114
Const ASC_LOWER_S:Int = 115
Const ASC_LOWER_T:Int = 116
Const ASC_LOWER_U:Int = 117
Const ASC_LOWER_V:Int = 118
Const ASC_LOWER_W:Int = 119
Const ASC_LOWER_X:Int = 120
Const ASC_LOWER_Y:Int = 121
Const ASC_LOWER_Z:Int = 122
Const ASC_OPEN_BRACE:Int = 123  '{'
Const ASC_PIPE:Int = 124        '|'
Const ASC_CLOSE_BRACE:Int = 125 '}'
Const ASC_TILDE:Int = 126       '~'
Const ASC_DELETE:Int = 127

#if HOST="macos" And TARGET="glfw"
	Import "native/diddy.${TARGET}.mac.${LANG}"
#else
	Import "native/diddy.${TARGET}.${LANG}"
#end
Import mojo
Import framework
Import assert

Extern

	#If LANG="cpp" Then
		Function RealMillisecs:Int() = "diddy::systemMillisecs"
		Function FlushKeys:Void() = "diddy::flushKeys"
		Function HideMouse:Void() = "diddy::hideMouse"
		Function ShowMouse:Void() = "diddy::showMouse"
		Function GetUpdateRate:Int() = "diddy::getUpdateRate"
		Function SetMouse:Void(x:Int, y:Int) = "diddy::setMouse"
		Function ShowKeyboard:Void() = "diddy::showKeyboard"
		Function LaunchBrowser:Void(address:String) = "diddy::launchBrowser"
		Function LaunchEmail:Void(email:String, subject:String, text:String) = "diddy::launchEmail"
		Function SetNativeGraphicsSize:Void(w:Int, h:Int) = "diddy::setGraphics"
		Function RealMod:Float(value:Float, amount:Float) = "diddy::realMod"
		Function StartVibrate:Void(millisec:Int) = "diddy::startVibrate"
		Function StopVibrate:Void() = "diddy::stopVibrate"
	#Else
		Function RealMillisecs:Int() = "diddy.systemMillisecs"
		Function FlushKeys:Void() = "diddy.flushKeys"
		Function HideMouse:Void() = "diddy.hideMouse"
		Function ShowMouse:Void() = "diddy.showMouse"
		Function GetUpdateRate:Int() = "diddy.getUpdateRate"
		Function SetMouse:Void(x:Int, y:Int) = "diddy.setMouse"
		Function ShowKeyboard:Void() = "diddy.showKeyboard"
		Function LaunchBrowser:Void(address:String) = "diddy.launchBrowser"
		Function LaunchEmail:Void(email:String, subject:String, text:String) = "diddy.launchEmail"
		Function SetNativeGraphicsSize:Void(w:Int, h:Int) = "diddy.setGraphics"
		Function RealMod:Float(value:Float, amount:Float) = "diddy.realMod"
		Function StartVibrate:Void(millisec:Int) = "diddy.startVibrate"
		Function StopVibrate:Void() = "diddy.stopVibrate"
	#End

	#If LANG="java" Then
		Function BuildString:String(arr:Int[], offset:Int, length:Int) = "diddy.buildString"
	#End
Public

#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

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()
	Error ""
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 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

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

' TODO: allow encoding of Int[]
' TODO: enabling lineWrap will wrap the string every lineWrapWidth characters (defaults to 80)
' enabling padOutput will pad the ending of the output with equals characters (=) to next 4-byte boundary
Function EncodeBase64:String(src:String, padOutput:Bool=False, lineWrap:Bool=False, lineWrapWidth:Int=80)
	Local rv:String = ""
	Local s1:Int, s2:Int, s3:Int, a:Int, b:Int, c:Int, d:Int, i:Int
	If src.Length = 0 Then Return ""
	Repeat
		s1 = src[i]
		If i+1 < src.Length Then s2 = src[i+1] Else s2 = 0
		If i+2 < src.Length Then s3 = src[i+2] Else s3 = 0
		a = s1 Shr 2
		b = ((s1 & 3) Shl 4) | (s2 Shr 4)
		c = ((s2 & 15) Shl 2) | (s3 Shr 6)
		d = s3 & 63
		If i+1 >= src.Length Then c = 64
		If i+2 >= src.Length Then d = 64
		rv += BASE64_CHARS[a..a+1] + BASE64_CHARS[b..b+1]
		If c < 64 Or padOutput Then rv += BASE64_CHARS[c..c+1]
		If d < 64 Or padOutput Then rv += BASE64_CHARS[d..d+1]
		i += 3
	Until i >= src.Length
	Return rv
End

' TODO: convert this to use BASE64_ARRAY
' I'll do this once Monkey has a decent string builder class.
Function DecodeBase64:String(src:String)
	Local rv:String = "", src2:String = ""
	Local s1:Int, s2:Int, s3:Int, a:Int, b:Int, c:Int, d:Int, i:Int
	' remove any non-base64 characters
	For i = 0 Until src.Length
		If BASE64_CHARS.Find(src[i..i+1]) >= 0 Then src2 += src[i..i+1]
	Next
	If src2.Length = 0 Then Return ""
	i = 0
	Repeat
		a = BASE64_CHARS.Find(src2[i..i+1])
		If i+1 > src2.Length Then Exit ' This shouldn't happen with base64, so something's wrong!
		b = BASE64_CHARS.Find(src2[i+1..i+2])
		If i+2 < src2.Length Then c = BASE64_CHARS.Find(src2[i+2..i+3]) Else c = 64
		If i+3 < src2.Length Then d = BASE64_CHARS.Find(src2[i+3..i+4]) Else d = 64
		s1 = (a Shl 2) | (b Shr 4)
		s2 = ((b & 15) Shl 4) | (c Shr 2)
		s3 = ((c & 3) Shl 6) | d
		rv += String.FromChar(s1)
		If c <> 64 Then rv += String.FromChar(s2)
		If d <> 64 Then rv += String.FromChar(s3)
		i += 4
	Until i >= src2.Length
	Return rv
End

Function DecodeBase64Bytes:Int[](src:String)
	InitBase64()
	Local a:Int, b:Int, c:Int, d:Int, i:Int, j:Int
	Local src2:Int[] = New Int[src.Length]
	Local padding:Int = 0

	' find out how many base64 characters
	Local srclen:Int = 0
	For i = 0 Until src.Length
		If BASE64_ARRAY[src[i]] >= 0 Then
			src2[srclen] = src[i]
			srclen += 1
			' check if it's a padding character and increment the count
			If BASE64_ARRAY[src[i]] = 64 Then padding += 1
		End
	Next

	' die if there are no base64 chars
	If srclen = 0 Then Return []

	' get the target length and create the array
	Local len:Int = 3*(srclen/4)
	If srclen Mod 4 = 0 Then
		len -= padding
	ElseIf padding = 0 Then
		If srclen Mod 4 >= 2 Then len += 1
		If srclen Mod 4 = 3 Then len += 1
	End
	Local rv:Int[] = New Int[len]

	i = 0
	j = 0
	Repeat
		a = BASE64_ARRAY[src2[i]]
		If i+1 > srclen Then Exit ' This shouldn't happen with base64, so something's wrong!
		b = BASE64_ARRAY[src2[i+1]]
		If i+2 < srclen Then c = BASE64_ARRAY[src2[i+2]] Else c = 64
		If i+3 < srclen Then d = BASE64_ARRAY[src2[i+3]] Else d = 64
		rv[j] = (a Shl 2) | (b Shr 4)
		If j+1 < len Then rv[j+1] = ((b & 15) Shl 4) | (c Shr 2)
		If j+2 < len Then rv[j+2] = ((c & 3) Shl 6) | d
		i += 4
		j += 3
	Until i >= srclen
	Return rv
End

Function InitBase64:Void()
	If BASE64_ARRAY.Length = 0 Then
		BASE64_ARRAY = New Int[256]
		Local i% = 0
		For i = 0 Until BASE64_ARRAY.Length
			BASE64_ARRAY[i] = -1
		Next
		For i = 0 Until BASE64_CHARS.Length
			BASE64_ARRAY[BASE64_CHARS[i]] = i
		Next
	End
End

' constants

Const BASE64_CHARS:String = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
Global BASE64_ARRAY:Int[] = []

' control characters
Const ASC_NUL:Int = 0       ' Null character
Const ASC_SOH:Int = 1       ' Start of Heading
Const ASC_STX:Int = 2       ' Start of Text
Const ASC_ETX:Int = 3       ' End of Text
Const ASC_EOT:Int = 4       ' End of Transmission
Const ASC_ENQ:Int = 5       ' Enquiry
Const ASC_ACK:Int = 6       ' Acknowledgment
Const ASC_BEL:Int = 7       ' Bell
Const ASC_BACKSPACE:Int = 8 ' Backspace
Const ASC_TAB:Int = 9       ' Horizontal tab
Const ASC_LF:Int = 10       ' Linefeed
Const ASC_VTAB:Int = 11     ' Vertical tab
Const ASC_FF:Int = 12       ' Form feed
Const ASC_CR:Int = 13       ' Carriage return
Const ASC_SO:Int = 14       ' Shift Out
Const ASC_SI:Int = 15       ' Shift In
Const ASC_DLE:Int = 16      ' Data Line Escape
Const ASC_DC1:Int = 17      ' Device Control 1
Const ASC_DC2:Int = 18      ' Device Control 2
Const ASC_DC3:Int = 19      ' Device Control 3
Const ASC_DC4:Int = 20      ' Device Control 4
Const ASC_NAK:Int = 21      ' Negative Acknowledgment
Const ASC_SYN:Int = 22      ' Synchronous Idle
Const ASC_ETB:Int = 23      ' End of Transmit Block
Const ASC_CAN:Int = 24      ' Cancel
Const ASC_EM:Int = 25       ' End of Medium
Const ASC_SUB:Int = 26      ' Substitute
Const ASC_ESCAPE:Int = 27   ' Escape
Const ASC_FS:Int = 28       ' File separator
Const ASC_GS:Int = 29       ' Group separator
Const ASC_RS:Int = 30       ' Record separator
Const ASC_US:Int = 31       ' Unit separator

' visible characters
Const ASC_SPACE:Int = 32                ' '
Const ASC_EXCLAMATION:Int = 33          '!'
Const ASC_DOUBLE_QUOTE:Int = 34         '"'
Const ASC_HASH:Int = 35                 '#'
Const ASC_DOLLAR:Int = 36               '$'
Const ASC_PERCENT:Int = 37              '%'
Const ASC_AMPERSAND:Int = 38            '&'
Const ASC_SINGLE_QUOTE:Int = 39         '''
Const ASC_OPEN_PARENTHESIS:Int = 40     '('
Const ASC_CLOSE_PARENTHESIS:Int = 41    ')'
Const ASC_ASTERISK:Int = 42             '*'
Const ASC_PLUS:Int = 43                 '+'
Const ASC_COMMA:Int = 44                ','
Const ASC_HYPHEN:Int = 45               '-'
Const ASC_PERIOD:Int = 46               '.'
Const ASC_SLASH:Int = 47                '/'
Const ASC_0:Int = 48
Const ASC_1:Int = 49
Const ASC_2:Int = 50
Const ASC_3:Int = 51
Const ASC_4:Int = 52
Const ASC_5:Int = 53
Const ASC_6:Int = 54
Const ASC_7:Int = 55
Const ASC_8:Int = 56
Const ASC_9:Int = 57
Const ASC_COLON:Int = 58        ':'
Const ASC_SEMICOLON:Int = 59    ';'
Const ASC_LESS_THAN:Int = 60    '<'
Const ASC_EQUALS:Int = 61       '='
Const ASC_GREATER_THAN:Int = 62 '>'
Const ASC_QUESTION:Int = 63     '?'
Const ASC_AT:Int = 64           '@'
Const ASC_UPPER_A:Int = 65
Const ASC_UPPER_B:Int = 66
Const ASC_UPPER_C:Int = 67
Const ASC_UPPER_D:Int = 68
Const ASC_UPPER_E:Int = 69
Const ASC_UPPER_F:Int = 70
Const ASC_UPPER_G:Int = 71
Const ASC_UPPER_H:Int = 72
Const ASC_UPPER_I:Int = 73
Const ASC_UPPER_J:Int = 74
Const ASC_UPPER_K:Int = 75
Const ASC_UPPER_L:Int = 76
Const ASC_UPPER_M:Int = 77
Const ASC_UPPER_N:Int = 78
Const ASC_UPPER_O:Int = 79
Const ASC_UPPER_P:Int = 80
Const ASC_UPPER_Q:Int = 81
Const ASC_UPPER_R:Int = 82
Const ASC_UPPER_S:Int = 83
Const ASC_UPPER_T:Int = 84
Const ASC_UPPER_U:Int = 85
Const ASC_UPPER_V:Int = 86
Const ASC_UPPER_W:Int = 87
Const ASC_UPPER_X:Int = 88
Const ASC_UPPER_Y:Int = 89
Const ASC_UPPER_Z:Int = 90
Const ASC_OPEN_BRACKET:Int = 91     '['
Const ASC_BACKSLASH:Int = 92        '\'
Const ASC_CLOSE_BRACKET:Int = 93    ']'
Const ASC_CIRCUMFLEX:Int = 94       '^'
Const ASC_UNDERSCORE:Int = 95       '_'
Const ASC_BACKTICK:Int = 96         '`'
Const ASC_LOWER_A:Int = 97
Const ASC_LOWER_B:Int = 98
Const ASC_LOWER_C:Int = 99
Const ASC_LOWER_D:Int = 100
Const ASC_LOWER_E:Int = 101
Const ASC_LOWER_F:Int = 102
Const ASC_LOWER_G:Int = 103
Const ASC_LOWER_H:Int = 104
Const ASC_LOWER_I:Int = 105
Const ASC_LOWER_J:Int = 106
Const ASC_LOWER_K:Int = 107
Const ASC_LOWER_L:Int = 108
Const ASC_LOWER_M:Int = 109
Const ASC_LOWER_N:Int = 110
Const ASC_LOWER_O:Int = 111
Const ASC_LOWER_P:Int = 112
Const ASC_LOWER_Q:Int = 113
Const ASC_LOWER_R:Int = 114
Const ASC_LOWER_S:Int = 115
Const ASC_LOWER_T:Int = 116
Const ASC_LOWER_U:Int = 117
Const ASC_LOWER_V:Int = 118
Const ASC_LOWER_W:Int = 119
Const ASC_LOWER_X:Int = 120
Const ASC_LOWER_Y:Int = 121
Const ASC_LOWER_Z:Int = 122
Const ASC_OPEN_BRACE:Int = 123  '{'
Const ASC_PIPE:Int = 124        '|'
Const ASC_CLOSE_BRACE:Int = 125 '}'
Const ASC_TILDE:Int = 126       '~'
Const ASC_DELETE:Int = 127


fix Appirater on non iOS targets

fix Appirater on non iOS targets

Strict

#If TARGET="ios"

' You need to include the following frameworks to your project:
'   * CFNetwork.framework
'   * SystemConfiguration.framework
'   * StoreKit.framework
'   * Appirater (=> https://github.com/arashpayan/appirater)

Private

Import "native/appirater.ios.cpp"

Extern

Class Appirater Abstract
    Function Launched:Void(canPrompt:Bool=True)="AppiraterBridge::Launched"
    Function EnteredForeground:Void(canPrompt:Bool=True)="AppiraterBridge::EnteredForeground"
    Function UserDidSignificantEvent:Void(canPrompt:Bool=True)="AppiraterBridge::UserDidSignificantEvent"
    Function SetAppId:Void(id:String)="AppiraterBridge::SetAppId"
    Function SetDaysUntilPrompt:Void(days:Int)="AppiraterBridge::SetDaysUntilPrompt"
    Function SetUsesUntilPrompt:Void(days:Int)="AppiraterBridge::SetUsesUntilPrompt"
    Function SetSignificantEventsUntilPrompt:Void(uses:Int)="AppiraterBridge::SetSignificantEventsUntilPrompt"
    Function SetTimeBeforeReminding:Void(time:Int)="AppiraterBridge::SetTimeBeforeReminding"
    Function SetDebug:Void(flag:Bool)="AppiraterBridge::SetDebug"
End

#Else

Private

Import bono

Public

Class Appirater Abstract
    Function Launched:Void(canPrompt:Bool=True)
        If Target.IS_DEBUG Then Print "[Appirater Launched:" + BoolToString(canPrompt) + "]"
    End

    Function EnteredForeground:Void(canPrompt:Bool=True)
        If Target.IS_DEBUG Then Print "[Appirater EnteredForeground:" + BoolToString(canPrompt) + "]"
    End

    Function UserDidSignificantEvent:Void(canPrompt:Bool=True)
        If Target.IS_DEBUG Then Print "[Appirater UserDidSignificantEvent:" + BoolToString(canPrompt) + "]"
    End

    Function SetAppId:Void(id:String)
        If Target.IS_DEBUG Then Print "[Appirater SetAppId:" + id + "]"
    End

    Function SetDaysUntilPrompt:Void(days:Int)
        If Target.IS_DEBUG Then Print "[Appirater SetDaysUntilPrompt:" + days + "]"
    End

    Function SetUsesUntilPrompt:Void(days:Int)
        If Target.IS_DEBUG Then Print "[Appirater SetUsesUntilPrompt:" + days + "]"
    End

    Function SetSignificantEventsUntilPrompt:Void(uses:Int)
        If Target.IS_DEBUG Then Print "[Appirater SetSignificantEventsUntilPrompt:" + uses + "]"
    End

    Function SetTimeBeforeReminding:Void(time:Int)
        If Target.IS_DEBUG Then Print "[Appirater SetTimeBeforeReminding:" + time + "]"
    End

    Function SetDebug:Void(flag:Bool)
        If Target.IS_DEBUG Then Print "[Appirater SetDebug:" + BoolToString(flag) + "]"
    End

    Private

    Function BoolToString:String(flag:Bool)
        If flag Then Return "YES"
        Return "NO"
    End
End

#End

Strict

#If TARGET="ios"

' You need to include the following frameworks to your project:
'   * CFNetwork.framework
'   * SystemConfiguration.framework
'   * StoreKit.framework
'   * Appirater (=> https://github.com/arashpayan/appirater)

Private

Import "native/appirater.ios.cpp"

Extern

Class Appirater Abstract
    Function Launched:Void(canPrompt:Bool=True)="AppiraterBridge::Launched"
    Function EnteredForeground:Void(canPrompt:Bool=True)="AppiraterBridge::EnteredForeground"
    Function UserDidSignificantEvent:Void(canPrompt:Bool=True)="AppiraterBridge::UserDidSignificantEvent"
    Function SetAppId:Void(id:String)="AppiraterBridge::SetAppId"
    Function SetDaysUntilPrompt:Void(days:Int)="AppiraterBridge::SetDaysUntilPrompt"
    Function SetUsesUntilPrompt:Void(days:Int)="AppiraterBridge::SetUsesUntilPrompt"
    Function SetSignificantEventsUntilPrompt:Void(uses:Int)="AppiraterBridge::SetSignificantEventsUntilPrompt"
    Function SetTimeBeforeReminding:Void(time:Int)="AppiraterBridge::SetTimeBeforeReminding"
    Function SetDebug:Void(flag:Bool)="AppiraterBridge::SetDebug"
End

#Else

Public

Class Appirater Abstract
    Function Launched:Void(canPrompt:Bool=True)
        If Target.IS_DEBUG Then Print "[Appirater Launched:" + canPrompt + "]"
    End

    Function EnteredForeground:Void(canPrompt:Bool=True)
        If Target.IS_DEBUG Then Print "[Appirater EnteredForeground:" + canPrompt + "]"
    End

    Function UserDidSignificantEvent:Void(canPrompt:Bool=True)
        If Target.IS_DEBUG Then Print "[Appirater UserDidSignificantEvent:" + canPrompt + "]"
    End

    Function SetAppId:Void(id:String)
        If Target.IS_DEBUG Then Print "[Appirater SetAppId:" + id + "]"
    End

    Function SetDaysUntilPrompt:Void(days:Int)
        If Target.IS_DEBUG Then Print "[Appirater SetDaysUntilPrompt:" + days + "]"
    End

    Function SetUsesUntilPrompt:Void(days:Int)
        If Target.IS_DEBUG Then Print "[Appirater SetUsesUntilPrompt:" + days + "]"
    End

    Function SetSignificantEventsUntilPrompt:Void(uses:Int)
        If Target.IS_DEBUG Then Print "[Appirater SetSignificantEventsUntilPrompt:" + uses + "]"
    End

    Function SetTimeBeforeReminding:Void(time:Int)
        If Target.IS_DEBUG Then Print "[Appirater SetTimeBeforeReminding:" + time + "]"
    End

    Function SetDebug:Void(flag:Bool)
        If Target.IS_DEBUG Then Print "[Appirater SetDebug:" + flag + "]"
    End
End

#End


Fixed a bug where long press and click were both firing.

Strict

Import mojo
Import assert
Import functions

Private

Global charForCodeArray:Int[]
Global charForShiftCodeArray:Int[]

' warning: do not try to use these externally! they are here until Mark fixes the official monkey constants in input.monkey
Const VK_ALT:Int = 18
Const VK_SEMICOLON:Int = 59
Const VK_EQUALS:Int = 107
Const VK_HYPHEN:Int = 109
Const VK_BACKSLASH:Int = 220

Public

Const EVENT_KEY_DOWN:Int = 1
Const EVENT_KEY_RELEASED:Int = 2
Const EVENT_KEY_HIT:Int = 3

Class InputCache
Private
	Field keyHitEnumerator:KeyEventEnumerator
	Field keyDownEnumerator:KeyEventEnumerator
	Field keyReleasedEnumerator:KeyEventEnumerator
	Field keyHitWrapper:EnumWrapper<KeyEventEnumerator>
	Field keyDownWrapper:EnumWrapper<KeyEventEnumerator>
	Field keyReleasedWrapper:EnumWrapper<KeyEventEnumerator>

Public
	Const FIRST_KEY:Int = KEY_BACKSPACE
	Const LAST_KEY:Int = KEY_QUOTES

	Const TOUCH_COUNT:Int = 32
	Const MOUSE_COUNT:Int = 3
	Const KEY_COUNT:Int = LAST_KEY-FIRST_KEY+1

	Const INPUT_COUNT:Int = 512

	Const FLING_THRESHOLD:Float = 250
	Const IGNORE_CLICK_DISTANCE:Float = 20
	Const LONG_PRESS_TIME:Int = 1000

	' TouchHit() for 0-31
	Field touchHit:Int[] = New Int[TOUCH_COUNT]
	' The time it was hit
	Field touchHitTime:Int[] = New Int[TOUCH_COUNT]
	' TouchDown() for 0-31
	Field touchDown:Int[] = New Int[TOUCH_COUNT]
	' The time it was first down
	Field touchDownTime:Int[] = New Int[TOUCH_COUNT]
	' TouchDown() is false but was true last loop
	Field touchReleased:Int[] = New Int[TOUCH_COUNT]
	' The time it was released
	Field touchReleasedTime:Int[] = New Int[TOUCH_COUNT]
	' the number of TouchDown() calls that returned true
	Field touchDownCount:Int
	' thte number of TouchHit() calls that returned true
	Field touchHitCount:Int
	' the number of released touches
	Field touchReleasedCount:Int
	' the highest index of TouchDown() that returned true
	Field maxTouchDown:Int = -1
	' the highest index of TouchHit() that returned true
	Field maxTouchHit:Int = -1
	' the highest index of a released touch
	Field maxTouchReleased:Int = -1
	' the lowest index of TouchDown() that returned true
	Field minTouchDown:Int = -1
	' the lowest index of TouchHit() that returned true
	Field minTouchHit:Int = -1
	' the lowest index of a released touch
	Field minTouchReleased:Int = -1
	' TouchX() And TouchY() For 0-31
	Field touchX:Float[] = New Float[TOUCH_COUNT]
	Field touchY:Float[] = New Float[TOUCH_COUNT]
	' should we monitor touch events?
	Field monitorTouch:Bool = False

	' the index of touches that were hit, released, or down (use touchHitCount, touchDownCount, touchReleasedCount]
	Field currentTouchHit:Int[] = New Int[TOUCH_COUNT]
	Field currentTouchDown:Int[] = New Int[TOUCH_COUNT]
	Field currentTouchReleased:Int[] = New Int[TOUCH_COUNT]

	' MouseHit() for 0-2
	Field mouseHit:Int[] = New Int[MOUSE_COUNT]
	' The time it was hit
	Field mouseHitTime:Int[] = New Int[MOUSE_COUNT]
	' MouseDown() for 0-2
	Field mouseDown:Int[] = New Int[MOUSE_COUNT]
	' The time it was first down
	Field mouseDownTime:Int[] = New Int[MOUSE_COUNT]
	' MouseDown() is false but was true last loop
	Field mouseReleased:Int[] = New Int[MOUSE_COUNT]
	' The time it was released
	Field mouseReleasedTime:Int[] = New Int[MOUSE_COUNT]
	' MouseX() And MouseY()
	Field mouseX:Int
	Field mouseY:Int
	' is any mouse button down?
	Field mouseDownCount:Int
	' was any mouse button hit?
	Field mouseHitCount:Int
	' was any mouse button released?
	Field mouseReleasedCount:Int
	' should we monitor mouse events?
	Field monitorMouse:Bool = False

	' the index of mouse buttons that were hit, released, or down (use mouseHitCount, mouseDownCount, mouseReleasedCount]
	Field currentMouseHit:Int[] = New Int[MOUSE_COUNT]
	Field currentMouseDown:Int[] = New Int[MOUSE_COUNT]
	Field currentMouseReleased:Int[] = New Int[MOUSE_COUNT]

	' KeyHit() for each monitored key
	Field keyHit:Int[] = New Int[INPUT_COUNT]
	' The time it was hit
	Field keyHitTime:Int[] = New Int[INPUT_COUNT]
	' KeyDown() for each monitored key
	Field keyDown:Int[] = New Int[INPUT_COUNT]
	' The time it was first down
	Field keyDownTime:Int[] = New Int[INPUT_COUNT]
	' KeyDown() is false but was true last loop
	Field keyReleased:Int[] = New Int[INPUT_COUNT]
	' The time it was released
	Field keyReleasedTime:Int[] = New Int[INPUT_COUNT]
	' is any monitored key down?
	Field keyDownCount:Int
	' was any monitored key hit?
	Field keyHitCount:Int
	' was any monitored key released?
	Field keyReleasedCount:Int
	' If true, we should monitor that key (defaults To false)
	Field monitorKey:Bool[] = New Bool[INPUT_COUNT]
	' the number of keys we are monitoring; this is so we can skip key checks if we aren't monitoring any
	Field monitorKeyCount:Int = 0

	' the index of keys that were hit, released, or down (use keyHitCount, keyDownCount, keyReleasedCount]
	Field currentKeysHit:Int[] = New Int[INPUT_COUNT]
	Field currentKeysDown:Int[] = New Int[INPUT_COUNT]
	Field currentKeysReleased:Int[] = New Int[INPUT_COUNT]

	' for touch handling
	Field touchData:TouchData[] = New TouchData[TOUCH_COUNT]
	Field flingThreshold:Float = FLING_THRESHOLD
	Field longPressTime:Int = LONG_PRESS_TIME

' Properties
	Method KeysHit:EnumWrapper<KeyEventEnumerator>() Property
		Return keyHitWrapper
	End
	Method KeysDown:EnumWrapper<KeyEventEnumerator>() Property
		Return keyDownWrapper
	End
	Method KeysReleased:EnumWrapper<KeyEventEnumerator>() Property
		Return keyReleasedWrapper
	End
	Method FlingThreshold:Float() Property
		Return flingThreshold
	End
	Method FlingThreshold:Void(flingThreshold:Float) Property
		Self.flingThreshold = flingThreshold
	End
	Method LongPressTime:Float() Property
		Return longPressTime
	End
	Method LongPressTime:Void(longPressTime:Float) Property
		Self.longPressTime = longPressTime
	End

' Constructors
	Method New()
		keyHitEnumerator = New KeyEventEnumerator(Self, EVENT_KEY_HIT)
		keyDownEnumerator = New KeyEventEnumerator(Self, EVENT_KEY_DOWN)
		keyReleasedEnumerator = New KeyEventEnumerator(Self, EVENT_KEY_RELEASED)
		keyHitWrapper = New EnumWrapper<KeyEventEnumerator>(keyHitEnumerator)
		keyDownWrapper = New EnumWrapper<KeyEventEnumerator>(keyDownEnumerator)
		keyReleasedWrapper = New EnumWrapper<KeyEventEnumerator>(keyReleasedEnumerator)
		For Local i:Int = 0 Until touchData.Length
			touchData[i] = New TouchData
		Next
		#If TARGET="android" Or TARGET="ios"
			monitorTouch = True
			monitorMouse = False
		#Else
			monitorTouch = False
			monitorMouse = True
		#End
	End

' Public methods
	Method MonitorNothing:Void()
		' clear mouse
		monitorMouse = False
		mouseDownCount = 0
		mouseHitCount = 0
		mouseReleasedCount = 0

		' clear touch
		monitorTouch = False
		touchDownCount = 0
		touchHitCount = 0
		touchReleasedCount = 0
		maxTouchDown = -1
		maxTouchHit = -1
		maxTouchReleased = -1
		minTouchDown = -1
		minTouchHit = -1
		minTouchReleased = -1

		' clear keys
		monitorKeyCount = 0
		For Local i:Int = 0 Until KEY_COUNT
			monitorKey[i] = False
			keyHit[i] = 0
			keyDown[i] = 0
			keyReleased[i] = 0
		Next
	End

	Method MonitorAll:Void(val:Bool=True)
		MonitorMouse(val)
		MonitorTouch(val)
		MonitorAllKeys(val)
	End

	Method MonitorAllKeys:Void(val:Bool=True)
		If val Then
			monitorKeyCount = KEY_COUNT
		Else
			monitorKeyCount = 0
		End
		For Local i:Int = FIRST_KEY To LAST_KEY
			monitorKey[i] = val
			keyHit[i] = 0
			keyDown[i] = 0
			keyReleased[i] = 0
		Next
	End

	Method MonitorKey:Void(keyNum:Int, val:Bool=True)
		If val And Not monitorKey[keyNum] Then
			monitorKeyCount += 1
		ElseIf Not val And monitorKey[keyNum] Then
			monitorKeyCount -= 1
		End
		monitorKey[keyNum] = val
		keyHit[keyNum] = 0
		keyDown[keyNum] = 0
		keyReleased[keyNum] = 0
	End

	Method MonitorTouch:Void(val:Bool=True)
		monitorTouch = val
		touchDownCount = 0
		touchHitCount = 0
		touchReleasedCount = 0
		maxTouchDown = -1
		maxTouchHit = -1
		maxTouchReleased = -1
		minTouchDown = -1
		minTouchHit = -1
		minTouchReleased = -1
	End

	Method MonitorMouse:Void(val:Bool=True)
		monitorMouse = val
		mouseDownCount = 0
		mouseHitCount = 0
		mouseReleasedCount = 0
	End

	Method MonitorControlKeys:Void(val:Bool=True)
		MonitorKey(KEY_CONTROL, val)
		MonitorKey(KEY_SHIFT, val)
		MonitorKey(VK_ALT, val)
	End

	Method ReadInput:Void()
		Local newval:Int = 0
		Local now:Int = Millisecs()

		' get the touch events
		If monitorTouch Then
			touchDownCount = 0
			touchHitCount = 0
			touchReleasedCount = 0
			maxTouchDown = -1
			maxTouchHit = -1
			maxTouchReleased = -1
			minTouchDown = -1
			minTouchHit = -1
			minTouchReleased = -1
			For Local i:Int = 0 Until TOUCH_COUNT
				' get the touch hit
				newval = TouchHit(i)
				If Not touchHit[i] And newval Then
					touchHitTime[i] = now
				End
				touchHit[i] = newval
				' get the touch down
				newval = TouchDown(i)
				If newval And Not touchDown[i] Then
					touchDownTime[i] = now
				End
				' get the touch released
				If touchDown[i] And Not newval Then
					touchReleasedTime[i] = now
					touchReleased[i] = True
				Else
					touchReleased[i] = False
				End
				touchDown[i] = newval
				' get the location of each touch
				touchX[i] = TouchX(i)
				touchY[i] = TouchY(i)
				' update the "current" arrays and the min/max indices
				If touchDown[i] Then
					currentTouchDown[touchDownCount] = i
					touchDownCount += 1
					If minTouchDown < 0 Then minTouchDown = i
					maxTouchDown = i
				End
				If touchHit[i] Then
					currentTouchHit[touchHitCount] = i
					touchHitCount += 1
					If minTouchHit < 0 Then minTouchHit = i
					maxTouchHit = i
				End
				If touchReleased[i] Then
					currentTouchReleased[touchReleasedCount] = i
					touchReleasedCount += 1
					If minTouchReleased < 0 Then minTouchReleased = i
					maxTouchReleased = i
				End
			Next
		End

		' get the mouse events
		If monitorMouse Then
			mouseDownCount = 0
			mouseHitCount = 0
			mouseReleasedCount = 0
			For Local i:Int = 0 Until MOUSE_COUNT
				' get the mouse hit
				newval = MouseHit(i)
				If Not mouseHit[i] And newval Then
					mouseHitTime[i] = now
				End
				mouseHit[i] = newval
				' get the mouse down
				newval = MouseDown(i)
				If newval And Not mouseDown[i] Then
					mouseDownTime[i] = now
				End
				' get the mouse released
				If mouseDown[i] And Not newval Then
					mouseReleasedTime[i] = now
					mouseReleased[i] = True
				Else
					mouseReleased[i] = False
				End
				mouseDown[i] = newval
				' update the "current" arrays
				If mouseDown[i] Then
					currentMouseDown[mouseDownCount] = i
					mouseDownCount += 1
				End
				If mouseHit[i] Then
					currentMouseHit[mouseHitCount] = i
					mouseHitCount += 1
				End
				If mouseReleased[i] Then
					currentMouseReleased[mouseReleasedCount] = i
					mouseReleasedCount += 1
				End
			Next
		End

		' get the key events for those we are monitoring
		keyDownCount = 0
		keyHitCount = 0
		keyReleasedCount = 0
		If monitorKeyCount > 0 Then
			For Local i:Int = FIRST_KEY To LAST_KEY
				If monitorKey[i] Then
					' get the key hit
					newval = KeyHit(i)
					If Not keyHit[i] And newval Then
						keyHitTime[i] = now
					End
					keyHit[i] = newval
					' get the key down
					newval = KeyDown(i)
					If newval And Not keyDown[i] Then
						keyDownTime[i] = now
					End
					' get key released
					If keyDown[i] And Not newval Then
						keyReleasedTime[i] = now
						keyReleased[i] = True
					Else
						keyReleased[i] = False
					End
					keyDown[i] = newval
					' update the "current" arrays
					If keyDown[i] Then
						currentKeysDown[keyDownCount] = i
						keyDownCount += 1
					End
					If keyHit[i] Then
						currentKeysHit[keyHitCount] = i
						keyHitCount += 1
					End
					If keyReleased[i] Then
						currentKeysReleased[keyReleasedCount] = i
						keyReleasedCount += 1
					End
				End
			Next
		End
	End

	Method HandleEvents:Void(screen:Screen)
		' handle calling touch hit
		For Local i:Int = 0 Until touchHitCount
			Local pointer:Int = currentTouchHit[i]
			Local x:Int = Int(touchX[pointer])
			Local y:Int = Int(touchY[pointer])
			touchData[pointer].Reset(x, y)
			screen.OnTouchHit(x, y, pointer)
		Next

		' handle calling touch click/released/fling
		For Local i:Int = 0 Until touchReleasedCount
			Local pointer:Int = currentTouchReleased[i]
			Local x:Int = Int(touchX[pointer])
			Local y:Int = Int(touchY[pointer])
			touchData[pointer].Update(x, y);
			If Not touchData[pointer].movedTooFar And Not touchData[pointer].firedLongPress Then
				screen.OnTouchClick(x, y, pointer)
			Else
				' check to see how fast we were moving to see if we fire a fling
				If touchData[pointer].touchVelocityX * touchData[pointer].touchVelocityX +
						touchData[pointer].touchVelocityY * touchData[pointer].touchVelocityY >=
						flingThreshold * flingThreshold Then
					screen.OnTouchFling(x, y, touchData[pointer].touchVelocityX, touchData[pointer].touchVelocityY,
					touchData[pointer].touchVelocitySpeed, pointer)
				End
			End
			screen.OnTouchReleased(x, y, pointer)
		Next

		For Local i:Int = 0 Until touchDownCount
			Local pointer:Int = currentTouchDown[i]
			Local x:Int = Int(touchX[pointer])
			Local y:Int = Int(touchY[pointer])
			touchData[pointer].Update(x, y)
			screen.OnTouchDragged(x, y, touchData[pointer].distanceMovedX, touchData[pointer].distanceMovedY, pointer)
			' check long press
			If Not touchData[pointer].testedLongPress And dt.currentticks-touchData[pointer].firstTouchTime >= longPressTime Then
				touchData[pointer].testedLongPress = True
				If Not touchData[pointer].movedTooFar Then
					' fire long press
					screen.OnTouchLongPress(x, y, pointer)
					touchData[pointer].firedLongPress = True
				End
			End
		Next
	End
End

' top level class for input events
Class InputEvent
Private
	Field eventType:Int
	Field eventTime:Int
	Field shiftDown:Bool
	Field ctrlDown:Bool
	Field altDown:Bool

Public
	Method EventType:Int() Property
		Return eventType
	End
	Method EventTime:Int() Property
		Return eventTime
	End
	Method ShiftDown:Bool() Property
		Return shiftDown
	End
	Method CtrlDown:Bool() Property
		Return ctrlDown
	End
	Method AltDown:Bool() Property
		Return altDown
	End

	Method New(eventType:Int)
		Self.eventType = eventType
	End
End

' subclass of InputEvent to store the key code
Class KeyEvent Extends InputEvent
Private
	Field keyCode:Int
	Field keyChar:Int

Public
	Method KeyCode:Int() Property
		Return keyCode
	End
	Method KeyChar:Int() Property
		Return keyChar
	End

	Method New(eventType:Int)
		Super.New(eventType)
	End
End

Private

Class InputEventEnumerator
Private
	Field ic:InputCache
	Field index:Int
	Field eventType:Int

Public
	Method New(ic:InputCache, eventType:Int)
		Self.ic = ic
		Self.eventType = eventType
	End

	Method Reset:Void()
		index = 0
	End
End

' enumerator class to handle KeyEvents
Class KeyEventEnumerator Extends InputEventEnumerator
Private
	Field event:KeyEvent

Public
	Method New(ic:InputCache, eventType:Int)
		Super.New(ic, eventType)
		Self.event = New KeyEvent
	End

	Method HasNext:Bool()
		If eventType = EVENT_KEY_DOWN Then Return index < ic.keyDownCount
		If eventType = EVENT_KEY_RELEASED Then Return index < ic.keyReleasedCount
		If eventType = EVENT_KEY_HIT Then Return index < ic.keyHitCount
		Return False
	End

	Method NextObject:KeyEvent()
		Assert(HasNext())
		Local idx:Int = 0
		If eventType = EVENT_KEY_DOWN Then
			idx = ic.currentKeysDown[index]
			event.eventTime = ic.keyDownTime[idx]
		ElseIf eventType = EVENT_KEY_RELEASED Then
			idx = ic.currentKeysReleased[index]
			event.eventTime = ic.keyReleasedTime[idx]
		ElseIf eventType = EVENT_KEY_HIT Then
			idx = ic.currentKeysHit[index]
			event.eventTime = ic.keyHitTime[idx]
		End
		index += 1
		event.shiftDown = ic.keyDown[KEY_SHIFT]<>0
		event.ctrlDown = ic.keyDown[KEY_CONTROL]<>0
		event.altDown = ic.keyDown[VK_ALT]<>0
		event.keyCode = idx
		event.keyChar = CharForCode(idx, event.shiftDown)
		Return event
	End
End

' i'd love to use <T Extends InputEventEnumerator> but monkey doesn't support it
Class EnumWrapper<T>
Private
	Field wrappedEnum:T

Public
	Method New(wrappedEnum:T)
		Self.wrappedEnum = wrappedEnum
	End

	Method ObjectEnumerator:T()
		If InputEventEnumerator(wrappedEnum) <> Null Then InputEventEnumerator(wrappedEnum).Reset()
		Return wrappedEnum
	End
End

Function CharForCode:Int(code:Int, shiftDown:Bool)
	BuildCharForCodeArray()
	If shiftDown Then
		Return charForShiftCodeArray[code]
	Else
		Return charForCodeArray[code]
	End
End

Private

Class TouchData
	Const FLING_SAMPLE_RATE:Int = 10

	Field firstTouchX:Int
	Field firstTouchY:Int
	Field firstTouchTime:Int
	Field lastTouchX:Int
	Field lastTouchY:Int
	Field flingSamplesX:Int[FLING_SAMPLE_RATE]
	Field flingSamplesY:Int[FLING_SAMPLE_RATE]
	Field flingSamplesTime:Int[FLING_SAMPLE_RATE]
	Field flingSampleCount:Int
	Field flingSampleNext:Int

	' calculated values
	Field movedTooFar:Bool
	Field testedLongPress:Bool
	Field firedLongPress:Bool
	Field distanceMovedX:Int
	Field distanceMovedY:Int
	Field touchVelocityX:Float
	Field touchVelocityY:Float
	Field touchVelocitySpeed:Float

	Method Reset:Void(x:Int, y:Int)
		firstTouchX = x
		firstTouchY = y
		lastTouchX = x
		lastTouchY = y
		firstTouchTime = Int(dt.currentticks)
		testedLongPress = False
		firedLongPress = False
		For Local i:Int = 0 Until FLING_SAMPLE_RATE
			flingSamplesX[i] = 0
			flingSamplesY[i] = 0
			flingSamplesTime[i] = 0
		Next
		flingSampleCount = 0
		flingSampleNext = 0
		movedTooFar = False
		touchVelocityX = 0
		touchVelocityY = 0
		touchVelocitySpeed = 0
		AddFlingSample(x, y)
	End

	Method AddFlingSample:Void(x:Int, y:Int)
		flingSamplesX[flingSampleNext] = x
		flingSamplesY[flingSampleNext] = y
		flingSamplesTime[flingSampleNext] = Int(dt.currentticks)
		If flingSampleCount < FLING_SAMPLE_RATE Then flingSampleCount += 1
		flingSampleNext += 1
		If flingSampleNext >= FLING_SAMPLE_RATE Then flingSampleNext = 0

		' find the first and last samples
		Local first:Int = flingSampleNext - flingSampleCount
		Local last:Int = flingSampleNext - 1
		While first < 0
			first += FLING_SAMPLE_RATE
		End
		While last < 0
			last += FLING_SAMPLE_RATE
		End

		' get the total delta
		If flingSampleCount > 0 Then
			' calculate the velocity in pixels per second
			Local secs:Float = Float(flingSamplesTime[last] - flingSamplesTime[first]) / 1000
			touchVelocityX = (flingSamplesX[last] - flingSamplesX[first]) / secs
			touchVelocityY = (flingSamplesY[last] - flingSamplesY[first]) / secs
			touchVelocitySpeed = Sqrt(touchVelocityX * touchVelocityX + touchVelocityY * touchVelocityY)
		End
	End

	Method Update:Void(x:Int, y:Int)
		' update distance
		distanceMovedX = x - lastTouchX
		distanceMovedY = y - lastTouchY
		lastTouchX = x
		lastTouchY = y
		' update the fling
		AddFlingSample(x, y)
		' check to see if we moved far enough away that we won't count this as a click
		If Not movedTooFar Then
			' get the first delta
			Local dx:Int = x - firstTouchX
			Local dy:Int = y - firstTouchY
			If dx * dx + dy * dy > InputCache.IGNORE_CLICK_DISTANCE * InputCache.IGNORE_CLICK_DISTANCE Then
				movedTooFar = True
			End
		End
	End
End

' Builds up arrays to convert key codes to characters.
' This is based off a US keyboard layout (sorry!)
' I may look at doing something native to correctly match keyboards to locale.
' warning: 107 seems to be the code for both the EQUALS in the number row and the PLUS on the number pad... wtf?
Function BuildCharForCodeArray:Void()
	If charForCodeArray.Length > 0 Then Return
	charForCodeArray = New Int[InputCache.INPUT_COUNT]
	charForShiftCodeArray = New Int[InputCache.INPUT_COUNT]

	' letters
	For Local i:Int = KEY_A To KEY_Z
		charForCodeArray[i] = i+32
		charForShiftCodeArray[i] = i
	Next

	' numbers
	For Local i:Int = KEY_0 To KEY_9
		charForCodeArray[i] = i
	Next

	' keypad numbers
	For Local i:Int = 96 To 105
		charForCodeArray[i] = KEY_0 + i - 96
	Next

	' shift-numbers
	charForShiftCodeArray[KEY_0] = ASC_CLOSE_PARENTHESIS
	charForShiftCodeArray[KEY_1] = ASC_EXCLAMATION
	charForShiftCodeArray[KEY_2] = ASC_AT
	charForShiftCodeArray[KEY_3] = ASC_HASH
	charForShiftCodeArray[KEY_4] = ASC_DOLLAR
	charForShiftCodeArray[KEY_5] = ASC_PERCENT
	charForShiftCodeArray[KEY_6] = ASC_CIRCUMFLEX
	charForShiftCodeArray[KEY_7] = ASC_AMPERSAND
	charForShiftCodeArray[KEY_8] = ASC_ASTERISK
	charForShiftCodeArray[KEY_9] = ASC_OPEN_PARENTHESIS

	charForCodeArray[VK_SEMICOLON] = ASC_SEMICOLON
	charForCodeArray[VK_EQUALS] = ASC_EQUALS
	charForCodeArray[KEY_COMMA] = ASC_COMMA
	charForCodeArray[VK_HYPHEN] = ASC_HYPHEN
	charForCodeArray[KEY_PERIOD] = ASC_PERIOD
	charForCodeArray[KEY_SLASH] = ASC_SLASH
	charForCodeArray[KEY_TILDE] = ASC_BACKTICK
	charForCodeArray[KEY_OPENBRACKET] = ASC_OPEN_BRACKET
	charForCodeArray[KEY_CLOSEBRACKET] = ASC_CLOSE_BRACKET
	charForCodeArray[KEY_QUOTES] = ASC_DOUBLE_QUOTE
	charForCodeArray[VK_BACKSLASH] = ASC_BACKSLASH
	charForCodeArray[KEY_SPACE] = ASC_SPACE

	charForShiftCodeArray[VK_SEMICOLON] = ASC_COLON
	charForShiftCodeArray[VK_EQUALS] = ASC_PLUS
	charForShiftCodeArray[KEY_COMMA] = ASC_LESS_THAN
	charForShiftCodeArray[VK_HYPHEN] = ASC_UNDERSCORE
	charForShiftCodeArray[KEY_PERIOD] = ASC_GREATER_THAN
	charForShiftCodeArray[KEY_SLASH] = ASC_QUESTION
	charForShiftCodeArray[KEY_TILDE] = ASC_TILDE
	charForShiftCodeArray[KEY_OPENBRACKET] = ASC_OPEN_BRACE
	charForShiftCodeArray[KEY_CLOSEBRACKET] = ASC_CLOSE_BRACE
	charForShiftCodeArray[KEY_QUOTES] = ASC_SINGLE_QUOTE
	charForShiftCodeArray[VK_BACKSLASH] = ASC_PIPE
	charForShiftCodeArray[KEY_SPACE] = ASC_SPACE
End

Strict

Import mojo
Import assert
Import functions

Private

Global charForCodeArray:Int[]
Global charForShiftCodeArray:Int[]

' warning: do not try to use these externally! they are here until Mark fixes the official monkey constants in input.monkey
Const VK_ALT:Int = 18
Const VK_SEMICOLON:Int = 59
Const VK_EQUALS:Int = 107
Const VK_HYPHEN:Int = 109
Const VK_BACKSLASH:Int = 220

Public

Const EVENT_KEY_DOWN:Int = 1
Const EVENT_KEY_RELEASED:Int = 2
Const EVENT_KEY_HIT:Int = 3

Class InputCache
Private
	Field keyHitEnumerator:KeyEventEnumerator
	Field keyDownEnumerator:KeyEventEnumerator
	Field keyReleasedEnumerator:KeyEventEnumerator
	Field keyHitWrapper:EnumWrapper<KeyEventEnumerator>
	Field keyDownWrapper:EnumWrapper<KeyEventEnumerator>
	Field keyReleasedWrapper:EnumWrapper<KeyEventEnumerator>

Public
	Const FIRST_KEY:Int = KEY_BACKSPACE
	Const LAST_KEY:Int = KEY_QUOTES

	Const TOUCH_COUNT:Int = 32
	Const MOUSE_COUNT:Int = 3
	Const KEY_COUNT:Int = LAST_KEY-FIRST_KEY+1

	Const INPUT_COUNT:Int = 512

	Const FLING_THRESHOLD:Float = 250
	Const IGNORE_CLICK_DISTANCE:Float = 20
	Const LONG_PRESS_TIME:Int = 1000

	' TouchHit() for 0-31
	Field touchHit:Int[] = New Int[TOUCH_COUNT]
	' The time it was hit
	Field touchHitTime:Int[] = New Int[TOUCH_COUNT]
	' TouchDown() for 0-31
	Field touchDown:Int[] = New Int[TOUCH_COUNT]
	' The time it was first down
	Field touchDownTime:Int[] = New Int[TOUCH_COUNT]
	' TouchDown() is false but was true last loop
	Field touchReleased:Int[] = New Int[TOUCH_COUNT]
	' The time it was released
	Field touchReleasedTime:Int[] = New Int[TOUCH_COUNT]
	' the number of TouchDown() calls that returned true
	Field touchDownCount:Int
	' thte number of TouchHit() calls that returned true
	Field touchHitCount:Int
	' the number of released touches
	Field touchReleasedCount:Int
	' the highest index of TouchDown() that returned true
	Field maxTouchDown:Int = -1
	' the highest index of TouchHit() that returned true
	Field maxTouchHit:Int = -1
	' the highest index of a released touch
	Field maxTouchReleased:Int = -1
	' the lowest index of TouchDown() that returned true
	Field minTouchDown:Int = -1
	' the lowest index of TouchHit() that returned true
	Field minTouchHit:Int = -1
	' the lowest index of a released touch
	Field minTouchReleased:Int = -1
	' TouchX() And TouchY() For 0-31
	Field touchX:Float[] = New Float[TOUCH_COUNT]
	Field touchY:Float[] = New Float[TOUCH_COUNT]
	' should we monitor touch events?
	Field monitorTouch:Bool = False

	' the index of touches that were hit, released, or down (use touchHitCount, touchDownCount, touchReleasedCount]
	Field currentTouchHit:Int[] = New Int[TOUCH_COUNT]
	Field currentTouchDown:Int[] = New Int[TOUCH_COUNT]
	Field currentTouchReleased:Int[] = New Int[TOUCH_COUNT]

	' MouseHit() for 0-2
	Field mouseHit:Int[] = New Int[MOUSE_COUNT]
	' The time it was hit
	Field mouseHitTime:Int[] = New Int[MOUSE_COUNT]
	' MouseDown() for 0-2
	Field mouseDown:Int[] = New Int[MOUSE_COUNT]
	' The time it was first down
	Field mouseDownTime:Int[] = New Int[MOUSE_COUNT]
	' MouseDown() is false but was true last loop
	Field mouseReleased:Int[] = New Int[MOUSE_COUNT]
	' The time it was released
	Field mouseReleasedTime:Int[] = New Int[MOUSE_COUNT]
	' MouseX() And MouseY()
	Field mouseX:Int
	Field mouseY:Int
	' is any mouse button down?
	Field mouseDownCount:Int
	' was any mouse button hit?
	Field mouseHitCount:Int
	' was any mouse button released?
	Field mouseReleasedCount:Int
	' should we monitor mouse events?
	Field monitorMouse:Bool = False

	' the index of mouse buttons that were hit, released, or down (use mouseHitCount, mouseDownCount, mouseReleasedCount]
	Field currentMouseHit:Int[] = New Int[MOUSE_COUNT]
	Field currentMouseDown:Int[] = New Int[MOUSE_COUNT]
	Field currentMouseReleased:Int[] = New Int[MOUSE_COUNT]

	' KeyHit() for each monitored key
	Field keyHit:Int[] = New Int[INPUT_COUNT]
	' The time it was hit
	Field keyHitTime:Int[] = New Int[INPUT_COUNT]
	' KeyDown() for each monitored key
	Field keyDown:Int[] = New Int[INPUT_COUNT]
	' The time it was first down
	Field keyDownTime:Int[] = New Int[INPUT_COUNT]
	' KeyDown() is false but was true last loop
	Field keyReleased:Int[] = New Int[INPUT_COUNT]
	' The time it was released
	Field keyReleasedTime:Int[] = New Int[INPUT_COUNT]
	' is any monitored key down?
	Field keyDownCount:Int
	' was any monitored key hit?
	Field keyHitCount:Int
	' was any monitored key released?
	Field keyReleasedCount:Int
	' If true, we should monitor that key (defaults To false)
	Field monitorKey:Bool[] = New Bool[INPUT_COUNT]
	' the number of keys we are monitoring; this is so we can skip key checks if we aren't monitoring any
	Field monitorKeyCount:Int = 0

	' the index of keys that were hit, released, or down (use keyHitCount, keyDownCount, keyReleasedCount]
	Field currentKeysHit:Int[] = New Int[INPUT_COUNT]
	Field currentKeysDown:Int[] = New Int[INPUT_COUNT]
	Field currentKeysReleased:Int[] = New Int[INPUT_COUNT]

	' for touch handling
	Field touchData:TouchData[] = New TouchData[TOUCH_COUNT]
	Field flingThreshold:Float = FLING_THRESHOLD
	Field longPressTime:Int = LONG_PRESS_TIME

' Properties
	Method KeysHit:EnumWrapper<KeyEventEnumerator>() Property
		Return keyHitWrapper
	End
	Method KeysDown:EnumWrapper<KeyEventEnumerator>() Property
		Return keyDownWrapper
	End
	Method KeysReleased:EnumWrapper<KeyEventEnumerator>() Property
		Return keyReleasedWrapper
	End
	Method FlingThreshold:Float() Property
		Return flingThreshold
	End
	Method FlingThreshold:Void(flingThreshold:Float) Property
		Self.flingThreshold = flingThreshold
	End
	Method LongPressTime:Float() Property
		Return longPressTime
	End
	Method LongPressTime:Void(longPressTime:Float) Property
		Self.longPressTime = longPressTime
	End

' Constructors
	Method New()
		keyHitEnumerator = New KeyEventEnumerator(Self, EVENT_KEY_HIT)
		keyDownEnumerator = New KeyEventEnumerator(Self, EVENT_KEY_DOWN)
		keyReleasedEnumerator = New KeyEventEnumerator(Self, EVENT_KEY_RELEASED)
		keyHitWrapper = New EnumWrapper<KeyEventEnumerator>(keyHitEnumerator)
		keyDownWrapper = New EnumWrapper<KeyEventEnumerator>(keyDownEnumerator)
		keyReleasedWrapper = New EnumWrapper<KeyEventEnumerator>(keyReleasedEnumerator)
		For Local i:Int = 0 Until touchData.Length
			touchData[i] = New TouchData
		Next
		#If TARGET="android" Or TARGET="ios"
			monitorTouch = True
			monitorMouse = False
		#Else
			monitorTouch = False
			monitorMouse = True
		#End
	End

' Public methods
	Method MonitorNothing:Void()
		' clear mouse
		monitorMouse = False
		mouseDownCount = 0
		mouseHitCount = 0
		mouseReleasedCount = 0

		' clear touch
		monitorTouch = False
		touchDownCount = 0
		touchHitCount = 0
		touchReleasedCount = 0
		maxTouchDown = -1
		maxTouchHit = -1
		maxTouchReleased = -1
		minTouchDown = -1
		minTouchHit = -1
		minTouchReleased = -1

		' clear keys
		monitorKeyCount = 0
		For Local i:Int = 0 Until KEY_COUNT
			monitorKey[i] = False
			keyHit[i] = 0
			keyDown[i] = 0
			keyReleased[i] = 0
		Next
	End

	Method MonitorAll:Void(val:Bool=True)
		MonitorMouse(val)
		MonitorTouch(val)
		MonitorAllKeys(val)
	End

	Method MonitorAllKeys:Void(val:Bool=True)
		If val Then
			monitorKeyCount = KEY_COUNT
		Else
			monitorKeyCount = 0
		End
		For Local i:Int = FIRST_KEY To LAST_KEY
			monitorKey[i] = val
			keyHit[i] = 0
			keyDown[i] = 0
			keyReleased[i] = 0
		Next
	End

	Method MonitorKey:Void(keyNum:Int, val:Bool=True)
		If val And Not monitorKey[keyNum] Then
			monitorKeyCount += 1
		ElseIf Not val And monitorKey[keyNum] Then
			monitorKeyCount -= 1
		End
		monitorKey[keyNum] = val
		keyHit[keyNum] = 0
		keyDown[keyNum] = 0
		keyReleased[keyNum] = 0
	End

	Method MonitorTouch:Void(val:Bool=True)
		monitorTouch = val
		touchDownCount = 0
		touchHitCount = 0
		touchReleasedCount = 0
		maxTouchDown = -1
		maxTouchHit = -1
		maxTouchReleased = -1
		minTouchDown = -1
		minTouchHit = -1
		minTouchReleased = -1
	End

	Method MonitorMouse:Void(val:Bool=True)
		monitorMouse = val
		mouseDownCount = 0
		mouseHitCount = 0
		mouseReleasedCount = 0
	End

	Method MonitorControlKeys:Void(val:Bool=True)
		MonitorKey(KEY_CONTROL, val)
		MonitorKey(KEY_SHIFT, val)
		MonitorKey(VK_ALT, val)
	End

	Method ReadInput:Void()
		Local newval:Int = 0
		Local now:Int = Millisecs()

		' get the touch events
		If monitorTouch Then
			touchDownCount = 0
			touchHitCount = 0
			touchReleasedCount = 0
			maxTouchDown = -1
			maxTouchHit = -1
			maxTouchReleased = -1
			minTouchDown = -1
			minTouchHit = -1
			minTouchReleased = -1
			For Local i:Int = 0 Until TOUCH_COUNT
				' get the touch hit
				newval = TouchHit(i)
				If Not touchHit[i] And newval Then
					touchHitTime[i] = now
				End
				touchHit[i] = newval
				' get the touch down
				newval = TouchDown(i)
				If newval And Not touchDown[i] Then
					touchDownTime[i] = now
				End
				' get the touch released
				If touchDown[i] And Not newval Then
					touchReleasedTime[i] = now
					touchReleased[i] = True
				Else
					touchReleased[i] = False
				End
				touchDown[i] = newval
				' get the location of each touch
				touchX[i] = TouchX(i)
				touchY[i] = TouchY(i)
				' update the "current" arrays and the min/max indices
				If touchDown[i] Then
					currentTouchDown[touchDownCount] = i
					touchDownCount += 1
					If minTouchDown < 0 Then minTouchDown = i
					maxTouchDown = i
				End
				If touchHit[i] Then
					currentTouchHit[touchHitCount] = i
					touchHitCount += 1
					If minTouchHit < 0 Then minTouchHit = i
					maxTouchHit = i
				End
				If touchReleased[i] Then
					currentTouchReleased[touchReleasedCount] = i
					touchReleasedCount += 1
					If minTouchReleased < 0 Then minTouchReleased = i
					maxTouchReleased = i
				End
			Next
		End

		' get the mouse events
		If monitorMouse Then
			mouseDownCount = 0
			mouseHitCount = 0
			mouseReleasedCount = 0
			For Local i:Int = 0 Until MOUSE_COUNT
				' get the mouse hit
				newval = MouseHit(i)
				If Not mouseHit[i] And newval Then
					mouseHitTime[i] = now
				End
				mouseHit[i] = newval
				' get the mouse down
				newval = MouseDown(i)
				If newval And Not mouseDown[i] Then
					mouseDownTime[i] = now
				End
				' get the mouse released
				If mouseDown[i] And Not newval Then
					mouseReleasedTime[i] = now
					mouseReleased[i] = True
				Else
					mouseReleased[i] = False
				End
				mouseDown[i] = newval
				' update the "current" arrays
				If mouseDown[i] Then
					currentMouseDown[mouseDownCount] = i
					mouseDownCount += 1
				End
				If mouseHit[i] Then
					currentMouseHit[mouseHitCount] = i
					mouseHitCount += 1
				End
				If mouseReleased[i] Then
					currentMouseReleased[mouseReleasedCount] = i
					mouseReleasedCount += 1
				End
			Next
		End

		' get the key events for those we are monitoring
		keyDownCount = 0
		keyHitCount = 0
		keyReleasedCount = 0
		If monitorKeyCount > 0 Then
			For Local i:Int = FIRST_KEY To LAST_KEY
				If monitorKey[i] Then
					' get the key hit
					newval = KeyHit(i)
					If Not keyHit[i] And newval Then
						keyHitTime[i] = now
					End
					keyHit[i] = newval
					' get the key down
					newval = KeyDown(i)
					If newval And Not keyDown[i] Then
						keyDownTime[i] = now
					End
					' get key released
					If keyDown[i] And Not newval Then
						keyReleasedTime[i] = now
						keyReleased[i] = True
					Else
						keyReleased[i] = False
					End
					keyDown[i] = newval
					' update the "current" arrays
					If keyDown[i] Then
						currentKeysDown[keyDownCount] = i
						keyDownCount += 1
					End
					If keyHit[i] Then
						currentKeysHit[keyHitCount] = i
						keyHitCount += 1
					End
					If keyReleased[i] Then
						currentKeysReleased[keyReleasedCount] = i
						keyReleasedCount += 1
					End
				End
			Next
		End
	End

	Method HandleEvents:Void(screen:Screen)
		' handle calling touch hit
		For Local i:Int = 0 Until touchHitCount
			Local pointer:Int = currentTouchHit[i]
			Local x:Int = Int(touchX[pointer])
			Local y:Int = Int(touchY[pointer])
			touchData[pointer].Reset(x, y)
			screen.OnTouchHit(x, y, pointer)
		Next

		' handle calling touch click/released/fling
		For Local i:Int = 0 Until touchReleasedCount
			Local pointer:Int = currentTouchReleased[i]
			Local x:Int = Int(touchX[pointer])
			Local y:Int = Int(touchY[pointer])
			touchData[pointer].Update(x, y);
			If Not touchData[pointer].movedTooFar Then
				screen.OnTouchClick(x, y, pointer)
			Else
				' check to see how fast we were moving to see if we fire a fling
				If touchData[pointer].touchVelocityX * touchData[pointer].touchVelocityX +
						touchData[pointer].touchVelocityY * touchData[pointer].touchVelocityY >=
						flingThreshold * flingThreshold Then
					screen.OnTouchFling(x, y, touchData[pointer].touchVelocityX, touchData[pointer].touchVelocityY,
					touchData[pointer].touchVelocitySpeed, pointer)
				End
			End
			screen.OnTouchReleased(x, y, pointer)
		Next

		For Local i:Int = 0 Until touchDownCount
			Local pointer:Int = currentTouchDown[i]
			Local x:Int = Int(touchX[pointer])
			Local y:Int = Int(touchY[pointer])
			touchData[pointer].Update(x, y)
			screen.OnTouchDragged(x, y, touchData[pointer].distanceMovedX, touchData[pointer].distanceMovedY, pointer)
			' check long press
			If Not touchData[pointer].testedLongPress And dt.currentticks-touchData[pointer].firstTouchTime >= longPressTime Then
				touchData[pointer].testedLongPress = True
				If Not touchData[pointer].movedTooFar Then
					' fire long press
					screen.OnTouchLongPress(x, y, pointer)
					touchData[pointer].firedLongPress = True
				End
			End
		Next
	End
End

' top level class for input events
Class InputEvent
Private
	Field eventType:Int
	Field eventTime:Int
	Field shiftDown:Bool
	Field ctrlDown:Bool
	Field altDown:Bool

Public
	Method EventType:Int() Property
		Return eventType
	End
	Method EventTime:Int() Property
		Return eventTime
	End
	Method ShiftDown:Bool() Property
		Return shiftDown
	End
	Method CtrlDown:Bool() Property
		Return ctrlDown
	End
	Method AltDown:Bool() Property
		Return altDown
	End

	Method New(eventType:Int)
		Self.eventType = eventType
	End
End

' subclass of InputEvent to store the key code
Class KeyEvent Extends InputEvent
Private
	Field keyCode:Int
	Field keyChar:Int

Public
	Method KeyCode:Int() Property
		Return keyCode
	End
	Method KeyChar:Int() Property
		Return keyChar
	End

	Method New(eventType:Int)
		Super.New(eventType)
	End
End

Private

Class InputEventEnumerator
Private
	Field ic:InputCache
	Field index:Int
	Field eventType:Int

Public
	Method New(ic:InputCache, eventType:Int)
		Self.ic = ic
		Self.eventType = eventType
	End

	Method Reset:Void()
		index = 0
	End
End

' enumerator class to handle KeyEvents
Class KeyEventEnumerator Extends InputEventEnumerator
Private
	Field event:KeyEvent

Public
	Method New(ic:InputCache, eventType:Int)
		Super.New(ic, eventType)
		Self.event = New KeyEvent
	End

	Method HasNext:Bool()
		If eventType = EVENT_KEY_DOWN Then Return index < ic.keyDownCount
		If eventType = EVENT_KEY_RELEASED Then Return index < ic.keyReleasedCount
		If eventType = EVENT_KEY_HIT Then Return index < ic.keyHitCount
		Return False
	End

	Method NextObject:KeyEvent()
		Assert(HasNext())
		Local idx:Int = 0
		If eventType = EVENT_KEY_DOWN Then
			idx = ic.currentKeysDown[index]
			event.eventTime = ic.keyDownTime[idx]
		ElseIf eventType = EVENT_KEY_RELEASED Then
			idx = ic.currentKeysReleased[index]
			event.eventTime = ic.keyReleasedTime[idx]
		ElseIf eventType = EVENT_KEY_HIT Then
			idx = ic.currentKeysHit[index]
			event.eventTime = ic.keyHitTime[idx]
		End
		index += 1
		event.shiftDown = ic.keyDown[KEY_SHIFT]<>0
		event.ctrlDown = ic.keyDown[KEY_CONTROL]<>0
		event.altDown = ic.keyDown[VK_ALT]<>0
		event.keyCode = idx
		event.keyChar = CharForCode(idx, event.shiftDown)
		Return event
	End
End

' i'd love to use <T Extends InputEventEnumerator> but monkey doesn't support it
Class EnumWrapper<T>
Private
	Field wrappedEnum:T

Public
	Method New(wrappedEnum:T)
		Self.wrappedEnum = wrappedEnum
	End

	Method ObjectEnumerator:T()
		If InputEventEnumerator(wrappedEnum) <> Null Then InputEventEnumerator(wrappedEnum).Reset()
		Return wrappedEnum
	End
End

Function CharForCode:Int(code:Int, shiftDown:Bool)
	BuildCharForCodeArray()
	If shiftDown Then
		Return charForShiftCodeArray[code]
	Else
		Return charForCodeArray[code]
	End
End

Private

Class TouchData
	Const FLING_SAMPLE_RATE:Int = 10

	Field firstTouchX:Int
	Field firstTouchY:Int
	Field firstTouchTime:Int
	Field lastTouchX:Int
	Field lastTouchY:Int
	Field flingSamplesX:Int[FLING_SAMPLE_RATE]
	Field flingSamplesY:Int[FLING_SAMPLE_RATE]
	Field flingSamplesTime:Int[FLING_SAMPLE_RATE]
	Field flingSampleCount:Int
	Field flingSampleNext:Int

	' calculated values
	Field movedTooFar:Bool
	Field testedLongPress:Bool
	Field firedLongPress:Bool
	Field distanceMovedX:Int
	Field distanceMovedY:Int
	Field touchVelocityX:Float
	Field touchVelocityY:Float
	Field touchVelocitySpeed:Float

	Method Reset:Void(x:Int, y:Int)
		firstTouchX = x
		firstTouchY = y
		lastTouchX = x
		lastTouchY = y
		firstTouchTime = Int(dt.currentticks)
		testedLongPress = False
		firedLongPress = False
		For Local i:Int = 0 Until FLING_SAMPLE_RATE
			flingSamplesX[i] = 0
			flingSamplesY[i] = 0
			flingSamplesTime[i] = 0
		Next
		flingSampleCount = 0
		flingSampleNext = 0
		movedTooFar = False
		touchVelocityX = 0
		touchVelocityY = 0
		touchVelocitySpeed = 0
		AddFlingSample(x, y)
	End

	Method AddFlingSample:Void(x:Int, y:Int)
		flingSamplesX[flingSampleNext] = x
		flingSamplesY[flingSampleNext] = y
		flingSamplesTime[flingSampleNext] = Int(dt.currentticks)
		If flingSampleCount < FLING_SAMPLE_RATE Then flingSampleCount += 1
		flingSampleNext += 1
		If flingSampleNext >= FLING_SAMPLE_RATE Then flingSampleNext = 0

		' find the first and last samples
		Local first:Int = flingSampleNext - flingSampleCount
		Local last:Int = flingSampleNext - 1
		While first < 0
			first += FLING_SAMPLE_RATE
		End
		While last < 0
			last += FLING_SAMPLE_RATE
		End

		' get the total delta
		If flingSampleCount > 0 Then
			' calculate the velocity in pixels per second
			Local secs:Float = Float(flingSamplesTime[last] - flingSamplesTime[first]) / 1000
			touchVelocityX = (flingSamplesX[last] - flingSamplesX[first]) / secs
			touchVelocityY = (flingSamplesY[last] - flingSamplesY[first]) / secs
			touchVelocitySpeed = Sqrt(touchVelocityX * touchVelocityX + touchVelocityY * touchVelocityY)
		End
	End

	Method Update:Void(x:Int, y:Int)
		' update distance
		distanceMovedX = x - lastTouchX
		distanceMovedY = y - lastTouchY
		lastTouchX = x
		lastTouchY = y
		' update the fling
		AddFlingSample(x, y)
		' check to see if we moved far enough away that we won't count this as a click
		If Not movedTooFar Then
			' get the first delta
			Local dx:Int = x - firstTouchX
			Local dy:Int = y - firstTouchY
			If dx * dx + dy * dy > InputCache.IGNORE_CLICK_DISTANCE * InputCache.IGNORE_CLICK_DISTANCE Then
				movedTooFar = True
			End
		End
	End
End

' Builds up arrays to convert key codes to characters.
' This is based off a US keyboard layout (sorry!)
' I may look at doing something native to correctly match keyboards to locale.
' warning: 107 seems to be the code for both the EQUALS in the number row and the PLUS on the number pad... wtf?
Function BuildCharForCodeArray:Void()
	If charForCodeArray.Length > 0 Then Return
	charForCodeArray = New Int[InputCache.INPUT_COUNT]
	charForShiftCodeArray = New Int[InputCache.INPUT_COUNT]

	' letters
	For Local i:Int = KEY_A To KEY_Z
		charForCodeArray[i] = i+32
		charForShiftCodeArray[i] = i
	Next

	' numbers
	For Local i:Int = KEY_0 To KEY_9
		charForCodeArray[i] = i
	Next

	' keypad numbers
	For Local i:Int = 96 To 105
		charForCodeArray[i] = KEY_0 + i - 96
	Next

	' shift-numbers
	charForShiftCodeArray[KEY_0] = ASC_CLOSE_PARENTHESIS
	charForShiftCodeArray[KEY_1] = ASC_EXCLAMATION
	charForShiftCodeArray[KEY_2] = ASC_AT
	charForShiftCodeArray[KEY_3] = ASC_HASH
	charForShiftCodeArray[KEY_4] = ASC_DOLLAR
	charForShiftCodeArray[KEY_5] = ASC_PERCENT
	charForShiftCodeArray[KEY_6] = ASC_CIRCUMFLEX
	charForShiftCodeArray[KEY_7] = ASC_AMPERSAND
	charForShiftCodeArray[KEY_8] = ASC_ASTERISK
	charForShiftCodeArray[KEY_9] = ASC_OPEN_PARENTHESIS

	charForCodeArray[VK_SEMICOLON] = ASC_SEMICOLON
	charForCodeArray[VK_EQUALS] = ASC_EQUALS
	charForCodeArray[KEY_COMMA] = ASC_COMMA
	charForCodeArray[VK_HYPHEN] = ASC_HYPHEN
	charForCodeArray[KEY_PERIOD] = ASC_PERIOD
	charForCodeArray[KEY_SLASH] = ASC_SLASH
	charForCodeArray[KEY_TILDE] = ASC_BACKTICK
	charForCodeArray[KEY_OPENBRACKET] = ASC_OPEN_BRACKET
	charForCodeArray[KEY_CLOSEBRACKET] = ASC_CLOSE_BRACKET
	charForCodeArray[KEY_QUOTES] = ASC_DOUBLE_QUOTE
	charForCodeArray[VK_BACKSLASH] = ASC_BACKSLASH
	charForCodeArray[KEY_SPACE] = ASC_SPACE

	charForShiftCodeArray[VK_SEMICOLON] = ASC_COLON
	charForShiftCodeArray[VK_EQUALS] = ASC_PLUS
	charForShiftCodeArray[KEY_COMMA] = ASC_LESS_THAN
	charForShiftCodeArray[VK_HYPHEN] = ASC_UNDERSCORE
	charForShiftCodeArray[KEY_PERIOD] = ASC_GREATER_THAN
	charForShiftCodeArray[KEY_SLASH] = ASC_QUESTION
	charForShiftCodeArray[KEY_TILDE] = ASC_TILDE
	charForShiftCodeArray[KEY_OPENBRACKET] = ASC_OPEN_BRACE
	charForShiftCodeArray[KEY_CLOSEBRACKET] = ASC_CLOSE_BRACE
	charForShiftCodeArray[KEY_QUOTES] = ASC_SINGLE_QUOTE
	charForShiftCodeArray[VK_BACKSLASH] = ASC_PIPE
	charForShiftCodeArray[KEY_SPACE] = ASC_SPACE
End


Patched filesystem.monkey with James Poag's fixes

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

Strict
Import mojo

Class FileSystem Extends DataConversion
'Private
	Field delimiter:String
	Field _header:String = "MKYDATA"
	Field fileData:String
	Field index:StringMap<FileStream>
'Public

	Function Create:FileSystem(delimiter:String = String.FromChar(9)) 'tab-delimited by default
		Local t:FileSystem = New FileSystem
		t.delimiter = delimiter
		t.LoadAll()
		Return t
	End

	Method WriteFile:FileStream(filename:String)
		Local f:FileStream = new FileStream
		f.filename = filename.ToLower()
		f.ptr = 0
		f.delimiter = Self.delimiter
		Self.index.Insert(f.filename.ToLower(),f)
		Return f
	End

	Method ReadFile:FileStream(filename:String)
		filename = filename.ToLower()

		' Check existence
		if Not Self.index.Contains(filename) Then Return Null

		Local f:FileStream
		f = Self.index.ValueForKey(filename)
		f.ptr = 0
		f.arr = f.data.Split(f.delimiter)
		Return f
	End

	Method FileExists:Bool(filename:String)
		filename = filename.ToLower()
		if Self.index.Contains(filename)
			Return True
		Else
			Return False
		End
		Return False
	End

	Method ListDir:Void()
		Local filename:String
		Local stream:FileStream
		Print "Directory Listing:"
		For filename = EachIn Self.index.Keys()
			stream = Self.index.ValueForKey(filename)
			Print filename + "   " + stream.data.Length()+" byte(s)."
		Next
	End

	Method DeleteFile:Void(filename:String)
		filename = filename.ToLower()
		if Self.index.Contains(filename)
			Self.index.Remove(filename)
		End
	End

	Method SaveAll:Void()
		Local f:FileStream
		Self.fileData = Self._header'header
		Self.fileData+= Self.delimiter
		self.fileData+= Self.IntToString(Self.index.Count())'number of files in index
		if Self.index.Count() > 0
			For f = EachIn Self.index.Values()
				'store filename
				Self.fileData+=Self.delimiter
				Self.fileData+= f.filename
				'store data
				Self.fileData+=Self.delimiter
				Self.fileData+= Self.IntToString(f.numElements)
				if f.numElements > 0
					Self.fileData+=Self.delimiter
					Self.fileData+=f.data
				End
			Next
		End
		SaveState(Self.fileData)
	End

	Method LoadAll:Void()
		Local numFiles:Int
		Local numElements:Int
		Local stream:FileStream
		Local len:Int
		Local ptr:Int
		Local elementCounter:Int
		Local arr:String[]
		Self.fileData = LoadState()
		self.index = New StringMap<FileStream>
		if Self.fileData.Length() > 0
			arr = Self.fileData.Split(Self.delimiter)
			if arr[ptr] = Self._header
				Self.index.Clear()
				ptr+=1
				numFiles = Self.StringToInt(arr[ptr])
				ptr+=1
				if numFiles > 0
					For Local n:Int = 1 to numFiles
						stream = New FileStream
						'filename
						stream.delimiter = Self.delimiter
						stream.filename = arr[ptr] ; ptr+=1
						'data
						stream.numElements = Int(arr[ptr]) ; ptr+=1
						if stream.numElements > 0
							elementCounter = 0
							Repeat
								stream.data+=arr[ptr]
								elementCounter+=1
								if elementCounter < stream.numElements
									stream.data+=Self.delimiter
								EndIf
								ptr+=1
							Until elementCounter = stream.numElements
						End
						Self.index.Insert(stream.filename,stream)
					Next
				End
			End
		Else
			SaveState("")'save empty file and indicate no files stored
		End
	End
End

Class FileStream Extends DataConversion
	Field filename:String
	Field ptr:Int
'Private
	Field data:String
	Field arr:String[]
	Field delimiter:String
	Field numElements:Int
	Field errStr:String
'Public

	Method HadError:Bool()
		Return errStr.Length() > 0
	End

	Method GetLastError:String()
		Return errStr
	End

	Method AtEOF:Bool()
		Return Self.ptr >= Self.arr.Length()
	End

	Method ReadInt:Int()
		Local result:Int
		if Self.ptr >= Self.arr.Length()
			Self.EofError()
			Return 0
		EndIf

		result = Int(Self.arr[Self.ptr])
		Self.ptr+=1
		Return result
	End

	Method WriteInt:Void(val:Int)
		If Self.data.Length() > 0
			Self.data+=Self.delimiter
		EndIf
		Self.data+=Self.IntToString(val)
		Self.numElements+=1
	End

	Method ReadString:String()
		Local result:String
		if Self.ptr >= Self.arr.Length()
			Self.EofError()
			Return ""
		EndIf
		result = Self.arr[Self.ptr]
		Self.ptr+=1
		Return result
	End

	Method WriteString:Void(val:String)
		If Self.data.Length() > 0
			Self.data+=Self.delimiter
		EndIf
		Self.data+=val
		Self.numElements+=1
	End

	Method ReadFloat:Float()
		Local result:float
		if Self.ptr >= Self.arr.Length()
			Self.EofError()
			Return 0.0
		EndIf

		result = Float(Self.arr[Self.ptr])
		Self.ptr+=1
		Return result
	End

	Method WriteFloat:Void(val:Float)
		Local s:String = self.FloatToString(val)
		If Self.data.Length() > 0
			Self.data+=Self.delimiter
		EndIf
		Self.data+=s
		Self.numElements+=1
	End

	Method ReadBool:Bool()
		Local result:Bool
		if Self.ptr >= Self.arr.Length()
			Self.EofError()
			Return False
		EndIf

		if Self.arr[Self.ptr] = "0"
			result = False
		Else
			result = True
		EndIf

		Self.ptr+=1
		Return result
	End Method

	Method WriteBool:Void(val:Bool)
		Local txt:String
		If Self.data.Length() > 0
			Self.data+=Self.delimiter
		EndIf
		If val
			txt = "1"
		Else
			txt = "0"
		EndIf
		Self.data+=txt
		Self.numElements+=1
	End Method

	Method EofError:Void()
		errStr = "End of file: " + Self.filename

		#If CONFIG="debug"
    	' how can we recover from an error if you stop the program?
			Error errStr
		#End
	End Method
End

Class DataConversion
	Method LittleEndianIntToString:String(val:Int)
		Local result:String
		result = String.FromChar((val) & $FF)
		result+= String.FromChar((val Shr 8) & $FF)
		result+= String.FromChar((val Shr 16) & $FF)
		result+= String.FromChar((val Shr 24) & $FF)
		Return result
	End

	Method StringToLittleEndianInt:Int(val:String)
		Local result:Int
		result = (val[0])
		result|= (val[1] Shl 8)
		result|= (val[2] Shl 16)
		result|= (val[3] Shl 24)
		Return result
	End

    Method IntToString:String(val:Int)
		Return String(val)
    End

    Method StringToInt:Int(val:String)
		Return Int(val)
    End

	Method FloatToString:String(val:Float)
		Return String(val)
	End

	Method StringToFloat:Float(val:String)
		Return Float(val)
	End
End

Strict
Import mojo

Class FileSystem Extends DataConversion
'Private
	Field delimiter:String
	Field _header:String = "MKYDATA"
	Field fileData:String
	Field index:StringMap<FileStream>
'Public

	Function Create:FileSystem(delimiter:String = String.FromChar(9)) 'tab-delimited by default
		Local t:FileSystem = New FileSystem
		t.delimiter = delimiter
		t.LoadAll()
		Return t
	End

	Method WriteFile:FileStream(filename:String)
		Local f:FileStream = new FileStream
		f.filename = filename.ToLower()
		f.ptr = 0
		f.delimiter = Self.delimiter
		Self.index.Insert(f.filename.ToLower(),f)
		Return f
	End

	Method ReadFile:FileStream(filename:String)
		filename = filename.ToLower()
		Local f:FileStream
		f = Self.index.ValueForKey(filename)
		f.ptr = 0
		f.arr = f.data.Split(f.delimiter)
		Return f
	End

	Method FileExists:Bool(filename:String)
		filename = filename.ToLower()
		if Self.index.Contains(filename)
			Return True
		Else
			Return False
		End
		Return False
	End

	Method ListDir:Void()
		Local filename:String
		Local stream:FileStream
		Print "Directory Listing:"
		For filename = EachIn Self.index.Keys()
			stream = Self.index.ValueForKey(filename)
			Print filename + "   " + stream.data.Length()+" byte(s)."
		Next
	End

	Method DeleteFile:Void(filename:String)
		filename = filename.ToLower()
		if Self.index.Contains(filename)
			Self.index.Remove(filename)
		End
	End

	Method SaveAll:Void()
		Local f:FileStream
		Self.fileData = Self._header'header
		Self.fileData+= Self.delimiter
		self.fileData+= Self.IntToString(Self.index.Count())'number of files in index
		if Self.index.Count() > 0
			For f = EachIn Self.index.Values()
				'store filename
				Self.fileData+=Self.delimiter
				Self.fileData+= f.filename
				'store data
				Self.fileData+=Self.delimiter
				Self.fileData+= Self.IntToString(f.numElements)
				if f.numElements > 0
					Self.fileData+=Self.delimiter
					Self.fileData+=f.data
				End
			Next
		End
		SaveState(Self.fileData)
	End

	Method LoadAll:Void()
		Local numFiles:Int
		Local numElements:Int
		Local stream:FileStream
		Local len:Int
		Local ptr:Int
		Local elementCounter:Int
		Local arr:String[]
		Self.fileData = LoadState()
		self.index = New StringMap<FileStream>
		if Self.fileData.Length() > 0
			arr = Self.fileData.Split(Self.delimiter)
			if arr[ptr] = Self._header
				Self.index.Clear()
				ptr+=1
				numFiles = Self.StringToInt(arr[ptr])
				ptr+=1
				if numFiles > 0
					For Local n:Int = 1 to numFiles
						stream = New FileStream
						'filename
						stream.delimiter = Self.delimiter
						stream.filename = arr[ptr] ; ptr+=1
						'data
						stream.numElements = Int(arr[ptr]) ; ptr+=1
						if stream.numElements > 0
							elementCounter = 0
							Repeat
								stream.data+=arr[ptr]
								elementCounter+=1
								if elementCounter < stream.numElements
									stream.data+=Self.delimiter
								EndIf
								ptr+=1
							Until elementCounter = stream.numElements
						End
						Self.index.Insert(stream.filename,stream)
					Next
				End
			End
		Else
			SaveState("")'save empty file and indicate no files stored
		End
	End
End

Class FileStream Extends DataConversion
	Field filename:String
	Field ptr:Int
'Private
	Field data:String
	Field arr:String[]
	Field delimiter:String
	Field numElements:Int
'Public

	Method ReadInt:Int()
		Local result:Int
		if Self.ptr >= Self.arr.Length()
			Self.EofError()
		EndIf

		result = Int(Self.arr[Self.ptr])
		Self.ptr+=1
		Return result
	End

	Method WriteInt:Void(val:Int)
		If Self.data.Length() > 0
			Self.data+=Self.delimiter
		EndIf
		Self.data+=Self.IntToString(val)
		Self.numElements+=1
	End

	Method ReadString:String()
		Local result:String
		if Self.ptr >= Self.arr.Length()
			Self.EofError()
		EndIf
		result = Self.arr[Self.ptr]
		Self.ptr+=1
		Return result
	End

	Method WriteString:Void(val:String)
		If Self.data.Length() > 0
			Self.data+=Self.delimiter
		EndIf
		Self.data+=val
		Self.numElements+=1
	End

	Method ReadFloat:Float()
		Local result:float
		if Self.ptr >= Self.arr.Length()
			Self.EofError()
		EndIf

		result = Float(Self.arr[Self.ptr])
		Self.ptr+=1
		Return result
	End

	Method WriteFloat:Void(val:Float)
		Local s:String = self.FloatToString(val)
		If Self.data.Length() > 0
			Self.data+=Self.delimiter
		EndIf
		Self.data+=s
		Self.numElements+=1
	End

	Method ReadBool:Bool()
		Local result:Bool
		if Self.ptr >= Self.arr.Length()
			Self.EofError()
		EndIf

		if Self.arr[Self.ptr] = "0"
			result = False
		Else
			result = True
		EndIf

		Self.ptr+=1
		Return result
	End Method

	Method WriteBool:Void(val:Bool)
		Local txt:String
		If Self.data.Length() > 0
			Self.data+=Self.delimiter
		EndIf
		If val
			txt = "1"
		Else
			txt = "0"
		EndIf
		Self.data+=txt
		Self.numElements+=1
	End Method

	Method EofError:Void()
		Error "End of file: " + Self.filename
	End Method
End

Class DataConversion
	Method LittleEndianIntToString:String(val:Int)
		Local result:String
		result = String.FromChar((val) & $FF)
		result+= String.FromChar((val Shr 8) & $FF)
		result+= String.FromChar((val Shr 16) & $FF)
		result+= String.FromChar((val Shr 24) & $FF)
		Return result
	End

	Method StringToLittleEndianInt:Int(val:String)
		Local result:Int
		result = (val[0])
		result|= (val[1] Shl 8)
		result|= (val[2] Shl 16)
		result|= (val[3] Shl 24)
		Return result
	End

    Method IntToString:String(val:Int)
		Return String(val)
    End

    Method StringToInt:Int(val:String)
		Return Int(val)
    End

	Method FloatToString:String(val:Float)
		Return String(val)
	End

	Method StringToFloat:Float(val:String)
		Return Float(val)
	End
End


DeltaTimer now expose the frameTime too

DeltaTimer now expose the frameTime too

Strict

Private

Import mojo

Public

Class DeltaTimer
    Private

    Field _delta:Float
    Field _frameTime:Float
    Field currentTicks:Float
    Field lastTicks:Float
    Field targetFps:Float

    Public

    Method New(fps:Float)
        targetFps = fps
        lastTicks = Millisecs()
    End

    Method OnUpdate:Void()
        currentTicks = Millisecs()
        _frameTime = currentTicks - lastTicks
        _delta = frameTime / (1000.0 / targetFps)
        lastTicks = currentTicks
    End

    Method delta:Float() Property
        Return _delta
    End

    Method frameTime:Float() Property
        Return frameTime
    End
End

Strict

Private

Import mojo

Public

Class DeltaTimer
    Private

    Field currentTicks:Float
    Field frameTime:Float
    Field lastTicks:Float
    Field targetFps:Float
    Field _delta:Float

    Public

    Method New(fps:Float)
        targetFps = fps
        lastTicks = Millisecs()
    End

    Method OnUpdate:Void()
        currentTicks = Millisecs()
        frameTime = currentTicks - lastTicks
        _delta = frameTime / (1000.0 / targetFps)
        lastTicks = currentTicks
    End

    Method delta:Float() Property
        Return _delta
    End
End


don't trigger OnTouchMove automatically after OnTouchDown

Strict

Private

Import bono
Import mojo.input

Public

Class TouchEmitter Implements Updateable, Suspendable
    Private

    Field _touchFingers:Int = MAX_TOUCH_FINGERS
    Field isTouchDown:Bool[MAX_TOUCH_FINGERS]
    Field touchDownDispatched:Bool[MAX_TOUCH_FINGERS]
    Field touchEvents:TouchEvent[MAX_TOUCH_FINGERS]
    Field touchHasMoved:Bool[MAX_TOUCH_FINGERS]

    Public

    Const MAX_TOUCH_FINGERS:Int = 31
    Const RETAIN_UNLIMITED:Int = -1
    Field minDistance:Float = 0
    Field retainSize:Int = RETAIN_UNLIMITED
    Field active:Bool = True
    Field handler:Touchable

    Method OnResume:Void()
    End

    Method OnSuspend:Void()
        For Local i:Int = 0 Until _touchFingers
            touchEvents[i] = Null
        End
    End

    Method OnUpdate:Void(deltatimer:DeltaTimer)
        If Not active Then Return
        If Not handler Then Return

        ReadTouch()
        ProcessTouch()
    End

    Method touchFingers:Void(number:Int) Property
        If number > MAX_TOUCH_FINGERS
            Error("Only " + MAX_TOUCH_FINGERS + " can be tracked.")
        End
        If Not number > 0
            Error("Number of fingers must be greater than 0.")
        End

        _touchFingers = number
    End

    Private

    Method ProcessTouch:Void()
        For Local i:Int = 0 Until _touchFingers
            If touchEvents[i] = Null Then Continue

            If Not touchDownDispatched[i]
                handler.OnTouchDown(touchEvents[i].Copy())
                touchDownDispatched[i] = True
            ElseIf Not isTouchDown[i]
                handler.OnTouchUp(touchEvents[i].Copy())
                touchEvents[i] = Null
            ElseIf touchHasMoved[i]
                handler.OnTouchMove(touchEvents[i])
                touchHasMoved[i] = False
            End
        End
    End

    Method ReadTouch:Void()
        Local diffVector:Vector2D = New Vector2D()
        Local vector:Vector2D
        Local lastTouchDown:Bool

        For Local i:Int = 0 Until _touchFingers
            lastTouchDown = isTouchDown[i]
            isTouchDown[i] = Bool(TouchDown(i))

            If Not isTouchDown[i] And Not lastTouchDown Then Continue
            vector = New Vector2D(TouchX(i), TouchY(i))

            If touchEvents[i] = Null
                touchDownDispatched[i] = False
                touchEvents[i] = New TouchEvent(i)
                touchEvents[i].Add(vector)
            Else
                diffVector.Set(vector).Sub(touchEvents[i].prevPos)
                If diffVector.Length() >= minDistance
                    touchEvents[i].Add(vector)
                    touchHasMoved[i] = True
                    If retainSize > -1 Then touchEvents[i].Trim(retainSize)
                End
            End
        End
    End
End

Strict

Private

Import bono
Import mojo.input

Public

Class TouchEmitter Implements Updateable, Suspendable
    Private

    Field _touchFingers:Int = MAX_TOUCH_FINGERS
    Field isTouchDown:Bool[MAX_TOUCH_FINGERS]
    Field touchDownDispatched:Bool[MAX_TOUCH_FINGERS]
    Field touchEvents:TouchEvent[MAX_TOUCH_FINGERS]
    Field touchHasMoved:Bool[MAX_TOUCH_FINGERS]

    Public

    Const MAX_TOUCH_FINGERS:Int = 31
    Const RETAIN_UNLIMITED:Int = -1
    Field minDistance:Float = 0
    Field retainSize:Int = RETAIN_UNLIMITED
    Field active:Bool = True
    Field handler:Touchable

    Method OnResume:Void()
    End

    Method OnSuspend:Void()
        For Local i:Int = 0 Until _touchFingers
            touchEvents[i] = Null
        End
    End

    Method OnUpdate:Void(deltatimer:DeltaTimer)
        If Not active Then Return
        If Not handler Then Return

        ReadTouch()
        ProcessTouch()
    End

    Method touchFingers:Void(number:Int) Property
        If number > MAX_TOUCH_FINGERS
            Error("Only " + MAX_TOUCH_FINGERS + " can be tracked.")
        End
        If Not number > 0
            Error("Number of fingers must be greater than 0.")
        End

        _touchFingers = number
    End

    Private

    Method ProcessTouch:Void()
        For Local i:Int = 0 Until _touchFingers
            If touchEvents[i] = Null Then Continue

            If Not touchDownDispatched[i]
                handler.OnTouchDown(touchEvents[i].Copy())
                touchDownDispatched[i] = True
            ElseIf Not isTouchDown[i]
                handler.OnTouchUp(touchEvents[i].Copy())
                touchEvents[i] = Null
            ElseIf touchHasMoved[i]
                handler.OnTouchMove(touchEvents[i])
                touchHasMoved[i] = False
            End
        End
    End

    Method ReadTouch:Void()
        Local diffVector:Vector2D = New Vector2D()
        Local vector:Vector2D
        Local lastTouchDown:Bool

        For Local i:Int = 0 Until _touchFingers
            lastTouchDown = isTouchDown[i]
            isTouchDown[i] = Bool(TouchDown(i))

            If Not isTouchDown[i] And Not lastTouchDown Then Continue

            If touchEvents[i] = Null
                touchDownDispatched[i] = False
                touchEvents[i] = New TouchEvent(i)
            End

            vector = New Vector2D(TouchX(i), TouchY(i))
            diffVector.Set(vector).Sub(touchEvents[i].prevPos)

            If diffVector.Length() >= minDistance
                touchEvents[i].Add(vector)
                touchHasMoved[i] = True
                If retainSize > -1 Then touchEvents[i].Trim(retainSize)
            End
        End
    End
End


Removed unused #TEXT_FILES option

Removed unused #TEXT_FILES option

#TEXT_FILES = "*.txt|*.xml|*.json"

#REFLECTION_FILTER += "flixel.flxbasic*|flixel.flxbutton*|flixel.flxcamera*|flixel.flxemitter*"
#REFLECTION_FILTER += "flixel.flxgroup*|flixel.flxmusic*|flixel.flxobject*|flixel.flxparticle*"
#REFLECTION_FILTER += "flixel.flxpath*|flixel.flxpoint*|flixel.flxrect*|flixel.flxsound*"
#REFLECTION_FILTER += "flixel.flxsprite*|flixel.flxstate*|flixel.flxtext*|flixel.flxtileblock*"
#REFLECTION_FILTER += "flixel.flxtilemap*|flixel.plugin*"

#If CONFIG = "debug"
	#FLX_DEBUG_ENABLED = True
#Else
	#FLX_DEBUG_ENABLED = False
#End

#FLX_WEBGL_ENABLED  = False
#FLX_ASYNC_EVENTS_ENABLED = False

#FLX_TEXT_DRIVER = "angelfont"
#FLX_FONT_EXTENSION = ""
#FLX_FONT_IMAGE_MASK = ""