Added a non-generic version of the 'AUTO' constant.

Strict

Public

' Preprocessor related:
#Rem
#If TARGET = "glfw" Or TARGET = "sexy"
	#GLFW_TARGET = True
#End
#End

#UTIL_IMPLEMENTED = True

#If IOELEMENT_IMPLEMENTED
	#UTIL_SUPPORT_IOELEMENTS = True
#Else
	#UTIL_SUPPORT_IOELEMENTS = False
#End

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

#If CONFIG = "release"
	#DEBUG_PRINT_ON_ERROR = True
#Else
	#DEBUG_PRINT_ON_ERROR = False
#End

#DEBUG_PRINT_QUOTES = False

#READ_LINE_QUICKLY = True

#If HOST = "winnt"
	#WRITE_LINE_ENDTYPE = "CRLF"
#Else
	#WRITE_LINE_ENDTYPE = "LF"
#End

' Imports (Public):
Import autostream

Import byteorder
Import imagedimensions
Import retrostrings
Import stringutil
Import vector

#If CONSOLE_IMPLEMENTED
	Import console
#End

Import sizeof
Import time

#If Not BRL_GAMETARGET_IMPLEMENTED
	Import mojoemulator
#End

' Imports (Private):
Private

' Unofficial:
#If UTIL_SUPPORT_IOELEMENTS
	Import ioelement
#End

' Official:

' BRL:
Import brl.databuffer
Import brl.stream
Import brl.filepath

Public

' Constant variable(s):

' Type codes (Mainly used for generic classes):
Const TYPE_OBJECT:Int			= 0
Const TYPE_INT:Int				= 1
Const TYPE_BOOL:Int				= 2
Const TYPE_FLOAT:Int			= 3
Const TYPE_STRING:Int			= 4

' Ascii codes:
Const ASCII_CARRIAGE_RETURN:Int = 13
Const ASCII_LINE_FEED:Int = 10

' Used mainly for external code where '-9999...' isn't ever going to be valid.
' You should stick to 'Null' in most cases; try not to use this.
Const NOVAR:Int = -999999

' This acts as a general "automatic-value".
' Basically, you can check against this for things like manually defined array-lengths.
Const UTIL_AUTO:Int = -1

' This is for situations where the length of something can be optional.
Const AUTOMATIC_LENGTH:Int = -1

Const ErrorTemplate:String = "[ERROR] {Debug}: " ' + Space
Const LogTemplate:String = "[Info] {Debug}: " ' + Space

' Global variable(s) (Public):
#If CONSOLE_IMPLEMENTED
	Global DebugConsole:Console = Null
#End

' A global stack logging randomization seeds.
' This is commonly accessed by the 'PushSeed' and 'PopSeed' functions.
Global SeedStack:Stack<Int> = Null

' Global variable(s) (Private):
Private

' Nothing so far.

Public

' Functions:
Function FLAG:Int(ID:Int)
	Return Pow(2, ID)
End

' This command is little-endian only:
Function AsByte:Int(I:Int)
	Return ((I Shl 24) Shr 24)
End

' This command treats data as 8-bit (Turns something like $F7, into $FFFFFFF7)
' This is only applied on little-endian systems,
' please use this command for hex values (Monkey converts hex on compile-time).
Function PaddedHex:Int(I:Int)
	If (Not BigEndian()) Then
		Return AsByte(I)
	Endif

	Return I
End

Function Transfer:Bool(InputStream:Stream, OutputStream:Stream, DataSize:Int)
	' Check for errors:
	If (DataSize = 0) Then Return False
	If (InputStream = Null Or InputStream.Eof()) Then Return False
	If (OutputStream = Null) Then Return False

	' Local variable(s):
	#If FLAG_TEMP_BUFFERS
		Local Data:DataBuffer = New DataBuffer(DataSize)
		Local DataLength:= DataSize ' Data.Length()

		' Read from the input-stream, then write to the output-stream:
		InputStream.Read(Data, 0, DataLength)
		OutputStream.Write(Data, 0, DataLength)

		Data.Discard()
	#Else
		'Local Data:Int[DataSize]

		For Local I:Int = 1 To DataSize ' Data.Length()
			OutputStream.WriteByte(InputStream.ReadByte())
		Next
	#End

	' Return the default response.
	Return True
End

Function Sq:Float(Input:Float)
	Return Pow(Input, 2.0) ' Input * Input
End

Function Sq:Int(Input:Int)
	Return Pow(Input, 2)
End

Function WrapAngle:Float(A:Float)
	While (A < 0.0)
		A += 360.0
	Wend

	While (A > 360.0)
		A -= 360.0
	Wend

	Return A
End

Function WrapColor:Float(C:Float)
	#Rem
	While (C < 0.0)
		C += 255.0
	Wend

	While (C > 255.0)
		C -= 255.0
	Wend
	#End

	Return C Mod 256.0
End

' This command is a helper function for
Function ProcessColorLocation:Int(Point:Int)
	' Check for errors:
	If (Point < 0) Then
		Return 0
	Endif

	Return (SizeOf_Integer_InBits-(SizeOf_Octet_InBits*(Point+1))) ' ((SizeOf_Integer_InBits-SizeOf_Octet_InBits)-(SizeOf_Octet_InBits*Point))
End

' NOTE: This command will produce incorrect color values without all characters present in the encode-string.
Function ColorToString:String(Pixel:Int, Encoding:String="ARGB")
	' Ensure the encoding is always described as uppercase.
	Encoding = Encoding.ToUpper()

	' Return the encoded color-string.
	Return ("R: " + ((Pixel Shr ProcessColorLocation(Encoding.Find("R"))) & $000000FF) +
			", G: " + ((Pixel Shr ProcessColorLocation(Encoding.Find("G"))) & $000000FF) +
			", B: " + ((Pixel Shr ProcessColorLocation(Encoding.Find("B"))) & $000000FF) +
			", A: " + ((Pixel Shr ProcessColorLocation(Encoding.Find("A"))) & $000000FF))
End

Function PrintColor:Void(Pixel:Int, Encoding:String="ARGB")
	Print(ColorToString(Pixel, Encoding))

	Return
End

' This is just a wrapper for the main implementation:
Function PushSeed:Bool()
	Return PushSeed(Seed)
End

' The return value of this function specifies if the seed-stack already existed.
Function PushSeed:Bool(Seed:Int)
	' Local variable(s):
	Local Response:Bool = (SeedStack <> Null)

	If (Not Response) Then
		SeedStack = New IntStack()
	Endif

	SeedStack.Push(Seed)

	' Return the calculated response.
	Return Response
End

' This command retrieves a seed from the seed-stack,
' if no seed exists, the current randomization seed is returned.
Function PopSeed:Int()
	If (SeedStack <> Null) Then
		If (Not SeedStack.IsEmpty()) Then
			Return SeedStack.Pop()
		Endif
	Endif

	' Return the default response.
	Return Seed
End

' This command writes a standard line to a 'Stream'.
' This supports both 'LF', and 'CRLF' line endings currently, and this can be configured with 'WRITE_LINE_ENDTYPE'.
' Though character encoding is supported with this, only ASCII is supported by 'ReadLine'.
Function WriteLine:Bool(S:Stream, Line:String, CharacterEncoding:String="ascii")
	' Check for errors:
	If (S = Null) Then
		Return False
	Endif

	S.WriteString(Line, CharacterEncoding)

	#If WRITE_LINE_ENDTYPE = "CRLF"
		S.WriteByte(ASCII_CARRIAGE_RETURN)
	#End

	S.WriteByte(ASCII_LINE_FEED)

	' Return the default response.
	Return True
End

' This command reads a standard line from a 'Stream'. (This supports both 'LF', and 'CRLF' line endings currently)
' In addition, only 'ASCII' is currently supported.
Function ReadLine:String(S:Stream)
	' Local variable(s):
	Local Position:= S.Position()
	Local Padding:Int = 0 ' * SizeOf_Char
	Local Str:String

	#If Not READ_LINE_QUICKLY
		Local Size:Int = 0
	#End

	While (Not S.Eof())
		Local Char:= S.ReadByte()

		If (Char <> ASCII_LINE_FEED) Then
			If (Char <> ASCII_CARRIAGE_RETURN) Then
				#If READ_LINE_QUICKLY
					Str += String.FromChar(Char)
				#Else
					Size += 1
				#End
			Else
				Padding = 2*SizeOf_Char
			Endif
		Else
			#Rem
				If (Padding = 0) Then
					Padding = 1
				Endif
			#End

			Padding = Max(Padding, 1*SizeOf_Char)

			Exit
		Endif
	Wend

	#If Not READ_LINE_QUICKLY
		S.Seek(Position)

		Str = S.ReadString(Size, "ascii")

		S.Seek(S.Position()+Padding)
	#End

	' Return the string we read from the stream.
	Return Str
End

Function ResizeBuffer:DataBuffer(Buffer:DataBuffer, Size:Int=AUTOMATIC_LENGTH, CopyData:Bool=True, DiscardOldBuffer:Bool=False, OnlyWhenDifferentSizes:Bool=False)
	Local BufferAvailable:Bool = (Buffer <> Null)

	If (BufferAvailable And OnlyWhenDifferentSizes) Then
		If (Size <> AUTOMATIC_LENGTH And Buffer.Length() = Size) Then
			Return Buffer
		Endif
	Endif

	If (Size = AUTOMATIC_LENGTH) Then
		Size = Buffer.Length()
	Endif

	' Allocate a new data-buffer.
	Local B:= New DataBuffer(Size)

	' Copy the buffer's bytes over to 'B'.
	If (BufferAvailable) Then
		If (CopyData) Then
			' Copy the contents of 'Buffer' to the newly generated buffer-object.
			Buffer.CopyBytes(0, B, 0, Buffer.Length())
		Endif

		If (DiscardOldBuffer) Then
			' Discard the old buffer.
			Buffer.Discard()
		Endif
	Endif

	' Return the newly generated buffer.
	Return B
End

#If CONSOLE_IMPLEMENTED
	Function DebugBind:Bool(C:Console)
		If (C = Null) Then Return False

		DebugConsole = C

		' Return the default response.
		Return True
	End
#End

Function DebugError:Void(E:Throwable, StopExecution:Bool=True)
	#If CONFIG = "debug"
		DebugStop()
	#Else
		DebugPrint("Unknown exception has been thrown; continuing anyway.")
	#End

	Return
End

Function DebugError:Void(Msg:String, StopExecution:Bool=True)
	#If DEBUG_PRINT
		' Local variable(s):
		Local TempMsg:String

		If (StopExecution) Then
			TempMsg = ErrorTemplate
		Else
			TempMsg = LogTemplate
		Endif

		#If DEBUG_PRINT_QUOTES
			Msg = Tempmsg + Quote + Msg + Quote
		#Else
			Msg = TempMsg + Msg
		#End

		#If CONSOLE_IMPLEMENTED
			If (DebugConsole <> Null) Then
				DebugConsole.WriteLine(Msg, True)
			Else
		#End

		Print(Msg)

		#If CONSOLE_IMPLEMENTED
			Endif
		#End
	#Else
		' This may change later:
		If (StopExecution) Then
			Msg = ErrorTemplate + Msg
		Endif
	#End

	If (StopExecution) Then
		#If CONFIG = "debug"
			#If DEBUG_PRINT_ON_ERROR
				DebugStop()
			#Else
				Error(Msg)
			#End
		#Else
			#If DEBUG_PRINT
				Local FinalStr:String = (ErrorTemplate + Quote + "Attempt to stop execution failed. (Reason: Release-mode)" + Quote)

				#If CONSOLE_IMPLEMENTED
					If (DebugConsole <> Null) Then
						DebugConsole.WriteLine(FinalStr, True)
					Else
				#End

				Print(FinalStr)

				#If CONSOLE_IMPLEMENTED
					Endif
				#End
			#Else
				Error(Msg)
			#End
		#End
	Endif

	#If CONFIG = "debug"
		' Nothing so far.
	#Else
		' Nothing so far.
	#End

	Return
