[VOXporter] WindowsForms files now also include a BEGIN NAMESPACE statement

USING System.IO
USING System.Text
USING System.Drawing
USING System.Collections.Generic

STATIC CLASS WinFormsConverter
	STATIC METHOD Convert(oProject AS VOProjectDescriptor, cAppFolder AS STRING) AS VOID
		LOCAL aWeds AS STRING[]

		aWeds := Directory.GetFiles(cAppFolder + "\tmp" , "*.wed")
		Directory.CreateDirectory(cAppFolder)

		LOCAL oApp AS ApplicationDescriptor
		oApp := oProject:AddApplication("Windows.Forms")
		oApp:SetWinForms()
		oApp:GACReferences:Add("System.Drawing")
		oApp:GACReferences:Add("System.Windows.Forms")

		FOREACH cWed AS STRING IN aWeds
			LOCAL aLines AS STRING[]
			aLines := File.ReadAllLines(cWed , Encoding.Default)

			LOCAL oCurrentWed AS WedDescriptor
			LOCAL oMainWed AS WedDescriptor
			oCurrentWed := WedDescriptor{}
			oMainWed := oCurrentWed

			FOR LOCAL nLine := 1 AS INT UPTO aLines:Length
				LOCAL cLine AS STRING
				cLine := aLines[nLine]:Trim()
				IF cLine:StartsWith("CONTROL=")
					oCurrentWed := WedDescriptor{oMainWed}
					oMainWed:Controls:Add(oCurrentWed)
				ENDIF
				IF cLine:IndexOf('=') != -1
					oCurrentWed:ReadProperty(cLine)
				END IF
			NEXT

			// Put control inside GroupBoxes
			AdjustContainers(oMainWed)

			LOCAL aCode, aClassCode, aInitCode, aConstrCode AS List<STRING>
			LOCAL cClassDeclCode AS STRING
			aClassCode := List<STRING>{}
			aConstrCode := List<STRING>{}
			aInitCode := List<STRING>{}
			aCode := List<STRING>{}

			LOCAL cFileName AS STRING
			LOCAL cNameSpace AS STRING
			LOCAL cPrg AS STRING
			cFileName := FileInfo{cWed}:Name:Replace(".wed","")
			cNameSpace := cFileName:Substring(0, cFileName:IndexOf('.'))
			FOREACH cChar AS Char IN e"!@#$%^&*()_+~`-=[]{};:',./<>?|\\\" "
				cNameSpace := cNameSpace:Replace(cChar:ToString(), "")
			NEXT

			cPrg := cAppFolder + "\" + cFileName + ".prg"
			oApp:AddModule(cFileName, ""):HasDesignerChild := TRUE

			LOCAL oWriter AS StreamWriter
			oWriter := StreamWriter{cPrg + ".wed" , FALSE , Encoding.Default}
			oWriter:WriteLine(String.Format("DESIGNERSTART = Form,Form,{0}" , oMainWed:Name ) )
			oWriter:WriteLine(String.Format("GUID={0}" , Guid.NewGuid():ToString()) )
			oWriter:WriteLine(String.Format("Name={0}" , oMainWed:Name) )
			oWriter:WriteLine(String.Format("Text={0}" , oMainWed:Caption) )
			oWriter:WriteLine(String.Format("Location={0},{1}" , oMainWed:Location:X , oMainWed:Location:Y) )
			oWriter:WriteLine(String.Format("ClientSize={0},{1}" , oMainWed:Size:Width , oMainWed:Size:Height) )
			oWriter:WriteLine(String.Format("ControlPrefix=None") )
			oWriter:WriteLine(String.Format("") )

			cClassDeclCode := String.Format("PARTIAL CLASS {0} INHERIT System.Windows.Forms.Form", oMainWed:Name)

			aConstrCode:Add( String.Format(e"" ) )
			aConstrCode:Add( String.Format(e"CONSTRUCTOR()" ) )
			aConstrCode:Add( String.Format(e"\tSELF:InitializeComponent()" ) )
			aConstrCode:Add( String.Format(e"RETURN" ) )

			aInitCode:Add( String.Format(e"" ) )
			aInitCode:Add( String.Format(e"PRIVATE METHOD InitializeComponent() AS VOID" ) )

			aCode:Add( String.Format(e"\tSELF:Name := {0}", Quoted(oMainWed:Name)) )
			aCode:Add( String.Format(e"\tSELF:Text := {0}", Quoted(oMainWed:Name)) )
			aCode:Add( String.Format(e"\tSELF:ClientSize := System.Drawing.Size{{{0},{1}}}", oMainWed:Size:Width , oMainWed:Size:Height) )
			aCode:Add( String.Format(e"") )

			oWriter:WriteLine(String.Format("SUBCONTROLSSTART") )

			FOREACH oControl AS WedDescriptor IN oMainWed:Controls
				WriteControl(oControl, "SELF", oWriter, aClassCode, aInitCode, aCode)
			NEXT

			oWriter:WriteLine(String.Format("SUBCONTROLSEND") )

			oWriter:WriteLine(String.Format("") )
			oWriter:WriteLine(String.Format("DESIGNEREND={0}" , oMainWed:Name) )
			oWriter:Close()

			aCode:Add( String.Format(e"RETURN" ) )
			aCode:Add( String.Format(e"" ) )

			oWriter := StreamWriter{cPrg , FALSE , Encoding.Default}
			oWriter:WriteLine("BEGIN NAMESPACE " + cNameSpace)
			oWriter:WriteLine("")
			oWriter:WriteLine(cClassDeclCode)
			FOREACH cLine AS STRING IN aConstrCode
				oWriter:WriteLine(cLine)
			NEXT
			oWriter:WriteLine("END CLASS")
			oWriter:WriteLine("")
			oWriter:WriteLine("END NAMESPACE")
			oWriter:Close()

			cFileName := FileInfo{cWed}:Name:Replace(".wed","")
			cPrg := cAppFolder + "\" + cFileName + ".Designer.prg"
			oApp:AddModule(cFileName + ".Designer", ""):IsDesignerChild := TRUE
			oWriter := StreamWriter{cPrg , FALSE , Encoding.Default}
			oWriter:WriteLine("BEGIN NAMESPACE " + cNameSpace)
			oWriter:WriteLine("")
			oWriter:WriteLine(cClassDeclCode)
			FOREACH cLine AS STRING IN aClassCode
				oWriter:WriteLine(cLine)
			NEXT
			FOREACH cLine AS STRING IN aInitCode
				oWriter:WriteLine(cLine)
			NEXT
			FOREACH cLine AS STRING IN aCode
				oWriter:WriteLine(cLine)
			NEXT
			oWriter:WriteLine("END CLASS")
			oWriter:WriteLine("")
			oWriter:WriteLine("END NAMESPACE")
			oWriter:Close()

		NEXT

		IF xPorter.ExportToXide
			oApp:CreateAppFile(cAppFolder , TRUE)
		ENDIF
		IF xPorter.ExportToXide
			oApp:CreateAppFile(cAppFolder , FALSE)
		ENDIF

		SafeDirectoryDelete(cAppFolder + "\tmp")
	RETURN

	INTERNAL STATIC METHOD WriteControl(oControl AS WedDescriptor, cParent AS STRING, oWriter AS StreamWriter, aClassCode AS List<STRING>, aInitCode AS List<STRING>, aCode AS List<STRING>) AS VOID
		oWriter:WriteLine(String.Format("CONTROL={0}" , oControl:Type) )
		oWriter:WriteLine(String.Format("GUID={0}" , Guid.NewGuid():ToString()) )
		oWriter:WriteLine(String.Format("Name={0}" , oControl:Name) )
		oWriter:WriteLine(String.Format("Text={0}" , oControl:Caption) )
		oWriter:WriteLine(String.Format("Location={0},{1}" , oControl:Location:X , oControl:Location:Y) )
		oWriter:WriteLine(String.Format("Size={0},{1}" , oControl:Size:Width , oControl:Size:Height) )
		oWriter:WriteLine(String.Format("TabIndex={0}" , oControl:Order) )

		aClassCode:Add(String.Format(e"\tPRIVATE {0} AS {1}" , oControl:Name, oControl:Type) )

		aInitCode:Add(String.Format(e"\tSELF:{0} := {1}{{}}" , oControl:Name, oControl:Type) )

		aCode:Add(String.Format(e"\tSELF:{0}:Name := {1}" , oControl:Name, Quoted(oControl:Name)) )
		aCode:Add(String.Format(e"\tSELF:{0}:Text := {1}" , oControl:Name, Quoted(oControl:Caption)) )
		aCode:Add(String.Format(e"\tSELF:{0}:Location := System.Drawing.Point{{{1},{2}}}" , oControl:Name, oControl:Location:X, oControl:Location:Y) )
		aCode:Add(String.Format(e"\tSELF:{0}:Size := System.Drawing.Size{{{1},{2}}}" , oControl:Name, oControl:Size:Width, oControl:Size:Height) )
		aCode:Add(String.Format(e"\t{1}:Controls:Add(SELF:{0})" , oControl:Name, cParent) )
		aCode:Add(String.Format(e"") )

		IF oControl:Controls:Count != 0
			oWriter:WriteLine("SUBCONTROLSSTART")
			FOREACH oChild AS WedDescriptor IN oControl:Controls
				WriteControl(oChild, "SELF:" + oControl:Name, oWriter, aClassCode, aInitCode, aCode)
			NEXT
			oWriter:WriteLine("SUBCONTROLSEND")
		END IF

		SWITCH oControl:VOType
			CASE "FIXEDTEXT"
				NOP
			CASE "RADIOBUTTON"
				oWriter:WriteLine(String.Format("AutoSize=TRUE") )
				aCode:Add(String.Format(e"\tSELF:{0}:AutoSize := true" , oControl:Name) )
			CASE "MULTILINEEDIT"
				oWriter:WriteLine(String.Format("Multiline=TRUE") )
				aCode:Add(String.Format(e"\tSELF:{0}:Multiline := true" , oControl:Name) )
		END SWITCH
	RETURN

	INTERNAL STATIC METHOD AdjustContainers(oWindow AS WedDescriptor) AS WedDescriptor
		LOCAL aContainers AS List<WedDescriptor>
		aContainers := List<WedDescriptor>{}
		FOREACH oControl AS WedDescriptor IN oWindow:Controls
			IF oControl:IsContainer
				aContainers:Add(oControl)
			END IF
		NEXT

		LOCAL nControl := 0 AS INT
		DO WHILE nControl < oWindow:Controls:Count
			LOCAL oControl AS WedDescriptor
			LOCAL oCurrent AS WedDescriptor
			oControl := oWindow:Controls[nControl]
			oCurrent := NULL
			FOREACH oContainer AS WedDescriptor IN aContainers
				IF oContainer == oControl
					LOOP
				END IF
				LOCAL oRectangle AS Rectangle
				oRectangle := Rectangle{oContainer:AbsoluteLocation , oContainer:Size}
				IF oRectangle:Contains(oControl:Location)
					IF oCurrent == NULL .or. ;
								(oContainer:AbsoluteLocation:X > oCurrent:AbsoluteLocation:X .and. ;
								oContainer:AbsoluteLocation:Y > oCurrent:AbsoluteLocation:Y)
						oCurrent := oContainer
					END IF
				END IF
			NEXT
			IF oCurrent != NULL
				oControl:SetParent(oCurrent)
			ELSE
				nControl ++
			END IF
		END DO

	RETURN NULL

//	INTERNAL STATIC METHOD GetAllContainers(oContainer AS WedDescriptor, aContainers as List<WedDescriptor>) AS VOID

	STATIC METHOD Quoted(cText AS STRING) AS STRING
	RETURN e"\"" + cText + e"\""

END CLASS

