[variables] (v0.24) 	Removed silly debuglog

Rem
Copyright (c) 2010 Tim Howard

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
End Rem

SuperStrict

Rem
bbdoc: Variables module
End Rem
Module duct.variables

ModuleInfo "Version: 0.24"
ModuleInfo "Copyright: Tim Howard"
ModuleInfo "License: MIT"

ModuleInfo "History: Version 0.24"
ModuleInfo "History: Removed silly debuglog"
ModuleInfo "History: Version 0.23"
ModuleInfo "History: dIdentifier now uses TListEx"
ModuleInfo "History: Version 0.22"
ModuleInfo "History: Fixed documentation, license"
ModuleInfo "History: Renamed TVariable to dVariable"
ModuleInfo "History: Renamed TIntVariable to dIntVariable"
ModuleInfo "History: Renamed TStringVariable to dStringVariable"
ModuleInfo "History: Renamed TFloatVariable to dFloatVariable"
ModuleInfo "History: Renamed TEvalVariable to dEvalVariable"
ModuleInfo "History: Renamed TIdentifier to dIdentifier"
ModuleInfo "History: Renamed TBoolVariable to dBoolVariable"
ModuleInfo "History: Version 0.21"
ModuleInfo "History: Corrected TVariable.RawToVariable parsing for eval variables; added allowevalvars option"
ModuleInfo "History: Version 0.20"
ModuleInfo "History: Added TBoolVariable support in TVariable.DeserializeUniversal"
ModuleInfo "History: Added TBoolVariable support in TVariable.RawToVariable"
ModuleInfo "History: Fixed an issue with eval variables in TVariable.RawToVariable"
ModuleInfo "History: Corrected some method names and some documentation."
ModuleInfo "History: Version 0.19"
ModuleInfo "History: Added TBoolVariable; documentation correction"
ModuleInfo "History: Version 0.18"
ModuleInfo "History: TIdentifier.AddValue now sets the variable's parent to itself"
ModuleInfo "History: Added SetParent and GetParent to TVaraible"
ModuleInfo "History: Version 0.17"
ModuleInfo "History: General cleanup"
ModuleInfo "History: Version 0.16"
ModuleInfo "History: Added SetAmbiguous to all TVariable types"
ModuleInfo "History: Added option to always use quoting with TStringVariables (on by default)"
ModuleInfo "History: Fixed TStringVariable.ConvToString returning of Null strings"
ModuleInfo "History: Version 0.15"
ModuleInfo "History: Change the Create method definition for easier use"
ModuleInfo "History: Changed the '/eval::' recognizer to '/e:' (as per SNode format change)"
ModuleInfo "History: Added the Copy method to every variable type"
ModuleInfo "History: Changed some formatting"
ModuleInfo "History: Version 0.14"
ModuleInfo "History: Changed type tabbing"
ModuleInfo "History: Fixed script output for TStringVariable (quotes are now only added if whitespace is present)"
ModuleInfo "History: Fixed script output for TIdentifier (identifier names can now contain whitespace, in which case they must be quoted)"
ModuleInfo "History: Version 0.13"
ModuleInfo "History: Added the DeserializeUniversal function to TVariable"
ModuleInfo "History: Added Serialize and Deserialize methods to all variable types"
ModuleInfo "History: Version 0.12"
ModuleInfo "History: Changed module name from 'variablemap' to 'variables'"
ModuleInfo "History: Moved all of duct.utilparser here"
ModuleInfo "History: Added the GetTVType function to all variable types"
ModuleInfo "History: Moved TV_* constants from duct.template here"
ModuleInfo "History: Version 0.11"
ModuleInfo "History: Added the ReportType function to all variable types"
ModuleInfo "History: Version 0.10"
ModuleInfo "History: Added the RawToVariable function to TVariable"
ModuleInfo "History: General code cleanup"
ModuleInfo "History: Version 0.09"
ModuleInfo "History: Corrected usage of syntax (in Returns, Cases, News and Selects)"
ModuleInfo "History: Version 0.08"
ModuleInfo "History: Added the TEvalVariable type"
ModuleInfo "History: Initial release"

Import brl.stream

Import duct.etc
Import duct.objectmap

Rem
	bbdoc: Template variable type for the #dIntVariable type.
End Rem
Const TV_INTEGER:Int = 1
Rem
	bbdoc: Template variable type for the #dStringVariable type.
End Rem
Const TV_STRING:Int = 2
Rem
	bbdoc: Template variable type for the #dFloatVariable type.
End Rem
Const TV_FLOAT:Int = 3
Rem
	bbdoc: Template variable type for the #dEvalVariable type.
End Rem
Const TV_EVAL:Int = 4
Rem
	bbdoc: Template variable type for the #dIdentifier type.
End Rem
Const TV_IDEN:Int = 5
Rem
	bbdoc: Template variable type for the #dBoolVariable type.
End Rem
Const TV_BOOL:Int = 6

Rem
	bbdoc: duct generic variable.
	about: This is the base variable type, you should extend from this to use it.
End Rem
Type dVariable Abstract

	Field m_name:String
	Field m_parent:dVariable

'#region Field accessors

	Rem
		bbdoc: Set the variable's name.
		returns: Nothing.
	End Rem
	Method SetName(name:String)
		m_name = name
	End Method

	Rem
		bbdoc: Get the variable's name.
		returns: The variable's name.
	End Rem
	Method GetName:String()
		Return m_name
	End Method

	Rem
		bbdoc: Set the variable's parent.
		returns: Nothing.
	End Rem
	Method SetParent(parent:dVariable)
		m_parent = parent
	End Method

	Rem
		bbdoc: Get the variable's parent.
		returns: The variable's parent.
	End Rem
	Method GetParent:dVariable()
		Return m_parent
	End Method

	Rem
		bbdoc: Base method for setting the value of the variable to the given string (conversion).
	End Rem
	Method SetAmbiguous(value:String) Abstract

	Rem
		bbdoc: Base method for converting variable data to a script-ready string.
	End Rem
	Method ConvToString:String() Abstract

	Rem
		bbdoc: Base method for converting variable data to a printable/usable-in-code string.
	End Rem
	Method ValueAsString:String() Abstract

'#end region (Field accessors)

'#region Data handling

	Rem
		bbdoc: Create a copy of the variable.
		returns: A clone of the variable.
	End Rem
	Method Copy:dVariable() Abstract

	Rem
		bbdoc: Serialize the variable to a stream.
		returns: Nothing.
		about: @tv tells the method whether it should serialize the TV type for the variable, or to not.
	End Rem
	Method Serialize(stream:TStream, tv:Int = True, name:Int = False) Abstract

	Rem
		bbdoc: Deserialize the variable from a stream.
		returns: The deserialized variable.
		about: @tv tells the method whether it should deserialize the TV type for the variable, or to not.
	End Rem
	Method Deserialize:dVariable(stream:TStream, tv:Int = True, name:Int = False) Abstract