End

Function DebugPrint:Void(Str:String, StopExecution:Bool=False)
	DebugError(Str, StopExecution)

	Return
End

' The following functions are designed to be used with generic classes:
Function TypeIsObject:Bool(O:Object)
	Return True
End

Function TypeIsObject:Bool(I:Int)
	Return False
End

Function TypeIsObject:Bool(B:Bool)
	Return False
End

Function TypeIsObject:Bool(F:Float)
	Return False
End

Function TypeIsObject:Bool(S:String)
	Return False
End

Function TypeOf:Int(O:Object)
	Return TYPE_OBJECT
End

Function TypeOf:Int(I:Int)
	Return TYPE_INT
End

Function TypeOf:Int(B:Bool)
	Return TYPE_BOOL
End

Function TypeOf:Int(F:Float)
	Return TYPE_FLOAT
End

Function TypeOf:Int(S:String)
	Return TYPE_STRING
End

' Classes:
Class GenericUtilities<T>
	' Constant variable(s):

	' Comparison response codes (Positive numbers are for error-reporting):
	Const COMPARE_RESPONSE_IDENTICAL:Int			= -1
	Const COMPARE_RESPONSE_WRONG_LENGTH:Int			= -2

	' Other:
	Const AUTO:= UTIL_AUTO

	' Global variable(s):

	' For internal and extern use. That being said, please do not modify this value.
	Global NIL:T

	' Functions:

	' This command gives the user the type-code of 'T'.
	Function Type:Int()
		' Return the type code of 'NIL'.
		Return TypeOf(NIL)
	End

	' This command specifies if 'T' is an object.
	Function IsObject:Bool()
		Return TypeIsObject(NIL)
	End

	' This command wraps the standard 'BoolToString' conversion command.
	Function AsString:String(Input:Bool)
		Return BoolToString(Input)
	End

	' This command reads a string from a standard 'Stream' object.
	Function AsString:String(S:Stream, Length:Int=AUTO, Encoding:String="utf8")
		If (Length = AUTO) Then
			Length = S.Length()
		Endif

		Return S.ReadString(Length, Encoding)
	End

	Function AsString:String(Input:T[], Offset:Int=0, Length:Int=AUTO, AddSpaces:Bool=True)
		' Local variable(s):
		Local Output:String = LeftBracket

		' If no length was specified, use the array's length.
		If (Length = AUTO) Then
			Length = Input.Length()
		Endif

		Local VirtualLength:= Length+Offset

		For Local Index:Int = Offset Until VirtualLength
			Output += Input[Index]

			If (Index+1 < VirtualLength) Then
				Output += Comma

				If (AddSpaces) Then
					Output += Space
				Endif
			Endif
		Next

		Return Output + RightBracket
	End

	Function CopyStringToArray:T[](S:String, Input:T[], Offset:Int=0, Length:Int=AUTO)
		If (Length = AUTO) Then
			Length = Min(S.Length(), Input.Length())
		Endif

		For Local I:Int = Offset Until Length
			Input[I] = S[I]
		Next

		' Just for the sake of convenience, return the 'Input' array.
		Return Input
	End

	Function IndexOfList:T(L:List<T>, Index:Int=0)
		' Local variable(s):
		Local Data:list.Node<T> = L.FirstNode()

		For Local I:= 0 Until Index
			Data = Data.NextNode()

			' Not my favorite method, but it works:
			If (Data = Null) Then
				Return NIL
			Endif
		Next

		' Return the value of the assessed node.
		Return Data.Value()
	End

	Function Zero:T[](Input:T[])
		Return Nil(Input)
	End

	Function Nil:T[](Input:T[])
		For Local Index:= 0 Until Input.Length()
			Input[Index] = NIL
		Next

		Return Input
	End

	Function PrintArray:Void(Input:T[])
		Print(AsString(Input))

		Return
	End

	#Rem
		NOTES:
			* In the event of an error, an empty array will be produced.

			* The 'CopyArray' command's 'FitSource' argument should only be enabled with the
			knowledge that the original destination may have been discarded.

			Your code must reflect this by disregarding the original array,
			and simply assigning the old array to the retrun value.

			EXAMPLE:
				' Local variable(s):
				Local A:T[10], A2:T[3]

				' This will likely produce a new 'T' array, based on the area-delta.
				A2 = CopyArray(A, A2, True)
	#End

	Function CopyArray:T[](Source:T[], Destination:T[], FitSource:Bool)
		Return CopyArray(Source, Destination, 0, 0, AUTO, AUTO, FitSource)
	End

	Function CopyArray:T[](Source:T[], Destination:T[], Source_Offset:Int=0, Destination_Offset:Int=0, Source_Length:Int=AUTO, Destination_Length:Int=AUTO, FitSource:Bool=False)
		If (Source_Length = AUTO) Then
			Source_Length = Source.Length()
		Endif

		If (Destination_Length = AUTO) Then
			Destination_Length = Destination.Length()
		Endif

		' Local variable(s):

		' These two are used as caches for the real lengths of the arrays:
		Local Destination_RealLength:= Destination.Length()
		Local Source_RealLength:= Source.Length()

		' Calculate the source and destination areas:
		Local Source_Area:Int = (Source_Length-Source_Offset)

		' Make sure we have a source to work with:
		If (Source_Area <= 0) Then
			' The source-area is too small for use, return an empty array.
			Return []
		Endif

		Local Destination_Area:Int = (Destination_Length-Destination_Offset)

		' For the sake of safety, we'll also check the destination area:
		If (Destination_Area <= 0) Then
			' The destination-area is too small for use, return an empty array.
			Return []
		Endif

		Local Operation_Area:Int

		If (FitSource And Destination_Area < Source_Area) Then
			Local AreaDelta:Int = (Source_Area-Destination_Area)
			Local NewArea:Int = (Destination_RealLength+AreaDelta)

			If (NewArea > Destination_RealLength) Then
				Destination = Destination.Resize(NewArea)
			Else
				Destination = Destination.Resize(Destination_Length+AreaDelta)
			Endif

			Destination_Area = Source_Area
			Operation_Area = Destination_Area
		Else
			Operation_Area = Min(Source_Area, Destination_Area)
		Endif

		' Copy the contents of the source-array into the destination-array:
		For Local Index:= 0 Until Operation_Area
			Destination[Index+Destination_Offset] = Source[Index+Source_Offset]
		Next

		' Return the destination-array.
		Return Destination
	End

	Function CopyArray:T[](Source:T[])
		Return CloneArray(Source)
	End

	Function CloneArray:T[](Source:T[])
		Return CopyArray(Source, New T[Source.Length()])
	End

	' This command returns a positive number upon an error,
	' otherwise a 'response code' will be given (See the "Contant variable(s)" section for details).
	Function Compare:Int(A1:T[], A2:T[], CheckLength:Bool=True)
		' Local variable(s):
		Local A1_Length:= A1.Length
		Local A2_Length:= A2.Length

		' Check for errors:
		If (CheckLength) Then
			' Make sure they're both the same length.
			If (A1_Length <> A2_Length) Then
				Return COMPARE_RESPONSE_WRONG_LENGTH
			Endif
		Endif

		For Local I:= 0 Until Min(A1_Length, A2_Length)
			If (A1[I] <> A2[I]) Then
				' Not the best of escape methods, but it works.
				Return I
			Endif
		Next

		' Return the default response.
		Return COMPARE_RESPONSE_IDENTICAL
	End

	' This function returns positive if both arrays are identical.
	Function SimpleCompare:Bool(A1:T[], A2:T[], CheckLength:Bool=True)
		Return (Compare(A1, A2, CheckLength) = COMPARE_RESPONSE_IDENTICAL)
	End

	Function Read:T(S:Stream)
		Return Read(S, NIL)
	End

	Function Read:T[](S:Stream, DataArray:T[], Size:Int=AUTO)
		If (Size = AUTO) Then Size = S.ReadInt()

		If (Size > 0) Then
			If (DataArray.Length() = 0) Then
				DataArray = New T[Size]
			Endif
		Endif

		' Local variable(s):
		Local DLength:Int = Min(DataArray.Length, Size)

		For Local Index:= 0 Until DLength
			DataArray[Index] = Read(S, NIL)
		Next

		Return DataArray
	End

	Function Write:Void(S:Stream, DataArray:T[], WriteLength:Bool=True)
		' Local variable(s):
		Local DLength:= DataArray.Length()

		If (WriteLength) Then S.WriteInt(DLength)

		For Local Index:= 0 Until DLength
			Write(S, DataArray[Index])
		Next

		Return
	End

	Function Read:Int(S:Stream, Indentifier:Int, Size:Int=4)
		' Local variable(s):
		Local Data:Int = 0

		If (Size > 1) Then
			If (Size < 4) Then
				Data = S.ReadShort()
			Else
				If (Size > 4) Then
					S.ReadInt()
				Endif

				Data = S.ReadInt()
			Endif
		Else
			Data = S.ReadByte()
		Endif

		Return Data
	End

	Function Write:Void(S:Stream, Data:Int, Size:Int=4)
		Size = IntSize(Size)

		If (Size > 1) Then
			If (Size < 4) Then
				S.WriteShort(Data)
			Else
				If (Size > 4) Then
					S.WriteInt(0)
				Endif

				S.WriteInt(Data)
			Endif
		Else
			S.WriteByte(Data)
		Endif

		Return
	End

	#Rem
	Function Read:Bool(S:Stream, Identifier:Bool)
		Return Read(S, Int(Identifier), 1)
	End

	Function Write:Void(S:Stream, B:Bool)
		Write(S, Int(B), 1)

		Return
	End
	#End

	Function Read:Float(S:Stream, Identifier:Float)
		Return S.ReadFloat()
	End

	Function Write:Void(S:Stream, Data:Float)
		S.WriteFloat(Data)

		Return
	End

	Function Read:String(S:Stream, Data:String, Encoding:String="utf8", Size:Int=AUTO)
		If (Size = AUTO) Then
			Size = S.ReadInt()
		Endif

		Data = S.ReadString(Size, Encoding)

		' Return the newly read string.
		Return Data
	End

	Function Write:Void(S:Stream, Data:String, Encoding:String="utf8", WriteSize:Bool=True)
		If (WriteSize) Then
			S.WriteInt(Data.Length())
		Endif

		S.WriteString(Data, Encoding)

		Return
	End

	#If UTIL_SUPPORT_IOELEMENTS
		Function Read:Void(S:Stream, Data:InputChildElement)
			If (Data = Null) Then
				Return
			Endif

			Data.Load(S)

			Return
		End

		Function Write:Void(S:Stream, Data:OutputChildElement)
			If (Data = Null) Then
				Return
			Endif

			Data.Save(S)
		End
	#End

	' Constructor(s):

	' DO NOT CREATE NEW INSTANCES OF THIS CLASS.
	Method New()
		DebugError("This class should not be used in this was.")
	End
End

Strict

Public

' Preprocessor related:
#Rem
#If TARGET = "glfw" Or TARGET = "sexy"
	#GLFW_TARGET = True