INTERNAL CLASS WedDescriptor
	PROTECT _cType AS STRING
	PROTECT _aControls AS List<WedDescriptor>
	PROTECT _oParent AS WedDescriptor
	PROTECT _lIsContainer AS LOGIC

	CONSTRUCTOR()
		SUPER()
		SELF:_cType := "System.Windows.Forms.Panel"
		SELF:_aControls := List<WedDescriptor>{}
		SELF:_lIsContainer := FALSE
	RETURN
	CONSTRUCTOR(oParent AS WedDescriptor)
		SELF()
		SELF:_oParent := oParent
	RETURN

	PROPERTY Name AS STRING AUTO
	PROPERTY Caption AS STRING AUTO
	PROPERTY Order AS INT AUTO
	PROPERTY Location AS Point AUTO
	PROPERTY Size AS Size AUTO
	PROPERTY VOType AS STRING AUTO
	PROPERTY Type AS STRING GET _cType
	PROPERTY IsContainer AS LOGIC GET SELF:_lIsContainer
	PROPERTY Controls AS List<WedDescriptor> GET SELF:_aControls
	PROPERTY AbsoluteLocation AS Point
		GET
			IF SELF:_oParent == NULL
				RETURN Point{0,0}
			ELSE
				RETURN Point{SELF:_oParent:AbsoluteLocation:X + SELF:Location:X , SELF:_oParent:AbsoluteLocation:Y + SELF:Location:Y}
			END IF
		END GET
	END PROPERTY

	METHOD SetParent(oParent AS WedDescriptor) AS VOID
		IF SELF:_oParent != NULL
			SELF:_oParent:Controls:Remove(SELF)
		END IF
		SELF:_oParent := oParent
		SELF:_oParent:Controls:Add(SELF)
		SELF:Location := Point{SELF:Location:X - SELF:_oParent:AbsoluteLocation:X , SELF:Location:Y - SELF:_oParent:AbsoluteLocation:Y}
	RETURN

	METHOD ReadProperty(cLine AS STRING) AS VOID
		LOCAL cProp AS STRING
		LOCAL cValue AS STRING
		LOCAL nAt AS INT
		nAt := cLine:IndexOf('=')
		cProp := cLine:Substring(0,nAt):Trim():ToUpperInvariant()
		cValue := cLine:Substring(nAt+1):Trim()
		SWITCH cProp
			CASE "NAME"
				SELF:Name := cValue
			CASE "CAPTION"
				SELF:Caption := cValue
			CASE "ORDER"
				SELF:Order := Int32.Parse(cValue)
			CASE "LEFT"
				SELF:Location := Point{Int32.Parse(cValue) , SELF:Location:Y}
			CASE "TOP"
				SELF:Location := Point{SELF:Location:X , Int32.Parse(cValue)}
			CASE "WIDTH"
				SELF:Size := Size{Int32.Parse(cValue) , SELF:Size:Height}
			CASE "HEIGHT"
				SELF:Size := Size{SELF:Size:Width , Int32.Parse(cValue)}
			CASE "CONTROL"
				SELF:VOType := cValue:ToUpperInvariant()
				SWITCH cValue:ToUpperInvariant()
					CASE "FIXEDTEXT"
						SELF:_cType := "System.Windows.Forms.Label"
					CASE "PUSHBUTTON"
						SELF:_cType := "System.Windows.Forms.Button"
					CASE "SINGLELINEEDIT"
					CASE "MULTILINEEDIT"
					CASE "IPADDRESS"
					CASE "RICHEDIT"
					CASE "HOTKEYEDIT"
						SELF:_cType := "System.Windows.Forms.TextBox"
					CASE "CHECKBOX"
						SELF:_cType := "System.Windows.Forms.CheckBox"
					CASE "RADIOBUTTON"
						SELF:_cType := "System.Windows.Forms.RadioButton"
					CASE "GROUPBOX"
					CASE "RADIOBUTTONGROUP"
						SELF:_cType := "System.Windows.Forms.GroupBox"
						SELF:_lIsContainer := TRUE
					CASE "COMBOBOX"
					CASE "COMBOBOXEX"
						SELF:_cType := "System.Windows.Forms.ComboBox"
					CASE "LISTBOX"
						SELF:_cType := "System.Windows.Forms.ListBox"
					CASE "LISTVIEW"
					CASE "DATALISTVIEW"
						SELF:_cType := "System.Windows.Forms.ListView"
					CASE "TREEVIEW"
						SELF:_cType := "System.Windows.Forms.TreeView"
					CASE "TABCONTROL"
						SELF:_cType := "System.Windows.Forms.TabControl"
					CASE "PROGRESSBAR"
						SELF:_cType := "System.Windows.Forms.ProgressBar"
					CASE "HORIZONTALSPINNER"
					CASE "VERTICALSPINNER"
					CASE "HORIZONTALSLIDER"
					CASE "VERTICALSLIDER"
						SELF:_cType := "System.Windows.Forms.TrackBar"
					CASE "VERTSCROLL"
						SELF:_cType := "System.Windows.Forms.HScrollBar"
					CASE "HORZSCROLL"
						SELF:_cType := "System.Windows.Forms.VScrollBar"
					CASE "SUBDATAWINDOW"
						SELF:_cType := "System.Windows.Forms.DataGridView"
				END SWITCH
		END SWITCH
	RETURN
END CLASS

USING System.IO
USING System.Text
USING System.Drawing
USING System.Collections.Generic

STATIC CLASS WinFormsConverter
	STATIC METHOD Convert(oProject AS VOProjectDescriptor, cAppFolder AS STRING) AS VOID
		LOCAL aWeds AS STRING[]

		aWeds := Directory.GetFiles(cAppFolder + "\tmp" , "*.wed")
		Directory.CreateDirectory(cAppFolder)

		LOCAL oApp AS ApplicationDescriptor
		oApp := oProject:AddApplication("Windows.Forms")
		oApp:SetWinForms()
		oApp:GACReferences:Add("System.Drawing")
		oApp:GACReferences:Add("System.Windows.Forms")

		FOREACH cWed AS STRING IN aWeds
			LOCAL aLines AS STRING[]
			aLines := File.ReadAllLines(cWed , Encoding.Default)

			LOCAL oCurrentWed AS WedDescriptor
			LOCAL oMainWed AS WedDescriptor
			oCurrentWed := WedDescriptor{}
			oMainWed := oCurrentWed

			FOR LOCAL nLine := 1 AS INT UPTO aLines:Length
				LOCAL cLine AS STRING
				cLine := aLines[nLine]:Trim()
				IF cLine:StartsWith("CONTROL=")
					oCurrentWed := WedDescriptor{oMainWed}
					oMainWed:Controls:Add(oCurrentWed)
				ENDIF
				IF cLine:IndexOf('=') != -1
					oCurrentWed:ReadProperty(cLine)
				END IF
			NEXT

			// Put control inside GroupBoxes
			AdjustContainers(oMainWed)

			LOCAL aCode, aClassCode, aInitCode, aConstrCode AS List<STRING>
			LOCAL cClassDeclCode AS STRING
			aClassCode := List<STRING>{}
			aConstrCode := List<STRING>{}
			aInitCode := List<STRING>{}
			aCode := List<STRING>{}

			LOCAL cFileName AS STRING
			LOCAL cPrg AS STRING
			cFileName := FileInfo{cWed}:Name:Replace(".wed","")
			cPrg := cAppFolder + "\" + cFileName + ".prg"
			oApp:AddModule(cFileName, ""):HasDesignerChild := TRUE

			LOCAL oWriter AS StreamWriter
			oWriter := StreamWriter{cPrg + ".wed" , FALSE , Encoding.Default}
			oWriter:WriteLine(String.Format("DESIGNERSTART = Form,Form,{0}" , oMainWed:Name ) )
			oWriter:WriteLine(String.Format("GUID={0}" , Guid.NewGuid():ToString()) )
			oWriter:WriteLine(String.Format("Name={0}" , oMainWed:Name) )
			oWriter:WriteLine(String.Format("Text={0}" , oMainWed:Caption) )
			oWriter:WriteLine(String.Format("Location={0},{1}" , oMainWed:Location:X , oMainWed:Location:Y) )
			oWriter:WriteLine(String.Format("ClientSize={0},{1}" , oMainWed:Size:Width , oMainWed:Size:Height) )
			oWriter:WriteLine(String.Format("ControlPrefix=None") )
			oWriter:WriteLine(String.Format("") )

			cClassDeclCode := String.Format("PARTIAL CLASS {0} INHERIT System.Windows.Forms.Form", oMainWed:Name)

			aConstrCode:Add( String.Format(e"" ) )
			aConstrCode:Add( String.Format(e"CONSTRUCTOR()" ) )
			aConstrCode:Add( String.Format(e"\tSELF:InitializeComponent()" ) )
			aConstrCode:Add( String.Format(e"RETURN" ) )

			aInitCode:Add( String.Format(e"" ) )
			aInitCode:Add( String.Format(e"PRIVATE METHOD InitializeComponent() AS VOID" ) )

			aCode:Add( String.Format(e"\tSELF:Name := {0}", Quoted(oMainWed:Name)) )
			aCode:Add( String.Format(e"\tSELF:Text := {0}", Quoted(oMainWed:Name)) )
			aCode:Add( String.Format(e"\tSELF:ClientSize := System.Drawing.Size{{{0},{1}}}", oMainWed:Size:Width , oMainWed:Size:Height) )
			aCode:Add( String.Format(e"") )

			oWriter:WriteLine(String.Format("SUBCONTROLSSTART") )

			FOREACH oControl AS WedDescriptor IN oMainWed:Controls
				WriteControl(oControl, "SELF", oWriter, aClassCode, aInitCode, aCode)
			NEXT

			oWriter:WriteLine(String.Format("SUBCONTROLSEND") )

			oWriter:WriteLine(String.Format("") )
			oWriter:WriteLine(String.Format("DESIGNEREND={0}" , oMainWed:Name) )
			oWriter:Close()

			aCode:Add( String.Format(e"RETURN" ) )
			aCode:Add( String.Format(e"" ) )

			oWriter := StreamWriter{cPrg , FALSE , Encoding.Default}
			oWriter:WriteLine(cClassDeclCode)
			FOREACH cLine AS STRING IN aConstrCode
				oWriter:WriteLine(cLine)
			NEXT
			oWriter:WriteLine("END CLASS")
			oWriter:Close()

			cFileName := FileInfo{cWed}:Name:Replace(".wed","")
			cPrg := cAppFolder + "\" + cFileName + ".Designer.prg"
			oApp:AddModule(cFileName + ".Designer", ""):IsDesignerChild := TRUE
			oWriter := StreamWriter{cPrg , FALSE , Encoding.Default}
			oWriter:WriteLine(cClassDeclCode)
			FOREACH cLine AS STRING IN aClassCode
				oWriter:WriteLine(cLine)
			NEXT
			FOREACH cLine AS STRING IN aInitCode
				oWriter:WriteLine(cLine)
			NEXT
			FOREACH cLine AS STRING IN aCode
				oWriter:WriteLine(cLine)
			NEXT
			oWriter:WriteLine("END CLASS")
			oWriter:Close()

		NEXT

		IF xPorter.ExportToXide
			oApp:CreateAppFile(cAppFolder , TRUE)
		ENDIF
		IF xPorter.ExportToXide
			oApp:CreateAppFile(cAppFolder , FALSE)
		ENDIF

		SafeDirectoryDelete(cAppFolder + "\tmp")
	RETURN

	INTERNAL STATIC METHOD WriteControl(oControl AS WedDescriptor, cParent AS STRING, oWriter AS StreamWriter, aClassCode AS List<STRING>, aInitCode AS List<STRING>, aCode AS List<STRING>) AS VOID
		oWriter:WriteLine(String.Format("CONTROL={0}" , oControl:Type) )
		oWriter:WriteLine(String.Format("GUID={0}" , Guid.NewGuid():ToString()) )
		oWriter:WriteLine(String.Format("Name={0}" , oControl:Name) )
		oWriter:WriteLine(String.Format("Text={0}" , oControl:Caption) )
		oWriter:WriteLine(String.Format("Location={0},{1}" , oControl:Location:X , oControl:Location:Y) )
		oWriter:WriteLine(String.Format("Size={0},{1}" , oControl:Size:Width , oControl:Size:Height) )
		oWriter:WriteLine(String.Format("TabIndex={0}" , oControl:Order) )

		aClassCode:Add(String.Format(e"\tPRIVATE {0} AS {1}" , oControl:Name, oControl:Type) )

		aInitCode:Add(String.Format(e"\tSELF:{0} := {1}{{}}" , oControl:Name, oControl:Type) )

		aCode:Add(String.Format(e"\tSELF:{0}:Name := {1}" , oControl:Name, Quoted(oControl:Name)) )
		aCode:Add(String.Format(e"\tSELF:{0}:Text := {1}" , oControl:Name, Quoted(oControl:Caption)) )
		aCode:Add(String.Format(e"\tSELF:{0}:Location := System.Drawing.Point{{{1},{2}}}" , oControl:Name, oControl:Location:X, oControl:Location:Y) )
		aCode:Add(String.Format(e"\tSELF:{0}:Size := System.Drawing.Size{{{1},{2}}}" , oControl:Name, oControl:Size:Width, oControl:Size:Height) )
		aCode:Add(String.Format(e"\t{1}:Controls:Add(SELF:{0})" , oControl:Name, cParent) )
		aCode:Add(String.Format(e"") )

		IF oControl:Controls:Count != 0
			oWriter:WriteLine("SUBCONTROLSSTART")
			FOREACH oChild AS WedDescriptor IN oControl:Controls
				WriteControl(oChild, "SELF:" + oControl:Name, oWriter, aClassCode, aInitCode, aCode)
			NEXT
			oWriter:WriteLine("SUBCONTROLSEND")
		END IF

		SWITCH oControl:VOType
			CASE "FIXEDTEXT"
				NOP
			CASE "RADIOBUTTON"
				oWriter:WriteLine(String.Format("AutoSize=TRUE") )
				aCode:Add(String.Format(e"\tSELF:{0}:AutoSize := true" , oControl:Name) )
			CASE "MULTILINEEDIT"
				oWriter:WriteLine(String.Format("Multiline=TRUE") )
				aCode:Add(String.Format(e"\tSELF:{0}:Multiline := true" , oControl:Name) )
		END SWITCH
	RETURN

	INTERNAL STATIC METHOD AdjustContainers(oWindow AS WedDescriptor) AS WedDescriptor
		LOCAL aContainers AS List<WedDescriptor>
		aContainers := List<WedDescriptor>{}
		FOREACH oControl AS WedDescriptor IN oWindow:Controls
			IF oControl:IsContainer
				aContainers:Add(oControl)
			END IF
		NEXT

		LOCAL nControl := 0 AS INT
		DO WHILE nControl < oWindow:Controls:Count
			LOCAL oControl AS WedDescriptor
			LOCAL oCurrent AS WedDescriptor
			oControl := oWindow:Controls[nControl]
			oCurrent := NULL
			FOREACH oContainer AS WedDescriptor IN aContainers
				IF oContainer == oControl
					LOOP
				END IF
				LOCAL oRectangle AS Rectangle
				oRectangle := Rectangle{oContainer:AbsoluteLocation , oContainer:Size}
				IF oRectangle:Contains(oControl:Location)
					IF oCurrent == NULL .or. ;
								(oContainer:AbsoluteLocation:X > oCurrent:AbsoluteLocation:X .and. ;
								oContainer:AbsoluteLocation:Y > oCurrent:AbsoluteLocation:Y)
						oCurrent := oContainer
					END IF
				END IF
			NEXT
			IF oCurrent != NULL
				oControl:SetParent(oCurrent)
			ELSE
				nControl ++
			END IF
		END DO

	RETURN NULL