'#end region (Data handling)

	Rem
		bbdoc: Base method: Get the type of this variable.
	End Rem
	Function ReportType:String() Abstract

	Rem
		bbdoc: Base method: Get the TV_* type of this variable.
	End Rem
	Function GetTVType:Int() Abstract

	Rem
		bbdoc: Convert raw data (raw data being things like: "/e:(a+b/0.4181)*a-b" - a dEvalVariable, "A String variable", 3452134 - a dIntVariable, 1204.00321 - a variable) into a Variable.
		returns: A new Variable, or Null if something whacky occured.
		about: @etype is optional, it is used to go automagically to one type of a Variable (1 & 4=String (will check for '/e:' - dEvalVariables and dBoolVariables ['true' or 'false']), 2=Integer, 3=Float).

		@varname is also an optional parameter. It will be used as the name of the variable.

		If @allowevalvars (defaults to False) is True, eval variables will be checked for.
	End Rem
	Function RawToVariable:dVariable(vraw:String, etype:Int = 0, varname:String = "", allowevalvars:Int = False)
		Local variable:dVariable
		If vraw = Null
			'DebugLog("(dVariable.RawToVariable) @vraw = Null; returning a dStringVariable (with @varname and Null value)")
			Return New dStringVariable.Create(varname, Null)
		End If
		If etype = 0 ' Determine the value's type (must be either integer, double, or a string with no spaces)
			' ASCII '0' to '9' = 48-57; '-' = 45, '+' = 43; and '.' = 46
			For Local i:Int = 0 To vraw.Length - 1
				Local c:Int = vraw[i]
				If c >= 48 And c <= 57 Or c = 43 Or c = 45
					If etype = 0 ' Leave float and string alone
						etype = 2 ' Integer so far..
					End If
				Else If c = 46
					If etype = 2 ' Already declared as an integer?
						etype = 3
					End If
				Else ' If the character is not numerical there is nothing else to deduce and the value is a string
					etype = 4
					Exit
				End If
			Next
		End If

		Select etype
			Case 2 ' Integer
				variable = dVariable(New dIntVariable.Create(varname, Int(vraw)))
			Case 3 ' Double/Float
				variable = dVariable(New dFloatVariable.Create(varname, Float(vraw)))
			Case 1, 4
				Local vrawlower:String = vraw.ToLower()
				If vrawlower.StartsWith("/e:") = True and allowevalvars = True
					variable = dVariable(New dEvalVariable.Create(varname, vraw[2..]))
				Else If vrawlower = "true" Or vrawlower = "false"
					variable = New dBoolVariable.Create(varname, (vrawlower = "true") And True Or False)
				Else
					variable = dVariable(New dStringVariable.Create(varname, vraw))
				End If
		End Select
		'DebugLog("TSNode.LoadScriptFromStream().RawToVariable(); vraw = ~q" + vraw + "~q \" + etype)
		?Debug
		If variable = Null Then DebugLog("(dVariable.RawToVariable) Unknown error, 'variable' is Null.")
		?
		Return variable
	End Function

	Rem
		bbdoc: Universally deserialize a variable from the given stream.
		returns: A Deserialized variable.
		about: This will deserialize any variable from the stream.

		This requires the variable to have been serialized with the template type (see #Serialize parameters).

		@name tells the further Deserialize calls if the name should be Deserialized or not.
	End Rem
	Function DeserializeUniversal:dVariable(stream:TStream, name:Int = False)
		Local tv:Int = Int(stream.ReadByte())
		Select tv
			Case TV_INTEGER
				Return New dIntVariable.Deserialize(stream, True, name)
			Case TV_STRING
				Return New dStringVariable.Deserialize(stream, True, name)
			Case TV_FLOAT
				Return New dFloatVariable.Deserialize(stream, True, name)
			Case TV_EVAL
				Return New dEvalVariable.Deserialize(stream, True, name)
			Case TV_IDEN
				Return New dIdentifier.Deserialize(stream, True, name)
			Case TV_BOOL
				Return New dBoolVariable.Deserialize(stream, True, name)
			Default
				DebugLog("(dVariable.DeserializeUniversal) Failed to recognize the TV in the stream: " + tv)
		End Select
		Return Null
	End Function

End Type

Rem
	bbdoc: duct string variable.
End Rem
Type dStringVariable Extends dVariable

	Global m_alwaysusequotes:Int = True

	Field m_value:String

	Rem
		bbdoc: Create a new String variable.
		returns: Itself.
	End Rem
	Method Create:dStringVariable(name:String = Null, value:String)
		SetName(name)
		Set(value)
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the variable's value.
		returns: Nothing.
	End Rem
	Method Set(value:String)
		m_value = value
	End Method

	Rem
		bbdoc: Get the variable's value.
		returns: The value of the variable.
	End Rem
	Method Get:String()
		Return m_value
	End Method

	Rem
		bbdoc: Set the value of the variable to the given string (ambiguous).
		returns: Nothing.
		about: For this type, this method is no different than calling #Set.
	End Rem
	Method SetAmbiguous(value:String)
		m_value = value
	End Method

	Rem
		bbdoc: Convert the variable to a String.
		returns: A String representation of the variable.
		about: This function is for script output, for in-code use see #ValueAsString.
	End Rem
	Method ConvToString:String()
		If m_alwaysusequotes = True Or m_value.Contains("~t") Or m_value.Contains(" ") Or m_value = Null
			Return "~q" + m_value + "~q"
		Else
			Return m_value
		End If
	End Method

	Rem
		bbdoc: Get the variable as a String.
		returns: The variable's value converted to a String.
		about: Here for complete-ness, no difference to `instance.Get()`.
	End Rem
	Method ValueAsString:String()
		Return m_value
	End Method

'#end region (Field accessors)

'#region Data handling

	Rem
		bbdoc: Create a copy of the variable
		returns: A clone of the variable.
	End Rem
	Method Copy:dStringVariable()
		Return New dStringVariable.Create(m_name, m_value)
	End Method

	Rem
		bbdoc: Serialize the variable to a stream.
		returns: Nothing.
		about: @tv tells the method whether it should serialize the TV type for the variable, or to not.
	End Rem
	Method Serialize(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True Then stream.WriteByte(TV_STRING)
		If name = True Then WriteLString(stream, m_name)
		WriteLString(stream, m_value)
	End Method

	Rem
		bbdoc: Deserialize the variable from a stream.
		returns: Itself.
		about: @tv tells the method whether it should deserialize the TV type for the variable, or to not.
	End Rem
	Method Deserialize:dStringVariable(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True Then stream.ReadByte()
		If name = True Then m_name = ReadLString(stream)
		m_value = ReadLString(stream)
		Return Self
	End Method

'#end region (Data handling)

	Rem
		bbdoc: Get the type of this variable.
		returns: The type of this variable ("string").
	End Rem
	Function ReportType:String()
		Return "string"
	End Function

	Rem
		bbdoc: Get the TV type of this variable.
		returns: The TV_* type of this variable (TV_STRING).
	End Rem
	Function GetTVType:Int()
		Return TV_STRING
	End Function

End Type

Rem
	bbdoc: duct Float variable.
End Rem
Type dFloatVariable Extends dVariable

	Field m_value:Float

	Rem
		bbdoc: Create a new Float variable.
		returns: Itself.
	End Rem
	Method Create:dFloatVariable(name:String = Null, value:Float)
		SetName(name)
		Set(value)
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the variable's value.
		returns: Nothing.
	End Rem
	Method Set(value:Float)
		m_value = value
	End Method

	Rem
		bbdoc: Get the variable's value.
		returns: The value of the variable.
	End Rem
	Method Get:Float()
		Return m_value
	End Method

	Rem
		bbdoc: Set the value of the variable to the given string (ambiguous).
		returns: Nothing.
	End Rem
	Method SetAmbiguous(value:String)
		m_value = Float(value)
	End Method

	Rem
		bbdoc: Convert the variable to a String.
		returns: A scriptable form of the variable.
		about: This function is for script output, for in-code use see #ValueAsString.
	End Rem
	Method ConvToString:String()
		Local conv:String = String(m_value), encountered:Int
		For Local i:Int = conv.Find(".") To conv.Length - 1
			If conv[i] = 48
				If encountered = True
					conv = conv[..i]
					Exit
				End If
			Else If conv[i] <> 46
				encountered = True
			End If
		Next
		Return conv
	End Method

	Rem
		bbdoc: Get the variable as a String.
		returns: The variable's value converted to a String.
	End Rem
	Method ValueAsString:String()
		Return String(m_value)
	End Method

'#end region (Field accessors)

'#region Data handling

	Rem
		bbdoc: Create a copy of the variable
		returns: A clone of the variable.
	End Rem
	Method Copy:dFloatVariable()
		Return New dFloatVariable.Create(m_name, m_value)
	End Method

	Rem
		bbdoc: Serialize the variable to a stream.
		returns: Nothing.
		about: @tv tells the method whether it should serialize the TV type for the variable, or to not.
	End Rem
	Method Serialize(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True Then stream.WriteByte(TV_FLOAT)
		If name = True Then WriteLString(stream, m_name)
		stream.WriteFloat(m_value)
	End Method

	Rem
		bbdoc: Deserialize the variable from a stream.
		returns: Itself.
		about: @tv tells the method whether it should deserialize the TV type for the variable, or to not.
	End Rem
	Method Deserialize:dFloatVariable(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True Then stream.ReadByte()
		If name = True Then m_name = ReadLString(stream)
		m_value = stream.ReadFloat()
		Return Self
	End Method

'#end region (Data handling)

	Rem
		bbdoc: Get the type of this variable.
		returns: The type of this variable ("float").
	End Rem
	Function ReportType:String()
		Return "float"
	End Function

	Rem
		bbdoc: Get the TV type of this variable.
		returns: The TV_* type of this variable (TV_FLOAT).
	End Rem
	Function GetTVType:Int()
		Return TV_FLOAT
	End Function

End Type

Rem
	bbdoc: duct Int variable.
End Rem
Type dIntVariable Extends dVariable

	Field m_value:Int

	Rem
		bbdoc: Create a new Int variable.
		returns: Itself.
	End Rem
	Method Create:dIntVariable(name:String = Null, value:Int)
		SetName(name)
		Set(value)
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the variable's value.
		returns: Nothing.
	End Rem
	Method Set(value:Int)
		m_value = value
	End Method

	Rem
		bbdoc: Get the variable's value.
		returns: The value of the variable.
	End Rem
	Method Get:Int()
		Return m_value
	End Method

	Rem
		bbdoc: Set the value of the variable to the given string (ambiguous).
		returns: Nothing.
	End Rem
	Method SetAmbiguous(value:String)
		m_value = Int(value)
	End Method

	Rem
		bbdoc: Convert the variable to a String.
		returns: A string representation of the variable.
		about: This function is for script output, for in-code use see #ValueAsString.
	End Rem
	Method ConvToString:String()
		Return String(m_value)
	End Method

	Rem
		bbdoc: Get the variable as a String.
		returns: The variable's value converted to a String.
	End Rem
	Method ValueAsString:String()
		Return String(m_value)
	End Method

'#end region (Field accessors)

'#region Data handling

	Rem
		bbdoc: Create a copy of the variable
		returns: A clone of the variable.
	End Rem
	Method Copy:dIntVariable()
		Return New dIntVariable.Create(m_name, m_value)
	End Method

	Rem
		bbdoc: Serialize the variable to a stream.
		returns: Nothing.
		about: @tv tells the method whether it should serialize the TV type for the variable, or to not.
	End Rem
	Method Serialize(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True Then stream.WriteByte(TV_INTEGER)
		If name = True Then WriteLString(stream, m_name)
		stream.WriteInt(m_value)
	End Method

	Rem
		bbdoc: Deserialize the variable from a stream.
		returns: Itself.
		about: @tv tells the method whether it should deserialize the TV type for the variable, or to not.
	End Rem
	Method Deserialize:dIntVariable(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True Then stream.ReadByte()
		If name = True Then m_name = ReadLString(stream)
		m_value = stream.ReadInt()
		Return Self
	End Method

'#end region (Data handling)

	Rem
		bbdoc: Get the type of this variable.
		returns: The type of this variable ("int").
	End Rem
	Function ReportType:String()
		Return "int"
	End Function

	Rem
		bbdoc: Get the TV type of this variable.
		returns: The TV_* type of this variable (TV_INTEGER).
	End Rem
	Function GetTVType:Int()
		Return TV_INTEGER
	End Function

End Type

Rem
	bbdoc: duct eval variable (for bah.muparser, and the likes).
End Rem
Type dEvalVariable Extends dVariable

	Field m_value:String

	Rem
		bbdoc: Create a new eval variable.
		returns: Itself.
	End Rem
	Method Create:dEvalVariable(name:String = Null, value:String)
		SetName(name)
		Set(value)
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the equation string for the variable.
		returns: Nothing
	End Rem
	Method Set(value:String)
		m_value = value
	End Method

	Rem
		bbdoc: Get the equation string.
		returns: The equation string.
	End Rem
	Method Get:String()
		Return m_value
	End Method

	Rem
		bbdoc: Set the value of the variable to the given string (ambiguous).
		returns: Nothing.
		about: For this type, this method is no different than calling #Set.
	End Rem
	Method SetAmbiguous(value:String)
		m_value = value
	End Method

	Rem
		bbdoc: Convert the variable to a visual representation of its data.
		returns: The scriptable form of the variable.
		about: This function is for script output, for in-code use see #ValueAsString.
	End Rem
	Method ConvToString:String()
		Return "~q/e:" + m_value + "~q"
	End Method

	Rem
		bbdoc: Get the variable as a String.
		returns: The variable's value converted to a String.
	End Rem
	Method ValueAsString:String()
		Return m_value
	End Method

'#end region (Field accessors)

'#region Data handling

	Rem
		bbdoc: Create a copy of the variable
		returns: A clone of the variable.
	End Rem
	Method Copy:dEvalVariable()
		Return New dEvalVariable.Create(m_name, m_value)
	End Method

	Rem
		bbdoc: Serialize the variable to a stream.
		returns: Nothing.
		about: @tv tells the method whether it should serialize the TV type for the variable, or to not.
	End Rem
	Method Serialize(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True Then stream.WriteByte(TV_EVAL)
		If name = True Then WriteLString(stream, m_name)
		WriteLString(stream, m_value)
	End Method

	Rem
		bbdoc: Deserialize the variable from a stream.
		returns: Itself.
		about: @tv tells the method whether it should deserialize the TV type for the variable, or to not.
	End Rem
	Method Deserialize:dEvalVariable(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True Then stream.ReadByte()
		If name = True Then m_name = ReadLString(stream)
		m_value = ReadLString(stream)
		Return Self
	End Method

'#end region (Data handling)

	Rem
		bbdoc: Get the type of this variable.
		returns: The type of this variable ("eval").
	End Rem
	Function ReportType:String()
		Return "eval"
	End Function

	Rem
		bbdoc: Get the TV type of this variable.
		returns: The TV_* type of this variable (TV_EVAL).
	End Rem
	Function GetTVType:Int()
		Return TV_EVAL
	End Function

End Type

Rem
	bbdoc: duct boolean variable.
End Rem
Type dBoolVariable Extends dVariable

	Field m_value:Int

	Rem
		bbdoc: Create a new boolean variable.
		returns: Itself.
	End Rem
	Method Create:dBoolVariable(name:String = Null, value:Int)
		SetName(name)
		Set(value)
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the variable's value.
		returns: Nothing.
	End Rem
	Method Set(value:Int)
		m_value = value
	End Method

	Rem
		bbdoc: Get the variable's value.
		returns: The value of the variable.
	End Rem
	Method Get:Int()
		Return m_value
	End Method

	Rem
		bbdoc: Set the value of the variable to the given string (ambiguous).
		returns: Nothing.
	End Rem
	Method SetAmbiguous(value:String)
		m_value = Int(value)
	End Method

	Rem
		bbdoc: Convert the variable to a String.
		returns: A string representation of the variable.
		about: This function is for script output, for in-code use see #ValueAsString.
	End Rem
	Method ConvToString:String()
		Return String(m_value)
	End Method

	Rem
		bbdoc: Get the dBoolVariable as a String.
		returns: The variable's value converted to a String.
	End Rem
	Method ValueAsString:String()
		Return String(m_value)
	End Method

'#end region (Field accessors)

'#region Data handling

	Rem
		bbdoc: Create a copy of the variable
		returns: A clone of the variable.
	End Rem
	Method Copy:dBoolVariable()
		Return New dBoolVariable.Create(m_name, m_value)
	End Method

	Rem
		bbdoc: Serialize the variable to a stream.
		returns: Nothing.
		about: @tv tells the method whether it should serialize the TV type for the variable, or to not.
	End Rem
	Method Serialize(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True Then stream.WriteByte(TV_BOOL)
		If name = True Then WriteLString(stream, m_name)
		stream.WriteByte(Byte(m_value))
	End Method

	Rem
		bbdoc: Deserialize the variable from a stream.
		returns: The deserialized variable.
		about: @tv tells the method whether it should deserialize the TV type for the variable, or to not.
	End Rem
	Method Deserialize:dBoolVariable(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True Then stream.ReadByte()
		If name = True Then m_name = ReadLString(stream)
		m_value = Int(stream.ReadByte())
		Return Self
	End Method

'#end region (Data handling)

	Rem
		bbdoc: Get the type of this variable.
		returns: The type of this variable ("bool").
	End Rem
	Function ReportType:String()
		Return "bool"
	End Function

	Rem
		bbdoc: Get the TV type of this variable.
		returns: The TV_* type of this variable (TV_BOOL).
	End Rem
	Function GetTVType:Int()
		Return TV_BOOL
	End Function

End Type

Rem
	bbdoc: duct identifier (used in parsers, mostly).
End Rem
Type dIdentifier Extends dVariable

	Field m_values:TListEx

	Method New()
	End Method

	Rem
		bbdoc: Create a new identifier.
		returns: Itself.
	End Rem
	Method Create:dIdentifier()
		m_values = New TListEx
		Return Self
	End Method

	Rem
		bbdoc: Create a new identifier with the given data.
		returns: Itself.
		about: If the @values parameter is Null a new list will be created.
	End Rem
	Method CreateByData:dIdentifier(name:String, values:TListEx = Null)
		SetName(name)
		If values = Null
			m_values = New TListEx
		Else
			m_values = values
		End If
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the identifier's values.
		returns: Nothing.
	End Rem
	Method SetValues(values:TListEx)
		m_values = values
	End Method

	Rem
		bbdoc: Get the identifier's values.
		returns: A list containing the values which the identifier holds.
	End Rem
	Method GetValues:TListEx()
		Return m_values
	End Method

	Rem
		bbdoc: Deprecated for this type.
		returns: Nothing.
		about: This does nothing.
	End Rem
	Method SetAmbiguous(value:String)
	End Method

	Rem
		bbdoc: Convert the identifier to a string.
		returns: A string representation of the identifier.
	End Rem
	Method ConvToString:String()
		Local op:String
		If m_name.Contains("~t") = True Or m_name.Contains(" ") = True
			op = "~q" + m_name + "~q "
		Else
			op = m_name + " "
		End If
		For Local variable:dVariable = EachIn m_values
			op:+variable.ConvToString() + " "
		Next
		op = op[..op.Length - 1]
		Return op
	End Method

	Rem
		bbdoc: Get the identifier as a string.
		returns: The identifier contents converted to a string.
		about: Here for complete-ness, simply calls #ConvToString.
	End Rem
	Method ValueAsString:String()
		Return ConvToString()
	End Method

'#end region (Field accessors)

'#region Value handling

	Rem
		bbdoc: Get a value at an index.
		returns: The value in the identifier's list at the given index, or Null if it could not be retrieved.
		about: The index is zero-based.
	End Rem
	Method GetValueAtIndex:dVariable(index:Int)
		If m_values <> Null And index > - 1 And index < m_values.Count()
			Return dVariable(m_values.ValueAtIndex(index))
		End If
		Return Null
	End Method

	Rem
		bbdoc: Get the number of values.
		returns: The number of values the identifier contains.
	End Rem
	Method GetValueCount:Int()
		If m_values <> Null
			Return m_values.Count()
		End If
		Return 0
	End Method

	Rem
		bbdoc: Add a value to the identifier.
		returns: True for success, or False for failure.
		about: NOTE: This will set the given value's parent.
	End Rem
	Method AddValue:Int(value:dVariable)
		If m_values <> Null And value <> Null
			m_values.AddLast(value)
			value.SetParent(Self)
			Return True
		End If
		Return False
	End Method

'#end region (Value handling)

'#region Data handling

	Rem
		bbdoc: Create a copy of the identifier
		returns: A clone of the identifier.
	End Rem
	Method Copy:dIdentifier()
		Local clone:dIdentifier
		clone = New dIdentifier.CreateByData(m_name)
		For Local variable:dVariable = EachIn m_values
			clone.AddValue(variable.Copy())
		Next
		Return clone
	End Method

	Rem
		bbdoc: Serialize the variable to a stream.
		returns: Nothing.
		about: @tv tells the method whether it should serialize the TV type for the variable, or to not.

		In this case @name tells the method whether it should serialize the values' name (the identifier's name is always read/written to the stream).
	End Rem
	Method Serialize(stream:TStream, tv:Int = True, name:Int = False)
		Local variable:dVariable
		If tv = True Then stream.WriteByte(TV_STRING)
		WriteLString(stream, m_name)
		If m_values = Null
			stream.WriteInt(0)
		Else
			stream.WriteInt(m_values.Count())
			For variable = EachIn m_values
				variable.Serialize(stream, True, name)
			Next
		End If
	End Method

	Rem
		bbdoc: Deserialize the variable from a stream.
		returns: The deserialized variable.
		about: @tv tells the method whether it should deserialize the TV type for the variable, or to not.

		In this case @name tells the method whether it should deserialize the values' name (the identifier's name is always read/written to the stream).
	End Rem
	Method Deserialize:dIdentifier(stream:TStream, tv:Int = True, name:Int = False)
		Local count:Int, n:Int
		If tv = True Then stream.ReadByte()
		m_name = ReadLString(stream)
		count = stream.ReadInt()
		If count > 0
			For n = 0 To count - 1
				DeserializeUniversal(stream, name)
			Next
		End If
		Return Self
	End Method

'#end region (Data handling)

	Rem
		bbdoc: Get the type of this variable.
		returns: The type of this variable ("identifier").
	End Rem
	Function ReportType:String()
		Return "identifier"
	End Function

	Rem
		bbdoc: Get the TV type of this variable.
		returns: The TV_* type of this variable (TV_IDEN).
	End Rem
	Function GetTVType:Int()
		Return TV_IDEN
	End Function

End Type

Rem
	bbdoc: #dVariable map (stores any variable).
EndRem
Type dVariableMap Extends dObjectMap

	Rem
		bbdoc: Create a new variable map.
		returns: Itself.
	End Rem
	Method Create:dVariableMap()
		Return Self
	End Method

'#region Collections

	Rem
		bbdoc: Insert a variable into the map.
		returns: True if the variable was added, or False if it was not (the variable's name is Null).
	End Rem
	Method InsertVariable:Int(variable:dVariable)
		If variable.GetName() <> Null
			_Insert(variable.GetName(), variable)
			Return True
		End If
		Return False
	End Method

	Rem
		bbdoc: Remove a variable by the name given.
		returns: True if a variable was removed, or False if it was not (the map contains no variable with the name given).
		about: The name is case-sensitive.
	End Rem
	Method RemoveVariableByName:Int(name:String)
		Return _Remove(name)
	End Method

	Rem
		bbdoc: Get a variable from the map by its name.
		returns: The variable object, or if the variable was not found, Null.
		about: The name is case-sensitive.
	End Rem
	Method GetVariableByName:dVariable(name:String)
		Return dVariable(_ValueByKey(name))
	End Method

'#end region (Collections)

End Type

Rem
Copyright (c) 2010 Tim Howard

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
End Rem

SuperStrict

Rem
bbdoc: Variables module
End Rem
Module duct.variables

ModuleInfo "Version: 0.23"
ModuleInfo "Copyright: Tim Howard"
ModuleInfo "License: MIT"

ModuleInfo "History: Version 0.23"
ModuleInfo "History: dIdentifier now uses TListEx"
ModuleInfo "History: Version 0.22"
ModuleInfo "History: Fixed documentation, license"
ModuleInfo "History: Renamed TVariable to dVariable"
ModuleInfo "History: Renamed TIntVariable to dIntVariable"
ModuleInfo "History: Renamed TStringVariable to dStringVariable"
ModuleInfo "History: Renamed TFloatVariable to dFloatVariable"
ModuleInfo "History: Renamed TEvalVariable to dEvalVariable"
ModuleInfo "History: Renamed TIdentifier to dIdentifier"
ModuleInfo "History: Renamed TBoolVariable to dBoolVariable"
ModuleInfo "History: Version 0.21"
ModuleInfo "History: Corrected TVariable.RawToVariable parsing for eval variables; added allowevalvars option"
ModuleInfo "History: Version 0.20"
ModuleInfo "History: Added TBoolVariable support in TVariable.DeserializeUniversal"
ModuleInfo "History: Added TBoolVariable support in TVariable.RawToVariable"
ModuleInfo "History: Fixed an issue with eval variables in TVariable.RawToVariable"
ModuleInfo "History: Corrected some method names and some documentation."
ModuleInfo "History: Version 0.19"
ModuleInfo "History: Added TBoolVariable; documentation correction"
ModuleInfo "History: Version 0.18"
ModuleInfo "History: TIdentifier.AddValue now sets the variable's parent to itself"
ModuleInfo "History: Added SetParent and GetParent to TVaraible"
ModuleInfo "History: Version 0.17"
ModuleInfo "History: General cleanup"
ModuleInfo "History: Version 0.16"
ModuleInfo "History: Added SetAmbiguous to all TVariable types"
ModuleInfo "History: Added option to always use quoting with TStringVariables (on by default)"
ModuleInfo "History: Fixed TStringVariable.ConvToString returning of Null strings"
ModuleInfo "History: Version 0.15"
ModuleInfo "History: Change the Create method definition for easier use"
ModuleInfo "History: Changed the '/eval::' recognizer to '/e:' (as per SNode format change)"
ModuleInfo "History: Added the Copy method to every variable type"
ModuleInfo "History: Changed some formatting"
ModuleInfo "History: Version 0.14"
ModuleInfo "History: Changed type tabbing"
ModuleInfo "History: Fixed script output for TStringVariable (quotes are now only added if whitespace is present)"
ModuleInfo "History: Fixed script output for TIdentifier (identifier names can now contain whitespace, in which case they must be quoted)"
ModuleInfo "History: Version 0.13"
ModuleInfo "History: Added the DeserializeUniversal function to TVariable"
ModuleInfo "History: Added Serialize and Deserialize methods to all variable types"
ModuleInfo "History: Version 0.12"
ModuleInfo "History: Changed module name from 'variablemap' to 'variables'"
ModuleInfo "History: Moved all of duct.utilparser here"
ModuleInfo "History: Added the GetTVType function to all variable types"
ModuleInfo "History: Moved TV_* constants from duct.template here"
ModuleInfo "History: Version 0.11"
ModuleInfo "History: Added the ReportType function to all variable types"
ModuleInfo "History: Version 0.10"
ModuleInfo "History: Added the RawToVariable function to TVariable"
ModuleInfo "History: General code cleanup"
ModuleInfo "History: Version 0.09"
ModuleInfo "History: Corrected usage of syntax (in Returns, Cases, News and Selects)"
ModuleInfo "History: Version 0.08"
ModuleInfo "History: Added the TEvalVariable type"
ModuleInfo "History: Initial release"

Import brl.stream

Import duct.etc
Import duct.objectmap

Rem
	bbdoc: Template variable type for the #dIntVariable type.
End Rem
Const TV_INTEGER:Int = 1
Rem
	bbdoc: Template variable type for the #dStringVariable type.
End Rem
Const TV_STRING:Int = 2
Rem
	bbdoc: Template variable type for the #dFloatVariable type.
End Rem
Const TV_FLOAT:Int = 3
Rem
	bbdoc: Template variable type for the #dEvalVariable type.
End Rem
Const TV_EVAL:Int = 4
Rem
	bbdoc: Template variable type for the #dIdentifier type.
End Rem
Const TV_IDEN:Int = 5
Rem
	bbdoc: Template variable type for the #dBoolVariable type.
End Rem
Const TV_BOOL:Int = 6

Rem
	bbdoc: duct generic variable.
	about: This is the base variable type, you should extend from this to use it.
End Rem
Type dVariable Abstract

	Field m_name:String
	Field m_parent:dVariable

'#region Field accessors

	Rem
		bbdoc: Set the variable's name.
		returns: Nothing.
	End Rem
	Method SetName(name:String)
		m_name = name
	End Method

	Rem
		bbdoc: Get the variable's name.
		returns: The variable's name.
	End Rem
	Method GetName:String()
		Return m_name
	End Method

	Rem
		bbdoc: Set the variable's parent.
		returns: Nothing.
	End Rem
	Method SetParent(parent:dVariable)
		m_parent = parent
	End Method

	Rem
		bbdoc: Get the variable's parent.
		returns: The variable's parent.
	End Rem
	Method GetParent:dVariable()
		Return m_parent
	End Method

	Rem
		bbdoc: Base method for setting the value of the variable to the given string (conversion).
	End Rem
	Method SetAmbiguous(value:String) Abstract

	Rem
		bbdoc: Base method for converting variable data to a script-ready string.
	End Rem
	Method ConvToString:String() Abstract

	Rem
		bbdoc: Base method for converting variable data to a printable/usable-in-code string.
	End Rem
	Method ValueAsString:String() Abstract

'#end region (Field accessors)

'#region Data handling

	Rem
		bbdoc: Create a copy of the variable.
		returns: A clone of the variable.
	End Rem
	Method Copy:dVariable() Abstract

	Rem
		bbdoc: Serialize the variable to a stream.
		returns: Nothing.
		about: @tv tells the method whether it should serialize the TV type for the variable, or to not.
	End Rem
	Method Serialize(stream:TStream, tv:Int = True, name:Int = False) Abstract

	Rem
		bbdoc: Deserialize the variable from a stream.
		returns: The deserialized variable.
		about: @tv tells the method whether it should deserialize the TV type for the variable, or to not.
	End Rem
	Method Deserialize:dVariable(stream:TStream, tv:Int = True, name:Int = False) Abstract

'#end region (Data handling)

	Rem
		bbdoc: Base method: Get the type of this variable.
	End Rem
	Function ReportType:String() Abstract

	Rem
		bbdoc: Base method: Get the TV_* type of this variable.
	End Rem
	Function GetTVType:Int() Abstract

	Rem
		bbdoc: Convert raw data (raw data being things like: "/e:(a+b/0.4181)*a-b" - a dEvalVariable, "A String variable", 3452134 - a dIntVariable, 1204.00321 - a variable) into a Variable.
		returns: A new Variable, or Null if something whacky occured.
		about: @etype is optional, it is used to go automagically to one type of a Variable (1 & 4=String (will check for '/e:' - dEvalVariables and dBoolVariables ['true' or 'false']), 2=Integer, 3=Float).

		@varname is also an optional parameter. It will be used as the name of the variable.

		If @allowevalvars (defaults to False) is True, eval variables will be checked for.
	End Rem
	Function RawToVariable:dVariable(vraw:String, etype:Int = 0, varname:String = "", allowevalvars:Int = False)
		Local variable:dVariable
		If vraw = Null
			DebugLog("(dVariable.RawToVariable) @vraw = Null; returning a dStringVariable (with @varname and Null value)")
			Return New dStringVariable.Create(varname, Null)
		End If
		If etype = 0 ' Determine the value's type (must be either integer, double, or a string with no spaces)
			' ASCII '0' to '9' = 48-57; '-' = 45, '+' = 43; and '.' = 46
			For Local i:Int = 0 To vraw.Length - 1
				Local c:Int = vraw[i]
				If c >= 48 And c <= 57 Or c = 43 Or c = 45
					If etype = 0 ' Leave float and string alone
						etype = 2 ' Integer so far..
					End If
				Else If c = 46
					If etype = 2 ' Already declared as an integer?
						etype = 3
					End If
				Else ' If the character is not numerical there is nothing else to deduce and the value is a string
					etype = 4
					Exit
				End If
			Next
		End If

		Select etype
			Case 2 ' Integer
				variable = dVariable(New dIntVariable.Create(varname, Int(vraw)))
			Case 3 ' Double/Float
				variable = dVariable(New dFloatVariable.Create(varname, Float(vraw)))
			Case 1, 4
				Local vrawlower:String = vraw.ToLower()
				If vrawlower.StartsWith("/e:") = True and allowevalvars = True
					variable = dVariable(New dEvalVariable.Create(varname, vraw[2..]))
				Else If vrawlower = "true" Or vrawlower = "false"
					variable = New dBoolVariable.Create(varname, (vrawlower = "true") And True Or False)
				Else
					variable = dVariable(New dStringVariable.Create(varname, vraw))
				End If
		End Select
		'DebugLog("TSNode.LoadScriptFromStream().RawToVariable(); vraw = ~q" + vraw + "~q \" + etype)
		?Debug
		If variable = Null Then DebugLog("(dVariable.RawToVariable) Unknown error, 'variable' is Null.")
		?
		Return variable
	End Function

	Rem
		bbdoc: Universally deserialize a variable from the given stream.
		returns: A Deserialized variable.
		about: This will deserialize any variable from the stream.

		This requires the variable to have been serialized with the template type (see #Serialize parameters).

		@name tells the further Deserialize calls if the name should be Deserialized or not.
	End Rem
	Function DeserializeUniversal:dVariable(stream:TStream, name:Int = False)
		Local tv:Int = Int(stream.ReadByte())
		Select tv
			Case TV_INTEGER
				Return New dIntVariable.Deserialize(stream, True, name)
			Case TV_STRING
				Return New dStringVariable.Deserialize(stream, True, name)
			Case TV_FLOAT
				Return New dFloatVariable.Deserialize(stream, True, name)
			Case TV_EVAL
				Return New dEvalVariable.Deserialize(stream, True, name)
			Case TV_IDEN
				Return New dIdentifier.Deserialize(stream, True, name)
			Case TV_BOOL
				Return New dBoolVariable.Deserialize(stream, True, name)
			Default
				DebugLog("(dVariable.DeserializeUniversal) Failed to recognize the TV in the stream: " + tv)
		End Select
		Return Null
	End Function

End Type

Rem
	bbdoc: duct string variable.
End Rem
Type dStringVariable Extends dVariable

	Global m_alwaysusequotes:Int = True

	Field m_value:String

	Rem
		bbdoc: Create a new String variable.
		returns: Itself.
	End Rem
	Method Create:dStringVariable(name:String = Null, value:String)
		SetName(name)
		Set(value)
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the variable's value.
		returns: Nothing.
	End Rem
	Method Set(value:String)
		m_value = value
	End Method

	Rem
		bbdoc: Get the variable's value.
		returns: The value of the variable.
	End Rem
	Method Get:String()
		Return m_value
	End Method

	Rem
		bbdoc: Set the value of the variable to the given string (ambiguous).
		returns: Nothing.
		about: For this type, this method is no different than calling #Set.
	End Rem
	Method SetAmbiguous(value:String)
		m_value = value
	End Method

	Rem
		bbdoc: Convert the variable to a String.
		returns: A String representation of the variable.
		about: This function is for script output, for in-code use see #ValueAsString.
	End Rem
	Method ConvToString:String()
		If m_alwaysusequotes = True Or m_value.Contains("~t") Or m_value.Contains(" ") Or m_value = Null
			Return "~q" + m_value + "~q"
		Else
			Return m_value
		End If
	End Method

	Rem
		bbdoc: Get the variable as a String.
		returns: The variable's value converted to a String.
		about: Here for complete-ness, no difference to `instance.Get()`.
	End Rem
	Method ValueAsString:String()
		Return m_value
	End Method

'#end region (Field accessors)

'#region Data handling

	Rem
		bbdoc: Create a copy of the variable
		returns: A clone of the variable.
	End Rem
	Method Copy:dStringVariable()
		Return New dStringVariable.Create(m_name, m_value)
	End Method

	Rem
		bbdoc: Serialize the variable to a stream.
		returns: Nothing.
		about: @tv tells the method whether it should serialize the TV type for the variable, or to not.
	End Rem
	Method Serialize(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True Then stream.WriteByte(TV_STRING)
		If name = True Then WriteLString(stream, m_name)
		WriteLString(stream, m_value)
	End Method

	Rem
		bbdoc: Deserialize the variable from a stream.
		returns: Itself.
		about: @tv tells the method whether it should deserialize the TV type for the variable, or to not.
	End Rem
	Method Deserialize:dStringVariable(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True Then stream.ReadByte()
		If name = True Then m_name = ReadLString(stream)
		m_value = ReadLString(stream)
		Return Self
	End Method

'#end region (Data handling)

	Rem
		bbdoc: Get the type of this variable.
		returns: The type of this variable ("string").
	End Rem
	Function ReportType:String()
		Return "string"
	End Function

	Rem
		bbdoc: Get the TV type of this variable.
		returns: The TV_* type of this variable (TV_STRING).
	End Rem
	Function GetTVType:Int()
		Return TV_STRING
	End Function

End Type

Rem
	bbdoc: duct Float variable.
End Rem
Type dFloatVariable Extends dVariable

	Field m_value:Float

	Rem
		bbdoc: Create a new Float variable.
		returns: Itself.
	End Rem
	Method Create:dFloatVariable(name:String = Null, value:Float)
		SetName(name)
		Set(value)
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the variable's value.
		returns: Nothing.
	End Rem
	Method Set(value:Float)
		m_value = value
	End Method

	Rem
		bbdoc: Get the variable's value.
		returns: The value of the variable.
	End Rem
	Method Get:Float()
		Return m_value
	End Method

	Rem
		bbdoc: Set the value of the variable to the given string (ambiguous).
		returns: Nothing.
	End Rem
	Method SetAmbiguous(value:String)
		m_value = Float(value)
	End Method

	Rem
		bbdoc: Convert the variable to a String.
		returns: A scriptable form of the variable.
		about: This function is for script output, for in-code use see #ValueAsString.
	End Rem
	Method ConvToString:String()
		Local conv:String = String(m_value), encountered:Int
		For Local i:Int = conv.Find(".") To conv.Length - 1
			If conv[i] = 48
				If encountered = True
					conv = conv[..i]
					Exit
				End If
			Else If conv[i] <> 46
				encountered = True
			End If
		Next
		Return conv
	End Method

	Rem
		bbdoc: Get the variable as a String.
		returns: The variable's value converted to a String.
	End Rem
	Method ValueAsString:String()
		Return String(m_value)
	End Method

'#end region (Field accessors)

'#region Data handling

	Rem
		bbdoc: Create a copy of the variable
		returns: A clone of the variable.
	End Rem
	Method Copy:dFloatVariable()
		Return New dFloatVariable.Create(m_name, m_value)
	End Method

	Rem
		bbdoc: Serialize the variable to a stream.
		returns: Nothing.
		about: @tv tells the method whether it should serialize the TV type for the variable, or to not.
	End Rem
	Method Serialize(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True Then stream.WriteByte(TV_FLOAT)
		If name = True Then WriteLString(stream, m_name)
		stream.WriteFloat(m_value)
	End Method

	Rem
		bbdoc: Deserialize the variable from a stream.
		returns: Itself.
		about: @tv tells the method whether it should deserialize the TV type for the variable, or to not.
	End Rem
	Method Deserialize:dFloatVariable(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True Then stream.ReadByte()
		If name = True Then m_name = ReadLString(stream)
		m_value = stream.ReadFloat()
		Return Self
	End Method

'#end region (Data handling)

	Rem
		bbdoc: Get the type of this variable.
		returns: The type of this variable ("float").
	End Rem
	Function ReportType:String()
		Return "float"
	End Function

	Rem
		bbdoc: Get the TV type of this variable.
		returns: The TV_* type of this variable (TV_FLOAT).
	End Rem
	Function GetTVType:Int()
		Return TV_FLOAT
	End Function

End Type

Rem
	bbdoc: duct Int variable.
End Rem
Type dIntVariable Extends dVariable

	Field m_value:Int

	Rem
		bbdoc: Create a new Int variable.
		returns: Itself.
	End Rem
	Method Create:dIntVariable(name:String = Null, value:Int)
		SetName(name)
		Set(value)
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the variable's value.
		returns: Nothing.
	End Rem
	Method Set(value:Int)
		m_value = value
	End Method

	Rem
		bbdoc: Get the variable's value.
		returns: The value of the variable.
	End Rem
	Method Get:Int()
		Return m_value
	End Method

	Rem
		bbdoc: Set the value of the variable to the given string (ambiguous).
		returns: Nothing.
	End Rem
	Method SetAmbiguous(value:String)
		m_value = Int(value)
	End Method

	Rem
		bbdoc: Convert the variable to a String.
		returns: A string representation of the variable.
		about: This function is for script output, for in-code use see #ValueAsString.
	End Rem
	Method ConvToString:String()
		Return String(m_value)
	End Method

	Rem
		bbdoc: Get the variable as a String.
		returns: The variable's value converted to a String.
	End Rem
	Method ValueAsString:String()
		Return String(m_value)
	End Method

'#end region (Field accessors)

'#region Data handling

	Rem
		bbdoc: Create a copy of the variable
		returns: A clone of the variable.
	End Rem
	Method Copy:dIntVariable()
		Return New dIntVariable.Create(m_name, m_value)
	End Method

	Rem
		bbdoc: Serialize the variable to a stream.
		returns: Nothing.
		about: @tv tells the method whether it should serialize the TV type for the variable, or to not.
	End Rem
	Method Serialize(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True Then stream.WriteByte(TV_INTEGER)
		If name = True Then WriteLString(stream, m_name)
		stream.WriteInt(m_value)
	End Method

	Rem
		bbdoc: Deserialize the variable from a stream.
		returns: Itself.
		about: @tv tells the method whether it should deserialize the TV type for the variable, or to not.
	End Rem
	Method Deserialize:dIntVariable(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True Then stream.ReadByte()
		If name = True Then m_name = ReadLString(stream)
		m_value = stream.ReadInt()
		Return Self
	End Method

'#end region (Data handling)

	Rem
		bbdoc: Get the type of this variable.
		returns: The type of this variable ("int").
	End Rem
	Function ReportType:String()
		Return "int"
	End Function

	Rem
		bbdoc: Get the TV type of this variable.
		returns: The TV_* type of this variable (TV_INTEGER).
	End Rem
	Function GetTVType:Int()
		Return TV_INTEGER
	End Function

End Type

Rem
	bbdoc: duct eval variable (for bah.muparser, and the likes).
End Rem
Type dEvalVariable Extends dVariable

	Field m_value:String

	Rem
		bbdoc: Create a new eval variable.
		returns: Itself.
	End Rem
	Method Create:dEvalVariable(name:String = Null, value:String)
		SetName(name)
		Set(value)
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the equation string for the variable.
		returns: Nothing
	End Rem
	Method Set(value:String)
		m_value = value
	End Method

	Rem
		bbdoc: Get the equation string.
		returns: The equation string.
	End Rem
	Method Get:String()
		Return m_value
	End Method

	Rem
		bbdoc: Set the value of the variable to the given string (ambiguous).
		returns: Nothing.
		about: For this type, this method is no different than calling #Set.
	End Rem
	Method SetAmbiguous(value:String)
		m_value = value
	End Method

	Rem
		bbdoc: Convert the variable to a visual representation of its data.
		returns: The scriptable form of the variable.
		about: This function is for script output, for in-code use see #ValueAsString.
	End Rem
	Method ConvToString:String()
		Return "~q/e:" + m_value + "~q"
	End Method

	Rem
		bbdoc: Get the variable as a String.
		returns: The variable's value converted to a String.
	End Rem
	Method ValueAsString:String()
		Return m_value
	End Method

'#end region (Field accessors)

'#region Data handling

	Rem
		bbdoc: Create a copy of the variable
		returns: A clone of the variable.
	End Rem
	Method Copy:dEvalVariable()
		Return New dEvalVariable.Create(m_name, m_value)
	End Method

	Rem
		bbdoc: Serialize the variable to a stream.
		returns: Nothing.
		about: @tv tells the method whether it should serialize the TV type for the variable, or to not.
	End Rem
	Method Serialize(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True Then stream.WriteByte(TV_EVAL)
		If name = True Then WriteLString(stream, m_name)
		WriteLString(stream, m_value)
	End Method

	Rem
		bbdoc: Deserialize the variable from a stream.
		returns: Itself.
		about: @tv tells the method whether it should deserialize the TV type for the variable, or to not.
	End Rem
	Method Deserialize:dEvalVariable(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True Then stream.ReadByte()
		If name = True Then m_name = ReadLString(stream)
		m_value = ReadLString(stream)
		Return Self
	End Method

'#end region (Data handling)

	Rem
		bbdoc: Get the type of this variable.
		returns: The type of this variable ("eval").
	End Rem
	Function ReportType:String()
		Return "eval"
	End Function

	Rem
		bbdoc: Get the TV type of this variable.
		returns: The TV_* type of this variable (TV_EVAL).
	End Rem
	Function GetTVType:Int()
		Return TV_EVAL
	End Function

End Type

Rem
	bbdoc: duct boolean variable.
End Rem
Type dBoolVariable Extends dVariable

	Field m_value:Int

	Rem
		bbdoc: Create a new boolean variable.
		returns: Itself.
	End Rem
	Method Create:dBoolVariable(name:String = Null, value:Int)
		SetName(name)
		Set(value)
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the variable's value.
		returns: Nothing.
	End Rem
	Method Set(value:Int)
		m_value = value
	End Method

	Rem
		bbdoc: Get the variable's value.
		returns: The value of the variable.
	End Rem
	Method Get:Int()
		Return m_value
	End Method

	Rem
		bbdoc: Set the value of the variable to the given string (ambiguous).
		returns: Nothing.
	End Rem
	Method SetAmbiguous(value:String)
		m_value = Int(value)
	End Method

	Rem
		bbdoc: Convert the variable to a String.
		returns: A string representation of the variable.
		about: This function is for script output, for in-code use see #ValueAsString.
	End Rem
	Method ConvToString:String()
		Return String(m_value)
	End Method

	Rem
		bbdoc: Get the dBoolVariable as a String.
		returns: The variable's value converted to a String.
	End Rem
	Method ValueAsString:String()
		Return String(m_value)
	End Method

'#end region (Field accessors)

'#region Data handling

	Rem
		bbdoc: Create a copy of the variable
		returns: A clone of the variable.
	End Rem
	Method Copy:dBoolVariable()
		Return New dBoolVariable.Create(m_name, m_value)
	End Method

	Rem
		bbdoc: Serialize the variable to a stream.
		returns: Nothing.
		about: @tv tells the method whether it should serialize the TV type for the variable, or to not.
	End Rem
	Method Serialize(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True Then stream.WriteByte(TV_BOOL)
		If name = True Then WriteLString(stream, m_name)
		stream.WriteByte(Byte(m_value))
	End Method

	Rem
		bbdoc: Deserialize the variable from a stream.
		returns: The deserialized variable.
		about: @tv tells the method whether it should deserialize the TV type for the variable, or to not.
	End Rem
	Method Deserialize:dBoolVariable(stream:TStream, tv:Int = True, name:Int = False)
		If tv = True Then stream.ReadByte()
		If name = True Then m_name = ReadLString(stream)
		m_value = Int(stream.ReadByte())
		Return Self
	End Method

'#end region (Data handling)

	Rem
		bbdoc: Get the type of this variable.
		returns: The type of this variable ("bool").
	End Rem
	Function ReportType:String()
		Return "bool"
	End Function

	Rem
		bbdoc: Get the TV type of this variable.
		returns: The TV_* type of this variable (TV_BOOL).
	End Rem
	Function GetTVType:Int()
		Return TV_BOOL
	End Function

End Type

Rem
	bbdoc: duct identifier (used in parsers, mostly).
End Rem
Type dIdentifier Extends dVariable

	Field m_values:TListEx

	Method New()
	End Method

	Rem
		bbdoc: Create a new identifier.
		returns: Itself.
	End Rem
	Method Create:dIdentifier()
		m_values = New TListEx
		Return Self
	End Method

	Rem
		bbdoc: Create a new identifier with the given data.
		returns: Itself.
		about: If the @values parameter is Null a new list will be created.
	End Rem
	Method CreateByData:dIdentifier(name:String, values:TListEx = Null)
		SetName(name)
		If values = Null
			m_values = New TListEx
		Else
			m_values = values
		End If
		Return Self
	End Method

'#region Field accessors

	Rem
		bbdoc: Set the identifier's values.
		returns: Nothing.
	End Rem
	Method SetValues(values:TListEx)
		m_values = values
	End Method

	Rem
		bbdoc: Get the identifier's values.
		returns: A list containing the values which the identifier holds.
	End Rem
	Method GetValues:TListEx()
		Return m_values
	End Method

	Rem
		bbdoc: Deprecated for this type.
		returns: Nothing.
		about: This does nothing.
	End Rem
	Method SetAmbiguous(value:String)
	End Method

	Rem
		bbdoc: Convert the identifier to a string.
		returns: A string representation of the identifier.
	End Rem
	Method ConvToString:String()
		Local op:String
		If m_name.Contains("~t") = True Or m_name.Contains(" ") = True
			op = "~q" + m_name + "~q "
		Else
			op = m_name + " "
		End If
		For Local variable:dVariable = EachIn m_values
			op:+variable.ConvToString() + " "
		Next
		op = op[..op.Length - 1]
		Return op
	End Method

	Rem
		bbdoc: Get the identifier as a string.
		returns: The identifier contents converted to a string.
		about: Here for complete-ness, simply calls #ConvToString.
	End Rem
	Method ValueAsString:String()
		Return ConvToString()
	End Method

'#end region (Field accessors)

'#region Value handling

	Rem
		bbdoc: Get a value at an index.
		returns: The value in the identifier's list at the given index, or Null if it could not be retrieved.
		about: The index is zero-based.
	End Rem
	Method GetValueAtIndex:dVariable(index:Int)
		If m_values <> Null And index > - 1 And index < m_values.Count()
			Return dVariable(m_values.ValueAtIndex(index))
		End If
		Return Null
	End Method

	Rem
		bbdoc: Get the number of values.
		returns: The number of values the identifier contains.
	End Rem
	Method GetValueCount:Int()
		If m_values <> Null
			Return m_values.Count()
		End If
		Return 0
	End Method

	Rem
		bbdoc: Add a value to the identifier.
		returns: True for success, or False for failure.
		about: NOTE: This will set the given value's parent.
	End Rem
	Method AddValue:Int(value:dVariable)
		If m_values <> Null And value <> Null
			m_values.AddLast(value)
			value.SetParent(Self)
			Return True
		End If
		Return False
	End Method

'#end region (Value handling)

'#region Data handling

	Rem
		bbdoc: Create a copy of the identifier
		returns: A clone of the identifier.
	End Rem
	Method Copy:dIdentifier()
		Local clone:dIdentifier
		clone = New dIdentifier.CreateByData(m_name)
		For Local variable:dVariable = EachIn m_values
			clone.AddValue(variable.Copy())
		Next
		Return clone
	End Method

	Rem
		bbdoc: Serialize the variable to a stream.
		returns: Nothing.
		about: @tv tells the method whether it should serialize the TV type for the variable, or to not.

		In this case @name tells the method whether it should serialize the values' name (the identifier's name is always read/written to the stream).
	End Rem
	Method Serialize(stream:TStream, tv:Int = True, name:Int = False)
		Local variable:dVariable
		If tv = True Then stream.WriteByte(TV_STRING)
		WriteLString(stream, m_name)
		If m_values = Null
			stream.WriteInt(0)
		Else
			stream.WriteInt(m_values.Count())
			For variable = EachIn m_values
				variable.Serialize(stream, True, name)
			Next
		End If
	End Method

	Rem
		bbdoc: Deserialize the variable from a stream.
		returns: The deserialized variable.
		about: @tv tells the method whether it should deserialize the TV type for the variable, or to not.

		In this case @name tells the method whether it should deserialize the values' name (the identifier's name is always read/written to the stream).
	End Rem
	Method Deserialize:dIdentifier(stream:TStream, tv:Int = True, name:Int = False)
		Local count:Int, n:Int
		If tv = True Then stream.ReadByte()
		m_name = ReadLString(stream)
		count = stream.ReadInt()
		If count > 0
			For n = 0 To count - 1
				DeserializeUniversal(stream, name)
			Next
		End If
		Return Self
	End Method

'#end region (Data handling)

	Rem
		bbdoc: Get the type of this variable.
		returns: The type of this variable ("identifier").
	End Rem
	Function ReportType:String()
		Return "identifier"
	End Function

	Rem
		bbdoc: Get the TV type of this variable.
		returns: The TV_* type of this variable (TV_IDEN).
	End Rem
	Function GetTVType:Int()
		Return TV_IDEN
	End Function

End Type

Rem
	bbdoc: #dVariable map (stores any variable).
EndRem
Type dVariableMap Extends dObjectMap

	Rem
		bbdoc: Create a new variable map.
		returns: Itself.
	End Rem
	Method Create:dVariableMap()
		Return Self
	End Method

'#region Collections

	Rem
		bbdoc: Insert a variable into the map.
		returns: True if the variable was added, or False if it was not (the variable's name is Null).
	End Rem
	Method InsertVariable:Int(variable:dVariable)
		If variable.GetName() <> Null
			_Insert(variable.GetName(), variable)
			Return True
		End If
		Return False
	End Method

	Rem
		bbdoc: Remove a variable by the name given.
		returns: True if a variable was removed, or False if it was not (the map contains no variable with the name given).
		about: The name is case-sensitive.
	End Rem
	Method RemoveVariableByName:Int(name:String)
		Return _Remove(name)
	End Method

	Rem
		bbdoc: Get a variable from the map by its name.
		returns: The variable object, or if the variable was not found, Null.
		about: The name is case-sensitive.
	End Rem
	Method GetVariableByName:dVariable(name:String)
		Return dVariable(_ValueByKey(name))
	End Method

'#end region (Collections)

End Type


Fixed TEntity.Point

Fixed TEntity.Point

Strict

Import BRL.LinkedList
Import MaxB3D.Math
Import "brush.bmx"
Import "collision.bmx"
Import "worldconfig.bmx"

Const PICKMODE_OFF		= 1
Const PICKMODE_SPHERE	= 2
Const PICKMODE_POLYGON	= 3
Const PICKMODE_BOX		= 4

Type TEntity
	Field _config:TWorldConfig
	Field _matrix:TMatrix=TMatrix.Identity(), _lockmatrix

	Field _name$
	Field _px#,_py#,_pz#
	Field _rx#,_ry#,_rz#
	Field _sx#,_sy#,_sz#

	Field _brush:TBrush=New TBrush

	Field _parent:TEntity,_childlist:TList=CreateList()
	Field _hidden,_order

	Field _collision:TCollision[]
	Field _oldx#,_oldy#,_oldz#
	Field _radiusx#,_radiusy#
	Field _boxx#,_boxy#,_boxz#,_boxwidth#,_boxheight#,_boxdepth#
	Field _type,_typelink:TLink
	Field _pickmode, _obscurer

	Field _cullradius#

	Field _alphaorder#

	Field _linklist:TList=CreateList()

	Method Init:TEntity(config:TWorldConfig,parent:TEntity)
		_config = config
		SetParent parent,False
		For Local l=EachIn Lists()
			AddLink _config.AddObject(Self,l)
		Next

		SetScale 1,1,1

		Return Self
	End Method

	Method Free()
		For Local child:TEntity=EachIn _childlist
			child.Free()
		Next
		For Local link:TLink=EachIn _linklist
			RemoveLink link
		Next
	End Method

	Method AddLink(link:TLink)
		_linklist.AddLast link
	End Method

	Method Lists[]()
		Return [WORLDLIST_ENTITY]
	End Method

	Method CopyData:TEntity(entity:TEntity)
		SetBrush entity.GetBrush()
		SetName entity.GetName()
		Return Self
	End Method

	Method Copy_:TEntity(parent:TEntity=Null)
		Return New Self.CopyData(Self).Init(_config,parent)
	End Method

	Method Copy:TEntity(parent:TEntity=Null) Abstract

	Method AddParent(parent:TEntity)
		_parent=parent
		If parent
			_matrix.Overwrite parent._matrix
			parent._childlist.AddLast Self
		EndIf
	End Method

	Method GetParent:TEntity()
		Return _parent
	End Method
	Method SetParent(parent:TEntity,glob=True)
		Local gpx#,gpy#,gpz#
		Local grx#,gry#,grz#
		Local gsx#,gsy#,gsz#

		GetPosition gpx,gpy,gpz,True
		GetRotation grx,gry,grz,True
		GetScale gsx,gsy,gsz,True

		If _parent
			For Local entity:TEntity=EachIn _parent._childlist
				If entity=Self ListRemove(_parent._childlist,Self)
			Next
			_parent=Null
		EndIf

		_px=gpx;_py=gpy;_pz=gpz
		_rx=grx;_ry=gry;_rz=grz
		_sx=gsx;_sy=gsy;_sz=gsz

		If parent=Null UpdateMatrix(True);Return

		AddParent(parent)
		If glob
			SetPosition(gpx,gpy,gpz,True)
			SetRotation(grx,gry,grz,True)
			SetScale(gsx,gsy,gsz,True)
		Else
			UpdateMatrix(False)
		EndIf
	End Method

	Method CountChildren(recursive=False)
		Local count
		If recursive
			For Local child:TEntity=EachIn _childlist
				count:+child.CountChildren(True)
			Next
		EndIf
		Return _childlist.Count()+count
	End Method

	Method FindChild:TEntity(name$,recursive=False)
		For Local child:TEntity=EachIn _childlist
			If child.GetName()=name Return child
		Next
		If recursive
			For Local child:TEntity=EachIn _childlist
				Local entity:TEntity = child.FindChild(name, True)
				If entity Return entity
			Next
		EndIf
	End Method

	Method GetName$()
		Return _name
	End Method
	Method SetName(name$)
		_name=name
	End Method

	Method GetBrush:TBrush()
		Return _brush.Copy()
	End Method
	Method SetBrush(brush:TBrush)
		_brush.Load brush
	End Method

	Method GetColor(red Var,green Var,blue Var)
		Return _brush.GetColor(red,green,blue)
	End Method
	Method SetColor(red,green,blue)
		Return _brush.SetColor(red,green,blue)
	End Method

	Method GetAlpha#()
		Return _brush.GetAlpha()
	End Method
	Method SetAlpha(alpha#)
		_brush.SetAlpha(alpha)
	End Method

	Method GetShine#()
		Return _brush.GetShine()
	End Method
	Method SetShine(shine#)
		_brush.SetShine(shine)
	End Method

	Method GetTexture:TTexture(index)
		Return _brush.GetTexture(index)
	End Method
	Method SetTexture(texture:TTexture,index=0,frame=0)
		Return _brush.SetTexture(texture,index,frame)
	End Method

	Method GetFX()
		Return _brush.GetFX()
	End Method
	Method SetFX(fx)
		Return _brush.SetFX(fx)
	End Method

	Method GetBlend()
		Return _brush.GetBlend()
	End Method
	Method SetBlend(blend)
		Return _brush.SetBlend(blend)
	End Method

	Method GetShader:TShader()
		Return _brush.GetShader()
	End Method
	Method SetShader(shader:TShader)
		Return _brush.SetShader(shader)
	End Method

	Method GetScale(x# Var,y# Var,z# Var,glob=False)
		If glob
			x=_matrix._m[0,0]
			y=_matrix._m[1,1]
			z=_matrix._m[2,2]
		Else
			x=_sx;y=_sy;z=_sz
		EndIf
	End Method
	Method SetScale(x#,y#,z#,glob=False)
		_sx=x;_sy=y;_sz=z
		If glob=True And _parent<>Null
			Local entity:TEntity=Self
			Repeat
				_sx:/entity._parent._sx
				_sy:/entity._parent._sy
				_sz:/entity._parent._sz
				entity=entity._parent
			Until entity._parent=Null
		EndIf
		RefreshMatrix()
	End Method

	Method Transform(matrix:TMatrix,glob=False)
		SetMatrix matrix.Multiply(_matrix),glob
	End Method

	Method Turn(pitch#,yaw#,roll#,glob=False)
		_rx:+pitch;_ry:+yaw;_rz:+roll
		RefreshMatrix()
	End Method

	Method Point(target:Object,roll#=0.0)
		Local x#,y#,z#,tx#,ty#,tz#
		GetPosition x,y,z,True
		GetTargetPosition target,tx,ty,tz

		Local xdiff#=x-tx,ydiff#=y-ty,zdiff#=z-tz

		Local dist22#=Sqr((xdiff*xdiff)+(zdiff*zdiff))
		Local pitch#=ATan2(ydiff,dist22)
		Local yaw#=ATan2(xdiff,-zdiff)

		SetRotation pitch,yaw,roll,True
	End Method

	Method GetDistance#(target:Object)
		Local x#,y#,z#,tx#,ty#,tz#
		GetPosition x,y,z,True
		GetTargetPosition target,tx,ty,tz
		Return Sqr((x-tx)*(x-tx)+(y-ty)*(y-ty)+(z-tz)*(z-tz))
	End Method

	Function GetTargetPosition(target:Object,x# Var,y# Var,z# Var)
		Assert target,"Null target given."
		If TEntity(target) TEntity(target).GetPosition x,y,z,True
		If Float[](target)
			Local coords#[]=Float[](target)
			If coords.length>0 x=coords[0]
			If coords.length>1 y=coords[1]
			If coords.length>2 z=coords[2]
		EndIf
		Assert "Invalid target given."
	End Function

	Method GetRotation(pitch# Var,yaw# Var,roll# Var,glob=False)
		If glob
			_matrix.GetRotation pitch,yaw,roll
			pitch:*-1
			yaw:*-1
		Else
			pitch=_rx;yaw=_ry;roll=_rz
		EndIf
	End Method
	Method SetRotation(pitch#,yaw#,roll#,glob=False)
		_rx=pitch;_ry=yaw;_rz=roll
		If glob And _parent<>Null
			Local rx#,ry#,rz#
			_parent.GetRotation rx,ry,rz
			_rx:+rx;_ry:+ry;_rz:+rz
		EndIf
		RefreshMatrix()
	End Method

	Method GetPosition(x# Var,y# Var,z# Var,glob=False)
		x=_px;y=_py;z=_pz
		If glob _matrix.GetPosition x,y,z
	End Method
	Method SetPosition(x#,y#,z#,glob=False)
		_px=x;_py=y;_pz=z

		If glob And _parent<>Null
			Local px#,py#,pz#
			_parent.GetPosition px,py,pz,True
			_px:-px;_py:-py;_pz:-pz
		EndIf

		RefreshMatrix()
	End Method

	Method Move(x#,y#,z#)
		Local matrix:TMatrix=TMatrix.Identity()
		matrix=TMatrix.YawPitchRoll(-_ry,-_rx,_rz).Multiply(matrix)
		matrix=TMatrix.Translation(x,y,z).Multiply(matrix)

		matrix.GetPosition(x,y,z)
		_px:+x;_py:+y;_pz:+z

		RefreshMatrix()
	End Method

	Method Translate(x#,y#,z#,glob=True)
		If glob And _parent
			Local ax#,ay#,az#
			GetRotation ax,ay,az,True

			Local matrix:TMatrix=TMatrix.Roll(-az)
			matrix=matrix.Multiply(TMatrix.Pitch(-ax))
			matrix=matrix.Multiply(TMatrix.Yaw(-ay))
			matrix=matrix.Multiply(TMatrix.Translation(x,y,z))

			matrix.GetPosition x,y,z
		EndIf

		_px:+x;_py:+y;_pz:+z

		RefreshMatrix()
	End Method

	Method GetVisible()
		Local entity:TEntity=Self
		While entity<>Null
			If entity._hidden=True Return False
			entity=entity._parent
		Wend
		Return True
	End Method
	Method SetVisible(visible)
		_hidden=Not visible
	End Method

	Method GetOrder()
		Return _order
	End Method
	Method SetOrder(order)
		_order=order
	End Method

	Method GetCollisions:TCollision[]()
		Return _collision
	End Method

	Method GetRadius(x# Var,y# Var)
		x=_radiusx;y=_radiusy
	End Method
	Method SetRadius(x#,y#=-1)
		_radiusx=x;_radiusy=x
		If y>-1 _radiusy=y
	End Method

	Method GetBox(x# Var,y# Var,z# Var,width# Var,height# Var,depth# Var)
		x=_boxx;y=_boxy;z=_boxz
		width=_boxwidth;height=_boxheight;depth=_boxdepth
	End Method
	Method SetBox(x#,y#,z#,width#,height#,depth#)
		_boxx=x;_boxy=y;_boxz=z
		_boxwidth=width;_boxheight=height;_boxdepth=depth
	End Method

	Method Reset()
		_collision=Null
		GetPosition _oldx,_oldy,_oldz,True
	End Method

	Method GetType()
		Return _type
	End Method
	Method SetType(typ,recursive=False)
		If _typelink
			_linklist.Remove(_typelink)
			_typelink.Remove()
			_typelink=Null
		EndIf

		If typ>0
			If _config.CollisionType[typ]=Null _config.CollisionType[typ]=CreateList()
			_typelink=_config.CollisionType[typ].AddLast(Self)
			AddLink _typelink
		EndIf

		_type=typ
		GetPosition _oldx,_oldy,_oldz,True

		If recursive
			For Local child:TEntity=EachIn _childlist
				child.SetType typ,True
			Next
		EndIf
	End Method

	Method GetCullRadius#()
		Return Abs(_cullradius)
	End Method
	Method SetCullRadius(radius#)
		_cullradius=-radius
	End Method

	Method GetCullParams(x# Var,y# Var,z# Var,radius# Var)
		GetPosition x,y,z,True
		radius=GetCullRadius()
	End Method

	Method GetPickMode(mode Var, obscurer Var)
		mode = _pickmode
		obscurer = _obscurer
	End Method
	Method SetPickMode(mode, obscurer=True)
		_pickmode = mode
		_obscurer = obscurer
	End Method

	Method HasAlpha()
		Return _brush.HasAlpha()
	End Method

	Method GetMatrix:TMatrix(alternate=False,copy=True)
		If copy Return _matrix.Copy()
		Return _matrix
	End Method
	Method SetMatrix(matrix:TMatrix,glob=True)
		Local x#,y#,z#,pitch#,yaw#,roll#,sx#,sy#,sz#
		matrix.GetPosition x,y,z
		matrix.GetRotation pitch,yaw,roll
		matrix.GetScale sx,sy,sz

		LockMatrix
		SetPosition x,y,z,glob
		SetRotation pitch,yaw,roll,glob
		SetScale sx,sy,sz,glob
		UnlockMatrix
	End Method

	Method LockMatrix()
		_lockmatrix=True
	End Method
	Method UnlockMatrix()
		_lockmatrix=False
		RefreshMatrix()
	End Method

	Method RefreshMatrix()
		If _lockmatrix Return
		If _parent<>Null
			_matrix.Overwrite _parent._matrix
			UpdateMatrix False
		Else
			UpdateMatrix True
		EndIf

		For Local child:TEntity=EachIn _childlist
			child.RefreshMatrix()
		Next
	End Method

	Method UpdateMatrix(loadidentity)
		If loadidentity _matrix=TMatrix.Identity()
		_matrix=TMatrix.Translation(_px,_py,_pz).Multiply(_matrix)
		_matrix=TMatrix.YawPitchRoll(-_ry,-_rx,_rz).Multiply(_matrix)
		_matrix=TMatrix.Scale(_sx,_sy,_sz).Multiply(_matrix)
	End Method

	Method ObjectEnumerator:Object()
		Return TChildrenEnumerator.Create(_childlist)
	End Method
End Type

Type TChildrenEnumerator
	Field _children:TEntity[],_index=-1

	Function Create:TChildrenEnumerator(list:TList)
		Local enum:TChildrenEnumerator=New TChildrenEnumerator
		enum._children=TEntity[](list.ToArray())
		Return enum
	End Function

	Method HasNext()
		Return _index>=_children.length-1
	End Method

	Method NextObject:Object()
		_index:+1
		Return _children[_index]
	End Method
End Type

Strict

Import BRL.LinkedList
Import MaxB3D.Math
Import "brush.bmx"
Import "collision.bmx"
Import "worldconfig.bmx"

Const PICKMODE_OFF		= 1
Const PICKMODE_SPHERE	= 2
Const PICKMODE_POLYGON	= 3
Const PICKMODE_BOX		= 4

Type TEntity
	Field _config:TWorldConfig
	Field _matrix:TMatrix=TMatrix.Identity(), _lockmatrix

	Field _name$
	Field _px#,_py#,_pz#
	Field _rx#,_ry#,_rz#
	Field _sx#,_sy#,_sz#

	Field _brush:TBrush=New TBrush

	Field _parent:TEntity,_childlist:TList=CreateList()
	Field _hidden,_order

	Field _collision:TCollision[]
	Field _oldx#,_oldy#,_oldz#
	Field _radiusx#,_radiusy#
	Field _boxx#,_boxy#,_boxz#,_boxwidth#,_boxheight#,_boxdepth#
	Field _type,_typelink:TLink
	Field _pickmode, _obscurer

	Field _cullradius#

	Field _alphaorder#

	Field _linklist:TList=CreateList()

	Method Init:TEntity(config:TWorldConfig,parent:TEntity)
		_config = config
		SetParent parent,False
		For Local l=EachIn Lists()
			AddLink _config.AddObject(Self,l)
		Next

		SetScale 1,1,1

		Return Self
	End Method

	Method Free()
		For Local child:TEntity=EachIn _childlist
			child.Free()
		Next
		For Local link:TLink=EachIn _linklist
			RemoveLink link
		Next
	End Method

	Method AddLink(link:TLink)
		_linklist.AddLast link
	End Method

	Method Lists[]()
		Return [WORLDLIST_ENTITY]
	End Method

	Method CopyData:TEntity(entity:TEntity)
		SetBrush entity.GetBrush()
		SetName entity.GetName()
		Return Self
	End Method

	Method Copy_:TEntity(parent:TEntity=Null)
		Return New Self.CopyData(Self).Init(_config,parent)
	End Method

	Method Copy:TEntity(parent:TEntity=Null) Abstract

	Method AddParent(parent:TEntity)
		_parent=parent
		If parent
			_matrix.Overwrite parent._matrix
			parent._childlist.AddLast Self
		EndIf
	End Method

	Method GetParent:TEntity()
		Return _parent
	End Method
	Method SetParent(parent:TEntity,glob=True)
		Local gpx#,gpy#,gpz#
		Local grx#,gry#,grz#
		Local gsx#,gsy#,gsz#

		GetPosition gpx,gpy,gpz,True
		GetRotation grx,gry,grz,True
		GetScale gsx,gsy,gsz,True

		If _parent
			For Local entity:TEntity=EachIn _parent._childlist
				If entity=Self ListRemove(_parent._childlist,Self)
			Next
			_parent=Null
		EndIf

		_px=gpx;_py=gpy;_pz=gpz
		_rx=grx;_ry=gry;_rz=grz
		_sx=gsx;_sy=gsy;_sz=gsz

		If parent=Null UpdateMatrix(True);Return

		AddParent(parent)
		If glob
			SetPosition(gpx,gpy,gpz,True)
			SetRotation(grx,gry,grz,True)
			SetScale(gsx,gsy,gsz,True)
		Else
			UpdateMatrix(False)
		EndIf
	End Method

	Method CountChildren(recursive=False)
		Local count
		If recursive
			For Local child:TEntity=EachIn _childlist
				count:+child.CountChildren(True)
			Next
		EndIf
		Return _childlist.Count()+count
	End Method

	Method FindChild:TEntity(name$,recursive=False)
		For Local child:TEntity=EachIn _childlist
			If child.GetName()=name Return child
		Next
		If recursive
			For Local child:TEntity=EachIn _childlist
				Local entity:TEntity = child.FindChild(name, True)
				If entity Return entity
			Next
		EndIf
	End Method

	Method GetName$()
		Return _name
	End Method
	Method SetName(name$)
		_name=name
	End Method

	Method GetBrush:TBrush()
		Return _brush.Copy()
	End Method
	Method SetBrush(brush:TBrush)
		_brush.Load brush
	End Method

	Method GetColor(red Var,green Var,blue Var)
		Return _brush.GetColor(red,green,blue)
	End Method
	Method SetColor(red,green,blue)
		Return _brush.SetColor(red,green,blue)
	End Method

	Method GetAlpha#()
		Return _brush.GetAlpha()
	End Method
	Method SetAlpha(alpha#)
		_brush.SetAlpha(alpha)
	End Method

	Method GetShine#()
		Return _brush.GetShine()
	End Method
	Method SetShine(shine#)
		_brush.SetShine(shine)
	End Method

	Method GetTexture:TTexture(index)
		Return _brush.GetTexture(index)
	End Method
	Method SetTexture(texture:TTexture,index=0,frame=0)
		Return _brush.SetTexture(texture,index,frame)
	End Method

	Method GetFX()
		Return _brush.GetFX()
	End Method
	Method SetFX(fx)
		Return _brush.SetFX(fx)
	End Method

	Method GetBlend()
		Return _brush.GetBlend()
	End Method
	Method SetBlend(blend)
		Return _brush.SetBlend(blend)
	End Method

	Method GetShader:TShader()
		Return _brush.GetShader()
	End Method
	Method SetShader(shader:TShader)
		Return _brush.SetShader(shader)
	End Method

	Method GetScale(x# Var,y# Var,z# Var,glob=False)
		If glob
			x=_matrix._m[0,0]
			y=_matrix._m[1,1]
			z=_matrix._m[2,2]
		Else
			x=_sx;y=_sy;z=_sz
		EndIf
	End Method
	Method SetScale(x#,y#,z#,glob=False)
		_sx=x;_sy=y;_sz=z
		If glob=True And _parent<>Null
			Local entity:TEntity=Self
			Repeat
				_sx:/entity._parent._sx
				_sy:/entity._parent._sy
				_sz:/entity._parent._sz
				entity=entity._parent
			Until entity._parent=Null
		EndIf
		RefreshMatrix()
	End Method

	Method Transform(matrix:TMatrix,glob=False)
		SetMatrix matrix.Multiply(_matrix),glob
	End Method

	Method Turn(pitch#,yaw#,roll#,glob=False)
		_rx:+pitch;_ry:+yaw;_rz:+roll
		RefreshMatrix()
	End Method

	Method Point(target:Object,roll#=0.0)
		Local x#,y#,z#,tx#,ty#,tz#
		GetPosition x,y,z,True
		GetTargetPosition target,tx,ty,tz

		Local xdiff#=x-tx,ydiff#=y-ty,zdiff#=z-tz

		Local dist22#=Sqr((xdiff*xdiff)+(zdiff*zdiff))
		Local pitch#=ATan2(ydiff,dist22)
		Local yaw#=-ATan2(xdiff,-zdiff)

		SetRotation pitch,yaw,roll,True
	End Method

	Method GetDistance#(target:Object)
		Local x#,y#,z#,tx#,ty#,tz#
		GetPosition x,y,z,True
		GetTargetPosition target,tx,ty,tz
		Return Sqr((x-tx)*(x-tx)+(y-ty)*(y-ty)+(z-tz)*(z-tz))
	End Method

	Function GetTargetPosition(target:Object,x# Var,y# Var,z# Var)
		Assert target,"Null target given."
		If TEntity(target) TEntity(target).GetPosition x,y,z,True
		If Float[](target)
			Local coords#[]=Float[](target)
			If coords.length>0 x=coords[0]
			If coords.length>1 y=coords[1]
			If coords.length>2 z=coords[2]
		EndIf
		Assert "Invalid target given."
	End Function

	Method GetRotation(pitch# Var,yaw# Var,roll# Var,glob=False)
		If glob
			_matrix.GetRotation pitch,yaw,roll
			pitch:*-1
			yaw:*-1
		Else
			pitch=_rx;yaw=_ry;roll=_rz
		EndIf
	End Method
	Method SetRotation(pitch#,yaw#,roll#,glob=False)
		_rx=pitch;_ry=yaw;_rz=roll
		If glob And _parent<>Null
			Local rx#,ry#,rz#
			_parent.GetRotation rx,ry,rz
			_rx:+rx;_ry:+ry;_rz:+rz
		EndIf
		RefreshMatrix()
	End Method

	Method GetPosition(x# Var,y# Var,z# Var,glob=False)
		x=_px;y=_py;z=_pz
		If glob _matrix.GetPosition x,y,z
	End Method
	Method SetPosition(x#,y#,z#,glob=False)
		_px=x;_py=y;_pz=z

		If glob And _parent<>Null
			Local px#,py#,pz#
			_parent.GetPosition px,py,pz,True
			_px:-px;_py:-py;_pz:-pz
		EndIf

		RefreshMatrix()
	End Method

	Method Move(x#,y#,z#)
		Local matrix:TMatrix=TMatrix.Identity()
		matrix=TMatrix.YawPitchRoll(-_ry,-_rx,_rz).Multiply(matrix)
		matrix=TMatrix.Translation(x,y,z).Multiply(matrix)

		matrix.GetPosition(x,y,z)
		_px:+x;_py:+y;_pz:+z

		RefreshMatrix()
	End Method

	Method Translate(x#,y#,z#,glob=True)
		If glob And _parent
			Local ax#,ay#,az#
			GetRotation ax,ay,az,True

			Local matrix:TMatrix=TMatrix.Roll(-az)
			matrix=matrix.Multiply(TMatrix.Pitch(-ax))
			matrix=matrix.Multiply(TMatrix.Yaw(-ay))
			matrix=matrix.Multiply(TMatrix.Translation(x,y,z))

			matrix.GetPosition x,y,z
		EndIf

		_px:+x;_py:+y;_pz:+z

		RefreshMatrix()
	End Method

	Method GetVisible()
		Local entity:TEntity=Self
		While entity<>Null
			If entity._hidden=True Return False
			entity=entity._parent
		Wend
		Return True
	End Method
	Method SetVisible(visible)
		_hidden=Not visible
	End Method

	Method GetOrder()
		Return _order
	End Method
	Method SetOrder(order)
		_order=order
	End Method

	Method GetCollisions:TCollision[]()
		Return _collision
	End Method

	Method GetRadius(x# Var,y# Var)
		x=_radiusx;y=_radiusy
	End Method
	Method SetRadius(x#,y#=-1)
		_radiusx=x;_radiusy=x
		If y>-1 _radiusy=y
	End Method

	Method GetBox(x# Var,y# Var,z# Var,width# Var,height# Var,depth# Var)
		x=_boxx;y=_boxy;z=_boxz
		width=_boxwidth;height=_boxheight;depth=_boxdepth
	End Method
	Method SetBox(x#,y#,z#,width#,height#,depth#)
		_boxx=x;_boxy=y;_boxz=z
		_boxwidth=width;_boxheight=height;_boxdepth=depth
	End Method

	Method Reset()
		_collision=Null
		GetPosition _oldx,_oldy,_oldz,True
	End Method

	Method GetType()
		Return _type
	End Method
	Method SetType(typ,recursive=False)
		If _typelink
			_linklist.Remove(_typelink)
			_typelink.Remove()
			_typelink=Null
		EndIf

		If typ>0
			If _config.CollisionType[typ]=Null _config.CollisionType[typ]=CreateList()
			_typelink=_config.CollisionType[typ].AddLast(Self)
			AddLink _typelink
		EndIf

		_type=typ
		GetPosition _oldx,_oldy,_oldz,True

		If recursive
			For Local child:TEntity=EachIn _childlist
				child.SetType typ,True
			Next
		EndIf
	End Method

	Method GetCullRadius#()
		Return Abs(_cullradius)
	End Method
	Method SetCullRadius(radius#)
		_cullradius=-radius
	End Method

	Method GetCullParams(x# Var,y# Var,z# Var,radius# Var)
		GetPosition x,y,z,True
		radius=GetCullRadius()
	End Method

	Method GetPickMode(mode Var, obscurer Var)
		mode = _pickmode
		obscurer = _obscurer
	End Method
	Method SetPickMode(mode, obscurer=True)
		_pickmode = mode
		_obscurer = obscurer
	End Method

	Method HasAlpha()
		Return _brush.HasAlpha()
	End Method

	Method GetMatrix:TMatrix(alternate=False,copy=True)
		If copy Return _matrix.Copy()
		Return _matrix
	End Method
	Method SetMatrix(matrix:TMatrix,glob=True)
		Local x#,y#,z#,pitch#,yaw#,roll#,sx#,sy#,sz#
		matrix.GetPosition x,y,z
		matrix.GetRotation pitch,yaw,roll
		matrix.GetScale sx,sy,sz

		LockMatrix
		SetPosition x,y,z,glob
		SetRotation pitch,yaw,roll,glob
		SetScale sx,sy,sz,glob
		UnlockMatrix
	End Method

	Method LockMatrix()
		_lockmatrix=True
	End Method
	Method UnlockMatrix()
		_lockmatrix=False
		RefreshMatrix()
	End Method

	Method RefreshMatrix()
		If _lockmatrix Return
		If _parent<>Null
			_matrix.Overwrite _parent._matrix
			UpdateMatrix False
		Else
			UpdateMatrix True
		EndIf

		For Local child:TEntity=EachIn _childlist
			child.RefreshMatrix()
		Next
	End Method

	Method UpdateMatrix(loadidentity)
		If loadidentity _matrix=TMatrix.Identity()
		_matrix=TMatrix.Translation(_px,_py,_pz).Multiply(_matrix)
		_matrix=TMatrix.YawPitchRoll(-_ry,-_rx,_rz).Multiply(_matrix)
		_matrix=TMatrix.Scale(_sx,_sy,_sz).Multiply(_matrix)
	End Method

	Method ObjectEnumerator:Object()
		Return TChildrenEnumerator.Create(_childlist)
	End Method
End Type

Type TChildrenEnumerator
	Field _children:TEntity[],_index=-1

	Function Create:TChildrenEnumerator(list:TList)
		Local enum:TChildrenEnumerator=New TChildrenEnumerator
		enum._children=TEntity[](list.ToArray())
		Return enum
	End Function

	Method HasNext()
		Return _index>=_children.length-1
	End Method

	Method NextObject:Object()
		_index:+1
		Return _children[_index]
	End Method
End Type