#End
#End

#UTIL_IMPLEMENTED = True

#If IOELEMENT_IMPLEMENTED
	#UTIL_SUPPORT_IOELEMENTS = True
#Else
	#UTIL_SUPPORT_IOELEMENTS = False
#End

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

#If CONFIG = "release"
	#DEBUG_PRINT_ON_ERROR = True
#Else
	#DEBUG_PRINT_ON_ERROR = False
#End

#DEBUG_PRINT_QUOTES = False

#READ_LINE_QUICKLY = True

#If HOST = "winnt"
	#WRITE_LINE_ENDTYPE = "CRLF"
#Else
	#WRITE_LINE_ENDTYPE = "LF"
#End

' Imports (Public):
Import autostream

Import byteorder
Import imagedimensions
Import retrostrings
Import stringutil
Import vector

#If CONSOLE_IMPLEMENTED
	Import console
#End

Import sizeof
Import time

#If Not BRL_GAMETARGET_IMPLEMENTED
	Import mojoemulator
#End

' Imports (Private):
Private

' Unofficial:
#If UTIL_SUPPORT_IOELEMENTS
	Import ioelement
#End

' Official:

' BRL:
Import brl.databuffer
Import brl.stream
Import brl.filepath

Public

' Constant variable(s):

' Type codes (Mainly used for generic classes):
Const TYPE_OBJECT:Int			= 0
Const TYPE_INT:Int				= 1
Const TYPE_BOOL:Int				= 2
Const TYPE_FLOAT:Int			= 3
Const TYPE_STRING:Int			= 4

' Ascii codes:
Const ASCII_CARRIAGE_RETURN:Int = 13
Const ASCII_LINE_FEED:Int = 10

' Used mainly for external code where '-9999...' isn't ever going to be valid.
' You should stick to 'Null' in most cases; try not to use this.
Const NOVAR:Int = -999999

' This is for situations where the length of something can be optional.
Const AUTOMATIC_LENGTH:Int = -1

Const ErrorTemplate:String = "[ERROR] {Debug}: " ' + Space
Const LogTemplate:String = "[Info] {Debug}: " ' + Space

' Global variable(s) (Public):
#If CONSOLE_IMPLEMENTED
	Global DebugConsole:Console = Null
#End

' A global stack logging randomization seeds.
' This is commonly accessed by the 'PushSeed' and 'PopSeed' functions.
Global SeedStack:Stack<Int> = Null

' Global variable(s) (Private):
Private

' Nothing so far.

Public

' Functions:
Function FLAG:Int(ID:Int)
	Return Pow(2, ID)
End

' This command is little-endian only:
Function AsByte:Int(I:Int)
	Return ((I Shl 24) Shr 24)
End

' This command treats data as 8-bit (Turns something like $F7, into $FFFFFFF7)
' This is only applied on little-endian systems,
' please use this command for hex values (Monkey converts hex on compile-time).
Function PaddedHex:Int(I:Int)
	If (Not BigEndian()) Then
		Return AsByte(I)
	Endif

	Return I
End

Function Transfer:Bool(InputStream:Stream, OutputStream:Stream, DataSize:Int)
	' Check for errors:
	If (DataSize = 0) Then Return False
	If (InputStream = Null Or InputStream.Eof()) Then Return False
	If (OutputStream = Null) Then Return False

	' Local variable(s):
	#If FLAG_TEMP_BUFFERS
		Local Data:DataBuffer = New DataBuffer(DataSize)
		Local DataLength:= DataSize ' Data.Length()

		' Read from the input-stream, then write to the output-stream:
		InputStream.Read(Data, 0, DataLength)
		OutputStream.Write(Data, 0, DataLength)

		Data.Discard()
	#Else
		'Local Data:Int[DataSize]

		For Local I:Int = 1 To DataSize ' Data.Length()
			OutputStream.WriteByte(InputStream.ReadByte())
		Next
	#End

	' Return the default response.
	Return True
End

Function Sq:Float(Input:Float)
	Return Pow(Input, 2.0) ' Input * Input
End

Function Sq:Int(Input:Int)
	Return Pow(Input, 2)
End

Function WrapAngle:Float(A:Float)
	While (A < 0.0)
		A += 360.0
	Wend

	While (A > 360.0)
		A -= 360.0
	Wend

	Return A
End

Function WrapColor:Float(C:Float)
	#Rem
	While (C < 0.0)
		C += 255.0
	Wend

	While (C > 255.0)
		C -= 255.0
	Wend
	#End

	Return C Mod 256.0
End

' This command is a helper function for
Function ProcessColorLocation:Int(Point:Int)
	' Check for errors:
	If (Point < 0) Then
		Return 0
	Endif

	Return (SizeOf_Integer_InBits-(SizeOf_Octet_InBits*(Point+1))) ' ((SizeOf_Integer_InBits-SizeOf_Octet_InBits)-(SizeOf_Octet_InBits*Point))
End

' NOTE: This command will produce incorrect color values without all characters present in the encode-string.
Function ColorToString:String(Pixel:Int, Encoding:String="ARGB")
	' Ensure the encoding is always described as uppercase.
	Encoding = Encoding.ToUpper()

	' Return the encoded color-string.
	Return ("R: " + ((Pixel Shr ProcessColorLocation(Encoding.Find("R"))) & $000000FF) +
			", G: " + ((Pixel Shr ProcessColorLocation(Encoding.Find("G"))) & $000000FF) +
			", B: " + ((Pixel Shr ProcessColorLocation(Encoding.Find("B"))) & $000000FF) +
			", A: " + ((Pixel Shr ProcessColorLocation(Encoding.Find("A"))) & $000000FF))
End

Function PrintColor:Void(Pixel:Int, Encoding:String="ARGB")
	Print(ColorToString(Pixel, Encoding))

	Return
End

' This is just a wrapper for the main implementation:
Function PushSeed:Bool()
	Return PushSeed(Seed)
End

' The return value of this function specifies if the seed-stack already existed.
Function PushSeed:Bool(Seed:Int)
	' Local variable(s):
	Local Response:Bool = (SeedStack <> Null)

	If (Not Response) Then
		SeedStack = New IntStack()
	Endif

	SeedStack.Push(Seed)

	' Return the calculated response.
	Return Response
End

' This command retrieves a seed from the seed-stack,
' if no seed exists, the current randomization seed is returned.
Function PopSeed:Int()
	If (SeedStack <> Null) Then
		If (Not SeedStack.IsEmpty()) Then
			Return SeedStack.Pop()
		Endif
	Endif

	' Return the default response.
	Return Seed
End

' This command writes a standard line to a 'Stream'.
' This supports both 'LF', and 'CRLF' line endings currently, and this can be configured with 'WRITE_LINE_ENDTYPE'.
' Though character encoding is supported with this, only ASCII is supported by 'ReadLine'.
Function WriteLine:Bool(S:Stream, Line:String, CharacterEncoding:String="ascii")
	' Check for errors:
	If (S = Null) Then
		Return False
	Endif

	S.WriteString(Line, CharacterEncoding)

	#If WRITE_LINE_ENDTYPE = "CRLF"
		S.WriteByte(ASCII_CARRIAGE_RETURN)
	#End

	S.WriteByte(ASCII_LINE_FEED)

	' Return the default response.
	Return True
End

' This command reads a standard line from a 'Stream'. (This supports both 'LF', and 'CRLF' line endings currently)
' In addition, only 'ASCII' is currently supported.
Function ReadLine:String(S:Stream)
	' Local variable(s):
	Local Position:= S.Position()
	Local Padding:Int = 0 ' * SizeOf_Char
	Local Str:String

	#If Not READ_LINE_QUICKLY
		Local Size:Int = 0
	#End

	While (Not S.Eof())
		Local Char:= S.ReadByte()

		If (Char <> ASCII_LINE_FEED) Then
			If (Char <> ASCII_CARRIAGE_RETURN) Then
				#If READ_LINE_QUICKLY
					Str += String.FromChar(Char)
				#Else
					Size += 1
				#End
			Else
				Padding = 2*SizeOf_Char
			Endif
		Else
			#Rem
				If (Padding = 0) Then
					Padding = 1
				Endif
			#End

			Padding = Max(Padding, 1*SizeOf_Char)

			Exit
		Endif
	Wend

	#If Not READ_LINE_QUICKLY
		S.Seek(Position)

		Str = S.ReadString(Size, "ascii")

		S.Seek(S.Position()+Padding)
	#End

	' Return the string we read from the stream.
	Return Str
End

Function ResizeBuffer:DataBuffer(Buffer:DataBuffer, Size:Int=AUTOMATIC_LENGTH, CopyData:Bool=True, DiscardOldBuffer:Bool=False, OnlyWhenDifferentSizes:Bool=False)
	Local BufferAvailable:Bool = (Buffer <> Null)

	If (BufferAvailable And OnlyWhenDifferentSizes) Then
		If (Size <> AUTOMATIC_LENGTH And Buffer.Length() = Size) Then
			Return Buffer
		Endif
	Endif

	If (Size = AUTOMATIC_LENGTH) Then
		Size = Buffer.Length()
	Endif

	' Allocate a new data-buffer.
	Local B:= New DataBuffer(Size)

	' Copy the buffer's bytes over to 'B'.
	If (BufferAvailable) Then
		If (CopyData) Then
			' Copy the contents of 'Buffer' to the newly generated buffer-object.
			Buffer.CopyBytes(0, B, 0, Buffer.Length())
		Endif

		If (DiscardOldBuffer) Then
			' Discard the old buffer.
			Buffer.Discard()
		Endif
	Endif

	' Return the newly generated buffer.
	Return B
End

#If CONSOLE_IMPLEMENTED
	Function DebugBind:Bool(C:Console)
		If (C = Null) Then Return False

		DebugConsole = C

		' Return the default response.
		Return True
	End
#End

Function DebugError:Void(E:Throwable, StopExecution:Bool=True)
	#If CONFIG = "debug"
		DebugStop()
	#Else
		DebugPrint("Unknown exception has been thrown; continuing anyway.")
	#End

	Return
End

Function DebugError:Void(Msg:String, StopExecution:Bool=True)
	#If DEBUG_PRINT
		' Local variable(s):
		Local TempMsg:String

		If (StopExecution) Then
			TempMsg = ErrorTemplate
		Else
			TempMsg = LogTemplate
		Endif

		#If DEBUG_PRINT_QUOTES
			Msg = Tempmsg + Quote + Msg + Quote
		#Else
			Msg = TempMsg + Msg
		#End

		#If CONSOLE_IMPLEMENTED
			If (DebugConsole <> Null) Then
				DebugConsole.WriteLine(Msg, True)
			Else
		#End

		Print(Msg)

		#If CONSOLE_IMPLEMENTED
			Endif
		#End
	#Else
		' This may change later:
		If (StopExecution) Then
			Msg = ErrorTemplate + Msg
		Endif
	#End

	If (StopExecution) Then
		#If CONFIG = "debug"
			#If DEBUG_PRINT_ON_ERROR
				DebugStop()
			#Else
				Error(Msg)
			#End
		#Else
			#If DEBUG_PRINT
				Local FinalStr:String = (ErrorTemplate + Quote + "Attempt to stop execution failed. (Reason: Release-mode)" + Quote)

				#If CONSOLE_IMPLEMENTED
					If (DebugConsole <> Null) Then
						DebugConsole.WriteLine(FinalStr, True)
					Else
				#End

				Print(FinalStr)

				#If CONSOLE_IMPLEMENTED
					Endif
				#End
			#Else
				Error(Msg)
			#End
		#End
	Endif

	#If CONFIG = "debug"
		' Nothing so far.
	#Else
		' Nothing so far.
	#End

	Return