//	INTERNAL STATIC METHOD GetAllContainers(oContainer AS WedDescriptor, aContainers as List<WedDescriptor>) AS VOID

	STATIC METHOD Quoted(cText AS STRING) AS STRING
	RETURN e"\"" + cText + e"\""

END CLASS

INTERNAL CLASS WedDescriptor
	PROTECT _cType AS STRING
	PROTECT _aControls AS List<WedDescriptor>
	PROTECT _oParent AS WedDescriptor
	PROTECT _lIsContainer AS LOGIC

	CONSTRUCTOR()
		SUPER()
		SELF:_cType := "System.Windows.Forms.Panel"
		SELF:_aControls := List<WedDescriptor>{}
		SELF:_lIsContainer := FALSE
	RETURN
	CONSTRUCTOR(oParent AS WedDescriptor)
		SELF()
		SELF:_oParent := oParent
	RETURN

	PROPERTY Name AS STRING AUTO
	PROPERTY Caption AS STRING AUTO
	PROPERTY Order AS INT AUTO
	PROPERTY Location AS Point AUTO
	PROPERTY Size AS Size AUTO
	PROPERTY VOType AS STRING AUTO
	PROPERTY Type AS STRING GET _cType
	PROPERTY IsContainer AS LOGIC GET SELF:_lIsContainer
	PROPERTY Controls AS List<WedDescriptor> GET SELF:_aControls
	PROPERTY AbsoluteLocation AS Point
		GET
			IF SELF:_oParent == NULL
				RETURN Point{0,0}
			ELSE
				RETURN Point{SELF:_oParent:AbsoluteLocation:X + SELF:Location:X , SELF:_oParent:AbsoluteLocation:Y + SELF:Location:Y}
			END IF
		END GET
	END PROPERTY

	METHOD SetParent(oParent AS WedDescriptor) AS VOID
		IF SELF:_oParent != NULL
			SELF:_oParent:Controls:Remove(SELF)
		END IF
		SELF:_oParent := oParent
		SELF:_oParent:Controls:Add(SELF)
		SELF:Location := Point{SELF:Location:X - SELF:_oParent:AbsoluteLocation:X , SELF:Location:Y - SELF:_oParent:AbsoluteLocation:Y}
	RETURN

	METHOD ReadProperty(cLine AS STRING) AS VOID
		LOCAL cProp AS STRING
		LOCAL cValue AS STRING
		LOCAL nAt AS INT
		nAt := cLine:IndexOf('=')
		cProp := cLine:Substring(0,nAt):Trim():ToUpperInvariant()
		cValue := cLine:Substring(nAt+1):Trim()
		SWITCH cProp
			CASE "NAME"
				SELF:Name := cValue
			CASE "CAPTION"
				SELF:Caption := cValue
			CASE "ORDER"
				SELF:Order := Int32.Parse(cValue)
			CASE "LEFT"
				SELF:Location := Point{Int32.Parse(cValue) , SELF:Location:Y}
			CASE "TOP"
				SELF:Location := Point{SELF:Location:X , Int32.Parse(cValue)}
			CASE "WIDTH"
				SELF:Size := Size{Int32.Parse(cValue) , SELF:Size:Height}
			CASE "HEIGHT"
				SELF:Size := Size{SELF:Size:Width , Int32.Parse(cValue)}
			CASE "CONTROL"
				SELF:VOType := cValue:ToUpperInvariant()
				SWITCH cValue:ToUpperInvariant()
					CASE "FIXEDTEXT"
						SELF:_cType := "System.Windows.Forms.Label"
					CASE "PUSHBUTTON"
						SELF:_cType := "System.Windows.Forms.Button"
					CASE "SINGLELINEEDIT"
					CASE "MULTILINEEDIT"
					CASE "IPADDRESS"
					CASE "RICHEDIT"
					CASE "HOTKEYEDIT"
						SELF:_cType := "System.Windows.Forms.TextBox"
					CASE "CHECKBOX"
						SELF:_cType := "System.Windows.Forms.CheckBox"
					CASE "RADIOBUTTON"
						SELF:_cType := "System.Windows.Forms.RadioButton"
					CASE "GROUPBOX"
					CASE "RADIOBUTTONGROUP"
						SELF:_cType := "System.Windows.Forms.GroupBox"
						SELF:_lIsContainer := TRUE
					CASE "COMBOBOX"
					CASE "COMBOBOXEX"
						SELF:_cType := "System.Windows.Forms.ComboBox"
					CASE "LISTBOX"
						SELF:_cType := "System.Windows.Forms.ListBox"
					CASE "LISTVIEW"
					CASE "DATALISTVIEW"
						SELF:_cType := "System.Windows.Forms.ListView"
					CASE "TREEVIEW"
						SELF:_cType := "System.Windows.Forms.TreeView"
					CASE "TABCONTROL"
						SELF:_cType := "System.Windows.Forms.TabControl"
					CASE "PROGRESSBAR"
						SELF:_cType := "System.Windows.Forms.ProgressBar"
					CASE "HORIZONTALSPINNER"
					CASE "VERTICALSPINNER"
					CASE "HORIZONTALSLIDER"
					CASE "VERTICALSLIDER"
						SELF:_cType := "System.Windows.Forms.TrackBar"
					CASE "VERTSCROLL"
						SELF:_cType := "System.Windows.Forms.HScrollBar"
					CASE "HORZSCROLL"
						SELF:_cType := "System.Windows.Forms.VScrollBar"
					CASE "SUBDATAWINDOW"
						SELF:_cType := "System.Windows.Forms.DataGridView"
				END SWITCH
		END SWITCH
	RETURN
END CLASS


[RDD] Fixes #593

[RDD] Fixes #593

//
// Copyright (c) XSharp B.V.  All Rights Reserved.
// Licensed under the Apache License, Version 2.0.
// See License.txt in the project root for license information.
//
USING System
USING System.Collections
USING System.Collections.Generic
USING System.Diagnostics
USING System.Globalization
USING System.IO
USING System.Reflection
USING System.Text
USING System.Threading
USING XSharp.RDD.Enums
USING XSharp.RDD.Support
USING STATIC XSharp.Conversions
BEGIN NAMESPACE XSharp.RDD.CDX

    INTERNAL DELEGATE CompareFunc(aLHS AS BYTE[], aRHS AS BYTE[], nLength AS LONG, recnoLHS AS LONG, recnoRHS AS LONG) AS LONG

    [DebuggerDisplay("Tag: {OrderName}, Key: {Expression}, For: {Condition}")];
    INTERNAL SEALED PARTIAL CLASS CdxTag
        #region constants
        PRIVATE CONST MAX_KEY_LEN       := 240  AS WORD
        #endregion
        #region fields
        PRIVATE _Encoding AS Encoding
        PRIVATE _Hot AS LOGIC
        PRIVATE _Conditional AS LOGIC
        PRIVATE _Descending AS LOGIC
        PRIVATE _Unique AS LOGIC
        PRIVATE _Custom AS LOGIC
        PRIVATE _SingleField AS LONG            // 0 based FieldIndex
        PRIVATE _SourceIndex AS LONG
        PRIVATE _KeyCodeBlock AS ICodeblock
        PRIVATE _ForCodeBlock AS ICodeblock
        PRIVATE _KeyExpr AS STRING
        PRIVATE _ForExpr AS STRING
        PRIVATE _currentvalue AS RddKeyData
        INTERNAL _newvalue     AS RddKeyData
        PRIVATE _newKeyLen AS LONG
        PRIVATE _KeyExprType AS LONG
        PRIVATE _keySize AS WORD
        PRIVATE _sourcekeySize AS WORD
        PRIVATE _rootPage AS LONG
        PRIVATE _ordCondInfo AS DbOrderCondInfo
        INTERNAL _orderName AS STRING
        INTERNAL _Ansi AS LOGIC
        // Scopes
        PRIVATE DIM _Scopes[2] AS ScopeInfo
        PRIVATE _scopeEmpty    AS LOGIC
        // siblings
        PRIVATE _oRdd   AS DBFCDX
        PRIVATE _Header AS CdxTagHeader

        PRIVATE _stack          AS CdxPageStack
        INTERNAL __Compare       AS CompareFunc

        PRIVATE _bag            AS CdxOrderBag
        PRIVATE getKeyValue     AS ValueBlock       // Delegate to calculate the key
        PRIVATE _Collation      AS VfpCollation
        PRIVATE _Valid          AS LOGIC

#endregion

#region Properties
        INTERNAL PROPERTY Binary            AS LOGIC AUTO
        INTERNAL PROPERTY IsOpen            AS LOGIC AUTO
        INTERNAL PROPERTY Collation         AS VfpCollation GET _Collation
        INTERNAL PROPERTY Expression        AS STRING GET _KeyExpr
        INTERNAL PROPERTY Encoding          AS Encoding GET _Encoding
        INTERNAL PROPERTY RDD               AS DBFCDX GET _oRdd
        INTERNAL PROPERTY Condition         AS STRING GET _ForExpr
        INTERNAL PROPERTY OrderName         AS STRING GET _orderName
	    INTERNAL PROPERTY Shared 	        AS LOGIC GET _bag:Shared
        INTERNAL PROPERTY _RecNo 	        AS LONG GET _oRdd:RecNo
        INTERNAL PROPERTY FileName 	        AS STRING GET _bag:FullPath
        INTERNAL PROPERTY OrderBag          AS CdxOrderBag GET SELF:_bag
        INTERNAL PROPERTY Page              AS Int32 AUTO
        INTERNAL PROPERTY Descending        AS LOGIC GET _Descending SET _Descending := value
        INTERNAL PROPERTY IsConditional     AS LOGIC GET Options:HasFlag(CdxOptions.HasFor)
        INTERNAL PROPERTY IsHot             AS LOGIC GET _Hot
        INTERNAL PROPERTY Header            AS CdxTagHeader GET _Header
        INTERNAL PROPERTY KeyType        	AS INT GET SELF:_KeyExprType
        INTERNAL PROPERTY KeyCodeBlock      AS ICodeblock GET _KeyCodeBlock
        INTERNAL PROPERTY KeyLength         AS WORD GET SELF:_keySize
        INTERNAL PROPERTY SourceKeyLength   AS WORD GET SELF:_sourcekeySize
        INTERNAL PROPERTY Partial        	AS LOGIC GET SELF:Custom
        INTERNAL PROPERTY Conditional       AS LOGIC GET SELF:_Conditional
        INTERNAL PROPERTY Custom         	AS LOGIC GET SELF:_Custom
        INTERNAL PROPERTY Unique         	AS LOGIC GET SELF:_Unique
        INTERNAL PROPERTY Signature      	AS BYTE AUTO
        INTERNAL PROPERTY Options        	AS CdxOptions AUTO
        INTERNAL PROPERTY LockOffSet     	AS LONG AUTO
        INTERNAL PROPERTY CurrentStack      AS CdxStackEntry GET  SELF:_stack:Top
        INTERNAL PROPERTY RootPage          AS LONG AUTO
        INTERNAL PROPERTY Stack             AS CdxPageStack GET _stack

        // Scopes
        INTERNAL PROPERTY HasTopScope       AS LOGIC    GET _Scopes[SELF:TopScopeNo]:IsSet
        INTERNAL PROPERTY TopScope          AS OBJECT   GET _Scopes[SELF:TopScopeNo]:Value
        INTERNAL PROPERTY HasBottomScope    AS LOGIC    GET _Scopes[SELF:BottomScopeNo]:IsSet
        INTERNAL PROPERTY BottomScope       AS OBJECT   GET _Scopes[SELF:BottomScopeNo]:Value
        INTERNAL PROPERTY HasScope          AS LOGIC    GET _Scopes[0]:IsSet .OR. _Scopes[1]:IsSet
        PRIVATE  PROPERTY TopScopeNo        AS LONG     GET IIF(SELF:Descending, BOTTOMSCOPE, TOPSCOPE)
        PRIVATE  PROPERTY BottomScopeNo     AS LONG     GET IIF(SELF:Descending, TOPSCOPE, BOTTOMSCOPE)

#endregion

        PRIVATE METHOD _InitFields(oBag AS CdxOrderBag) AS VOID
            SELF:_bag           := oBag
            SELF:_oRdd          := oBag:_oRdd
            SELF:_stack         := CdxPageStack{SELF}
            SELF:_Encoding      := _oRdd:_Encoding

            SELF:_SingleField   := -1
            SELF:_currentvalue := RddKeyData{MAX_KEY_LEN}
            SELF:_newvalue     := RddKeyData{MAX_KEY_LEN}

        // Constructor for Creation of tags
        INTERNAL CONSTRUCTOR (oBag AS CdxOrderBag)
	        SUPER()
            SELF:_InitFields(oBag)

        // Constructor for Opening of tags
        INTERNAL CONSTRUCTOR (oBag AS CdxOrderBag, nPage AS Int32, cName AS STRING)
	        SUPER()
            SELF:_InitFields(oBag)
            SELF:_orderName := cName
            SELF:Page := nPage
            SELF:_Header := CdxTagHeader{oBag, nPage,SELF:OrderName, SELF}
            SELF:_bag:SetPage(SELF:_Header)
            SELF:IsOpen := SELF:Open()

         INTERNAL METHOD ReadHeader AS VOID
            SELF:_Header:Read()
            SELF:_keySize       := SELF:_Header:KeySize
            SELF:Options        := SELF:_Header:Options
            SELF:Signature      := SELF:_Header:Signature
            SELF:_Descending    := SELF:_Header:Descending
            SELF:_rootPage      := SELF:_Header:RootPage
            SELF:_KeyExpr       := SELF:_Header:KeyExpression
            SELF:_ForExpr       := SELF:_Header:ForExpression
            SELF:_Unique        := SELF:Options:HasFlag(CdxOptions.Unique)
            SELF:_Custom        := SELF:Options:HasFlag(CdxOptions.Custom)
            SELF:_Conditional   := SELF:Options:HasFlag(CdxOptions.HasFor)
            RETURN

	    INTERNAL METHOD Open() AS LOGIC
            SELF:_oRdd:GoTo(1)
            SELF:_Hot := FALSE
            SELF:ClearStack()
            SELF:ReadHeader()
            IF !String.IsNullOrEmpty(SELF:_Header:VFPCollation)
                IF SELF:_oRdd IS DBFVFP
                    SELF:_Collation := VfpCollation{SELF:_Header:VFPCollation,SELF:_oRdd:Header:CodePage:ToCodePage()}
                ELSE
                    SELF:_oRdd:_dbfError( Subcodes.ERDD_CORRUPT, Gencode.EG_CORRUPTION, "CdxTag.Open","Indexes with collation must be opened with the DBFVFP driver")
                ENDIF
            ENDIF
            SELF:_oRdd:__Goto(0)
            IF ! SELF:EvaluateExpressions()
                RETURN FALSE
            ENDIF
            SELF:AllocateBuffers()
            RETURN TRUE

        INTERNAL METHOD AllocateBuffers() AS VOID
            SELF:_newvalue          := RddKeyData{_keySize}
            SELF:_currentvalue      := RddKeyData{_keySize}
            SELF:_Scopes[0]:SetBuffer(_keySize)
            SELF:_Scopes[1]:SetBuffer(_keySize)
            RETURN

        INTERNAL METHOD SetLeafProperties(page AS CdxLeafPage) AS VOID
            VAR numRecs     := SELF:RDD:RecCount
            page:ClearRecordsAndKeys()
            page:KeyLength      := SELF:KeyLength
            page:SetRecordBits(numRecs)
            RETURN

        INTERNAL METHOD EvaluateExpressions() AS LOGIC
            LOCAL evalOk AS LOGIC
            LOCAL oKey := NULL AS OBJECT
            evalOk := TRUE
            SELF:_Valid := TRUE
            TRY
                SELF:_KeyCodeBlock := SELF:_oRdd:Compile(SELF:_KeyExpr)
            CATCH AS Exception
                SELF:_Valid := FALSE
                evalOk := FALSE
            END TRY
            IF ! evalOk
                RETURN FALSE
            ENDIF
            TRY
                oKey := SELF:_oRdd:EvalBlock(SELF:_KeyCodeBlock)
            CATCH AS Exception
                evalOk := FALSE
            END TRY
            IF !evalOk
                SELF:_Valid := FALSE
                RETURN FALSE
            ENDIF
            SELF:_KeyExprType := SELF:_oRdd:_getUsualType(oKey)
            IF SELF:_KeyExprType == __UsualType.String
                IF SELF:_Collation != NULL
                    SELF:__Compare := _compareCollation
                ELSE
                    SELF:__Compare := _compareText
                ENDIF
            ELSE
                SELF:__Compare := _compareBin
            ENDIF

            // If the Key Expression contains only a Field Name

            IF SELF:_oRdd IS DBFVFPSQL
                SELF:_SingleField := -1
            ELSE
                SELF:_SingleField := SELF:_oRdd:FieldIndex(SELF:_KeyExpr) -1
            ENDIF
            SELF:_SourceIndex := 0
            LOCAL isOk AS LOGIC
            IF SELF:_SingleField >= 0
                SELF:_SourceIndex   := SELF:_oRdd:_Fields[_SingleField]:Offset
                VAR fType           := SELF:_oRdd:_Fields[_SingleField]:FieldType
                SWITCH fType
                CASE DbFieldType.Number
                    SELF:_sourcekeySize := SELF:_keySize   := 8
                    SELF:getKeyValue := _getNumFieldValue
                    SELF:Binary      := TRUE
                CASE DbFieldType.Date
                    // CDX converts the Date to a Julian Number and stores that as a Real8 in the index
                    SELF:_sourcekeySize := SELF:_keySize   := 8
                    SELF:getKeyValue := _getDateFieldValue
                    SELF:Binary      := TRUE
                CASE DbFieldType.Integer
                    SELF:_sourcekeySize := SELF:_keySize   := 4
                    SELF:getKeyValue := _getIntFieldValue
                    SELF:Binary      := TRUE
                OTHERWISE
                    SELF:_sourcekeySize := SELF:_keySize   := (WORD) SELF:_oRdd:_Fields[_SingleField]:Length
                    SELF:getKeyValue := _getFieldValue
                    SELF:Binary      := FALSE
                    IF SELF:_Collation != NULL
                        SELF:_keySize := SELF:Header:KeySize
                        SELF:getKeyValue := _getFieldValueWithCollation
                    ENDIF

                END SWITCH
                isOk := TRUE
            ELSE
                SELF:_sourcekeySize := SELF:_keySize    := 0
                SELF:getKeyValue := _getExpressionValue
                isOk             := SELF:_determineSize(oKey)
                IF SELF:Header != NULL .AND. SELF:Header:KeySize != SELF:_keySize .AND. SELF:Header:KeySize != SELF:_keySize+1
                    IF SELF:_Collation != NULL
                        SELF:_keySize := SELF:Header:KeySize
                    ELSE
                        var ex := Exception{"Expression length in index does not match calculated expression length"}
                        SELF:_oRdd:_dbfError(ex, Subcodes.EDB_EXPRESSION,Gencode.EG_SYNTAX,  "DBFCDX.EvaluateExpressions", FALSE)
                    ENDIF
                ENDIF
            ENDIF
            IF ! isOk
                RETURN FALSE
            ENDIF
            SELF:AllocateBuffers()
            SELF:_Conditional       := FALSE
            IF SELF:_ForExpr:Length > 0
                TRY
                    SELF:_ForCodeBlock := SELF:_oRdd:Compile(SELF:_ForExpr)
                CATCH ex AS Exception
                    IF RuntimeState.LastRddError == NULL
                        SELF:_oRdd:_dbfError( ex, Subcodes.EDB_EXPRESSION, Gencode.EG_SYNTAX,"DBFCDX.EvaluateExpressions", FALSE)
                    ENDIF
                    SELF:_Valid := FALSE
                    RETURN FALSE
                END TRY
                SELF:_oRdd:GoTo(1)
                evalOk := TRUE
                TRY
                    VAR oValue := SELF:_oRdd:EvalBlock(SELF:_ForCodeBlock)
                    evalOk     := SELF:_oRdd:_getUsualType(oValue) == __UsualType.Logic
                CATCH ex AS Exception
                    IF RuntimeState.LastRddError == NULL
                        SELF:_oRdd:_dbfError( ex, Subcodes.EDB_EXPRESSION, Gencode.EG_SYNTAX,"DBFCDX.EvaluateExpressions", FALSE)
                    ENDIF
                    SELF:_Valid := FALSE
                    evalOk := FALSE
                END TRY
                IF !evalOk
                    RETURN FALSE
                ENDIF
                SELF:_Conditional := TRUE
            ENDIF

            RETURN isOk

        INTERNAL METHOD Flush() AS LOGIC
            // do NOT Call bag:flush to avoid recursion
            //SELF:_bag:Flush()
            RETURN TRUE

        INTERNAL METHOD Commit() AS LOGIC
            SELF:GoCold()
            SELF:_bag:Flush()
            RETURN TRUE

        INTERNAL METHOD Close() AS LOGIC
            RETURN SELF:Commit()

        INTERNAL METHOD GoCold() AS LOGIC
            IF SELF:_oRdd:IsHot
                RETURN SELF:_keyUpdate( SELF:_RecNo, SELF:_oRdd:IsNewRecord )
            ENDIF
            RETURN TRUE

        INTERNAL METHOD GoHot() AS LOGIC
            // Is called to save the current for value and key value
            RETURN SELF:_saveCurrentKey(SELF:_oRdd:RecNo, SELF:_currentvalue)

        INTERNAL METHOD GetPage(nPage AS LONG) AS CdxTreePage
            IF nPage == -1
                RETURN NULL
            ENDIF
            VAR page := SELF:_bag:GetPage(nPage, SELF:_keySize, SELF)
            RETURN page ASTYPE CdxTreePage

        INTERNAL STATIC METHOD _compareText(aLHS AS BYTE[], aRHS AS BYTE[], nLength AS LONG, recnoLHS AS LONG, recnoRHS AS LONG) AS LONG
            IF aRHS == NULL
                RETURN 0
            ENDIF
            VAR result := RuntimeState.StringCompare(aLHS, aRHS, nLength)
            IF result == 0
                result := _compareRecno(recnoLHS, recnoRHS)
            ENDIF
            RETURN result

        INTERNAL STATIC METHOD _compareCollation(aLHS AS BYTE[], aRHS AS BYTE[], nLength AS LONG, recnoLHS AS LONG, recnoRHS AS LONG) AS LONG
            RETURN _compareBin(aLHS, aRHS, nLength, recnoLHS, recnoRHS)

        INTERNAL STATIC METHOD _compareRecno(recnoLHS AS LONG, recnoRHS AS LONG) AS LONG
            IF recnoLHS == 0 .or. recnoRHS == 0
                // no record to compare against
                RETURN 0
            ENDIF
            IF recnoLHS < recnoRHS
                RETURN -1
            ELSEIF recnoLHS > recnoRHS
                RETURN 1
            ENDIF
            RETURN 0

        INTERNAL STATIC METHOD _compareBin(aLHS AS BYTE[], aRHS AS BYTE[], nLength AS LONG, recnoLHS AS LONG, recnoRHS AS LONG) AS LONG
            IF aRHS == NULL
                RETURN 0
            ENDIF
            FOR VAR nI := 0 TO nLength -1
                VAR bL := aLHS[nI]
                VAR bR := aRHS[nI]
                IF bL < bR
                    RETURN -1
                ELSEIF bL > bR
                    RETURN 1
                ENDIF
            NEXT
            RETURN _compareRecno(recnoLHS, recnoRHS)

        INTERNAL METHOD SetOffLine() AS VOID
            SELF:ClearStack()

        PRIVATE METHOD _saveCurrentRecord( node AS CdxNode) AS VOID
            IF SELF:_currentvalue:Recno != node:Recno .AND. ! SELF:RDD:EoF
                SELF:_currentvalue:Recno := node:Recno
                Array.Copy(node:KeyBytes, SELF:_currentvalue:Key, _keySize)
            ENDIF
        STATIC PRIVATE Jan_1_1901 := DateTime{1901, 1, 1 } AS DateTime

        PRIVATE STATIC METHOD _toJulian(dt AS DateTime) AS LONG
            VAR days      := dt:Subtract( Jan_1_1901 )
            RETURN Convert.ToInt32( days:TotalDays ) + 2415386   // Julian date number of 1901-01-01

        PRIVATE METHOD _ToString( toConvert AS OBJECT , sLen AS LONG ,  buffer AS BYTE[] ) AS LOGIC
            LOCAL resultLength AS LONG
            resultLength := 0
            RETURN SELF:_ToString( toConvert, sLen, buffer,  REF resultLength)

        PRIVATE METHOD _ToString( toConvert AS OBJECT , sLen AS LONG ,  buffer AS BYTE[] ,  resultLength REF LONG ) AS LOGIC
            LOCAL text AS STRING
            LOCAL typeCde AS TypeCode
            text := NULL
            IF toConvert IS IFloat // Float Value ?
                typeCde   := TypeCode.Double
            ELSEIF toConvert IS IDate VAR valueDate // Date Value
                toConvert := DateTime{valueDate:Year, valueDate:Month, valueDate:Day}
                typeCde   := TypeCode.DateTime
            ELSE
                typeCde := Type.GetTypeCode(toConvert:GetType())
            ENDIF
            IF typeCde == TypeCode.DateTime
                // Convert to Julian number and then process as normal numeric value
                toConvert     := _toJulian((DateTime) toConvert)
                typeCde       := TypeCode.Int32
            ENDIF
            SWITCH typeCde
            CASE TypeCode.Int16
            CASE TypeCode.Int32
            CASE TypeCode.Int64
            CASE TypeCode.Single
            CASE TypeCode.Double
            CASE TypeCode.Decimal
                IF sLen == 4
                    LongToFoxOrder(Convert.ToInt32(toConvert), buffer)
                    resultLength := 4
                ELSE
                    DoubleToFoxOrder(Convert.ToDouble(toConvert), buffer)
                    resultLength := 8
                ENDIF
                RETURN TRUE
            CASE TypeCode.String
                text := (STRING)toConvert
            CASE TypeCode.Boolean
                text := IIF ((LOGIC) toConvert, "T", "F")
            END SWITCH
            IF sLen > text:Length
                sLen := text:Length
            ENDIF
            resultLength := sLen
            SELF:_Encoding:GetBytes( text, 0, sLen, buffer, 0)
            IF SELF:_Collation != NULL
                SELF:_Collation:Translate(buffer)
            ENDIF
            RETURN TRUE

        INTERNAL METHOD SetOrderScope(itmScope AS OBJECT , uiScope AS DbOrder_Info ) AS LOGIC
            LOCAL uiRealLen AS LONG
            LOCAL result AS LOGIC
            uiRealLen := 0
            result := TRUE
            SWITCH uiScope
            CASE DbOrder_Info.DBOI_SCOPETOPCLEAR
                SELF:_Scopes[TOPSCOPE]:Clear()
                SELF:_scopeEmpty := FALSE
                SELF:_mustCheckEof := TRUE
            CASE DbOrder_Info.DBOI_SCOPEBOTTOMCLEAR
                SELF:_Scopes[BOTTOMSCOPE]:Clear()
                SELF:_scopeEmpty := FALSE
                SELF:_mustCheckEof := TRUE
            CASE DbOrder_Info.DBOI_SCOPETOP
            CASE DbOrder_Info.DBOI_SCOPEBOTTOM
                VAR nItem := IIF( uiScope == DbOrder_Info.DBOI_SCOPETOP, SELF:TopScopeNo, SELF:BottomScopeNo)
                IF (itmScope != SELF:_Scopes[nItem]:Value)
                    SELF:_scopeEmpty := FALSE
                    SELF:_Scopes[nItem]:Value := itmScope
                    IF itmScope != NULL
                        SELF:_ToString(itmScope, SELF:_keySize,  SELF:_Scopes[nItem]:Buffer, REF uiRealLen)
                        SELF:_Scopes[nItem]:Size := uiRealLen
                    ENDIF
                    SELF:_mustCheckEof := TRUE
                ENDIF
            OTHERWISE
                result := FALSE
            END SWITCH
            RETURN result

        INTERNAL METHOD _CountRecords(records REF LONG ) AS LOGIC
            LOCAL isOk AS LOGIC
            LOCAL oldRec AS LONG
            LOCAL recno AS LONG
            LOCAL last AS LONG
            LOCAL count AS LONG
            LOCAL isLocked := FALSE AS LOGIC
            isOk := TRUE
            SELF:_bag:Flush()
            SELF:_oRdd:GoCold()
            oldRec := SELF:_RecNo
            TRY
                IF SELF:Shared
                    isLocked := SELF:Slock()
                    IF !isLocked
                        RETURN FALSE
                    ENDIF
                ENDIF
                IF SELF:HasScope .AND. ! XSharp.RuntimeState.Deleted .AND. ! SELF:_oRdd:FilterInfo:Active
                    SELF:_ScopeSeek(DbOrder_Info.DBOI_SCOPEBOTTOM)
                    records := SELF:_getScopePos()
                ELSE
                    IF  XSharp.RuntimeState.Deleted  .OR. SELF:_oRdd:FilterInfo:Active
                        SELF:_oRdd:SkipFilter(1)
                        oldRec := SELF:_RecNo
                        records := 0
                        LOCAL lWasDescending AS LOGIC
                        lWasDescending := SELF:Descending
                        IF lWasDescending
                            SELF:Descending := FALSE
                        ENDIF
                        SELF:_oRdd:_SetEOF(FALSE)
                        SELF:_oRdd:_SetBOF(FALSE)
                        isOk := SELF:GoTop()
                        recno := SELF:_RecNo
                        last := SELF:_oRdd:RecCount + 1
                        count := 0
                        LOCAL previous AS LONG
                        previous := recno
                        DO WHILE recno != 0 .AND. recno < last
                            count++
                            recno := SELF:_ScopeSkip(1)
                            IF recno == previous
                                EXIT
                            ENDIF
                            previous := recno
                        ENDDO
                        records := count
                        SELF:Descending := lWasDescending
                    ELSE
                         records := 0
                        IF SELF:GoTop() .AND. ! SELF:Stack:Empty
                            VAR topStack := SELF:CurrentStack
                            VAR page     := topStack:Page
                            DO WHILE TRUE
                                IF page == NULL
                                    EXIT
                                ENDIF
                                records += page:NumKeys
                                IF SELF:_Descending
                                    IF page:HasLeft
                                        VAR pageNo := page:LeftPtr
                                        page := SELF:GetPage(pageNo)
                                    ELSE
                                        EXIT
                                    ENDIF
                                ELSE
                                    IF page:HasRight
                                        VAR pageNo := page:RightPtr
                                        page := SELF:GetPage(pageNo)
                                    ELSE
                                        EXIT
                                    ENDIF
                                ENDIF
                            ENDDO
                        ENDIF
                    ENDIF
                ENDIF
                SELF:_GoToRecno(oldRec)
            CATCH
                isOk := FALSE
            FINALLY
                IF SELF:Shared .AND. isLocked
                    isOk := SELF:UnLock()
                ENDIF
            END TRY
            RETURN isOk

        INTERNAL METHOD _getRecPos(record REF LONG ) AS LOGIC
            LOCAL oldRec AS LONG
            LOCAL recno AS LONG
            LOCAL count AS LONG
            LOCAL isLocked := FALSE AS LOGIC
            IF SELF:RDD:EoF
               record := 0
               RETURN TRUE
            ENDIF
            SELF:_oRdd:GoCold()
            oldRec := SELF:_RecNo
            TRY
                IF SELF:Shared
                    isLocked := SELF:Slock()
                    IF !isLocked
                        RETURN FALSE
                    ENDIF
                ENDIF
                recno := record
                IF recno == 0
                    recno := SELF:_RecNo
                ENDIF
                IF SELF:Stack:Empty
                    SELF:_GoToRecno(recno)
                ENDIF
                IF SELF:HasScope
                    record := SELF:_getScopePos()
                ELSE
                    IF XSharp.RuntimeState.Deleted .OR. SELF:_oRdd:FilterInfo:Active
                        SELF:_oRdd:SkipFilter(1)
                        oldRec := SELF:_RecNo
                        record := 0
                        IF !SELF:_oRdd:EoF
                            SELF:GoTop()
                            recno := SELF:_RecNo
                            count := 1
                            VAR dir := IIF(SELF:Descending, -1, 1)
                            DO WHILE recno != 0 .AND. recno != oldRec
                                count++
                                recno := SELF:_ScopeSkip(dir)
                            ENDDO
                            record := count
                        ENDIF
                    ELSE
                        record := SELF:_findItemPos()
                    ENDIF
                ENDIF
                SELF:_oRdd:__Goto(oldRec)
            FINALLY
                IF SELF:Shared .AND. isLocked
                    SELF:UnLock()
                ENDIF
            END TRY
            RETURN TRUE

        PRIVATE METHOD _nextKey( keyMove AS LONG ) AS LONG
            LOCAL recno			AS LONG
            LOCAL moveDirection	AS SkipDirection
            IF keyMove == 1
                recno := SELF:_getNextKey(SkipDirection.Forward)
            ELSE
                IF keyMove < 0
                    keyMove := -keyMove
                    moveDirection := SkipDirection.Backward
                ELSE
                    moveDirection := SkipDirection.Forward
                ENDIF
                IF keyMove != 0
                    REPEAT
                        recno := SELF:_getNextKey( moveDirection)
                        keyMove--
                    UNTIL !(recno != 0 .AND. keyMove != 0)
                ELSE
                    recno := 0
                ENDIF
                if (recno == 0 )
                    if moveDirection == SkipDirection.Backward
                        IF !SELF:Descending
                            recno := -1
                        ENDIF
                    else
                        // Goto 0 moves the record pointer to EOF which is correct
                    endif
                endif
            ENDIF
            RETURN recno

        INTERNAL METHOD PopPage() AS CdxStackEntry
            SELF:_stack:Pop()
            RETURN SELF:_stack:Top

        INTERNAL METHOD PushPage(oPage AS CdxTreePage) AS VOID
            SELF:_stack:Push(oPage, 0)
            RETURN

        INTERNAL PROPERTY CurrentLeaf AS CdxLeafPage GET SELF:_stack:Top:Page ASTYPE  CdxLeafPage

        INTERNAL PROPERTY CurrentTop  AS CdxTreePage GET SELF:_stack:Top:Page

        INTERNAL METHOD InsertOnTop(oPage AS CdxTreePage) AS LOGIC
            SELF:_stack:InsertOnTop(oPage)
            RETURN TRUE

        INTERNAL METHOD AdjustStack(originalPage AS CdxTreePage, oPage AS CdxTreePage, nPos AS WORD) AS VOID
            SELF:_stack:Replace(originalPage, oPage, nPos)
            RETURN

        INTERNAL METHOD PushPage(oPage AS CdxTreePage , nPos AS WORD) AS VOID
            SELF:_stack:Push(oPage, nPos)
            RETURN

        INTERNAL METHOD ClearStack() AS VOID
            SELF:_stack:Clear()
            RETURN

        INTERNAL METHOD _dump() AS VOID

            LOCAL hDump     AS IntPtr
            LOCAL cFile     AS STRING
            LOCAL sBlock    AS STRING
            LOCAL sName AS STRING
            sName := SELF:_bag:FullPath+"_"+SELF:OrderName
            VAR sRecords := System.Text.StringBuilder{}
            cFile := sName+".DMP"
            hDump := FCreate(cFile)
            IF hDump != F_ERROR
                SELF:_bag:_PageList:DumpHandle := hDump
                SELF:_bag:_PageList:Flush(FALSE)
                sBlock := SELF:_Header:Dump("Filedump for:"+sName)
                FWrite(hDump, sBlock)
                // Collect all pages
                LOCAL aPages AS List<LONG>
                LOCAL oPage AS CdxTreePage
                LOCAL nLevel AS LONG
                oPage  := SELF:GetPage(SELF:_rootPage)
                aPages := (List<INT>) oPage:GetChildren()
                nLevel := 1
                LOCAL sbLevels AS StringBuilder
                sbLevels := StringBuilder{}
                sbLevels:AppendLine("------------------------------")
                sbLevels:AppendLine("CDX Levels:")
                sbLevels:AppendLine("Level 1 : 1 page")
                DO WHILE aPages:Count > 0
                    LOCAL aChildren AS List<LONG>
                    nLevel += 1
                    sbLevels:AppendLine("Level "+nLevel:ToString()+" : "+aPages:Count:ToString()+" pages")
                    aChildren := List<LONG>{}
                    FOREACH pageNo AS LONG IN aPages
                        oPage := SELF:GetPage(pageNo)
                        aChildren:AddRange(oPage:GetChildren())
                    NEXT
                    aPages := aChildren
                ENDDO
                sbLevels:AppendLine("------------------------------")

                _oRdd:GoTop()
                sRecords:AppendLine("------------------------------")
                sRecords:AppendLine("List of Records in Index order")
                sRecords:AppendLine("------------------------------")
                sRecords:AppendLine("Recno      KeyValue")
                sRecords:AppendLine("------------------------------")
                DO WHILE ! _oRdd:EoF
                    VAR key := _oRdd:EvalBlock(SELF:_KeyCodeBlock)
                    IF key IS IDate VAR d
                        key := DateTime{d:Year, d:Month, d:Day}:ToString("yyyyMMdd")
                    ELSEIF key IS IFloat VAR f
                        key   :=  f:Value:ToString("F"+f:Decimals:ToString())
                    ENDIF
                    sRecords:AppendLine(String.Format("{0,10} {1}", _oRdd:RecNo, key))
                    _oRdd:Skip(1)
                ENDDO
                // Collect Recycled pages
                LOCAL sbFree AS StringBuilder
                sbFree := StringBuilder{}
                LOCAL nPage := SELF:OrderBag:Root:FreeList AS LONG
                sbFree:AppendLine("-------------------------------")
                sbFree:AppendLine("List of Free pages (Bag Level) ")
                sbFree:AppendLine("-------------------------------")
                sbFree:AppendLine("First free: "+ nPage:ToString("X8"))
                // stop dumping pages because from now on we only dump the page numbers
                SELF:_bag:_PageList:DumpHandle := IntPtr.Zero
                DO WHILE nPage != 0 .AND. nPage != -1
                    VAR oFree := SELF:GetPage(nPage) ASTYPE CdxTreePage
                    IF oFree != NULL
                        nPage := oFree:NextFree
                        sbFree:AppendLine("Next free : "+ nPage:ToString("X8"))
                    ELSE
                        EXIT
                    ENDIF
                ENDDO
                FWrite(hDump, sbFree:ToString())
                FWrite(hDump, sRecords:ToString())
                FWrite(hDump, sbLevels:ToString())
                FClose(hDump)

            ENDIF
            RETURN

        // Methods to calculate keys. We have split these to optimize index creating
        PRIVATE METHOD _getNumFieldValue(sourceIndex AS LONG, byteArray AS BYTE[]) AS LOGIC
            LOCAL oValue := SELF:_oRdd:GetValue(_SingleField+1) AS OBJECT
            DoubleToFoxOrder(Convert.ToDouble(oValue), byteArray)
            RETURN TRUE

       PRIVATE METHOD _getIntFieldValue(sourceIndex AS LONG, byteArray AS BYTE[]) AS LOGIC
            LOCAL oValue := SELF:_oRdd:GetValue(_SingleField+1) AS OBJECT
            LongToFoxOrder(Convert.ToInt32(oValue),byteArray)
            RETURN TRUE

        PRIVATE METHOD _getDateFieldValue(sourceIndex AS LONG, byteArray AS BYTE[]) AS LOGIC
            LOCAL oValue := SELF:_oRdd:GetValue(_SingleField+1) AS OBJECT
            IF oValue IS IDate VAR valueDate
                IF !valueDate:IsEmpty
                    oValue := DateTime{valueDate:Year, valueDate:Month, valueDate:Day}
                ELSE
                    oValue := DateTime.MinValue
                ENDIF
            ENDIF
            IF oValue IS DateTime VAR dt
                VAR longValue  := _toJulian(dt)
                DoubleToFoxOrder(Convert.ToDouble(longValue), byteArray)
                RETURN TRUE
            ENDIF
            RETURN FALSE

        PRIVATE METHOD _getFieldValue(sourceIndex AS LONG, byteArray AS BYTE[]) AS LOGIC
            SELF:_oRdd:Validate()
            Array.Copy(SELF:_oRdd:RecordBuffer, sourceIndex, byteArray, 0, SELF:_keySize)
            RETURN TRUE

        PRIVATE METHOD _getFieldValueWithCollation(sourceIndex AS LONG, byteArray AS BYTE[]) AS LOGIC
            SELF:_getFieldValue(sourceIndex, byteArray)
            SELF:_Collation:Translate(byteArray)
            RETURN TRUE

        PRIVATE METHOD _getExpressionValue(sourceIndex AS LONG, byteArray AS BYTE[]) AS LOGIC
            LOCAL result := TRUE AS LOGIC
            TRY
                SELF:_oRdd:Validate()
                VAR oKeyValue := SELF:_oRdd:EvalBlock(SELF:_KeyCodeBlock)
                LOCAL uiRealLen := 0 AS LONG
                result := SELF:_ToString(oKeyValue, SELF:_keySize,  byteArray,  REF uiRealLen)
            CATCH ex AS Exception
                SELF:_oRdd:_dbfError(ex, Subcodes.EDB_EXPRESSION,Gencode.EG_SYNTAX,  "DBFCDX._GetExpressionValue", FALSE)
                result := FALSE
            END TRY
            RETURN result

    END CLASS