End

Function DebugPrint:Void(Str:String, StopExecution:Bool=False)
	DebugError(Str, StopExecution)

	Return
End

' The following functions are designed to be used with generic classes:
Function TypeIsObject:Bool(O:Object)
	Return True
End

Function TypeIsObject:Bool(I:Int)
	Return False
End

Function TypeIsObject:Bool(B:Bool)
	Return False
End

Function TypeIsObject:Bool(F:Float)
	Return False
End

Function TypeIsObject:Bool(S:String)
	Return False
End

Function TypeOf:Int(O:Object)
	Return TYPE_OBJECT
End

Function TypeOf:Int(I:Int)
	Return TYPE_INT
End

Function TypeOf:Int(B:Bool)
	Return TYPE_BOOL
End

Function TypeOf:Int(F:Float)
	Return TYPE_FLOAT
End

Function TypeOf:Int(S:String)
	Return TYPE_STRING
End

' Classes:
Class GenericUtilities<T>
	' Constant variable(s):

	' Comparison response codes (Positive numbers are for error-reporting):
	Const COMPARE_RESPONSE_IDENTICAL:Int			= -1
	Const COMPARE_RESPONSE_WRONG_LENGTH:Int			= -2

	' Other:
	Const AUTO:Int = -1

	' Global variable(s):

	' For internal and extern use. That being said, please do not modify this value.
	Global NIL:T

	' Functions:

	' This command gives the user the type-code of 'T'.
	Function Type:Int()
		' Return the type code of 'NIL'.
		Return TypeOf(NIL)
	End

	' This command specifies if 'T' is an object.
	Function IsObject:Bool()
		Return TypeIsObject(NIL)
	End

	' This command wraps the standard 'BoolToString' conversion command.
	Function AsString:String(Input:Bool)
		Return BoolToString(Input)
	End

	' This command reads a string from a standard 'Stream' object.
	Function AsString:String(S:Stream, Length:Int=AUTO, Encoding:String="utf8")
		If (Length = AUTO) Then
			Length = S.Length()
		Endif

		Return S.ReadString(Length, Encoding)
	End

	Function AsString:String(Input:T[], Offset:Int=0, Length:Int=AUTO, AddSpaces:Bool=True)
		' Local variable(s):
		Local Output:String = LeftBracket

		' If no length was specified, use the array's length.
		If (Length = AUTO) Then
			Length = Input.Length()
		Endif

		Local VirtualLength:= Length+Offset

		For Local Index:Int = Offset Until VirtualLength
			Output += Input[Index]

			If (Index+1 < VirtualLength) Then
				Output += Comma

				If (AddSpaces) Then
					Output += Space
				Endif
			Endif
		Next

		Return Output + RightBracket
	End

	Function CopyStringToArray:T[](S:String, Input:T[], Offset:Int=0, Length:Int=AUTO)
		If (Length = AUTO) Then
			Length = Min(S.Length(), Input.Length())
		Endif

		For Local I:Int = Offset Until Length
			Input[I] = S[I]
		Next

		' Just for the sake of convenience, return the 'Input' array.
		Return Input
	End

	Function IndexOfList:T(L:List<T>, Index:Int=0)
		' Local variable(s):
		Local Data:list.Node<T> = L.FirstNode()

		For Local I:= 0 Until Index
			Data = Data.NextNode()

			' Not my favorite method, but it works:
			If (Data = Null) Then
				Return NIL
			Endif
		Next

		' Return the value of the assessed node.
		Return Data.Value()
	End

	Function Zero:T[](Input:T[])
		Return Nil(Input)
	End

	Function Nil:T[](Input:T[])
		For Local Index:= 0 Until Input.Length()
			Input[Index] = NIL
		Next

		Return Input
	End

	Function PrintArray:Void(Input:T[])
		Print(AsString(Input))

		Return
	End

	#Rem
		NOTES:
			* In the event of an error, an empty array will be produced.

			* The 'CopyArray' command's 'FitSource' argument should only be enabled with the
			knowledge that the original destination may have been discarded.

			Your code must reflect this by disregarding the original array,
			and simply assigning the old array to the retrun value.

			EXAMPLE:
				' Local variable(s):
				Local A:T[10], A2:T[3]

				' This will likely produce a new 'T' array, based on the area-delta.
				A2 = CopyArray(A, A2, True)
	#End

	Function CopyArray:T[](Source:T[], Destination:T[], FitSource:Bool)
		Return CopyArray(Source, Destination, 0, 0, AUTO, AUTO, FitSource)
	End

	Function CopyArray:T[](Source:T[], Destination:T[], Source_Offset:Int=0, Destination_Offset:Int=0, Source_Length:Int=AUTO, Destination_Length:Int=AUTO, FitSource:Bool=False)
		If (Source_Length = AUTO) Then
			Source_Length = Source.Length()
		Endif

		If (Destination_Length = AUTO) Then
			Destination_Length = Destination.Length()
		Endif

		' Local variable(s):

		' These two are used as caches for the real lengths of the arrays:
		Local Destination_RealLength:= Destination.Length()
		Local Source_RealLength:= Source.Length()

		' Calculate the source and destination areas:
		Local Source_Area:Int = (Source_Length-Source_Offset)

		' Make sure we have a source to work with:
		If (Source_Area <= 0) Then
			' The source-area is too small for use, return an empty array.
			Return []
		Endif

		Local Destination_Area:Int = (Destination_Length-Destination_Offset)

		' For the sake of safety, we'll also check the destination area:
		If (Destination_Area <= 0) Then
			' The destination-area is too small for use, return an empty array.
			Return []
		Endif

		Local Operation_Area:Int

		If (FitSource And Destination_Area < Source_Area) Then
			Local AreaDelta:Int = (Source_Area-Destination_Area)
			Local NewArea:Int = (Destination_RealLength+AreaDelta)

			If (NewArea > Destination_RealLength) Then
				Destination = Destination.Resize(NewArea)
			Else
				Destination = Destination.Resize(Destination_Length+AreaDelta)
			Endif

			Destination_Area = Source_Area
			Operation_Area = Destination_Area
		Else
			Operation_Area = Min(Source_Area, Destination_Area)
		Endif

		' Copy the contents of the source-array into the destination-array:
		For Local Index:= 0 Until Operation_Area
			Destination[Index+Destination_Offset] = Source[Index+Source_Offset]
		Next

		' Return the destination-array.
		Return Destination
	End

	Function CopyArray:T[](Source:T[])
		Return CloneArray(Source)
	End

	Function CloneArray:T[](Source:T[])
		Return CopyArray(Source, New T[Source.Length()])
	End

	' This command returns a positive number upon an error,
	' otherwise a 'response code' will be given (See the "Contant variable(s)" section for details).
	Function Compare:Int(A1:T[], A2:T[], CheckLength:Bool=True)
		' Local variable(s):
		Local A1_Length:= A1.Length
		Local A2_Length:= A2.Length

		' Check for errors:
		If (CheckLength) Then
			' Make sure they're both the same length.
			If (A1_Length <> A2_Length) Then
				Return COMPARE_RESPONSE_WRONG_LENGTH
			Endif
		Endif

		For Local I:= 0 Until Min(A1_Length, A2_Length)
			If (A1[I] <> A2[I]) Then
				' Not the best of escape methods, but it works.
				Return I
			Endif
		Next

		' Return the default response.
		Return COMPARE_RESPONSE_IDENTICAL
	End

	' This function returns positive if both arrays are identical.
	Function SimpleCompare:Bool(A1:T[], A2:T[], CheckLength:Bool=True)
		Return (Compare(A1, A2, CheckLength) = COMPARE_RESPONSE_IDENTICAL)
	End

	Function Read:T(S:Stream)
		Return Read(S, NIL)
	End

	Function Read:T[](S:Stream, DataArray:T[], Size:Int=AUTO)
		If (Size = AUTO) Then Size = S.ReadInt()

		If (Size > 0) Then
			If (DataArray.Length() = 0) Then
				DataArray = New T[Size]
			Endif
		Endif

		' Local variable(s):
		Local DLength:Int = Min(DataArray.Length, Size)

		For Local Index:= 0 Until DLength
			DataArray[Index] = Read(S, NIL)
		Next

		Return DataArray
	End

	Function Write:Void(S:Stream, DataArray:T[], WriteLength:Bool=True)
		' Local variable(s):
		Local DLength:= DataArray.Length()

		If (WriteLength) Then S.WriteInt(DLength)

		For Local Index:= 0 Until DLength
			Write(S, DataArray[Index])
		Next

		Return
	End

	Function Read:Int(S:Stream, Indentifier:Int, Size:Int=4)
		' Local variable(s):
		Local Data:Int = 0

		If (Size > 1) Then
			If (Size < 4) Then
				Data = S.ReadShort()
			Else
				If (Size > 4) Then
					S.ReadInt()
				Endif

				Data = S.ReadInt()
			Endif
		Else
			Data = S.ReadByte()
		Endif

		Return Data
	End

	Function Write:Void(S:Stream, Data:Int, Size:Int=4)
		Size = IntSize(Size)

		If (Size > 1) Then
			If (Size < 4) Then
				S.WriteShort(Data)
			Else
				If (Size > 4) Then
					S.WriteInt(0)
				Endif

				S.WriteInt(Data)
			Endif
		Else
			S.WriteByte(Data)
		Endif

		Return
	End

	#Rem
	Function Read:Bool(S:Stream, Identifier:Bool)
		Return Read(S, Int(Identifier), 1)
	End

	Function Write:Void(S:Stream, B:Bool)
		Write(S, Int(B), 1)

		Return
	End
	#End

	Function Read:Float(S:Stream, Identifier:Float)
		Return S.ReadFloat()
	End

	Function Write:Void(S:Stream, Data:Float)
		S.WriteFloat(Data)

		Return
	End

	Function Read:String(S:Stream, Data:String, Encoding:String="utf8", Size:Int=AUTO)
		If (Size = AUTO) Then
			Size = S.ReadInt()
		Endif

		Data = S.ReadString(Size, Encoding)

		' Return the newly read string.
		Return Data
	End

	Function Write:Void(S:Stream, Data:String, Encoding:String="utf8", WriteSize:Bool=True)
		If (WriteSize) Then
			S.WriteInt(Data.Length())
		Endif

		S.WriteString(Data, Encoding)

		Return
	End

	#If UTIL_SUPPORT_IOELEMENTS
		Function Read:Void(S:Stream, Data:InputChildElement)
			If (Data = Null) Then
				Return
			Endif

			Data.Load(S)

			Return
		End

		Function Write:Void(S:Stream, Data:OutputChildElement)
			If (Data = Null) Then
				Return
			Endif

			Data.Save(S)
		End
	#End

	' Constructor(s):

	' DO NOT CREATE NEW INSTANCES OF THIS CLASS.
	Method New()
		DebugError("This class should not be used in this was.")
	End
End


Fixed bounds-checking issues, added comments, etc.

Strict

Public

#Rem
	TODO:
		* Replace uses of 'Int' and 'Float' with 'Long' and 'Double'.
			('LongArrayView', 'DoubleArrayView', etc)
#End

' Preprocessor related:
#REGAL_UTIL_BUFFERVIEW = True

#REGAL_UTIL_BUFFERVIEW_BYTE_OPTIMIZATIONS = True