END NAMESPACE

//
// Copyright (c) XSharp B.V.  All Rights Reserved.
// Licensed under the Apache License, Version 2.0.
// See License.txt in the project root for license information.
//
USING System
USING System.Collections
USING System.Collections.Generic
USING System.Diagnostics
USING System.Globalization
USING System.IO
USING System.Reflection
USING System.Text
USING System.Threading
USING XSharp.RDD.Enums
USING XSharp.RDD.Support
USING STATIC XSharp.Conversions
BEGIN NAMESPACE XSharp.RDD.CDX

    INTERNAL DELEGATE CompareFunc(aLHS AS BYTE[], aRHS AS BYTE[], nLength AS LONG, recnoLHS AS LONG, recnoRHS AS LONG) AS LONG

    [DebuggerDisplay("Tag: {OrderName}, Key: {Expression}, For: {Condition}")];
    INTERNAL SEALED PARTIAL CLASS CdxTag
        #region constants
        PRIVATE CONST MAX_KEY_LEN       := 240  AS WORD
        #endregion
        #region fields
        PRIVATE _Encoding AS Encoding
        PRIVATE _Hot AS LOGIC
        PRIVATE _Conditional AS LOGIC
        PRIVATE _Descending AS LOGIC
        PRIVATE _Unique AS LOGIC
        PRIVATE _Custom AS LOGIC
        PRIVATE _SingleField AS LONG            // 0 based FieldIndex
        PRIVATE _SourceIndex AS LONG
        PRIVATE _KeyCodeBlock AS ICodeblock
        PRIVATE _ForCodeBlock AS ICodeblock
        PRIVATE _KeyExpr AS STRING
        PRIVATE _ForExpr AS STRING
        PRIVATE _currentvalue AS RddKeyData
        INTERNAL _newvalue     AS RddKeyData
        PRIVATE _newKeyLen AS LONG
        PRIVATE _KeyExprType AS LONG
        PRIVATE _keySize AS WORD
        PRIVATE _sourcekeySize AS WORD
        PRIVATE _rootPage AS LONG
        PRIVATE _ordCondInfo AS DbOrderCondInfo
        INTERNAL _orderName AS STRING
        INTERNAL _Ansi AS LOGIC
        // Scopes
        PRIVATE DIM _Scopes[2] AS ScopeInfo
        PRIVATE _scopeEmpty    AS LOGIC
        // siblings
        PRIVATE _oRdd   AS DBFCDX
        PRIVATE _Header AS CdxTagHeader

        PRIVATE _stack          AS CdxPageStack
        INTERNAL __Compare       AS CompareFunc

        PRIVATE _bag            AS CdxOrderBag
        PRIVATE getKeyValue     AS ValueBlock       // Delegate to calculate the key
        PRIVATE _Collation      AS VfpCollation
        PRIVATE _Valid          AS LOGIC

#endregion

#region Properties
        INTERNAL PROPERTY Binary            AS LOGIC AUTO
        INTERNAL PROPERTY IsOpen            AS LOGIC AUTO
        INTERNAL PROPERTY Collation         AS VfpCollation GET _Collation
        INTERNAL PROPERTY Expression        AS STRING GET _KeyExpr
        INTERNAL PROPERTY Encoding          AS Encoding GET _Encoding
        INTERNAL PROPERTY RDD               AS DBFCDX GET _oRdd
        INTERNAL PROPERTY Condition         AS STRING GET _ForExpr
        INTERNAL PROPERTY OrderName         AS STRING GET _orderName
	    INTERNAL PROPERTY Shared 	        AS LOGIC GET _bag:Shared
        INTERNAL PROPERTY _RecNo 	        AS LONG GET _oRdd:RecNo
        INTERNAL PROPERTY FileName 	        AS STRING GET _bag:FullPath
        INTERNAL PROPERTY OrderBag          AS CdxOrderBag GET SELF:_bag
        INTERNAL PROPERTY Page              AS Int32 AUTO
        INTERNAL PROPERTY Descending        AS LOGIC GET _Descending SET _Descending := value
        INTERNAL PROPERTY IsConditional     AS LOGIC GET Options:HasFlag(CdxOptions.HasFor)
        INTERNAL PROPERTY IsHot             AS LOGIC GET _Hot
        INTERNAL PROPERTY Header            AS CdxTagHeader GET _Header
        INTERNAL PROPERTY KeyType        	AS INT GET SELF:_KeyExprType
        INTERNAL PROPERTY KeyCodeBlock      AS ICodeblock GET _KeyCodeBlock
        INTERNAL PROPERTY KeyLength         AS WORD GET SELF:_keySize
        INTERNAL PROPERTY SourceKeyLength   AS WORD GET SELF:_sourcekeySize
        INTERNAL PROPERTY Partial        	AS LOGIC GET SELF:Custom
        INTERNAL PROPERTY Conditional       AS LOGIC GET SELF:_Conditional
        INTERNAL PROPERTY Custom         	AS LOGIC GET SELF:_Custom
        INTERNAL PROPERTY Unique         	AS LOGIC GET SELF:_Unique
        INTERNAL PROPERTY Signature      	AS BYTE AUTO
        INTERNAL PROPERTY Options        	AS CdxOptions AUTO
        INTERNAL PROPERTY LockOffSet     	AS LONG AUTO
        INTERNAL PROPERTY CurrentStack      AS CdxStackEntry GET  SELF:_stack:Top
        INTERNAL PROPERTY RootPage          AS LONG AUTO
        INTERNAL PROPERTY Stack             AS CdxPageStack GET _stack

        // Scopes
        INTERNAL PROPERTY HasTopScope       AS LOGIC    GET _Scopes[SELF:TopScopeNo]:IsSet
        INTERNAL PROPERTY TopScope          AS OBJECT   GET _Scopes[SELF:TopScopeNo]:Value
        INTERNAL PROPERTY HasBottomScope    AS LOGIC    GET _Scopes[SELF:BottomScopeNo]:IsSet
        INTERNAL PROPERTY BottomScope       AS OBJECT   GET _Scopes[SELF:BottomScopeNo]:Value
        INTERNAL PROPERTY HasScope          AS LOGIC    GET _Scopes[0]:IsSet .OR. _Scopes[1]:IsSet
        PRIVATE  PROPERTY TopScopeNo        AS LONG     GET IIF(SELF:Descending, BOTTOMSCOPE, TOPSCOPE)
        PRIVATE  PROPERTY BottomScopeNo     AS LONG     GET IIF(SELF:Descending, TOPSCOPE, BOTTOMSCOPE)