'#If CONFIG = "debug"
	'#REGAL_UTIL_BUFFERVIEW_SAFE = True
'#End

' Imports (Public):
Import brl.databuffer

' Imports (Private):
Private

Import memory

Import regal.sizeof
Import regal.typetool

Import monkey.math

Public

' Interfaces:
Interface BufferView
	' Constant variable(s):
	Const MAX_VIEW_ELEMENTS:= 0 ' -1

	' Methods:
	' Nothing so far.

	' Properties:
	Method Data:DataBuffer() Property
	Method Offset:UInt() Property ' Int
End

' Classes:
Class ArrayView<ValueType> Implements BufferView Abstract
	' Constant variable(s) (Public):
	Const MAX_VIEW_ELEMENTS:= BufferView.MAX_VIEW_ELEMENTS

	' Global variable(s) (Protected):
	Protected

	Global NIL:ValueType

	Public

	' Constructor(s) (Public):

	#Rem
		NOTES:
			* The 'ElementCount' argument(s) is/are bound by the amount of memory given to the view.
				This means the segment of a buffer that you intend to map must be at least the
				length you specify, scaled according to the requested size for each element.

				An invalid size will result in an exception.
	#End

	Method New(ElementSize:UInt, ElementCount:UInt, __Direct:Bool=False)
		Self.ElementSize = ElementSize

		InitializeCustomBuffer(ElementSize, ElementCount, __Direct)
	End

	Method New(ElementSize:UInt, Data:DataBuffer, OffsetInBytes:UInt=0, ElementCount:UInt=MAX_VIEW_ELEMENTS)
		Self.Offset = OffsetInBytes
		Self.ElementSize = ElementSize
		Self.Data = Data

		Self.RawSize = GetRawSize(Data, ElementSize, ElementCount)
	End

	' The 'ExtraOffset' argument is used to offset from the 'View' object's offset.
	Method New(ElementSize:UInt, View:BufferView, ElementCount:UInt=MAX_VIEW_ELEMENTS, ExtraOffset:UInt=0)
		Self.ElementSize = ElementSize

		Self.Offset = (View.Offset + ExtraOffset)

		Self.Data = View.Data

		Self.RawSize = GetRawSize(Self.Data, ElementSize, ElementCount)
	End

	' Constructor(s) (Private):
	Private

	Method GetRawSize:UInt(Data:DataBuffer, ElementSize:UInt, ElementCount:UInt=MAX_VIEW_ELEMENTS)
		If (ElementCount = MAX_VIEW_ELEMENTS) Then
			Return Data.Length
		Endif

		Local IntendedSize:= (ElementCount * ElementSize) ' CountInBytes(ElementCount)

		' Make sure the intended size is correct.
		If (IntendedSize < ElementSize Or IntendedSize > Data.Length) Then
			' The intended size is either too large, or too small.
			Throw New InvalidViewMappingOperation(Self, Offset, IntendedSize)
		Endif

		Return IntendedSize
	End

	' NOTE: It is considered unsafe to call this constructor before assigning a value to the 'ElementSize' property.
	Method InitializeCustomBuffer:Void(ElementSize:UInt, ElementCount:UInt, __Direct:Bool=False) Final
		If (ElementSize = 0 Or ElementCount = 0) Then
			Throw New BulkAllocationException<BufferView>(Self, 0) ' IntendedSize
		Endif

		Local IntendedSize:= (ElementCount * ElementSize)

		Self.Data = New DataBuffer(IntendedSize, __Direct) ' CountInBytes(ElementCount)

		Self.Offset = 0
		Self.RawSize = Self.Data.Length
	End

	Public

	' Methods (Public):

	' If overridden, these methods must respect the 'Offset' property:
	Method Get:ValueType(Index:UInt)
		Return GetRaw(Offset + IndexToAddress(Index))
	End

	Method Set:Void(Index:UInt, Value:ValueType)
		SetRaw(Offset + IndexToAddress(Index), Value)

		Return
	End

	' This returns 'False' if the bounds specified are considered invalid.
	Method GetArray:Bool(Index:UInt, Output:ValueType[], Count:UInt, OutputOffset:UInt=0)
		' Calculate the end-point we'll be reaching.
		Local ByteBounds:= OffsetIndexToAddress(Index+Count)

		' Make sure the end-point fits within our buffer-segment.
		If (ByteBounds > RawSize) Then
			Return False
		Endif

		' This will be used to store our current write-location in 'Output'.
		Local OutputPosition:= OutputOffset

		' The current raw address in the internal buffer.
		Local Address:= OffsetIndexToAddress(Index)

		' Continue until we've reached our described bounds.
		While (Address < ByteBounds)
			' Copy the value located at 'Address' into the output.
			Output[OutputPosition] = GetRaw_Unsafe(Address)

			' Move to the next target-location for the output-data.
			OutputPosition += 1

			' Move forward by one entry.
			Address += ElementSize
		Wend

		' Return the default response.
		Return True
	End

	Method GetArray:ValueType[](Index:UInt, Count:UInt, _ArrOff:UInt=0)
		Local Output:= New ValueType[_ArrOff + Count]

		If (Not GetArray(Index, Output, Count, _ArrOff)) Then
			Return []
		Endif

		Return Output
	End

	' This returns 'False' if the bounds specified are considered invalid.
	Method SetArray:Bool(Index:UInt, Input:ValueType[], Count:UInt, InputOffset:UInt=0)
		' Calculate the end-point we'll be reaching.
		Local ByteBounds:= OffsetIndexToAddress(Index+Count)

		' Make sure the end-point fits within our buffer-segment.
		If (ByteBounds > RawSize) Then
			Return False
		Endif

		' This will store our current position in the 'Input' array.
		Local InputPosition:= InputOffset

		' The current raw address in the internal buffer.
		Local Address:= OffsetIndexToAddress(Index)

		' The amount 'Address' will move by on each iteration.
		Local Stride:= ElementSize

		' Continue until we've reached our described bounds.
		While (Address < ByteBounds)
			' Write a value at the current address using an entry from the input-data.
			SetRaw_Unsafe(Address, Input[InputPosition])

			' Move to the next entry in the input-data.
			InputPosition += 1

			' Move forward by one entry.
			Address += Stride
		Wend

		' Return the default response.
		Return True
	End

	Method SetArray:Bool(Index:UInt, Input:ValueType[])
		Return SetArray(Index, Input, Input.Length)
	End

	' TODO: Optimize this overload to bypass index conversion.
	Method SetArray:Bool(Input:ValueType[])
		Return SetArray(0, Input)
	End

	' This returns 'False' if the bounds specified are considered invalid.
	Method Clear:Bool(Index:UInt, Count:UInt)
		' Calculate the end-point we'll be reaching.
		Local ByteBounds:= OffsetIndexToAddress(Index+Count)

		' Make sure the end-point fits within our buffer-segment.
		If (ByteBounds > RawSize) Then
			Return False
		Endif

		' The current raw address in the internal buffer.
		Local Address:= OffsetIndexToAddress(Index)

		' The amount 'Address' will move by on each iteration.
		Local Stride:= ElementSize

		' Continue until we've reached our described bounds.
		While (Address < ByteBounds)
			' Clear an entry at our current location in the buffer.
			ClearRaw(Address)

			' Move forward by one entry.
			Address += Stride
		Wend

		' Return the default response.
		Return True
	End

	' TODO: Optimize this overload to bypass either index conversion or standard assignment.
	Method Clear:Bool()
		Return Clear(0, Length)
	End

	Method Add:ValueType(Index:UInt, Value:ValueType)
		Local Result:= (Get(Index) + Value)

		Set(Index, Result)

		Return Result
	End

	Method Subtract:ValueType(Index:UInt, Value:ValueType)
		Return Add(Index, -Value)
	End

	' This supplies the raw size of 'Count' elements in bytes.
	Method CountInBytes:UInt(Count:Int)
		Return IndexToAddress(Count) ' (Count * ElementSize)
	End

	' This converts an index to a raw address for use with the internal buffer.
	Method IndexToAddress:UInt(Index:UInt)
		#If REGAL_UTIL_BUFFERVIEW_BYTE_OPTIMIZATIONS
			If (ElementSize = 1) Then
				Return Index
			Endif
		#End

		Return (Index * ElementSize)
	End

	' This converts a raw address to an index.
	Method AddressToIndex:UInt(Address:UInt)
		#If REGAL_UTIL_BUFFERVIEW_BYTE_OPTIMIZATIONS
			If (ElementSize = 1) Then
				Return Address
			Endif
		#End

		Return (Address / ElementSize)
	End

	#Rem
		The result of this method is a non-convertible raw address.
		By definition, this address may not be used with commands like
		'AddressToIndex', as the value would be skewed.

		To fix this address for such commands, subtract a stored result gathered
		from the 'Offset' property at the time this method was called.

		Because the value of 'Offset' can be changed, you must always ensure that
		the offset you apply to restore the appropriate value corresponds with the
		original value associated with this object's 'Offset' property.

		This type of address manipulation is not recommended,
		and should be avoided wherever possible.
	#End

	Method OffsetIndexToAddress:UInt(Index:UInt) Final
		Return (IndexToAddress(Index) + Offset)
	End

	' This method is an extension, which should only be used when converting
	' a value from a real address to a convertible address.
	' This command is not recommended, and is subject to deletion.
	Method __OffsetAddressToIndex:UInt(_Address:UInt) Final
		Return AddressToIndex(_Address - Offset)
	End

	' Methods (Protected):
	Protected

	' Abstract:

	' These two methods operate on raw addresses.
	' This means the input should not be an index,
	' nor should it be a non-offset address.
	' Implementations should not bother with out-of-bounds checks.
	Method GetRaw_Unsafe:ValueType(RawAddress:UInt) Abstract
	Method SetRaw_Unsafe:Void(RawAddress:UInt, Value:ValueType) Abstract

	' Implemented:
	Method SetRaw:Void(Address:UInt, Value:ValueType) Final
		Local ElementSize:= Self.ElementSize

		If ((Address + ElementSize) > RawSize) Then
			Throw New InvalidViewWriteOperation(Self, Address, ElementSize)
		Endif

		SetRaw_Unsafe(Address, Value)

		Return
	End

	Method GetRaw:ValueType(Address:UInt) Final
		Local ElementSize:= Self.ElementSize

		If ((Address + ElementSize) > RawSize) Then
			Throw New InvalidViewReadOperation(Self, Address, ElementSize)
		Endif

		Return GetRaw_Unsafe(Address)
	End

	Method ClearRaw:Void(Address:UInt)
		SetRaw(Address, NIL)

		Return
	End

	Public

	' Properties (Public):

	' This provides the number of elements in this view.
	Method Length:UInt() Property
		#If REGAL_UTIL_BUFFERVIEW_BYTE_OPTIMIZATIONS
			If (ElementSize = 1) Then
				Return Size
			Endif
		#End

		Return (Size / ElementSize)
	End

	' This provides the raw size of the internal buffer (In bytes), offset by the internal view-location.
	Method Size:UInt() Property
		Return (RawSize - Offset)
	End

	' This returns the raw size of the internal buffer (In bytes), without adjusting for offsets.
	' This is mainly useful for raw bounds checks that already account for offsets.
	' For a good example of this, view the 'GetArray' command's implementation(s).
	Method RawSize:UInt() Property ' Int
		Return UInt(Data.Length)
	End

	' This specifies the size of an element.
	Method ElementSize:UInt() Property
		Return Self._ElementSize
	End

	' This provides access to the internal buffer-offset.
	' Note: This is already accounted for when calculating 'Size'.
	Method Offset:UInt() Property ' Int
		Return Self._Offset
	End

	Method Offset:Void(Value:UInt) Property
		Self._Offset = Value

		#If REGAL_UTIL_BUFFERVIEW_SAFE
			' Nothing so far.
		#End

		Return
	End

	' This provides access to the internal buffer.
	Method Data:DataBuffer() Property ' Final
		Return Self._Data
	End

	' Properties (Protected):
	Protected

	Method RawSize:Void(Value:UInt) Property
		Self._RawSize = Value

		Return
	End

	Method Data:Void(Value:DataBuffer) Property
		Self._Data = Value

		Return
	End

	Method ElementSize:Void(Value:UInt) Property
		Self._ElementSize = Value

		Return
	End

	Public

	' Fields (Protected):
	Protected

	Field _ElementSize:UInt ' Int
	Field _Offset:UInt ' Int
	Field _RawSize:UInt ' Int
	Field _Data:DataBuffer

	Public
End

' This is an intermediate class which defines mathematical routines for both integral and floating-point types.
Class MathArrayView<ValueType> Extends ArrayView<ValueType> Abstract
	' Constructor(s):
	Method New(ElementSize:UInt, ElementCount:UInt, __Direct:Bool=False)
		Super.New(ElementSize, ElementCount, __Direct)
	End

	Method New(ElementSize:UInt, Data:DataBuffer, OffsetInBytes:UInt=0, ElementCount:UInt=MAX_VIEW_ELEMENTS)
		Super.New(ElementSize, Data, OffsetInBytes, ElementCount)
	End

	Method New(ElementSize:UInt, View:BufferView, ElementCount:UInt=MAX_VIEW_ELEMENTS, ExtraOffset:UInt=0)
		Super.New(ElementSize, View, ElementCount, ExtraOffset)
	End

	' Methods:

	' This increments the value located at 'Index' by one.
	Method Increment:ValueType(Index:UInt)
		Return Add(Index, ValueType(1))
	End

	' This decrements the value located at 'Index' by one.
	Method Decrement:ValueType(Index:UInt)
		Return Subtract(Index, ValueType(1))
	End

	' This multiplies the value located at 'Index' using the value specified.
	' The result is written into memory, then returned.
	Method Multiply:ValueType(Index:UInt, Value:ValueType)
		Local Result:= (Get(Index) * Value)

		Set(Index, Result)

		Return Result
	End

	' This divides the value located at 'Index' using the value specified.
	' The result is written into memory, then returned.
	Method Divide:ValueType(Index:UInt, Value:ValueType)
		Local Result:= (Get(Index) / Value)

		Set(Index, Result)

		Return Result
	End

	' This squares the value located at 'Index'.
	' The result is both returned by the method, and
	' written into memory at the specified location.
	Method Sq:ValueType(Index:UInt) ' Square
		Local Value:= Get(Index)
		Local Result:= (Value * Value)

		Set(Index, Result)

		Return Result
	End

	' This calculates the square-root of the value located at 'Index'.
	' The result is both returned by this method, and
	' written into memory at the specified location.
	Method Sqrt:ValueType(Index:UInt) ' SquareRoot
		Local Result:= Sqrt(Get(Index))

		Set(Index, Result)

		Return Result
	End
End

Class IntArrayView Extends MathArrayView<Int> ' ArrayView<Long> ' Int ' LongArrayView
	' Constant variable(s):
	Const Type_Size:= SizeOf_Integer ' 4

	' Constructor(s) (Public):
	Method New(Count:UInt, __Direct:Bool=False)
		Super.New(Type_Size, Count, __Direct)
	End

	Method New(Data:DataBuffer, OffsetInBytes:UInt=0, ElementCount:UInt=MAX_VIEW_ELEMENTS)
		Super.New(Type_Size, Data, OffsetInBytes, ElementCount)
	End

	Method New(View:BufferView, ElementCount:UInt=MAX_VIEW_ELEMENTS, ExtraOffset:UInt=0)
		Super.New(Type_Size, View, ElementCount, ExtraOffset)
	End

	' Constructor(s) (Protected):
	Protected

	Method New(Type_Size:UInt, ElementCount:UInt, __Direct:Bool=False)
		Super.New(Type_Size, ElementCount, __Direct)
	End

	Method New(Type_Size:UInt, Data:DataBuffer, OffsetInBytes:UInt=0, ElementCount:UInt=MAX_VIEW_ELEMENTS)
		Super.New(Type_Size, Data, OffsetInBytes, ElementCount)
	End

	Method New(Type_Size:UInt, View:BufferView, ElementCount:UInt=MAX_VIEW_ELEMENTS, ExtraOffset:UInt=0)
		Super.New(Type_Size, View, ElementCount, ExtraOffset)
	End

	Public

	' Methods (Public):
	Method GetUnsigned:Int(Index:UInt)
		Return (Get(Index) & $FFFFFFFF)
	End

	Method SetUnsigned:Void(Index:UInt, Value:Int)
		Set(Index, (Value & $FFFFFFFF))

		Return
	End

	' Methods (Protected):
	Protected

	Method GetRaw_Unsafe:Int(Address:UInt) ' Final
		Return Data.PeekInt(Address)
	End

	Method SetRaw_Unsafe:Void(Address:UInt, Value:Int) ' Final
		Data.PokeInt(Address, Value)

		Return
	End

	Public
End

Class ShortArrayView Extends IntArrayView ' ArrayView<Int> ' Short
	' Constant variable(s):
	Const Type_Size:= SizeOf_Short

	' Constructor(s) (Public):
	Method New(Count:UInt, __Direct:Bool=False)
		Super.New(Type_Size, Count, __Direct)
	End

	Method New(Data:DataBuffer, OffsetInBytes:UInt=0, ElementCount:UInt=MAX_VIEW_ELEMENTS)
		Super.New(Type_Size, Data, OffsetInBytes, ElementCount)
	End

	Method New(View:BufferView, ElementCount:UInt=MAX_VIEW_ELEMENTS, ExtraOffset:UInt=0)
		Super.New(Type_Size, View, ElementCount, ExtraOffset)
	End

	' Constructor(s) (Protected):
	Protected

	Method New(Type_Size:UInt, ElementCount:UInt, __Direct:Bool=False)
		Super.New(Type_Size, ElementCount, __Direct)
	End

	Method New(Type_Size:UInt, Data:DataBuffer, OffsetInBytes:UInt=0, ElementCount:UInt=MAX_VIEW_ELEMENTS)
		Super.New(Type_Size, Data, OffsetInBytes, ElementCount)
	End

	Method New(Type_Size:UInt, View:BufferView, ElementCount:UInt=MAX_VIEW_ELEMENTS, ExtraOffset:UInt=0)
		Super.New(Type_Size, View, ElementCount, ExtraOffset)
	End

	Public

	' Methods (Public):
	Method GetUnsigned:Int(Index:UInt) ' Short
		Return (Get(Index) & $FFFF)
	End

	Method SetUnsigned:Void(Index:UInt, Value:Int) ' Short
		Set(Index, (Value & $FFFF))

		Return
	End

	' Methods (Protected):
	Protected

	Method GetRaw_Unsafe:Int(Address:UInt) ' Final ' Short
		Return Data.PeekShort(Address)
	End

	Method SetRaw_Unsafe:Void(Address:UInt, Value:Int) ' Final ' Short
		Data.PokeShort(Address, Value)

		Return
	End

	Public
End

Class ByteArrayView Extends ShortArrayView ' ArrayView<Int> ' Byte
	' Constant variable(s):
	Const Type_Size:= SizeOf_Byte

	' Constructor(s):
	Method New(Count:UInt, __Direct:Bool=False)
		Super.New(Type_Size, Count, __Direct)
	End

	Method New(Data:DataBuffer, OffsetInBytes:UInt=0, ElementCount:UInt=MAX_VIEW_ELEMENTS)
		Super.New(Type_Size, Data, OffsetInBytes, ElementCount)
	End

	Method New(View:BufferView, ElementCount:UInt=MAX_VIEW_ELEMENTS, ExtraOffset:UInt=0)
		Super.New(Type_Size, View, ElementCount, ExtraOffset)
	End

	' Methods (Public):
	#If REGAL_UTIL_BUFFERVIEW_BYTE_OPTIMIZATIONS
		' These two methods follow the integrity rules of 'Offset':
		Method Get:Int(Address:UInt) ' Byte
			Return GetRaw(Offset + Address)
		End

		Method Set:Void(Address:UInt, Value:Int) ' Byte
			SetRaw(Offset + Address, Value)

			Return
		End

		#Rem
			Method IndexToAddress:UInt(Address:UInt) ' Index:UInt
				Return Address
			End

			Method AddressToIndex:UInt(Address:UInt)
				Return Address
			End
		#End
	#End

	Method GetUnsigned:Int(Address:UInt) ' Byte
		Return (Get(Address) & $FF)
	End

	Method SetUnsigned:Void(Index:UInt, Value:Int) ' Byte
		Set(Index, (Value & $FF))

		Return
	End

	' Methods (Protected):
	Protected

	Method GetRaw_Unsafe:Int(Address:UInt) ' Final ' Byte
		Return Data.PeekByte(Address)
	End

	Method SetRaw_Unsafe:Void(Address:UInt, Value:Int) ' Final ' Byte
		Data.PokeByte(Address, Value)

		Return
	End

	Public
End

Class FloatArrayView Extends MathArrayView<Float> ' DoubleArrayView
	' Constant variable(s):
	Const Type_Size:= SizeOf_Float ' 4 ' 8

	' Constructor(s):
	Method New(Count:UInt, __Direct:Bool=False)
		Super.New(Type_Size, Count, __Direct)
	End

	Method New(Data:DataBuffer, OffsetInBytes:UInt=0, ElementCount:UInt=MAX_VIEW_ELEMENTS)
		Super.New(Type_Size, Data, OffsetInBytes, ElementCount)
	End

	Method New(View:BufferView, ElementCount:UInt=MAX_VIEW_ELEMENTS, ExtraOffset:UInt=0)
		Super.New(Type_Size, View, ElementCount, ExtraOffset)
	End

	' Methods (Protected):
	Protected

	Method GetRaw_Unsafe:Float(Address:UInt) ' Final ' Double
		Return Data.PeekFloat(Address)
	End

	Method SetRaw_Unsafe:Void(Address:UInt, Value:Float) ' Final ' Double
		Data.PokeFloat(Address, Value)

		Return
	End

	Public
End

Strict

Public

#Rem
	TODO:
		* Replace uses of 'Int' and 'Float' with 'Long' and 'Double'.
			('LongArrayView', 'DoubleArrayView', etc)
#End

' Preprocessor related:
#REGAL_UTIL_BUFFERVIEW = True

#REGAL_UTIL_BUFFERVIEW_BYTE_OPTIMIZATIONS = True

'#If CONFIG = "debug"
	'#REGAL_UTIL_BUFFERVIEW_SAFE = True
'#End

' Imports (Public):
Import brl.databuffer

' Imports (Private):
Private

Import memory

Import regal.sizeof
Import regal.typetool

Import monkey.math

Public

' Interfaces:
Interface BufferView
	' Constant variable(s):
	Const MAX_VIEW_ELEMENTS:= 0 ' -1

	' Methods:
	' Nothing so far.

	' Properties:
	Method Data:DataBuffer() Property
	Method Offset:UInt() Property ' Int
End