#endregion

        PRIVATE METHOD _InitFields(oBag AS CdxOrderBag) AS VOID
            SELF:_bag           := oBag
            SELF:_oRdd          := oBag:_oRdd
            SELF:_stack         := CdxPageStack{SELF}
            SELF:_Encoding      := _oRdd:_Encoding

            SELF:_SingleField   := -1
            SELF:_currentvalue := RddKeyData{MAX_KEY_LEN}
            SELF:_newvalue     := RddKeyData{MAX_KEY_LEN}

        // Constructor for Creation of tags
        INTERNAL CONSTRUCTOR (oBag AS CdxOrderBag)
	        SUPER()
            SELF:_InitFields(oBag)

        // Constructor for Opening of tags
        INTERNAL CONSTRUCTOR (oBag AS CdxOrderBag, nPage AS Int32, cName AS STRING)
	        SUPER()
            SELF:_InitFields(oBag)
            SELF:_orderName := cName
            SELF:Page := nPage
            SELF:_Header := CdxTagHeader{oBag, nPage,SELF:OrderName, SELF}
            SELF:_bag:SetPage(SELF:_Header)
            SELF:IsOpen := SELF:Open()

         INTERNAL METHOD ReadHeader AS VOID
            SELF:_Header:Read()
            SELF:_keySize       := SELF:_Header:KeySize
            SELF:Options        := SELF:_Header:Options
            SELF:Signature      := SELF:_Header:Signature
            SELF:_Descending    := SELF:_Header:Descending
            SELF:_rootPage      := SELF:_Header:RootPage
            SELF:_KeyExpr       := SELF:_Header:KeyExpression
            SELF:_ForExpr       := SELF:_Header:ForExpression
            SELF:_Unique        := SELF:Options:HasFlag(CdxOptions.Unique)
            SELF:_Custom        := SELF:Options:HasFlag(CdxOptions.Custom)
            SELF:_Conditional   := SELF:Options:HasFlag(CdxOptions.HasFor)
            RETURN

	    INTERNAL METHOD Open() AS LOGIC
            SELF:_oRdd:GoTo(1)
            SELF:_Hot := FALSE
            SELF:ClearStack()
            SELF:ReadHeader()
            IF !String.IsNullOrEmpty(SELF:_Header:VFPCollation)
                IF SELF:_oRdd IS DBFVFP
                    SELF:_Collation := VfpCollation{SELF:_Header:VFPCollation,SELF:_oRdd:Header:CodePage:ToCodePage()}
                ELSE
                    SELF:_oRdd:_dbfError( Subcodes.ERDD_CORRUPT, Gencode.EG_CORRUPTION, "CdxTag.Open","Indexes with collation must be opened with the DBFVFP driver")
                ENDIF
            ENDIF
            SELF:_oRdd:__Goto(0)
            IF ! SELF:EvaluateExpressions()
                RETURN FALSE
            ENDIF
            SELF:AllocateBuffers()
            RETURN TRUE

        INTERNAL METHOD AllocateBuffers() AS VOID
            SELF:_newvalue          := RddKeyData{_keySize}
            SELF:_currentvalue      := RddKeyData{_keySize}
            SELF:_Scopes[0]:SetBuffer(_keySize)
            SELF:_Scopes[1]:SetBuffer(_keySize)
            RETURN

        INTERNAL METHOD SetLeafProperties(page AS CdxLeafPage) AS VOID
            VAR numRecs     := SELF:RDD:RecCount
            page:ClearRecordsAndKeys()
            page:KeyLength      := SELF:KeyLength
            page:SetRecordBits(numRecs)
            RETURN

        INTERNAL METHOD EvaluateExpressions() AS LOGIC
            LOCAL evalOk AS LOGIC
            LOCAL oKey := NULL AS OBJECT
            evalOk := TRUE
            SELF:_Valid := TRUE
            TRY
                SELF:_KeyCodeBlock := SELF:_oRdd:Compile(SELF:_KeyExpr)
            CATCH AS Exception
                SELF:_Valid := FALSE
                evalOk := FALSE
            END TRY
            IF ! evalOk
                RETURN FALSE
            ENDIF
            TRY
                oKey := SELF:_oRdd:EvalBlock(SELF:_KeyCodeBlock)
            CATCH AS Exception
                evalOk := FALSE
            END TRY
            IF !evalOk
                SELF:_Valid := FALSE
                RETURN FALSE
            ENDIF
            SELF:_KeyExprType := SELF:_oRdd:_getUsualType(oKey)
            IF SELF:_KeyExprType == __UsualType.String
                IF SELF:_Collation != NULL
                    SELF:__Compare := _compareCollation
                ELSE
                    SELF:__Compare := _compareText
                ENDIF
            ELSE
                SELF:__Compare := _compareBin
            ENDIF

            // If the Key Expression contains only a Field Name

            IF SELF:_oRdd IS DBFVFPSQL
                SELF:_SingleField := -1
            ELSE
                SELF:_SingleField := SELF:_oRdd:FieldIndex(SELF:_KeyExpr) -1
            ENDIF
            SELF:_SourceIndex := 0
            LOCAL isOk AS LOGIC
            IF SELF:_SingleField >= 0
                SELF:_SourceIndex   := SELF:_oRdd:_Fields[_SingleField]:Offset
                VAR fType           := SELF:_oRdd:_Fields[_SingleField]:FieldType
                SWITCH fType
                CASE DbFieldType.Number
                    SELF:_sourcekeySize := SELF:_keySize   := 8
                    SELF:getKeyValue := _getNumFieldValue
                    SELF:Binary      := TRUE
                CASE DbFieldType.Date
                    // CDX converts the Date to a Julian Number and stores that as a Real8 in the index
                    SELF:_sourcekeySize := SELF:_keySize   := 8
                    SELF:getKeyValue := _getDateFieldValue
                    SELF:Binary      := TRUE
                CASE DbFieldType.Integer
                    SELF:_sourcekeySize := SELF:_keySize   := 4
                    SELF:getKeyValue := _getIntFieldValue
                    SELF:Binary      := TRUE
                OTHERWISE
                    SELF:_sourcekeySize := SELF:_keySize   := (WORD) SELF:_oRdd:_Fields[_SingleField]:Length
                    SELF:getKeyValue := _getFieldValue
                    SELF:Binary      := FALSE
                    IF SELF:_Collation != NULL
                        SELF:_keySize := SELF:Header:KeySize
                        SELF:getKeyValue := _getFieldValueWithCollation
                    ENDIF

                END SWITCH
                isOk := TRUE
            ELSE
                SELF:_sourcekeySize := SELF:_keySize    := 0
                SELF:getKeyValue := _getExpressionValue
                isOk             := SELF:_determineSize(oKey)
                IF SELF:Header != NULL .AND. SELF:Header:KeySize != SELF:_keySize .AND. SELF:Header:KeySize != SELF:_keySize+1
                    IF SELF:_Collation != NULL
                        SELF:_keySize := SELF:Header:KeySize
                    ELSE
                        var ex := Exception{"Expression length in index does not match calculated expression length"}
                        SELF:_oRdd:_dbfError(ex, Subcodes.EDB_EXPRESSION,Gencode.EG_SYNTAX,  "DBFCDX.EvaluateExpressions", FALSE)
                    ENDIF
                ENDIF
            ENDIF
            IF ! isOk
                RETURN FALSE
            ENDIF
            SELF:AllocateBuffers()
            SELF:_Conditional       := FALSE
            IF SELF:_ForExpr:Length > 0
                TRY
                    SELF:_ForCodeBlock := SELF:_oRdd:Compile(SELF:_ForExpr)
                CATCH ex AS Exception
                    IF RuntimeState.LastRddError == NULL
                        SELF:_oRdd:_dbfError( ex, Subcodes.EDB_EXPRESSION, Gencode.EG_SYNTAX,"DBFCDX.EvaluateExpressions", FALSE)
                    ENDIF
                    SELF:_Valid := FALSE
                    RETURN FALSE
                END TRY
                SELF:_oRdd:GoTo(1)
                evalOk := TRUE
                TRY
                    VAR oValue := SELF:_oRdd:EvalBlock(SELF:_ForCodeBlock)
                    evalOk     := SELF:_oRdd:_getUsualType(oValue) == __UsualType.Logic
                CATCH ex AS Exception
                    IF RuntimeState.LastRddError == NULL
                        SELF:_oRdd:_dbfError( ex, Subcodes.EDB_EXPRESSION, Gencode.EG_SYNTAX,"DBFCDX.EvaluateExpressions", FALSE)
                    ENDIF
                    SELF:_Valid := FALSE
                    evalOk := FALSE
                END TRY
                IF !evalOk
                    RETURN FALSE
                ENDIF
                SELF:_Conditional := TRUE
            ENDIF

            RETURN isOk

        INTERNAL METHOD Flush() AS LOGIC
            // do NOT Call bag:flush to avoid recursion
            //SELF:_bag:Flush()
            RETURN TRUE

        INTERNAL METHOD Commit() AS LOGIC
            SELF:GoCold()
            SELF:_bag:Flush()
            RETURN TRUE

        INTERNAL METHOD Close() AS LOGIC
            RETURN SELF:Commit()

        INTERNAL METHOD GoCold() AS LOGIC
            IF SELF:_oRdd:IsHot
                RETURN SELF:_keyUpdate( SELF:_RecNo, SELF:_oRdd:IsNewRecord )
            ENDIF
            RETURN TRUE

        INTERNAL METHOD GoHot() AS LOGIC
            // Is called to save the current for value and key value
            RETURN SELF:_saveCurrentKey(SELF:_oRdd:RecNo, SELF:_currentvalue)

        INTERNAL METHOD GetPage(nPage AS LONG) AS CdxTreePage
            IF nPage == -1
                RETURN NULL
            ENDIF
            VAR page := SELF:_bag:GetPage(nPage, SELF:_keySize, SELF)
            RETURN page ASTYPE CdxTreePage

        INTERNAL STATIC METHOD _compareText(aLHS AS BYTE[], aRHS AS BYTE[], nLength AS LONG, recnoLHS AS LONG, recnoRHS AS LONG) AS LONG
            IF aRHS == NULL
                RETURN 0
            ENDIF
            VAR result := RuntimeState.StringCompare(aLHS, aRHS, nLength)
            IF result == 0
                result := _compareRecno(recnoLHS, recnoRHS)
            ENDIF
            RETURN result

        INTERNAL STATIC METHOD _compareCollation(aLHS AS BYTE[], aRHS AS BYTE[], nLength AS LONG, recnoLHS AS LONG, recnoRHS AS LONG) AS LONG
            RETURN _compareBin(aLHS, aRHS, nLength, recnoLHS, recnoRHS)

        INTERNAL STATIC METHOD _compareRecno(recnoLHS AS LONG, recnoRHS AS LONG) AS LONG
            IF recnoLHS == 0 .or. recnoRHS == 0
                // no record to compare against
                RETURN 0
            ENDIF
            IF recnoLHS < recnoRHS
                RETURN -1
            ELSEIF recnoLHS > recnoRHS
                RETURN 1
            ENDIF
            RETURN 0

        INTERNAL STATIC METHOD _compareBin(aLHS AS BYTE[], aRHS AS BYTE[], nLength AS LONG, recnoLHS AS LONG, recnoRHS AS LONG) AS LONG
            IF aRHS == NULL
                RETURN 0
            ENDIF
            FOR VAR nI := 0 TO nLength -1
                VAR bL := aLHS[nI]
                VAR bR := aRHS[nI]
                IF bL < bR
                    RETURN -1
                ELSEIF bL > bR
                    RETURN 1
                ENDIF
            NEXT
            RETURN _compareRecno(recnoLHS, recnoRHS)

        INTERNAL METHOD SetOffLine() AS VOID
            SELF:ClearStack()

        PRIVATE METHOD _saveCurrentRecord( node AS CdxNode) AS VOID
            IF SELF:_currentvalue:Recno != node:Recno .AND. ! SELF:RDD:EoF
                SELF:_currentvalue:Recno := node:Recno
                Array.Copy(node:KeyBytes, SELF:_currentvalue:Key, _keySize)
            ENDIF
        STATIC PRIVATE Jan_1_1901 := DateTime{1901, 1, 1 } AS DateTime

        PRIVATE STATIC METHOD _toJulian(dt AS DateTime) AS LONG
            VAR days      := dt:Subtract( Jan_1_1901 )
            RETURN Convert.ToInt32( days:TotalDays ) + 2415386   // Julian date number of 1901-01-01

        PRIVATE METHOD _ToString( toConvert AS OBJECT , sLen AS LONG ,  buffer AS BYTE[] ) AS LOGIC
            LOCAL resultLength AS LONG
            resultLength := 0
            RETURN SELF:_ToString( toConvert, sLen, buffer,  REF resultLength)

        PRIVATE METHOD _ToString( toConvert AS OBJECT , sLen AS LONG ,  buffer AS BYTE[] ,  resultLength REF LONG ) AS LOGIC
            LOCAL text AS STRING
            LOCAL typeCde AS TypeCode
            text := NULL
            IF toConvert IS IFloat // Float Value ?
                typeCde   := TypeCode.Double
            ELSEIF toConvert IS IDate VAR valueDate // Date Value
                toConvert := DateTime{valueDate:Year, valueDate:Month, valueDate:Day}
                typeCde   := TypeCode.DateTime
            ELSE
                typeCde := Type.GetTypeCode(toConvert:GetType())
            ENDIF
            IF typeCde == TypeCode.DateTime
                // Convert to Julian number and then process as normal numeric value
                toConvert     := _toJulian((DateTime) toConvert)
                typeCde       := TypeCode.Int32
            ENDIF
            SWITCH typeCde
            CASE TypeCode.Int16
            CASE TypeCode.Int32
            CASE TypeCode.Int64
            CASE TypeCode.Single
            CASE TypeCode.Double
            CASE TypeCode.Decimal
                IF sLen == 4
                    LongToFoxOrder(Convert.ToInt32(toConvert), buffer)
                    resultLength := 4
                ELSE
                    DoubleToFoxOrder(Convert.ToDouble(toConvert), buffer)
                    resultLength := 8
                ENDIF
                RETURN TRUE
            CASE TypeCode.String
                text := (STRING)toConvert
            CASE TypeCode.Boolean
                text := IIF ((LOGIC) toConvert, "T", "F")
            END SWITCH
            IF sLen > text:Length
                sLen := text:Length
            ENDIF
            resultLength := sLen
            SELF:_Encoding:GetBytes( text, 0, sLen, buffer, 0)
            IF SELF:_Collation != NULL
                SELF:_Collation:Translate(buffer)
            ENDIF
            RETURN TRUE

        INTERNAL METHOD SetOrderScope(itmScope AS OBJECT , uiScope AS DbOrder_Info ) AS LOGIC
            LOCAL uiRealLen AS LONG
            LOCAL result AS LOGIC
            uiRealLen := 0
            result := TRUE
            SWITCH uiScope
            CASE DbOrder_Info.DBOI_SCOPETOPCLEAR
                SELF:_Scopes[TOPSCOPE]:Clear()
                SELF:_scopeEmpty := FALSE
                SELF:_mustCheckEof := TRUE
            CASE DbOrder_Info.DBOI_SCOPEBOTTOMCLEAR
                SELF:_Scopes[BOTTOMSCOPE]:Clear()
                SELF:_scopeEmpty := FALSE
                SELF:_mustCheckEof := TRUE
            CASE DbOrder_Info.DBOI_SCOPETOP
            CASE DbOrder_Info.DBOI_SCOPEBOTTOM
                VAR nItem := IIF( uiScope == DbOrder_Info.DBOI_SCOPETOP, SELF:TopScopeNo, SELF:BottomScopeNo)
                IF (itmScope != SELF:_Scopes[nItem]:Value)
                    SELF:_scopeEmpty := FALSE
                    SELF:_Scopes[nItem]:Value := itmScope
                    IF itmScope != NULL
                        SELF:_ToString(itmScope, SELF:_keySize,  SELF:_Scopes[nItem]:Buffer, REF uiRealLen)
                        SELF:_Scopes[nItem]:Size := uiRealLen
                    ENDIF
                    SELF:_mustCheckEof := TRUE
                ENDIF
            OTHERWISE
                result := FALSE
            END SWITCH
            RETURN result

        INTERNAL METHOD _CountRecords(records REF LONG ) AS LOGIC
            LOCAL isOk AS LOGIC
            LOCAL oldRec AS LONG
            LOCAL recno AS LONG
            LOCAL last AS LONG
            LOCAL count AS LONG
            LOCAL isLocked := FALSE AS LOGIC
            isOk := TRUE
            SELF:_bag:Flush()
            SELF:_oRdd:GoCold()
            oldRec := SELF:_RecNo
            TRY
                IF SELF:Shared
                    isLocked := SELF:Slock()
                    IF !isLocked
                        RETURN FALSE
                    ENDIF
                ENDIF
                IF SELF:HasScope .AND. ! XSharp.RuntimeState.Deleted .AND. ! SELF:_oRdd:FilterInfo:Active
                    SELF:_ScopeSeek(DbOrder_Info.DBOI_SCOPEBOTTOM)
                    records := SELF:_getScopePos()
                ELSE
                    IF  XSharp.RuntimeState.Deleted  .OR. SELF:_oRdd:FilterInfo:Active
                        SELF:_oRdd:SkipFilter(1)
                        oldRec := SELF:_RecNo
                        records := 0
                        LOCAL lWasDescending AS LOGIC
                        lWasDescending := SELF:Descending
                        IF lWasDescending
                            SELF:Descending := FALSE
                        ENDIF
                        SELF:_oRdd:_SetEOF(FALSE)
                        SELF:_oRdd:_SetBOF(FALSE)
                        isOk := SELF:GoTop()
                        recno := SELF:_RecNo
                        last := SELF:_oRdd:RecCount + 1
                        count := 0
                        LOCAL previous AS LONG
                        previous := recno
                        DO WHILE recno != 0 .AND. recno < last
                            count++
                            recno := SELF:_ScopeSkip(1)
                            IF recno == previous
                                EXIT
                            ENDIF
                            previous := recno
                        ENDDO
                        records := count
                        SELF:Descending := lWasDescending
                    ELSE
                         records := 0
                        IF SELF:GoTop() .AND. ! SELF:Stack:Empty
                            VAR topStack := SELF:CurrentStack
                            VAR page     := topStack:Page
                            DO WHILE TRUE
                                IF page == NULL
                                    EXIT
                                ENDIF
                                records += page:NumKeys
                                IF SELF:_Descending
                                    IF page:HasLeft
                                        VAR pageNo := page:LeftPtr
                                        page := SELF:GetPage(pageNo)
                                    ELSE
                                        EXIT
                                    ENDIF
                                ELSE
                                    IF page:HasRight
                                        VAR pageNo := page:RightPtr
                                        page := SELF:GetPage(pageNo)
                                    ELSE
                                        EXIT
                                    ENDIF
                                ENDIF
                            ENDDO
                        ENDIF
                    ENDIF
                ENDIF
                SELF:_GoToRecno(oldRec)
            CATCH
                isOk := FALSE
            FINALLY
                IF SELF:Shared .AND. isLocked
                    isOk := SELF:UnLock()
                ENDIF
            END TRY
            RETURN isOk

        INTERNAL METHOD _getRecPos(record REF LONG ) AS LOGIC
            LOCAL oldRec AS LONG
            LOCAL recno AS LONG
            LOCAL count AS LONG
            LOCAL isLocked := FALSE AS LOGIC

            SELF:_oRdd:GoCold()
            oldRec := SELF:_RecNo
            TRY
                IF SELF:Shared
                    isLocked := SELF:Slock()
                    IF !isLocked
                        RETURN FALSE
                    ENDIF
                ENDIF
                recno := record
                IF recno == 0
                    recno := SELF:_RecNo
                ENDIF
                IF SELF:Stack:Empty
                    SELF:_GoToRecno(recno)
                ENDIF
                IF SELF:HasScope
                    record := SELF:_getScopePos()
                ELSE
                    IF XSharp.RuntimeState.Deleted .OR. SELF:_oRdd:FilterInfo:Active
                        SELF:_oRdd:SkipFilter(1)
                        oldRec := SELF:_RecNo
                        record := 0
                        IF !SELF:_oRdd:EoF
                            SELF:GoTop()
                            recno := SELF:_RecNo
                            count := 1
                            VAR dir := IIF(SELF:Descending, -1, 1)
                            DO WHILE recno != 0 .AND. recno != oldRec
                                count++
                                recno := SELF:_ScopeSkip(dir)
                            ENDDO
                            record := count
                        ENDIF
                    ELSE
                        record := SELF:_findItemPos()
                    ENDIF
                ENDIF
                SELF:_oRdd:__Goto(oldRec)
            FINALLY
                IF SELF:Shared .AND. isLocked
                    SELF:UnLock()
                ENDIF
            END TRY
            RETURN TRUE

        PRIVATE METHOD _nextKey( keyMove AS LONG ) AS LONG
            LOCAL recno			AS LONG
            LOCAL moveDirection	AS SkipDirection
            IF keyMove == 1
                recno := SELF:_getNextKey(SkipDirection.Forward)
            ELSE
                IF keyMove < 0
                    keyMove := -keyMove
                    moveDirection := SkipDirection.Backward
                ELSE
                    moveDirection := SkipDirection.Forward
                ENDIF
                IF keyMove != 0
                    REPEAT
                        recno := SELF:_getNextKey( moveDirection)
                        keyMove--
                    UNTIL !(recno != 0 .AND. keyMove != 0)
                ELSE
                    recno := 0
                ENDIF
                if (recno == 0 )
                    if moveDirection == SkipDirection.Backward
                        IF !SELF:Descending
                            recno := -1
                        ENDIF
                    else
                        // Goto 0 moves the record pointer to EOF which is correct
                    endif
                endif
            ENDIF
            RETURN recno

        INTERNAL METHOD PopPage() AS CdxStackEntry
            SELF:_stack:Pop()
            RETURN SELF:_stack:Top

        INTERNAL METHOD PushPage(oPage AS CdxTreePage) AS VOID
            SELF:_stack:Push(oPage, 0)
            RETURN

        INTERNAL PROPERTY CurrentLeaf AS CdxLeafPage GET SELF:_stack:Top:Page ASTYPE  CdxLeafPage

        INTERNAL PROPERTY CurrentTop  AS CdxTreePage GET SELF:_stack:Top:Page

        INTERNAL METHOD InsertOnTop(oPage AS CdxTreePage) AS LOGIC
            SELF:_stack:InsertOnTop(oPage)
            RETURN TRUE

        INTERNAL METHOD AdjustStack(originalPage AS CdxTreePage, oPage AS CdxTreePage, nPos AS WORD) AS VOID
            SELF:_stack:Replace(originalPage, oPage, nPos)
            RETURN

        INTERNAL METHOD PushPage(oPage AS CdxTreePage , nPos AS WORD) AS VOID
            SELF:_stack:Push(oPage, nPos)
            RETURN

        INTERNAL METHOD ClearStack() AS VOID
            SELF:_stack:Clear()
            RETURN

        INTERNAL METHOD _dump() AS VOID

            LOCAL hDump     AS IntPtr
            LOCAL cFile     AS STRING
            LOCAL sBlock    AS STRING
            LOCAL sName AS STRING
            sName := SELF:_bag:FullPath+"_"+SELF:OrderName
            VAR sRecords := System.Text.StringBuilder{}
            cFile := sName+".DMP"
            hDump := FCreate(cFile)
            IF hDump != F_ERROR
                SELF:_bag:_PageList:DumpHandle := hDump
                SELF:_bag:_PageList:Flush(FALSE)
                sBlock := SELF:_Header:Dump("Filedump for:"+sName)
                FWrite(hDump, sBlock)
                // Collect all pages
                LOCAL aPages AS List<LONG>
                LOCAL oPage AS CdxTreePage
                LOCAL nLevel AS LONG
                oPage  := SELF:GetPage(SELF:_rootPage)
                aPages := (List<INT>) oPage:GetChildren()
                nLevel := 1
                LOCAL sbLevels AS StringBuilder
                sbLevels := StringBuilder{}
                sbLevels:AppendLine("------------------------------")
                sbLevels:AppendLine("CDX Levels:")
                sbLevels:AppendLine("Level 1 : 1 page")
                DO WHILE aPages:Count > 0
                    LOCAL aChildren AS List<LONG>
                    nLevel += 1
                    sbLevels:AppendLine("Level "+nLevel:ToString()+" : "+aPages:Count:ToString()+" pages")
                    aChildren := List<LONG>{}
                    FOREACH pageNo AS LONG IN aPages
                        oPage := SELF:GetPage(pageNo)
                        aChildren:AddRange(oPage:GetChildren())
                    NEXT
                    aPages := aChildren
                ENDDO
                sbLevels:AppendLine("------------------------------")

                _oRdd:GoTop()
                sRecords:AppendLine("------------------------------")
                sRecords:AppendLine("List of Records in Index order")
                sRecords:AppendLine("------------------------------")
                sRecords:AppendLine("Recno      KeyValue")
                sRecords:AppendLine("------------------------------")
                DO WHILE ! _oRdd:EoF
                    VAR key := _oRdd:EvalBlock(SELF:_KeyCodeBlock)
                    IF key IS IDate VAR d
                        key := DateTime{d:Year, d:Month, d:Day}:ToString("yyyyMMdd")
                    ELSEIF key IS IFloat VAR f
                        key   :=  f:Value:ToString("F"+f:Decimals:ToString())
                    ENDIF
                    sRecords:AppendLine(String.Format("{0,10} {1}", _oRdd:RecNo, key))
                    _oRdd:Skip(1)
                ENDDO
                // Collect Recycled pages
                LOCAL sbFree AS StringBuilder
                sbFree := StringBuilder{}
                LOCAL nPage := SELF:OrderBag:Root:FreeList AS LONG
                sbFree:AppendLine("-------------------------------")
                sbFree:AppendLine("List of Free pages (Bag Level) ")
                sbFree:AppendLine("-------------------------------")
                sbFree:AppendLine("First free: "+ nPage:ToString("X8"))
                // stop dumping pages because from now on we only dump the page numbers
                SELF:_bag:_PageList:DumpHandle := IntPtr.Zero
                DO WHILE nPage != 0 .AND. nPage != -1
                    VAR oFree := SELF:GetPage(nPage) ASTYPE CdxTreePage
                    IF oFree != NULL
                        nPage := oFree:NextFree
                        sbFree:AppendLine("Next free : "+ nPage:ToString("X8"))
                    ELSE
                        EXIT
                    ENDIF
                ENDDO
                FWrite(hDump, sbFree:ToString())
                FWrite(hDump, sRecords:ToString())
                FWrite(hDump, sbLevels:ToString())
                FClose(hDump)

            ENDIF
            RETURN

        // Methods to calculate keys. We have split these to optimize index creating
        PRIVATE METHOD _getNumFieldValue(sourceIndex AS LONG, byteArray AS BYTE[]) AS LOGIC
            LOCAL oValue := SELF:_oRdd:GetValue(_SingleField+1) AS OBJECT
            DoubleToFoxOrder(Convert.ToDouble(oValue), byteArray)
            RETURN TRUE

       PRIVATE METHOD _getIntFieldValue(sourceIndex AS LONG, byteArray AS BYTE[]) AS LOGIC
            LOCAL oValue := SELF:_oRdd:GetValue(_SingleField+1) AS OBJECT
            LongToFoxOrder(Convert.ToInt32(oValue),byteArray)
            RETURN TRUE

        PRIVATE METHOD _getDateFieldValue(sourceIndex AS LONG, byteArray AS BYTE[]) AS LOGIC
            LOCAL oValue := SELF:_oRdd:GetValue(_SingleField+1) AS OBJECT
            IF oValue IS IDate VAR valueDate
                IF !valueDate:IsEmpty
                    oValue := DateTime{valueDate:Year, valueDate:Month, valueDate:Day}
                ELSE
                    oValue := DateTime.MinValue
                ENDIF
            ENDIF
            IF oValue IS DateTime VAR dt
                VAR longValue  := _toJulian(dt)
                DoubleToFoxOrder(Convert.ToDouble(longValue), byteArray)
                RETURN TRUE
            ENDIF
            RETURN FALSE

        PRIVATE METHOD _getFieldValue(sourceIndex AS LONG, byteArray AS BYTE[]) AS LOGIC
            SELF:_oRdd:Validate()
            Array.Copy(SELF:_oRdd:RecordBuffer, sourceIndex, byteArray, 0, SELF:_keySize)
            RETURN TRUE

        PRIVATE METHOD _getFieldValueWithCollation(sourceIndex AS LONG, byteArray AS BYTE[]) AS LOGIC
            SELF:_getFieldValue(sourceIndex, byteArray)
            SELF:_Collation:Translate(byteArray)
            RETURN TRUE

        PRIVATE METHOD _getExpressionValue(sourceIndex AS LONG, byteArray AS BYTE[]) AS LOGIC
            LOCAL result := TRUE AS LOGIC
            TRY
                SELF:_oRdd:Validate()
                VAR oKeyValue := SELF:_oRdd:EvalBlock(SELF:_KeyCodeBlock)
                LOCAL uiRealLen := 0 AS LONG
                result := SELF:_ToString(oKeyValue, SELF:_keySize,  byteArray,  REF uiRealLen)
            CATCH ex AS Exception
                SELF:_oRdd:_dbfError(ex, Subcodes.EDB_EXPRESSION,Gencode.EG_SYNTAX,  "DBFCDX._GetExpressionValue", FALSE)
                result := FALSE
            END TRY
            RETURN result

    END CLASS
END NAMESPACE