' Classes:
Class ArrayView<ValueType> Implements BufferView Abstract
	' Constant variable(s) (Public):
	Const MAX_VIEW_ELEMENTS:= BufferView.MAX_VIEW_ELEMENTS

	' Global variable(s) (Protected):
	Protected

	Global NIL:ValueType

	Public

	' Constructor(s) (Public):

	#Rem
		NOTES:
			* The 'ElementCount' argument(s) is/are bound by the amount of memory given to the view.
				This means the segment of a buffer that you intend to map must be at least the
				length you specify, scaled according to the requested size for each element.

				An invalid size will result in an exception.
	#End

	Method New(ElementSize:UInt, ElementCount:UInt, __Direct:Bool=False)
		Self.ElementSize = ElementSize

		InitializeCustomBuffer(ElementSize, ElementCount, __Direct)
	End

	Method New(ElementSize:UInt, Data:DataBuffer, OffsetInBytes:UInt=0, ElementCount:UInt=MAX_VIEW_ELEMENTS)
		Self.Offset = OffsetInBytes
		Self.ElementSize = ElementSize
		Self.Data = Data

		Self.RawSize = GetRawSize(Data, ElementSize, ElementCount)
	End

	' The 'ExtraOffset' argument is used to offset from the 'View' object's offset.
	Method New(ElementSize:UInt, View:BufferView, ElementCount:UInt=MAX_VIEW_ELEMENTS, ExtraOffset:UInt=0)
		Self.ElementSize = ElementSize

		Self.Offset = (View.Offset + ExtraOffset)

		Self.Data = View.Data

		Self.RawSize = GetRawSize(Self.Data, ElementSize, ElementCount)
	End

	' Constructor(s) (Private):
	Private

	Method GetRawSize:UInt(Data:DataBuffer, ElementSize:UInt, ElementCount:UInt=MAX_VIEW_ELEMENTS)
		If (ElementCount = MAX_VIEW_ELEMENTS) Then
			Return Data.Length
		Endif

		Local IntendedSize:= (ElementCount * ElementSize) ' CountInBytes(ElementCount)

		' Make sure the intended size is correct.
		If (IntendedSize < ElementSize Or IntendedSize > Data.Length) Then
			' The intended size is either too large, or too small.
			Throw New InvalidViewMappingOperation(Self, Offset, IntendedSize)
		Endif

		Return IntendedSize
	End

	' NOTE: It is considered unsafe to call this constructor before assigning a value to the 'ElementSize' property.
	Method InitializeCustomBuffer:Void(ElementSize:UInt, ElementCount:UInt, __Direct:Bool=False) Final
		If (ElementSize = 0 Or ElementCount = 0) Then
			Throw New BulkAllocationException<BufferView>(Self, 0) ' IntendedSize
		Endif

		Local IntendedSize:= (ElementCount * ElementSize)

		Self.Data = New DataBuffer(IntendedSize, __Direct) ' CountInBytes(ElementCount)

		Self.Offset = 0
		Self.RawSize = Self.Data.Length
	End

	Public

	' Methods (Public):

	' If overridden, these methods must respect the 'Offset' property:
	Method Get:ValueType(Index:UInt)
		Return GetRaw(Offset + IndexToAddress(Index))
	End

	Method Set:Void(Index:UInt, Value:ValueType)
		SetRaw(Offset + IndexToAddress(Index), Value)

		Return
	End

	' This returns 'False' if the bounds specified are considered invalid.
	Method GetArray:Bool(Index:UInt, Output:ValueType[], Count:UInt, OutputOffset:UInt=0)
		Local ByteBounds:= OffsetIndexToAddress(Index+Count)

		If (ByteBounds > Size) Then
			Return False
		Endif

		Local OutputPosition:= OutputOffset

		Local Address:= OffsetIndexToAddress(Index)

		While (Address < ByteBounds)
			Output[OutputPosition] = GetRaw_Unsafe(Address)

			OutputPosition += 1

			Address += ElementSize
		Wend

		' Return the default response.
		Return True
	End

	Method GetArray:ValueType[](Index:UInt, Count:UInt, _ArrOff:UInt=0)
		Local Output:= New ValueType[_ArrOff + Count]

		If (Not GetArray(Index, Output, Count, _ArrOff)) Then
			Return []
		Endif

		Return Output
	End

	' This returns 'False' if the bounds specified are considered invalid.
	Method SetArray:Bool(Index:UInt, Input:ValueType[], Count:UInt, InputOffset:UInt=0)
		Local ByteBounds:= OffsetIndexToAddress(Index+Count)

		If (ByteBounds > Size) Then
			Return False
		Endif

		Local InputPosition:= InputOffset

		Local Address:= OffsetIndexToAddress(Index)
		Local Stride:= ElementSize

		While (Address < ByteBounds)
			SetRaw_Unsafe(Address, Input[InputPosition])

			InputPosition += 1

			Address += Stride
		Wend

		' Return the default response.
		Return True
	End

	Method SetArray:Bool(Index:UInt, Input:ValueType[])
		Return SetArray(Index, Input, Input.Length)
	End

	' TODO: Optimize this overload to bypass index conversion.
	Method SetArray:Bool(Input:ValueType[])
		Return SetArray(0, Input)
	End

	' This returns 'False' if the bounds specified are considered invalid.
	Method Clear:Bool(Index:UInt, Count:UInt)
		Local ByteBounds:= OffsetIndexToAddress(Index+Count)

		If (ByteBounds > Size) Then
			Return False
		Endif

		Local Address:= OffsetIndexToAddress(Index)
		Local Stride:= ElementSize

		While (Address < ByteBounds)
			ClearRaw(Address)

			Address += Stride
		Wend

		' Return the default response.
		Return True
	End

	' TODO: Optimize this overload to bypass either index conversion or standard assignment.
	Method Clear:Bool()
		Return Clear(0, Length)
	End

	Method Add:ValueType(Index:UInt, Value:ValueType)
		Local Result:= (Get(Index) + Value)

		Set(Index, Result)

		Return Result
	End

	Method Subtract:ValueType(Index:UInt, Value:ValueType)
		Return Add(Index, -Value)
	End

	' This supplies the raw size of 'Count' elements in bytes.
	Method CountInBytes:UInt(Count:Int)
		Return IndexToAddress(Count) ' (Count * ElementSize)
	End

	' This converts an index to a raw address for use with the internal buffer.
	Method IndexToAddress:UInt(Index:UInt)
		#If REGAL_UTIL_BUFFERVIEW_BYTE_OPTIMIZATIONS
			If (ElementSize = 1) Then
				Return Index
			Endif
		#End

		Return (Index * ElementSize)
	End

	' This converts a raw address to an index.
	Method AddressToIndex:UInt(Address:UInt)
		#If REGAL_UTIL_BUFFERVIEW_BYTE_OPTIMIZATIONS
			If (ElementSize = 1) Then
				Return Address
			Endif
		#End

		Return (Address / ElementSize)
	End

	#Rem
		The result of this method is a non-convertible raw address.
		By definition, this address may not be used with commands like
		'AddressToIndex', as the value would be skewed.

		To fix this address for such commands, subtract a stored result gathered
		from the 'Offset' property at the time this method was called.

		Because the value of 'Offset' can be changed, you must always ensure that
		the offset you apply to restore the appropriate value corresponds with the
		original value associated with this object's 'Offset' property.

		This type of address manipulation is not recommended,
		and should be avoided wherever possible.
	#End

	Method OffsetIndexToAddress:UInt(Index:UInt) Final
		Return (IndexToAddress(Index) + Offset)
	End

	' This method is an extension, which should only be used when converting
	' a value from a real address to a convertible address.
	' This command is not recommended, and is subject to deletion.
	Method __OffsetAddressToIndex:UInt(_Address:UInt) Final
		Return AddressToIndex(_Address - Offset)
	End

	' Methods (Protected):
	Protected

	' Abstract:

	' These two methods operate on raw addresses.
	' This means the input should not be an index,
	' nor should it be a non-offset address.
	' Implementations should not bother with out-of-bounds checks.
	Method GetRaw_Unsafe:ValueType(RawAddress:UInt) Abstract
	Method SetRaw_Unsafe:Void(RawAddress:UInt, Value:ValueType) Abstract

	' Implemented:
	Method SetRaw:Void(Address:UInt, Value:ValueType) Final
		Local ElementSize:= Self.ElementSize

		If ((Address + ElementSize) > Size) Then
			Throw New InvalidViewWriteOperation(Self, Address, ElementSize)
		Endif

		SetRaw_Unsafe(Address, Value)

		Return
	End

	Method GetRaw:ValueType(Address:UInt) Final
		Local ElementSize:= Self.ElementSize

		If ((Address + ElementSize) > Size) Then
			Throw New InvalidViewReadOperation(Self, Address, ElementSize)
		Endif

		Return GetRaw_Unsafe(Address)
	End

	Method ClearRaw:Void(Address:UInt)
		SetRaw(Address, NIL)

		Return
	End

	Public

	' Properties (Public):

	' This provides the number of elements in this view.
	Method Length:UInt() Property
		#If REGAL_UTIL_BUFFERVIEW_BYTE_OPTIMIZATIONS
			If (ElementSize = 1) Then
				Return Size
			Endif
		#End

		Return (Size / ElementSize)
	End

	' This provides the raw size of the internal buffer (In bytes), offset by the internal view-location.
	Method Size:UInt() Property
		Return (RawSize - Offset)
	End

	' This returns the raw size of the internal buffer (In bytes), without adjusting for offsets.
	Method RawSize:UInt() Property ' Int
		Return UInt(Data.Length)
	End

	' This specifies the size of an element.
	Method ElementSize:UInt() Property
		Return Self._ElementSize
	End

	' This provides access to the internal buffer-offset.
	' Note: This is already accounted for when calculating 'Size'.
	Method Offset:UInt() Property ' Int
		Return Self._Offset
	End

	Method Offset:Void(Value:UInt) Property
		Self._Offset = Value

		#If REGAL_UTIL_BUFFERVIEW_SAFE
			' Nothing so far.
		#End

		Return
	End

	' This provides access to the internal buffer.
	Method Data:DataBuffer() Property ' Final
		Return Self._Data
	End

	' Properties (Protected):
	Protected

	Method RawSize:Void(Value:UInt) Property
		Self._RawSize = Value

		Return
	End

	Method Data:Void(Value:DataBuffer) Property
		Self._Data = Value

		Return
	End

	Method ElementSize:Void(Value:UInt) Property
		Self._ElementSize = Value

		Return
	End

	Public

	' Fields (Protected):
	Protected

	Field _ElementSize:UInt ' Int
	Field _Offset:UInt ' Int
	Field _RawSize:UInt ' Int
	Field _Data:DataBuffer

	Public
End

' This is an intermediate class which defines mathematical routines for both integral and floating-point types.
Class MathArrayView<ValueType> Extends ArrayView<ValueType> Abstract
	' Constructor(s):
	Method New(ElementSize:UInt, ElementCount:UInt, __Direct:Bool=False)
		Super.New(ElementSize, ElementCount, __Direct)
	End

	Method New(ElementSize:UInt, Data:DataBuffer, OffsetInBytes:UInt=0, ElementCount:UInt=MAX_VIEW_ELEMENTS)
		Super.New(ElementSize, Data, OffsetInBytes, ElementCount)
	End

	Method New(ElementSize:UInt, View:BufferView, ElementCount:UInt=MAX_VIEW_ELEMENTS, ExtraOffset:UInt=0)
		Super.New(ElementSize, View, ElementCount, ExtraOffset)
	End

	' Methods:

	' This increments the value located at 'Index' by one.
	Method Increment:ValueType(Index:UInt)
		Return Add(Index, ValueType(1))
	End

	' This decrements the value located at 'Index' by one.
	Method Decrement:ValueType(Index:UInt)
		Return Subtract(Index, ValueType(1))
	End

	' This multiplies the value located at 'Index' using the value specified.
	' The result is written into memory, then returned.
	Method Multiply:ValueType(Index:UInt, Value:ValueType)
		Local Result:= (Get(Index) * Value)

		Set(Index, Result)

		Return Result
	End

	' This divides the value located at 'Index' using the value specified.
	' The result is written into memory, then returned.
	Method Divide:ValueType(Index:UInt, Value:ValueType)
		Local Result:= (Get(Index) / Value)

		Set(Index, Result)

		Return Result
	End

	' This squares the value located at 'Index'.
	' The result is both returned by the method, and
	' written into memory at the specified location.
	Method Sq:ValueType(Index:UInt) ' Square
		Local Value:= Get(Index)
		Local Result:= (Value * Value)

		Set(Index, Result)

		Return Result
	End

	' This calculates the square-root of the value located at 'Index'.
	' The result is both returned by this method, and
	' written into memory at the specified location.
	Method Sqrt:ValueType(Index:UInt) ' SquareRoot
		Local Result:= Sqrt(Get(Index))

		Set(Index, Result)

		Return Result
	End
End

Class IntArrayView Extends MathArrayView<Int> ' ArrayView<Long> ' Int ' LongArrayView
	' Constant variable(s):
	Const Type_Size:= SizeOf_Integer ' 4

	' Constructor(s) (Public):
	Method New(Count:UInt, __Direct:Bool=False)
		Super.New(Type_Size, Count, __Direct)
	End

	Method New(Data:DataBuffer, OffsetInBytes:UInt=0, ElementCount:UInt=MAX_VIEW_ELEMENTS)
		Super.New(Type_Size, Data, OffsetInBytes, ElementCount)
	End

	Method New(View:BufferView, ElementCount:UInt=MAX_VIEW_ELEMENTS, ExtraOffset:UInt=0)
		Super.New(Type_Size, View, ElementCount, ExtraOffset)
	End

	' Constructor(s) (Protected):
	Protected

	Method New(Type_Size:UInt, ElementCount:UInt, __Direct:Bool=False)
		Super.New(Type_Size, ElementCount, __Direct)
	End

	Method New(Type_Size:UInt, Data:DataBuffer, OffsetInBytes:UInt=0, ElementCount:UInt=MAX_VIEW_ELEMENTS)
		Super.New(Type_Size, Data, OffsetInBytes, ElementCount)
	End

	Method New(Type_Size:UInt, View:BufferView, ElementCount:UInt=MAX_VIEW_ELEMENTS, ExtraOffset:UInt=0)
		Super.New(Type_Size, View, ElementCount, ExtraOffset)
	End

	Public

	' Methods (Public):
	Method GetUnsigned:Int(Index:UInt)
		Return (Get(Index) & $FFFFFFFF)
	End

	Method SetUnsigned:Void(Index:UInt, Value:Int)
		Set(Index, (Value & $FFFFFFFF))

		Return
	End

	' Methods (Protected):
	Protected

	Method GetRaw_Unsafe:Int(Address:UInt) ' Final
		Return Data.PeekInt(Address)
	End

	Method SetRaw_Unsafe:Void(Address:UInt, Value:Int) ' Final
		Data.PokeInt(Address, Value)

		Return
	End

	Public
End

Class ShortArrayView Extends IntArrayView ' ArrayView<Int> ' Short
	' Constant variable(s):
	Const Type_Size:= SizeOf_Short

	' Constructor(s) (Public):
	Method New(Count:UInt, __Direct:Bool=False)
		Super.New(Type_Size, Count, __Direct)
	End

	Method New(Data:DataBuffer, OffsetInBytes:UInt=0, ElementCount:UInt=MAX_VIEW_ELEMENTS)
		Super.New(Type_Size, Data, OffsetInBytes, ElementCount)
	End

	Method New(View:BufferView, ElementCount:UInt=MAX_VIEW_ELEMENTS, ExtraOffset:UInt=0)
		Super.New(Type_Size, View, ElementCount, ExtraOffset)
	End

	' Constructor(s) (Protected):
	Protected

	Method New(Type_Size:UInt, ElementCount:UInt, __Direct:Bool=False)
		Super.New(Type_Size, ElementCount, __Direct)
	End

	Method New(Type_Size:UInt, Data:DataBuffer, OffsetInBytes:UInt=0, ElementCount:UInt=MAX_VIEW_ELEMENTS)
		Super.New(Type_Size, Data, OffsetInBytes, ElementCount)
	End

	Method New(Type_Size:UInt, View:BufferView, ElementCount:UInt=MAX_VIEW_ELEMENTS, ExtraOffset:UInt=0)
		Super.New(Type_Size, View, ElementCount, ExtraOffset)
	End

	Public

	' Methods (Public):
	Method GetUnsigned:Int(Index:UInt) ' Short
		Return (Get(Index) & $FFFF)
	End

	Method SetUnsigned:Void(Index:UInt, Value:Int) ' Short
		Set(Index, (Value & $FFFF))

		Return
	End

	' Methods (Protected):
	Protected

	Method GetRaw_Unsafe:Int(Address:UInt) ' Final ' Short
		Return Data.PeekShort(Address)
	End

	Method SetRaw_Unsafe:Void(Address:UInt, Value:Int) ' Final ' Short
		Data.PokeShort(Address, Value)

		Return
	End

	Public
End

Class ByteArrayView Extends ShortArrayView ' ArrayView<Int> ' Byte
	' Constant variable(s):
	Const Type_Size:= SizeOf_Byte

	' Constructor(s):
	Method New(Count:UInt, __Direct:Bool=False)
		Super.New(Type_Size, Count, __Direct)
	End

	Method New(Data:DataBuffer, OffsetInBytes:UInt=0, ElementCount:UInt=MAX_VIEW_ELEMENTS)
		Super.New(Type_Size, Data, OffsetInBytes, ElementCount)
	End

	Method New(View:BufferView, ElementCount:UInt=MAX_VIEW_ELEMENTS, ExtraOffset:UInt=0)
		Super.New(Type_Size, View, ElementCount, ExtraOffset)
	End

	' Methods (Public):
	#If REGAL_UTIL_BUFFERVIEW_BYTE_OPTIMIZATIONS
		' These two methods follow the integrity rules of 'Offset':
		Method Get:Int(Address:UInt) ' Byte
			Return GetRaw(Offset + Address)
		End

		Method Set:Void(Address:UInt, Value:Int) ' Byte
			SetRaw(Offset + Address, Value)

			Return
		End

		#Rem
			Method IndexToAddress:UInt(Address:UInt) ' Index:UInt
				Return Address
			End

			Method AddressToIndex:UInt(Address:UInt)
				Return Address
			End
		#End
	#End

	Method GetUnsigned:Int(Address:UInt) ' Byte
		Return (Get(Address) & $FF)
	End

	Method SetUnsigned:Void(Index:UInt, Value:Int) ' Byte
		Set(Index, (Value & $FF))

		Return
	End

	' Methods (Protected):
	Protected

	Method GetRaw_Unsafe:Int(Address:UInt) ' Final ' Byte
		Return Data.PeekByte(Address)
	End

	Method SetRaw_Unsafe:Void(Address:UInt, Value:Int) ' Final ' Byte
		Data.PokeByte(Address, Value)

		Return
	End

	Public
End

Class FloatArrayView Extends MathArrayView<Float> ' DoubleArrayView
	' Constant variable(s):
	Const Type_Size:= SizeOf_Float ' 4 ' 8

	' Constructor(s):
	Method New(Count:UInt, __Direct:Bool=False)
		Super.New(Type_Size, Count, __Direct)
	End

	Method New(Data:DataBuffer, OffsetInBytes:UInt=0, ElementCount:UInt=MAX_VIEW_ELEMENTS)
		Super.New(Type_Size, Data, OffsetInBytes, ElementCount)
	End

	Method New(View:BufferView, ElementCount:UInt=MAX_VIEW_ELEMENTS, ExtraOffset:UInt=0)
		Super.New(Type_Size, View, ElementCount, ExtraOffset)
	End

	' Methods (Protected):
	Protected

	Method GetRaw_Unsafe:Float(Address:UInt) ' Final ' Double
		Return Data.PeekFloat(Address)
	End

	Method SetRaw_Unsafe:Void(Address:UInt, Value:Float) ' Final ' Double
		Data.PokeFloat(Address, Value)

		Return
	End

	Public
End


Patched filesystem.monkey with James Poag's fixes

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


Use ArrayHelper.Randomize in StringHelper.Randomize

Strict

Private

Import arrayhelper

Public

Class StringHelper Abstract
    Function Randomize:String(input:String)
        Local split:String[] = input.Split("")
        ArrayHelper.Randomize(split)
        Return "".Join(split)
    End
End

Strict

Public

Class StringHelper Abstract
    Function Randomize:String(input:String)
        Local split:String[] = input.Split("")
        Local len:Int = split.Length()

        For Local idx:Int = 0 Until len
            Local randomIdx:Int = Floor(Rnd(0, len))
            Local swap:String = split[idx]

            split[idx] = split[randomIdx]
            split[randomIdx] = swap
        End

        Return "".Join(split)
    End
End


Reset color in Color.Deactivate() only once

Strict

Private

Import mojo.graphics

Public

Class Color
    Private

    Field oldColor:Color

    Public

    Field red:Float
    Field green:Float
    Field blue:Float
    Field alpha:Float

    Method New(red:Float=255, green:Float=255, blue:Float=255, alpha:Float=1)
        Self.red = red
        Self.green = green
        Self.blue = blue
        Self.alpha = alpha
    End

    Method Activate:Void()
        If Not oldColor Then oldColor = New Color(0, 0, 0, 0)

        Local colorStack:Float[] = GetColor()
        oldColor.red = colorStack[0]
        oldColor.green = colorStack[1]
        oldColor.blue = colorStack[2]
        oldColor.alpha = GetAlpha()

        Set(Self)
    End

    Method Deactivate:Void()
        If Not oldColor Then Return
        Set(oldColor)
        oldColor = Null
    End

    Private

    Method Set:Void(color:Color)
        SetColor(color.red, color.green, color.blue)
        SetAlpha(color.alpha)
    End
End

Strict

Private

Import mojo.graphics

Public

Class Color
    Private

    Field oldColor:Color

    Public

    Field red:Float
    Field green:Float
    Field blue:Float
    Field alpha:Float

    Method New(red:Float=255, green:Float=255, blue:Float=255, alpha:Float=1)
        Self.red = red
        Self.green = green
        Self.blue = blue
        Self.alpha = alpha
    End

    Method Activate:Void()
        If Not oldColor Then oldColor = New Color(0, 0, 0, 0)

        Local colorStack:Float[] = GetColor()
        oldColor.red = colorStack[0]
        oldColor.green = colorStack[1]
        oldColor.blue = colorStack[2]
        oldColor.alpha = GetAlpha()

        Set(Self)
    End

    Method Deactivate:Void()
        If oldColor Then Set(oldColor)
    End

    Private

    Method Set:Void(color:Color)
        SetColor(color.red, color.green, color.blue)
        SetAlpha(color.alpha)
    End
End