[Runtime] Fix to the OleAutoObject class, so its own properties (fInit, dwVars, dwFuncs) can be read.

//
// 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.Diagnostics
USING System.Reflection
USING System.Runtime.Remoting
USING System.Runtime.InteropServices
USING System.Runtime.CompilerServices
USING XSharp.Internal
USING System.Collections.Generic
/// VO Compatible OLE Automation class
[AllowLateBinding];
[DebuggerDisplay( "Type= {__ComObject}", Type := "OleAutoObject" )];
CLASS XSharp.OleAutoObject IMPLEMENTS IDynamicProperties
	PROTECTED oComObject AS OBJECT
	PROTECTED lOk        AS LOGIC
	PROTECTED _liFuncs   AS LONG
	PROTECTED _liVars    AS LONG
	PROTECTED oType      AS System.Type
	PROTECTED lDateTimeAsDate   AS LOGIC

	#region Constructors
	INTERNAL CONSTRUCTOR
		_liFuncs                := _liVars := -1
		lOk                     := FALSE
		oComObject              := NULL
		oType                   := NULL
		SELF:lDateTimeAsDate    := OleDateTimeAsDate()
		RETURN

   /// Construct an OleAutoObject
	CONSTRUCTOR(cProgId AS STRING)
		SELF()
		oComObject        := OleCreateObject(cProgId)
		lOk := oComObject != NULL
		IF lOk
			oType := oComObject:GetType()
		ENDIF
		RETURN

   /// Construct an OleAutoObject
	CONSTRUCTOR(cProgId AS STRING, fRotCheck AS LOGIC)
		SELF()
		IF fRotCheck
			oComObject := OleGetObject(cProgId)
		ENDIF
		IF oComObject == NULL
			oComObject := OleCreateObject(cProgId)
		ENDIF
		lOk := oComObject != NULL
		IF lOk
			oType := oComObject:GetType()
		ENDIF
		RETURN

	// Builds an OleAutoObject on Any OBJECT (including another AutoObject)
   /// Construct an OleAutoObject
	CONSTRUCTOR(oObject AS OBJECT)
		SELF()
		oComObject :=  OleUnWrapObject(oObject)
		lOk := oComObject != NULL
		IF lOk
			oType := oComObject:GetType()
		ENDIF
		RETURN

		// Builds an OleAutoObject on Any OBJECT (including another AutoObject). Type already known
   /// Construct an OleAutoObject
	CONSTRUCTOR(oObject AS OBJECT, _type AS System.Type)
		SELF()
		oComObject	:=  OleUnWrapObject(oObject)
		oType		:= _type
		lOk			:= oComObject != NULL
		RETURN

	#endregion

	#region No..() EntryPoints

	    /// <exclude />
    PRIVATE METHOD GetOurFieldInfo(cName AS STRING, result OUT USUAL) AS LOGIC
        SWITCH cName:ToLower()
        CASE "finit"
            result := SELF:lOk
            return TRUE
        CASE "dwfuncs"
            result := SELF:_liFuncs
            return TRUE
        CASE "dwVars"
            result := SELF:_liVars
            return TRUE
        OTHERWISE
            result := NIL
            RETURN FALSE
        END SWITCH

    PRIVATE METHOD SetOurFieldInfo(cName AS STRING, newvalue AS USUAL) AS LOGIC
        SWITCH cName:ToLower()
        CASE "dwfuncs"
            SELF:_liFuncs := newvalue
            return TRUE
        END SWITCH
        RETURN FALSE

	/// <exclude />
    // ? oObject:Property
    VIRTUAL METHOD NoIvarGet(cName AS STRING ) AS USUAL
		LOCAL oRet AS OBJECT
        IF SELF:GetOurFieldInfo(cName, OUT VAR result)
            return result
        ENDIF
		oRet := OleAutoObject.__OleIvarGet(oComObject,oType, cName, NULL)
		RETURN OleAutoObject.OleWrapObject(oRet, lDateTimeAsDate)

		// oObject:Property := Value
	    /// <exclude />
	VIRTUAL METHOD NoIvarPut(cName AS STRING, uValue AS USUAL) AS VOID
        IF SELF:SetOurFieldInfo(cName, uValue)
            return
        ENDIF
		OleAutoObject.__OleIVarPut(oComObject, oType, cName , uValue, NULL)
		RETURN

    VIRTUAL METHOD GetPropertyNames() AS STRING[]
        var props := oType:GetProperties()
        var names := List<string>{}
        FOREACH VAR prop in props
            names:Add(prop:Name)
        NEXT
        return names:ToArray()

	    /// <exclude />
	METHOD NoMethod( ) AS USUAL CLIPPER
		LOCAL cName AS STRING
		LOCAL args  AS USUAL[]
		LOCAL nArg, nArgs  AS INT
		LOCAL oRet  AS OBJECT
		nArgs := PCOUNT()
		cName := NoMethod()
        IF RuntimeState.Dialect == XSharpDialect.Vulcan
		    args  := USUAL[]{nArgs-1}
		    FOR nArg := 2 TO nArgs
			    args[nArg-1] :=  _GetMParam(nArg)
            NEXT
        ELSE
		    args  := USUAL[]{nArgs}
		    FOR nArg := 1 TO nArgs
			    args[nArg] :=  _GetMParam(nArg)
            NEXT
        ENDIF
		oRet := OleAutoObject.OleSend(oComObject, oType, cName, args)
		RETURN OleAutoObject.OleWrapObject(oRet, lDateTimeAsDate)

	// ? oObject:Property[dims]
	// The compiler needs to be changed to look for this method
	    /// <exclude />
	METHOD NoIVarGetCollection(cName AS STRING, dims AS USUAL[]) AS USUAL
		LOCAL oRet AS OBJECT
		oRet := OleAutoObject.OleIvarGet(oComObject, cName, dims)
		RETURN OleAutoObject.OleWrapObject(oRet, lDateTimeAsDate)

	// ? oObject:Property[dims] := Value
	// The compiler needs to be changed to look for this method
	    /// <exclude />
	METHOD NoIVarPutCollection(cName AS STRING, uValue AS USUAL, dims AS USUAL[]) AS VOID
		OleAutoObject.OleIVarPut(oComObject, cName, uValue , dims )
		RETURN
	#endregion

	#region VO Compatibility Accesses
	    /// <exclude />

	ACCESS dwFuncs  AS LONG
		IF (lOk)
			LOCAL t AS System.Type
			IF (_liFuncs < 0)
				t := oComObject:GetType()
				_liFuncs := t:GetMethods():Length
			ENDIF
			RETURN _liFuncs
		ENDIF
		RETURN 0

	ASSIGN dwFuncs(value AS LONG)
		SELF:_liFuncs := value
		RETURN

	    /// <exclude />
	ACCESS dwVars   AS LONG
		IF (lOk)
			LOCAL t AS System.Type
			IF (_liVars < 0)
				t := oComObject:GetType()
				_liVars := t:GetProperties():Length
			ENDIF
			RETURN _liVars
		ENDIF
		RETURN 0

	    /// <exclude />
	ACCESS fInit    AS LOGIC
		RETURN lOk

	#endregion

	#region	INTERNAL Properties

	// Access to OBJECT inside AutoObject
    /// <exclude />

    ACCESS __ComObject AS OBJECT
		RETURN SELF:oComObject

	#endregion

	#region STATIC Methods
	    /// <exclude />
	STATIC METHOD OleCreateObject(cProgId AS STRING) AS OBJECT
		LOCAL oComObject := NULL AS OBJECT
		LOCAL objecttype AS System.Type
		TRY
			objecttype        := System.Type.GetTypeFromProgID(cProgId, FALSE)
			IF objecttype != NULL
				oComObject        := System.Activator.CreateInstance (objecttype)
			ENDIF
		CATCH AS Exception
			// We catch the exception but do not process it
		END TRY
		RETURN oComObject

    /// <exclude />
	STATIC METHOD  OleGetObject(cProgId AS STRING) AS OBJECT
		LOCAL oComObject AS OBJECT
		TRY
			oComObject := Marshal.GetActiveObject(cProgId)
		CATCH AS Exception
			oComObject := NULL
		END TRY
		RETURN oComObject

	#region Wrapping Objects
    /// <exclude />
    STATIC METHOD  OleWrapObject(oObject AS OBJECT,lDateTimeAsDate AS LOGIC) AS USUAL
		LOCAL t  AS System.Type
		LOCAL tc  AS System.TypeCode
		LOCAL oDt AS OleDateTime
		LOCAL uResult := NIL AS USUAL
		IF (oObject != NULL)
			t := oObject:GetType()
			tc := Type.GetTypeCode(t)
			IF tc == TypeCode.DateTime
				IF (lDateTimeAsDate)
					LOCAL oD AS System.DateTime
					oD := (System.DateTime)oObject
					uResult := XSharp.__Date{oD}
				ELSE
					oDt := (DateTime) oObject
					uResult := oDt
				ENDIF
			ELSE
				IF t:IsCOMObject
					uResult := OleAutoObject{oObject,t}
				ELSE
					uResult := oObject
				ENDIF
			ENDIF
		ENDIF
		RETURN uResult

    /// <exclude />
	STATIC METHOD  OleUnWrapObject(oObject AS OBJECT) AS OBJECT
		LOCAL oAuto AS OleAutoObject
		IF oObject != NULL
			IF oObject IS OleAutoObject
				oAuto := (OleAutoObject) oObject
				oObject := oAuto:__ComObject
			ENDIF
		ENDIF
		RETURN oObject

	#endregion

	#region OBJECT Properties

    /// <exclude />
	STATIC METHOD  OleIvarGet(oObject AS OBJECT,strName AS STRING) AS OBJECT
		RETURN OleAutoObject.__OleIvarGet(oObject, oObject:GetType(), strName, NULL)

    /// <exclude />
	STATIC METHOD  OleIvarGet(oObject AS OBJECT, symName AS STRING, dims  AS USUAL[]) AS OBJECT
		RETURN OleAutoObject.__OleIvarGet(oObject, oObject:GetType(), symName, dims)

    /// <exclude />
	STATIC METHOD  OleIvarGet(oObject AS OBJECT, symName AS STRING, index  AS USUAL) AS OBJECT
		LOCAL dims AS USUAL[]
		dims := USUAL[]{1}
		dims[1] := index
		RETURN OleAutoObject.__OleIvarGet(oObject, oObject:GetType(), symName, dims)

    /// <exclude />
	STATIC METHOD  OleIVarPut(oObject AS OBJECT, symName AS STRING, uValue AS USUAL) AS VOID
		OleAutoObject.__OleIVarPut(oObject, oObject:GetType(), symName, uValue, NULL)
		RETURN

    /// <exclude />
	STATIC METHOD  OleIVarPut(oObject AS OBJECT, symName AS STRING, uValue AS USUAL, dims AS USUAL[]) AS VOID
		OleAutoObject.__OleIVarPut(oObject, oObject:GetType(), symName, uValue, dims)
		RETURN

    /// <exclude />
	STATIC METHOD  OleIVarPut(oObject AS OBJECT, symName AS STRING, uValue AS USUAL, index AS USUAL) AS VOID
		LOCAL dims AS USUAL[]
		dims := USUAL[]{1}
		dims[1] := index
		OleAutoObject.__OleIVarPut(oObject, oObject:GetType(), symName, uValue, dims)
		RETURN

	#endregion

	#region Worker Functions

    /// <exclude />
	STATIC METHOD  __OleIvarGet(oComObject AS OBJECT, oType AS System.Type, cName AS STRING, dims AS USUAL[]) AS OBJECT
		LOCAL fi       AS FieldInfo
		LOCAL oRet     AS OBJECT
		LOCAL t        AS System.Type
		LOCAL lIndexed AS LOGIC
		LOCAL cMethod  AS STRING
		LOCAL bf AS BindingFlags

		cMethod := __ENTITY__
		IF oComObject == NULL
			THROW Error.NullArgumentError( cMethod, "oComObject", 1 )
		ENDIF
		lIndexed := (dims != NULL)
		t  := oType
		oComObject := OleUnWrapObject( oComObject )
		bf := BindingFlags.FlattenHierarchy | BindingFlags.GetField | ;
		BindingFlags.IgnoreCase | BindingFlags.GetProperty |  BindingFlags.Instance | BindingFlags.Public ;

		fi :=t:GetField( cName, bf )

		IF fi != NULL
			IF ! fi:IsPublic
				THROW Error.VOError( EG_NOVARMETHOD,  cMethod, "cMethod", 2,  <OBJECT>{cName } )
			ELSE
				oRet := fi:GetValue( oComObject )
			ENDIF
		ELSE
			IF lIndexed

				LOCAL oArgs AS OBJECT[]
				LOCAL i     AS INT
				oArgs := OBJECT[]{dims:Length }
				FOR i := 1 UPTO dims:Length
					oArgs[i] :=  OleUnWrapObject(dims[i])
				NEXT
				oRet := oType:InvokeMember(cName,bf ,NULL, oComObject,oArgs)
			ELSE
				oRet := oType:InvokeMember(cName, bf,NULL, oComObject, NULL)
			ENDIF
		ENDIF
		RETURN oRet

		#endregion

#region Putting OBJECT Properties

    /// <exclude />
STATIC METHOD  __OleIVarPut(oComObject AS OBJECT, oType AS System.Type, cName AS STRING, uValue AS USUAL, dims AS USUAL[]) AS VOID
	LOCAL t        AS System.Type
	LOCAL fi       AS FieldInfo
	LOCAL lIndexed AS LOGIC
	LOCAL cMethod  AS STRING
	cMethod := __ENTITY__

	IF oComObject == NULL
		THROW Error.NullArgumentError( cMethod, NAMEOF(oComObject), 1 )
	ENDIF

	// Get the inner OBJECT from OleAutoObjects
	// For both the Target and the value
	oComObject  := OleUnWrapObject( oComObject)
	uValue      := OleUnWrapObject( uValue )

	t := oType

	lIndexed := (dims != NULL)

	fi := t:GetField( cName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase  )

	IF fi != NULL
		fi:SetValue( oComObject, uValue )
	ELSE
		LOCAL bf      AS BindingFlags
		LOCAL oArgs   AS OBJECT[]
		bf := BindingFlags.FlattenHierarchy | BindingFlags.SetField | ;
		BindingFlags.IgnoreCase | BindingFlags.SetProperty
		IF lIndexed
			LOCAL i     AS INT
			oArgs := OBJECT[]{dims:Length+1 }
			FOR i := 1 UPTO dims:Length
				oArgs[i+1] := OleUnWrapObject(dims[i])
			NEXT
		ELSE
			oArgs := OBJECT[]{1 }
		ENDIF
		oArgs[1] := uValue
		TRY
			oType:InvokeMember(cName, bf,NULL, oComObject, oArgs)
			CATCH e AS Exception
			THROW e
		END TRY

	ENDIF

	RETURN

	#endregion
    /// <exclude />

STATIC METHOD  OleSend(oComObject AS OBJECT, oType AS System.Type, cName AS STRING, args AS USUAL[]) AS OBJECT
	LOCAL mi       AS MethodInfo
	LOCAL pi       AS ParameterInfo[]
	LOCAL t        AS Type
	LOCAL cMethod  AS STRING
	LOCAL x        AS INT
	LOCAL bf       AS BindingFlags

	LOCAL retval := NULL AS OBJECT
	cMethod  := __ENTITY__

	IF oComObject == NULL
		THROW Error.NullArgumentError( __ENTITY__, NAMEOF(oComObject), 1 )
	ENDIF
	t  :=oType
	oComObject := OleUnWrapObject(oComObject)
	bf :=  BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy| BindingFlags.InvokeMethod |  BindingFlags.IgnoreCase

	TRY
		mi := t:GetMethod( cName, bf)
		IF mi == NULL
			mi := t:GetMethod( cName)
		ENDIF

		IF mi == NULL
			// Ok we know nothing, just try to Invoke the method
			TRY

				VAR oArgs1 := OBJECT[] {args:Length} // Array of arguments for call to Invoke
				FOR x := 1 UPTO args:Length
					oArgs1[x] := OleUnWrapObject(args[x])
				NEXT
				retval := t:InvokeMember(cName, bf, NULL, oComObject, oArgs1)
			CATCH AS ArgumentException
				THROW Error.VOError(EG_ARG,  cMethod,"args", 4, <OBJECT>{args} )
			CATCH AS TargetException
				THROW Error.VOError(EG_ARG,  cMethod, "args", 4, <OBJECT>{args})
			CATCH AS MissingMethodException
				THROW Error.VOError(EG_NOMETHOD,  cMethod, "cName", 2,  <OBJECT>{cName}  )
			CATCH e AS Exception
				THROW Error{e}
			END TRY
		ELSE
			pi := mi:GetParameters()
			IF pi:Length == 1 .AND. mi:IsDefined( TYPEOF( XSharp.Internal.ClipperCallingConventionAttribute ), FALSE )
				retval := mi:Invoke(oComObject, <OBJECT>{args})
			ELSEIF pi:Length == 0
				retval := mi:Invoke(oComObject, NULL )
			ELSE
				LOCAL nArgs          AS INT   // # of arguments passed in
				LOCAL pArg           AS USUAL PTR   // This is used to derefence arguments
				LOCAL nLastUnNamed   AS INT  // Last unnamed argument passed in
				LOCAL lFound         AS LOGIC // Was the Named Arg Found

				VAR nDefArgs := pi:Length			// # of arguments defined for method
				VAR oArgs := OBJECT[]{ nDefArgs }	// Array of arguments for call to Invoke
				IF args != NULL
					nArgs := args:Length
					nArgs := Math.Min( nArgs, nDefArgs )
				ELSE
					nArgs := 0
				ENDIF
				nLastUnNamed := nArgs
				FOR x := 1 UPTO nArgs
					VAR oArg := OleUnWrapObject(args[x])

					IF IsPtr(oArg)
						// By Reference ?
						pArg     := Usual{oArg}
						oArgs[x] := pArg[1]

					ELSEIF oArg IS NamedArg
						// Named Argument ?
						nLastUnNamed:= Math.Min( x - 1, nLastUnNamed )
						VAR oNamedArg   := (NamedArg) oArg
						VAR cArgName    := oNamedArg:ArgName
						lFound      := FALSE
						FOR VAR y := 1 TO  nDefArgs
							VAR paramInfo := pi[y]
							IF String.Compare( paramInfo:Name, cArgName, StringComparison.OrdinalIgnoreCase ) == 0
								oArgs[y] := oNamedArg:Value
								lFound   := TRUE
								EXIT
							ENDIF
						NEXT
						IF ! lFound
							THROW Error.VOError(EG_ARG,  cMethod, cArgName, (DWORD) x, <OBJECT>{oNamedArg} )
						ENDIF

					ELSE
						// Normal argument
						oArgs[x] := oArg
					ENDIF
				NEXT
				// Fill in missing method parameter values
				// nLastUnNamed has # of last unnamed argument
				FOR x :=nLastUnNamed+1 TO nDefArgs
					IF oArgs[x] == NULL
						// When no value, fill in the default
						VAR paramInfo := pi[x]
						oArgs[x] := paramInfo:DefaultValue
					ENDIF
				NEXT
				LOCAL lOk AS LOGIC
				// Try MethodInfo:Invoke first.
				TRY
					retval := mi:Invoke(oComObject, oArgs)
					lOk := TRUE
					// return Out parameters
					FOR x := 1 TO nArgs
						VAR paramInfo := pi[x]
						IF paramInfo:Attributes:HasFlag( ParameterAttributes.Out)
							IF IsPtr(args[x])
								pArg := args[x]
								pArg[1] := oArgs[x]
							ENDIF
						ENDIF
					NEXT
				CATCH AS Exception
					lOk := FALSE
				END TRY
				// If that fails we try t:InvokeMember. You never know if that will work...
				IF ! lOk
					TRY
						retval := t:InvokeMember(cName, bf, NULL, oComObject, oArgs)
						lOk    := TRUE
					CATCH AS ArgumentException
						THROW Error.VOError(EG_ARG,  cMethod, cMethod, 1, <OBJECT>{args})
						//lOk := FALSE
					CATCH AS TargetException
						THROW Error.VOError(EG_ARG,  cMethod, cMethod, 1, <OBJECT>{args} )
						//lOk := FALSE
					CATCH AS MissingMethodException
						THROW Error.VOError(EG_NOMETHOD,  cMethod, "cName", 2, <OBJECT>{cName}  )
						//lOk := FALSE
					CATCH AS Exception
						lOk := FALSE
					END TRY
				ENDIF
				IF ! lOk
					THROW Error.VOError(EG_ARG,  cMethod, cMethod, 1, <OBJECT>{args}  )
				ENDIF
			ENDIF
		ENDIF
	CATCH AS AmbiguousMatchException
		THROW Error.VOError( EG_AMBIGUOUSMETHOD,  cMethod, "cMethod", 2, <OBJECT>{cMethod} )
	END TRY
	RETURN retval
	#endregion

END CLASS

//
// 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.Diagnostics
USING System.Reflection
USING System.Runtime.Remoting
USING System.Runtime.InteropServices
USING System.Runtime.CompilerServices
USING XSharp.Internal
USING System.Collections.Generic
/// VO Compatible OLE Automation class
[AllowLateBinding];
[DebuggerDisplay( "Type= {__ComObject}", Type := "OleAutoObject" )];
CLASS XSharp.OleAutoObject IMPLEMENTS IDynamicProperties
	PROTECTED oComObject AS OBJECT
	PROTECTED lOk        AS LOGIC
	PROTECTED _liFuncs   AS LONG
	PROTECTED _liVars    AS LONG
	PROTECTED oType      AS System.Type
	PROTECTED lDateTimeAsDate   AS LOGIC

	#region Constructors
	INTERNAL CONSTRUCTOR
		_liFuncs                := _liVars := -1
		lOk                     := FALSE
		oComObject              := NULL
		oType                   := NULL
		SELF:lDateTimeAsDate    := OleDateTimeAsDate()
		RETURN

   /// Construct an OleAutoObject
	CONSTRUCTOR(cProgId AS STRING)
		SELF()
		oComObject        := OleCreateObject(cProgId)
		lOk := oComObject != NULL
		IF lOk
			oType := oComObject:GetType()
		ENDIF

		RETURN

   /// Construct an OleAutoObject
	CONSTRUCTOR(cProgId AS STRING, fRotCheck AS LOGIC)
		SELF()
		IF fRotCheck
			oComObject := OleGetObject(cProgId)
		ENDIF
		IF oComObject == NULL
			oComObject := OleCreateObject(cProgId)
		ENDIF
		lOk := oComObject != NULL
		IF lOk
			oType := oComObject:GetType()
		ENDIF
		RETURN

	// Builds an OleAutoObject on Any OBJECT (including another AutoObject)
   /// Construct an OleAutoObject
	CONSTRUCTOR(oObject AS OBJECT)
		SELF()
		oComObject :=  OleUnWrapObject(oObject)
		lOk := oComObject != NULL
		IF lOk
			oType := oComObject:GetType()
		ENDIF

		RETURN

		// Builds an OleAutoObject on Any OBJECT (including another AutoObject). Type already known
   /// Construct an OleAutoObject
	CONSTRUCTOR(oObject AS OBJECT, _type AS System.Type)
		SELF()
		oComObject	:=  OleUnWrapObject(oObject)
		oType		:= _type
		lOk			:= oComObject != NULL
		RETURN

	#endregion

	#region No..() EntryPoints

	// ? oObject:Property
	    /// <exclude />
    VIRTUAL METHOD NoIvarGet(cName AS STRING ) AS USUAL
		LOCAL oRet AS OBJECT
		oRet := OleAutoObject.__OleIvarGet(oComObject,oType, cName, NULL)
		RETURN OleAutoObject.OleWrapObject(oRet, lDateTimeAsDate)

		// oObject:Property := Value
	    /// <exclude />
	VIRTUAL METHOD NoIvarPut(cName AS STRING, uValue AS USUAL) AS VOID
		OleAutoObject.__OleIVarPut(oComObject, oType, cName , uValue, NULL)
		RETURN

    VIRTUAL METHOD GetPropertyNames() AS STRING[]
        var props := oType:GetProperties()
        var names := List<string>{}
        FOREACH VAR prop in props
            names:Add(prop:Name)
        NEXT
        return names:ToArray()

	    /// <exclude />
	METHOD NoMethod( ) AS USUAL CLIPPER
		LOCAL cName AS STRING
		LOCAL args  AS USUAL[]
		LOCAL nArg, nArgs  AS INT
		LOCAL oRet  AS OBJECT
		nArgs := PCOUNT()
        IF RuntimeState.Dialect == XSharpDialect.Vulcan
		    cName := NoMethod()
		    args  := USUAL[]{nArgs-1}
		    FOR nArg := 2 TO nArgs
			    args[nArg-1] :=  _GetMParam(nArg)
            NEXT
        ELSE
		    cName := NoMethod()
		    args  := USUAL[]{nArgs}
		    FOR nArg := 1 TO nArgs
			    args[nArg] :=  _GetMParam(nArg)
            NEXT
        ENDIF
		oRet := OleAutoObject.OleSend(oComObject, oType, cName, args)

		RETURN OleAutoObject.OleWrapObject(oRet, lDateTimeAsDate)

	// ? oObject:Property[dims]
	// The compiler needs to be changed to look for this method
	    /// <exclude />
	METHOD NoIVarGetCollection(cName AS STRING, dims AS USUAL[]) AS USUAL
		LOCAL oRet AS OBJECT
		oRet := OleAutoObject.OleIvarGet(oComObject, cName, dims)
		RETURN OleAutoObject.OleWrapObject(oRet, lDateTimeAsDate)

	// ? oObject:Property[dims] := Value
	// The compiler needs to be changed to look for this method
	    /// <exclude />
	METHOD NoIVarPutCollection(cName AS STRING, uValue AS USUAL, dims AS USUAL[]) AS VOID
		OleAutoObject.OleIVarPut(oComObject, cName, uValue , dims )
		RETURN
	#endregion

	#region VO Compatibility Accesses
	    /// <exclude />

	ACCESS dwFuncs  AS LONG
		IF (lOk)
			LOCAL t AS System.Type
			IF (_liFuncs < 0)
				t := oComObject:GetType()
				_liFuncs := t:GetMethods():Length
			ENDIF
			RETURN _liFuncs
		ENDIF
		RETURN 0

	ASSIGN dwFuncs(value AS LONG)
		SELF:_liFuncs := value
		RETURN

	    /// <exclude />
	ACCESS dwVars   AS LONG
		IF (lOk)
			LOCAL t AS System.Type
			IF (_liVars < 0)
				t := oComObject:GetType()
				_liVars := t:GetProperties():Length
			ENDIF
			RETURN _liVars
		ENDIF
		RETURN 0

	    /// <exclude />
	ACCESS fInit    AS LOGIC
		RETURN lOk

	#endregion

	#region	INTERNAL Properties

	// Access to OBJECT inside AutoObject
    /// <exclude />

    ACCESS __ComObject AS OBJECT
		RETURN SELF:oComObject

	#endregion

	#region STATIC Methods
	    /// <exclude />
	STATIC METHOD OleCreateObject(cProgId AS STRING) AS OBJECT
		LOCAL oComObject := NULL AS OBJECT
		LOCAL objecttype AS System.Type
		TRY
			objecttype        := System.Type.GetTypeFromProgID(cProgId, FALSE)
			IF objecttype != NULL
				oComObject        := System.Activator.CreateInstance (objecttype)
			ENDIF
		CATCH AS Exception
			// We catch the exception but do not process it
		END TRY
		RETURN oComObject

    /// <exclude />
	STATIC METHOD  OleGetObject(cProgId AS STRING) AS OBJECT
		LOCAL oComObject AS OBJECT
		TRY
			oComObject := Marshal.GetActiveObject(cProgId)
		CATCH AS Exception
			oComObject := NULL
		END TRY
		RETURN oComObject

	#region Wrapping Objects
    /// <exclude />
    STATIC METHOD  OleWrapObject(oObject AS OBJECT,lDateTimeAsDate AS LOGIC) AS USUAL
		LOCAL t  AS System.Type
		LOCAL tc  AS System.TypeCode
		LOCAL oDt AS OleDateTime
		LOCAL uResult := NIL AS USUAL
		IF (oObject != NULL)
			t := oObject:GetType()
			tc := Type.GetTypeCode(t)
			IF tc == TypeCode.DateTime
				IF (lDateTimeAsDate)
					LOCAL oD AS System.DateTime
					oD := (System.DateTime)oObject
					uResult := XSharp.__Date{oD}
				ELSE
					oDt := (DateTime) oObject
					uResult := oDt
				ENDIF
			ELSE
				IF t:IsCOMObject
					uResult := OleAutoObject{oObject,t}
				ELSE
					uResult := oObject
				ENDIF
			ENDIF
		ENDIF
		RETURN uResult

    /// <exclude />
	STATIC METHOD  OleUnWrapObject(oObject AS OBJECT) AS OBJECT
		LOCAL oAuto AS OleAutoObject
		IF oObject != NULL
			IF oObject IS OleAutoObject
				oAuto := (OleAutoObject) oObject
				oObject := oAuto:__ComObject
			ENDIF
		ENDIF
		RETURN oObject

	#endregion

	#region OBJECT Properties

    /// <exclude />
	STATIC METHOD  OleIvarGet(oObject AS OBJECT,strName AS STRING) AS OBJECT
		RETURN OleAutoObject.__OleIvarGet(oObject, oObject:GetType(), strName, NULL)

    /// <exclude />
	STATIC METHOD  OleIvarGet(oObject AS OBJECT, symName AS STRING, dims  AS USUAL[]) AS OBJECT
		RETURN OleAutoObject.__OleIvarGet(oObject, oObject:GetType(), symName, dims)

    /// <exclude />
	STATIC METHOD  OleIvarGet(oObject AS OBJECT, symName AS STRING, index  AS USUAL) AS OBJECT
		LOCAL dims AS USUAL[]
		dims := USUAL[]{1}
		dims[1] := index
		RETURN OleAutoObject.__OleIvarGet(oObject, oObject:GetType(), symName, dims)

    /// <exclude />
	STATIC METHOD  OleIVarPut(oObject AS OBJECT, symName AS STRING, uValue AS USUAL) AS VOID
		OleAutoObject.__OleIVarPut(oObject, oObject:GetType(), symName, uValue, NULL)
		RETURN

    /// <exclude />
	STATIC METHOD  OleIVarPut(oObject AS OBJECT, symName AS STRING, uValue AS USUAL, dims AS USUAL[]) AS VOID
		OleAutoObject.__OleIVarPut(oObject, oObject:GetType(), symName, uValue, dims)
		RETURN

    /// <exclude />
	STATIC METHOD  OleIVarPut(oObject AS OBJECT, symName AS STRING, uValue AS USUAL, index AS USUAL) AS VOID
		LOCAL dims AS USUAL[]
		dims := USUAL[]{1}
		dims[1] := index
		OleAutoObject.__OleIVarPut(oObject, oObject:GetType(), symName, uValue, dims)
		RETURN

	#endregion

	#region Worker Functions

    /// <exclude />
	STATIC METHOD  __OleIvarGet(oComObject AS OBJECT, oType AS System.Type, cName AS STRING, dims AS USUAL[]) AS OBJECT
		LOCAL fi       AS FieldInfo
		LOCAL oRet     AS OBJECT
		LOCAL t        AS System.Type
		LOCAL lIndexed AS LOGIC
		LOCAL cMethod  AS STRING
		LOCAL bf AS BindingFlags

		cMethod := __ENTITY__
		IF oComObject == NULL
			THROW Error.NullArgumentError( cMethod, "oComObject", 1 )
		ENDIF
		lIndexed := (dims != NULL)
		t  := oType
		oComObject := OleUnWrapObject( oComObject )
		bf := BindingFlags.FlattenHierarchy | BindingFlags.GetField | ;
		BindingFlags.IgnoreCase | BindingFlags.GetProperty |  BindingFlags.Instance | BindingFlags.Public ;

		fi :=t:GetField( cName, bf )

		IF fi != NULL
			IF ! fi:IsPublic
				THROW Error.VOError( EG_NOVARMETHOD,  cMethod, "cMethod", 2,  <OBJECT>{cName } )
			ELSE
				oRet := fi:GetValue( oComObject )
			ENDIF
		ELSE
			IF lIndexed

				LOCAL oArgs AS OBJECT[]
				LOCAL i     AS INT
				oArgs := OBJECT[]{dims:Length }
				FOR i := 1 UPTO dims:Length
					oArgs[i] :=  OleUnWrapObject(dims[i])
				NEXT
				oRet := oType:InvokeMember(cName,bf ,NULL, oComObject,oArgs)
			ELSE
				oRet := oType:InvokeMember(cName, bf,NULL, oComObject, NULL)
			ENDIF
		ENDIF
		RETURN oRet

		#endregion

#region Putting OBJECT Properties

    /// <exclude />
STATIC METHOD  __OleIVarPut(oComObject AS OBJECT, oType AS System.Type, cName AS STRING, uValue AS USUAL, dims AS USUAL[]) AS VOID
	LOCAL t        AS System.Type
	LOCAL fi       AS FieldInfo
	LOCAL lIndexed AS LOGIC
	LOCAL cMethod  AS STRING
	cMethod := __ENTITY__

	IF oComObject == NULL
		THROW Error.NullArgumentError( cMethod, NAMEOF(oComObject), 1 )
	ENDIF

	// Get the inner OBJECT from OleAutoObjects
	// For both the Target and the value
	oComObject  := OleUnWrapObject( oComObject)
	uValue      := OleUnWrapObject( uValue )

	t := oType

	lIndexed := (dims != NULL)

	fi := t:GetField( cName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase  )

	IF fi != NULL
		fi:SetValue( oComObject, uValue )
	ELSE
		LOCAL bf      AS BindingFlags
		LOCAL oArgs   AS OBJECT[]
		bf := BindingFlags.FlattenHierarchy | BindingFlags.SetField | ;
		BindingFlags.IgnoreCase | BindingFlags.SetProperty
		IF lIndexed
			LOCAL i     AS INT
			oArgs := OBJECT[]{dims:Length+1 }
			FOR i := 1 UPTO dims:Length
				oArgs[i+1] := OleUnWrapObject(dims[i])
			NEXT
		ELSE
			oArgs := OBJECT[]{1 }
		ENDIF
		oArgs[1] := uValue
		TRY
			oType:InvokeMember(cName, bf,NULL, oComObject, oArgs)
			CATCH e AS Exception
			THROW e
		END TRY

	ENDIF

	RETURN

	#endregion
    /// <exclude />

STATIC METHOD  OleSend(oComObject AS OBJECT, oType AS System.Type, cName AS STRING, args AS USUAL[]) AS OBJECT
	LOCAL mi       AS MethodInfo
	LOCAL pi       AS ParameterInfo[]
	LOCAL t        AS Type
	LOCAL cMethod  AS STRING
	LOCAL x        AS INT
	LOCAL bf       AS BindingFlags

	LOCAL retval := NULL AS OBJECT
	cMethod  := __ENTITY__

	IF oComObject == NULL
		THROW Error.NullArgumentError( __ENTITY__, NAMEOF(oComObject), 1 )
	ENDIF
	t  :=oType
	oComObject := OleUnWrapObject(oComObject)
	bf :=  BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy| BindingFlags.InvokeMethod |  BindingFlags.IgnoreCase

	TRY
		mi := t:GetMethod( cName, bf)
		IF mi == NULL
			mi := t:GetMethod( cName)
		ENDIF

		IF mi == NULL
			// Ok we know nothing, just try to Invoke the method
			TRY

				VAR oArgs1 := OBJECT[] {args:Length} // Array of arguments for call to Invoke
				FOR x := 1 UPTO args:Length
					oArgs1[x] := OleUnWrapObject(args[x])
				NEXT
				retval := t:InvokeMember(cName, bf, NULL, oComObject, oArgs1)
			CATCH AS ArgumentException
				THROW Error.VOError(EG_ARG,  cMethod,"args", 4, <OBJECT>{args} )
			CATCH AS TargetException
				THROW Error.VOError(EG_ARG,  cMethod, "args", 4, <OBJECT>{args})
			CATCH AS MissingMethodException
				THROW Error.VOError(EG_NOMETHOD,  cMethod, "cName", 2,  <OBJECT>{cName}  )
			CATCH e AS Exception
				THROW Error{e}
			END TRY
		ELSE
			pi := mi:GetParameters()
			IF pi:Length == 1 .AND. mi:IsDefined( TYPEOF( XSharp.Internal.ClipperCallingConventionAttribute ), FALSE )
				retval := mi:Invoke(oComObject, <OBJECT>{args})
			ELSEIF pi:Length == 0
				retval := mi:Invoke(oComObject, NULL )
			ELSE
				LOCAL nArgs          AS INT   // # of arguments passed in
				LOCAL pArg           AS USUAL PTR   // This is used to derefence arguments
				LOCAL nLastUnNamed   AS INT  // Last unnamed argument passed in
				LOCAL lFound         AS LOGIC // Was the Named Arg Found

				VAR nDefArgs := pi:Length			// # of arguments defined for method
				VAR oArgs := OBJECT[]{ nDefArgs }	// Array of arguments for call to Invoke
				IF args != NULL
					nArgs := args:Length
					nArgs := Math.Min( nArgs, nDefArgs )
				ELSE
					nArgs := 0
				ENDIF
				nLastUnNamed := nArgs
				FOR x := 1 UPTO nArgs
					VAR oArg := OleUnWrapObject(args[x])

					IF IsPtr(oArg)
						// By Reference ?
						pArg     := Usual{oArg}
						oArgs[x] := pArg[1]

					ELSEIF oArg IS NamedArg
						// Named Argument ?
						nLastUnNamed:= Math.Min( x - 1, nLastUnNamed )
						VAR oNamedArg   := (NamedArg) oArg
						VAR cArgName    := oNamedArg:ArgName
						lFound      := FALSE
						FOR VAR y := 1 TO  nDefArgs
							VAR paramInfo := pi[y]
							IF String.Compare( paramInfo:Name, cArgName, StringComparison.OrdinalIgnoreCase ) == 0
								oArgs[y] := oNamedArg:Value
								lFound   := TRUE
								EXIT
							ENDIF
						NEXT
						IF ! lFound
							THROW Error.VOError(EG_ARG,  cMethod, cArgName, (DWORD) x, <OBJECT>{oNamedArg} )
						ENDIF

					ELSE
						// Normal argument
						oArgs[x] := oArg
					ENDIF
				NEXT
				// Fill in missing method parameter values
				// nLastUnNamed has # of last unnamed argument
				FOR x :=nLastUnNamed+1 TO nDefArgs
					IF oArgs[x] == NULL
						// When no value, fill in the default
						VAR paramInfo := pi[x]
						oArgs[x] := paramInfo:DefaultValue
					ENDIF
				NEXT
				LOCAL lOk AS LOGIC
				// Try MethodInfo:Invoke first.
				TRY
					retval := mi:Invoke(oComObject, oArgs)
					lOk := TRUE
					// return Out parameters
					FOR x := 1 TO nArgs
						VAR paramInfo := pi[x]
						IF paramInfo:Attributes:HasFlag( ParameterAttributes.Out)
							IF IsPtr(args[x])
								pArg := args[x]
								pArg[1] := oArgs[x]
							ENDIF
						ENDIF
					NEXT
				CATCH AS Exception
					lOk := FALSE
				END TRY
				// If that fails we try t:InvokeMember. You never know if that will work...
				IF ! lOk
					TRY
						retval := t:InvokeMember(cName, bf, NULL, oComObject, oArgs)
						lOk    := TRUE
					CATCH AS ArgumentException
						THROW Error.VOError(EG_ARG,  cMethod, cMethod, 1, <OBJECT>{args})
						//lOk := FALSE
					CATCH AS TargetException
						THROW Error.VOError(EG_ARG,  cMethod, cMethod, 1, <OBJECT>{args} )
						//lOk := FALSE
					CATCH AS MissingMethodException
						THROW Error.VOError(EG_NOMETHOD,  cMethod, "cName", 2, <OBJECT>{cName}  )
						//lOk := FALSE
					CATCH AS Exception
						lOk := FALSE
					END TRY
				ENDIF
				IF ! lOk
					THROW Error.VOError(EG_ARG,  cMethod, cMethod, 1, <OBJECT>{args}  )
				ENDIF
			ENDIF
		ENDIF
	CATCH AS AmbiguousMatchException
		THROW Error.VOError( EG_AMBIGUOUSMETHOD,  cMethod, "cMethod", 2, <OBJECT>{cMethod} )
	END TRY
	RETURN retval
	#endregion

END CLASS


[DBFCDX] Fixes #612

[DBFCDX] Fixes #612

//
// 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 XSharp.RDD.Support
USING XSharp.RDD.CDX
USING XSharp.RDD.Enums
USING System.IO
USING System.Diagnostics

//#define TESTCDX

#ifdef TESTCDX
    GLOBAL LOGGING := TRUE AS LOGIC
    GLOBAL VALIDATETREE := FALSE AS LOGIC

#endif

BEGIN NAMESPACE XSharp.RDD
    // Inherits all standard DBF and Memo behavior
    // Only adds Order Handling
    /// <summary>DBFCDX RDD. For DBF/FPT/CDX.</summary>
    [DebuggerDisplay("DBFCDX ({Alias,nq})")];
    CLASS DBFCDX INHERIT DBFFPT
        INTERNAL _indexList  AS CdxOrderBagList
        INTERNAL PROPERTY CurrentOrder AS CdxTag GET _indexList:CurrentOrder
        VIRTUAL PROPERTY Driver  AS STRING GET "DBFCDX"
        INTERNAL PROPERTY MustForceRel AS LOGIC GET _RelInfoPending != NULL

        CONSTRUCTOR()
            SUPER()
            _indexList := CdxOrderBagList{SELF}
            RETURN

            #ifdef TESTCDX
            PUBLIC STATIC METHOD StartLogging AS VOID
                LOGGING := TRUE
                RETURN

            PUBLIC STATIC METHOD StopLogging AS VOID
                LOGGING := FALSE
                RETURN

            PUBLIC STATIC METHOD ValidateTree AS VOID
                VALIDATETREE := TRUE
                CoreDb.GoTop()
                VALIDATETREE := FALSE
                RETURN
            #endif

            #region Order Support

            VIRTUAL METHOD OrderCreate(orderInfo AS DbOrderCreateInfo ) AS LOGIC
                VAR result := SELF:_indexList:Create(orderInfo)
                if result
                    SELF:MarkDbfHeader(SELF:_FileName,TRUE)
                endif
                RETURN result

            VIRTUAL METHOD OrderDestroy(orderInfo AS DbOrderInfo ) AS LOGIC
                RETURN SELF:_indexList:Destroy(orderInfo)

            METHOD OrderCondition(info AS DbOrderCondInfo) AS LOGIC
                RETURN SUPER:OrderCondition(info)

            VIRTUAL METHOD OrderListAdd( orderInfo AS DbOrderInfo) AS LOGIC
                BEGIN LOCK SELF
                    SELF:GoCold()
                    LOCAL fullPath AS STRING
                    fullPath := orderInfo:BagName
                    IF File(fullPath)
                        fullPath := FPathName()
                    ELSEIF String.IsNullOrEmpty(Path.GetExtension(fullPath))
                        fullPath := Path.ChangeExtension(fullPath, CdxOrderBag.CDX_EXTENSION)
                        IF File(fullPath)
                            fullPath := FPathName()
                            orderInfo:BagName := fullPath
                        ENDIF
                    ENDIF
                    IF String.IsNullOrEmpty(System.IO.Path.GetDirectoryName(fullPath))
                        fullPath := System.IO.Path.Combine(System.IO.Path.GetDirectoryName(SELF:_FileName), fullPath)
                        orderInfo:BagName := fullPath
                    ENDIF
                    LOCAL lOk := FALSE AS LOGIC
                    IF SELF:_indexList:FindOrderBag(orderInfo:BagName) == NULL
                        lOk := SELF:_indexList:Add(orderInfo)
                    ELSE
                        // Already open, do nothing
                        lOk := TRUE
                    ENDIF
                    IF lOk
                        orderInfo:Order := 1
                        lOk := SELF:OrderListFocus(orderInfo)
                    ENDIF
                    RETURN lOk
                END LOCK

            METHOD _CloseAllIndexes(orderInfo AS DbOrderInfo, lCloseStructural AS LOGIC) AS LOGIC
                RETURN SELF:_indexList:Delete(orderInfo, lCloseStructural)

            VIRTUAL METHOD OrderListDelete(orderInfo AS DbOrderInfo) AS LOGIC
                BEGIN LOCK SELF
                    SELF:GoCold()
                    RETURN SELF:_CloseAllIndexes(orderInfo, FALSE)
                END LOCK

            VIRTUAL METHOD OrderListFocus(orderInfo AS DbOrderInfo) AS LOGIC
                BEGIN LOCK SELF
                    SELF:GoCold()
                    RETURN SELF:_indexList:Focus(orderInfo)
                END LOCK

            VIRTUAL METHOD OrderListRebuild() AS LOGIC
                BEGIN LOCK SELF
                    IF SELF:Shared
                        // Error !! Cannot be written !
                        SELF:_dbfError( ERDD.SHARED, XSharp.Gencode.EG_SHARED )
                        RETURN FALSE
                    ENDIF
                    IF SELF:_ReadOnly
                        SELF:_dbfError( ERDD.READONLY, XSharp.Gencode.EG_READONLY)
                        RETURN FALSE
                    ENDIF

                    SELF:GoCold()
                    RETURN SELF:_indexList:Rebuild()
                END LOCK

            OVERRIDE METHOD OrderInfo(nOrdinal AS DWORD , info AS DbOrderInfo ) AS OBJECT
                LOCAL result AS LONG
                LOCAL isOk := FALSE AS LOGIC
                LOCAL oBag := NULL AS CdxOrderBag
                result := 0
                SELF:_indexList:FindOrder(info, OUT VAR workOrder)
                IF ! String.IsNullOrEmpty(info:BagName)
                     oBag := SELF:_indexList:FindOrderBag(info:BagName)
                ENDIF

                IF workOrder == NULL .AND. info:IsEmpty
                    workOrder := SELF:CurrentOrder
                ENDIF

                BEGIN SWITCH nOrdinal
                CASE DBOI_DEFBAGEXT
                    info:Result := CdxOrderBag.CDX_EXTENSION
                CASE DBOI_CONDITION
                    IF workOrder != NULL
                        info:Result := workOrder:Condition
                    ELSE
                        info:Result := ""
                    ENDIF
                CASE DBOI_EXPRESSION
                    IF workOrder != NULL
                        info:Result := workOrder:Expression
                    ELSE
                        info:Result := ""
                    ENDIF
                CASE DBOI_ORDERCOUNT
                    IF oBag == NULL
                        info:Result := SELF:_indexList:Count
                    ELSE
                        info:Result := oBag:Tags:Count
                    ENDIF
                CASE DBOI_POSITION
                CASE DBOI_RECNO
                    VAR oState := SELF:_GetState()
                    IF workOrder == NULL
                        info:Result := SELF:RecNo
                    ELSE
                        isOk := workOrder:_getRecPos( REF result)
                        IF isOk
                            info:Result := result
                        ENDIF
                    ENDIF
                    SELF:_SetState(oState)
                CASE DBOI_KEYCOUNT
                    result := 0
                    VAR oState := SELF:_GetState()
                    IF workOrder != NULL
                        info:Result := 0
                        isOk := workOrder:_CountRecords(REF result)
                    ELSE
                        isOk := TRUE
                    ENDIF
                    IF isOk
                        info:Result := result
                    ENDIF
                    SELF:_SetState(oState)
                CASE DBOI_NUMBER
                    info:Result := SELF:_indexList:OrderPos(workOrder)
                CASE DBOI_BAGEXT
                    // according to the docs this should always return the default extension and not the actual extension
                    IF workOrder != NULL
                        info:Result := System.IO.Path.GetExtension(workOrder:OrderBag:FullPath)
                    ELSE
                        info:Result := CdxOrderBag.CDX_EXTENSION
                    ENDIF
                CASE DBOI_FULLPATH
                    IF workOrder != NULL
                        info:Result := workOrder:OrderBag:FullPath
                    ELSE
                        info:Result := String.Empty
                    ENDIF
                CASE DBOI_BAGCOUNT
                    info:Result := SELF:_indexList:BagCount
                CASE DBOI_BAGNAME
                    //CASE DBOI_INDEXNAME // alias
                    IF info:Order IS LONG VAR nOrder
                        info:Result := SELF:_indexList:BagName(nOrder)
                    ELSEIF workOrder != NULL
                        info:Result := workOrder:FileName
                    ELSE
                        info:Result :=String.Empty
                    ENDIF

                CASE DBOI_NAME
                    IF workOrder != NULL
                        info:Result := workOrder:_orderName
                    ELSE
                        info:Result := String.Empty
                    ENDIF
                CASE DBOI_COLLATION
                     info:Result := ""
                     IF workOrder != NULL
                        LOCAL collation as VfpCollation
                        collation := workOrder:Collation
                        if collation  != NULL
                            info:Result := collation:Name
                        ENDIF
                    ENDIF

                CASE DBOI_FILEHANDLE
                    IF workOrder != NULL
                        info:Result := workOrder:OrderBag:Handle
                    ELSE
                        info:Result := IntPtr.Zero
                    ENDIF
                CASE DBOI_FILESTREAM
                    IF workOrder != NULL
                        info:Result := workOrder:OrderBag:Stream
                    ELSE
                        info:Result := NULL
                    ENDIF
                CASE DBOI_ISDESC
                    IF workOrder != NULL
                        VAR oldValue  := workOrder:Descending
                        IF info:Result IS LOGIC VAR descend
                            workOrder:Descending := descend
                        ENDIF
                        info:Result := oldValue
                    ELSE
                        info:Result := FALSE
                    ENDIF
                CASE DBOI_ISCOND
                    IF workOrder != NULL
                        info:Result := workOrder:Conditional
                    ELSE
                        info:Result := FALSE
                    ENDIF
                CASE DBOI_KEYTYPE
                    IF workOrder != NULL
                        info:Result := workOrder:KeyType
                    ELSE
                        info:Result := 0
                    ENDIF
                CASE DBOI_KEYSIZE
                    IF workOrder != NULL
                        info:Result := workOrder:KeyLength
                    ELSE
                        info:Result := 0
                    ENDIF
                CASE DBOI_KEYDEC
                    info:Result := 0
                CASE DBOI_HPLOCKING
                    info:Result := FALSE
                CASE DBOI_UNIQUE
                    IF workOrder != NULL
                        info:Result := workOrder:Unique
                    ELSE
                        info:Result := FALSE
                    ENDIF
                CASE DBOI_LOCKOFFSET
                    IF workOrder != NULL
                        info:Result := workOrder:OrderBag:_LockOffSet
                    ELSE
                        info:Result := 0
                    ENDIF
                CASE DBOI_SETCODEBLOCK
                    IF workOrder != NULL
                        info:Result := workOrder:KeyCodeBlock
                    ENDIF
                CASE DBOI_KEYVAL
                    IF workOrder != NULL
                        isOk := TRUE
                        TRY
                            info:Result := SELF:EvalBlock(workOrder:KeyCodeBlock)
                        CATCH ex AS Exception
                            isOk := FALSE
                            SELF:_dbfError(ex, Subcodes.EDB_EXPRESSION, Gencode.EG_SYNTAX, "DBFCDX.OrderInfo")
                        END TRY
                        IF !isOk
                            info:Result := DBNull.Value
                        ENDIF
                    ELSE
                        info:Result := DBNull.Value
                    ENDIF
                CASE DBOI_SCOPETOPCLEAR
                CASE DBOI_SCOPEBOTTOMCLEAR
                    IF workOrder != NULL
                        workOrder:SetOrderScope(info:Result, (DbOrder_Info) nOrdinal)
                    ENDIF
                    info:Result := NULL
                CASE DBOI_SCOPETOP
                CASE DBOI_SCOPEBOTTOM
                    IF workOrder != NULL
                        LOCAL oldValue as OBJECT
                        IF nOrdinal == DBOI_SCOPETOP
                            oldValue := workOrder:TopScope
                        ELSEIF nOrdinal == DBOI_SCOPEBOTTOM
                            oldValue := workOrder:BottomScope
                        ELSE
                            oldValue := DBNull.Value
                        ENDIF
                        IF info:Result != NULL
                            workOrder:SetOrderScope(info:Result, (DbOrder_Info) nOrdinal)
                        ENDIF
                        info:Result := oldValue
                    ELSE
                        info:Result := DBNull.Value
                    ENDIF
                CASE DBOI_KEYADD
                    IF workOrder != NULL
                        info:Result := workOrder:AddKey(SELF:RecNo)
                    ELSE
                        info:Result := FALSE
                    ENDIF
                CASE DBOI_KEYDELETE
                    IF workOrder != NULL
                        info:Result := workOrder:DeleteKey(SELF:RecNo)
                    ELSE
                        info:Result := FALSE
                    ENDIF
                CASE DBOI_CUSTOM
                    IF workOrder != NULL
                        LOCAL lOld AS LOGIC
                        lOld := workOrder:Custom
                        IF info:Result IS LOGIC VAR custom
                            IF custom
                                workOrder:SetCustom()
                            ENDIF
                        ENDIF
                        info:Result := lOld
                    ELSE
                        info:Result := FALSE
                    ENDIF

                CASE DBOI_USER + 42
                CASE DBOI_DUMP
                    // Dump Cdx to Txt file
                    VAR oState := SELF:_GetState()
                    IF workOrder != NULL
                        workOrder:_dump()
                    ENDIF
                    SELF:_SetState(oState)
                CASE DBOI_VALIDATE
                    // Validate integrity of the current Order
                     VAR oState := SELF:_GetState()
                    IF workOrder != NULL
                        info:Result := workOrder:_validate()
                    ENDIF
                    SELF:_SetState(oState)
                OTHERWISE
                    SUPER:OrderInfo(nOrdinal, info)
                END SWITCH
                RETURN info:Result

            #endregion
        #region relations
        METHOD ForceRel() AS LOGIC
            LOCAL isOk    := TRUE AS LOGIC
            IF SELF:_RelInfoPending != NULL
                // Save the current context
                LOCAL currentRelation := SELF:_RelInfoPending AS DbRelInfo
                SELF:_RelInfoPending := NULL
                VAR oParent := (DBFCDX) currentRelation:Parent
                IF oParent:EoF
                    //
                    isOk := SELF:GoTo( 0 )
                ELSE
                    isOk := SELF:RelEval( currentRelation )

                    IF isOk .AND. !((DBFCDX)currentRelation:Parent):EoF
                        TRY
                            LOCAL seekInfo AS DbSeekInfo
                            seekInfo := DbSeekInfo{}
                            seekInfo:Value := SELF:_EvalResult
                            seekInfo:SoftSeek := FALSE
                            isOk := SELF:Seek(seekInfo)

                        CATCH ex AS InvalidCastException
                            SELF:_dbfError(ex, Subcodes.ERDD_DATATYPE,Gencode.EG_DATATYPE,  "DBFNTX.ForceRel")

                        END TRY
                    ENDIF
                ENDIF
            ENDIF

            RETURN isOk
            #endregion
        #region Pack, Zap
        METHOD Pack() AS LOGIC
            LOCAL isOk AS LOGIC

            isOk := SUPER:Pack()
            IF isOk
                isOk := SELF:OrderListRebuild()
            ENDIF
            RETURN isOk

        PUBLIC METHOD Zap() AS LOGIC
            LOCAL isOk AS LOGIC

            isOk := SUPER:Zap()
            IF isOk
                isOk := SELF:OrderListRebuild()
            ENDIF
            RETURN isOk

        #endregion

        #region Open, Close, Create

        PUBLIC OVERRIDE METHOD Close() AS LOGIC
            LOCAL orderInfo AS DbOrderInfo
            BEGIN LOCK SELF
                SELF:GoCold()
                orderInfo := DbOrderInfo{}
                orderInfo:AllTags := TRUE
                SELF:_CloseAllIndexes(orderInfo, TRUE)
                RETURN SUPER:Close()
            END LOCK

        PUBLIC OVERRIDE METHOD Create( openInfo AS DbOpenInfo ) AS LOGIC
            LOCAL isOk AS LOGIC
            LOCAL lMemo := FALSE AS LOGIC
            isOk := SUPER:Create(openInfo)
            IF isOk
                FOREACH VAR fld IN SELF:_Fields
                    IF fld:FieldType:IsMemo()
                        lMemo := TRUE
                        EXIT
                    ENDIF
                NEXT
                /*
                RvdH 2021-08-16 DO not delete the file. VO also does not DO that
                LOCAL cIndex AS STRING
                // Delete index because it may have incorrect index expressions
                cIndex := System.IO.Path.ChangeExtension(SELF:FullPath, ".CDX")
                IF System.IO.File.Exists(cIndex)
                    System.IO.File.Delete(cIndex)
                ENDIF
                */
                IF lMemo
                    SELF:_Header:Version := DBFVersion.FoxPro2WithMemo
                ELSE
                    SELF:_Header:Version := DBFVersion.FoxBaseDBase3NoMemo
                ENDIF
            ENDIF
            RETURN isOk

        METHOD Open(info AS DbOpenInfo) AS LOGIC
            LOCAL lOk AS LOGIC
            lOk := SUPER:Open(info)
            IF lOk
                // Open structural index
                IF RuntimeState.AutoOpen
                    SELF:OpenProductionIndex(info)
                ENDIF
                SELF:GoTop()
            ENDIF
            RETURN lOk

        PROTECTED METHOD OpenProductionIndex(info AS DbOpenInfo) AS VOID
            VAR cExt  := CdxOrderBag.GetIndexExtFromDbfExt(info:FileName)
            IF ! String.IsNullOrEmpty(cExt)
                VAR cCdxFileName := System.IO.Path.ChangeExtension(info:FileName, cExt)
                IF System.IO.File.Exists(cCdxFileName)
                    LOCAL orderinfo := DbOrderInfo{} AS DbOrderInfo
                    orderinfo:BagName := cCdxFileName
                    SELF:_indexList:Add(orderinfo, TRUE)
                ENDIF
                SELF:MarkDbfHeader(info:FileName,FALSE)
            ENDIF
         PROTECTED METHOD MarkDbfHeader(cFileName as STRING, lForce as LOGIC) AS VOID
            VAR cExt  := CdxOrderBag.GetIndexExtFromDbfExt(cFileName)
            IF ! String.IsNullOrEmpty(cExt)
                VAR cCdxFileName := System.IO.Path.ChangeExtension(cFileName, cExt)
                var wanted := SELF:Header:TableFlags
                IF System.IO.File.Exists(cCdxFileName)
                    wanted |= DBFTableFlags.HasStructuralCDX
                ELSE
                    wanted &= _NOT(DBFTableFlags.HasStructuralCDX)
                ENDIF
                IF wanted != SELF:Header:TableFlags .or. lForce
                    IF ! SELF:_ReadOnly
                        SELF:Header:TableFlags := wanted
                        SELF:Header:Write()
                    ENDIF
                ENDIF
            ENDIF

        #endregion

        #REGION Move

        INTERNAL METHOD ReadRecord() AS LOGIC
            RETURN SELF:_readRecord()

        PUBLIC METHOD Seek(seekInfo AS DbSeekInfo ) AS LOGIC
            LOCAL isOk AS LOGIC

            isOk := FALSE
            BEGIN LOCK SELF
                VAR index := SELF:CurrentOrder
                IF index != NULL
                    isOk := index:Seek(seekInfo)
                ENDIF
                IF  !isOk
                    SELF:_dbfError(Subcodes.ERDD_DATATYPE, Gencode.EG_NOORDER )
                ENDIF
                SELF:_CheckEofBof()
            END LOCK
            RETURN isOk

        PUBLIC METHOD GoBottom() AS LOGIC
            BEGIN LOCK SELF
                LOCAL result AS LOGIC
                IF SELF:CurrentOrder != NULL
                    result := SELF:CurrentOrder:GoBottom()
                    if (! result)
                        SELF:_SetEOF(TRUE)
                        SELF:_SetBOF(TRUE)
                    ELSE
                        SELF:_CheckEofBof()
                    ENDIF
                ELSE
                    result := SUPER:GoBottom()
                ENDIF
                RETURN result
            END LOCK

        PUBLIC METHOD GoTop() AS LOGIC
            BEGIN LOCK SELF
                LOCAL result AS LOGIC
                IF SELF:CurrentOrder != NULL
                    result := SELF:CurrentOrder:GoTop()
                    if (! result)
                        SELF:_SetEOF(TRUE)
                        SELF:_SetBOF(TRUE)
                        result := TRUE
                    ELSE
                        SELF:_CheckEofBof()
                    ENDIF
                ELSE
                    result := SUPER:GoTop()
                ENDIF
                RETURN result
            END LOCK

        METHOD __Goto(nRec AS LONG) AS LOGIC
            // Skip without reset of topstack
            RETURN SUPER:GoTo(nRec)

        METHOD GoTo(nRec AS LONG) AS LOGIC
            LOCAL result AS LOGIC
            SELF:GoCold()
            IF SELF:CurrentOrder != NULL
                SELF:CurrentOrder:ClearStack() // force to reseek later
            ENDIF
            result := SUPER:GoTo(nRec)
            RETURN result

        PUBLIC METHOD SkipRaw( move AS LONG ) AS LOGIC
            BEGIN LOCK SELF
                LOCAL result AS LOGIC
                IF SELF:CurrentOrder != NULL
                    result := SELF:CurrentOrder:SkipRaw(move)
                    SELF:_CheckEofBof()
                ELSE
                    result := SUPER:SkipRaw(move)
                ENDIF
                RETURN result
            END LOCK

        #ENDREGION

        #REGION GoCold, GoHot, Flush
        PUBLIC OVERRIDE METHOD GoCold() AS LOGIC
            LOCAL isOk AS LOGIC

            isOk := TRUE
            BEGIN LOCK SELF
                IF !SELF:IsHot
                    RETURN isOk
                ENDIF
                isOk := SELF:_indexList:GoCold()
                IF !isOk
                    RETURN isOk
                ENDIF
                RETURN SUPER:GoCold()
            END LOCK

        PUBLIC OVERRIDE METHOD GoHot() AS LOGIC
            LOCAL isOk AS LOGIC

            isOk := TRUE
            BEGIN LOCK SELF
                isOk := SUPER:GoHot()
                IF !isOk
                    RETURN isOk
                ENDIF
                RETURN SELF:_indexList:GoHot()
            END LOCK

        PUBLIC OVERRIDE METHOD Flush() AS LOGIC
            LOCAL isOk AS LOGIC

            isOk := TRUE
            BEGIN LOCK SELF
                isOk := SUPER:Flush()
                RETURN SELF:_indexList:Flush() .AND. isOk
            END LOCK

        #endregion

        INTERNAL METHOD _GetState() AS CdxState
            RETURN CdxState{} {EoF := SELF:EoF, BoF := SELF:BoF, RecNo := SELF:RecNo}

        INTERNAL METHOD _SetState(oState AS CdxState) AS VOID
            SELF:__Goto(oState:RecNo)
            SELF:_SetEOF(oState:EoF)
            SELF:_SetBOF(oState:BoF)

        INTERNAL CLASS CdxState
            PROPERTY EoF AS LOGIC AUTO
            PROPERTY BoF AS LOGIC AUTO
            PROPERTY RecNo AS LONG AUTO
        END CLASS
    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 XSharp.RDD.Support
USING XSharp.RDD.CDX
USING XSharp.RDD.Enums
USING System.IO
USING System.Diagnostics

//#define TESTCDX

#ifdef TESTCDX
    GLOBAL LOGGING := TRUE AS LOGIC
    GLOBAL VALIDATETREE := FALSE AS LOGIC

#endif

BEGIN NAMESPACE XSharp.RDD
    // Inherits all standard DBF and Memo behavior
    // Only adds Order Handling
    /// <summary>DBFCDX RDD. For DBF/FPT/CDX.</summary>
    [DebuggerDisplay("DBFCDX ({Alias,nq})")];
    CLASS DBFCDX INHERIT DBFFPT
        INTERNAL _indexList  AS CdxOrderBagList
        INTERNAL PROPERTY CurrentOrder AS CdxTag GET _indexList:CurrentOrder
        VIRTUAL PROPERTY Driver  AS STRING GET "DBFCDX"
        INTERNAL PROPERTY MustForceRel AS LOGIC GET _RelInfoPending != NULL

        CONSTRUCTOR()
            SUPER()
            _indexList := CdxOrderBagList{SELF}
            RETURN

            #ifdef TESTCDX
            PUBLIC STATIC METHOD StartLogging AS VOID
                LOGGING := TRUE
                RETURN

            PUBLIC STATIC METHOD StopLogging AS VOID
                LOGGING := FALSE
                RETURN

            PUBLIC STATIC METHOD ValidateTree AS VOID
                VALIDATETREE := TRUE
                CoreDb.GoTop()
                VALIDATETREE := FALSE
                RETURN
            #endif

            #region Order Support

            VIRTUAL METHOD OrderCreate(orderInfo AS DbOrderCreateInfo ) AS LOGIC
                VAR result := SELF:_indexList:Create(orderInfo)
                if result
                    SELF:MarkDbfHeader(SELF:_FileName,TRUE)
                endif
                RETURN result

            VIRTUAL METHOD OrderDestroy(orderInfo AS DbOrderInfo ) AS LOGIC
                RETURN SELF:_indexList:Destroy(orderInfo)

            METHOD OrderCondition(info AS DbOrderCondInfo) AS LOGIC
                RETURN SUPER:OrderCondition(info)

            VIRTUAL METHOD OrderListAdd( orderInfo AS DbOrderInfo) AS LOGIC
                BEGIN LOCK SELF
                    SELF:GoCold()
                    LOCAL fullPath AS STRING
                    fullPath := orderInfo:BagName
                    IF File(fullPath)
                        fullPath := FPathName()
                    ELSEIF String.IsNullOrEmpty(Path.GetExtension(fullPath))
                        fullPath := Path.ChangeExtension(fullPath, CdxOrderBag.CDX_EXTENSION)
                        IF File(fullPath)
                            fullPath := FPathName()
                            orderInfo:BagName := fullPath
                        ENDIF
                    ENDIF
                    IF String.IsNullOrEmpty(System.IO.Path.GetDirectoryName(fullPath))
                        fullPath := System.IO.Path.Combine(System.IO.Path.GetDirectoryName(SELF:_FileName), fullPath)
                        orderInfo:BagName := fullPath
                    ENDIF
                    LOCAL lOk := FALSE AS LOGIC
                    IF SELF:_indexList:FindOrderBag(orderInfo:BagName) == NULL
                        lOk := SELF:_indexList:Add(orderInfo)
                    ELSE
                        // Already open, do nothing
                        lOk := TRUE
                    ENDIF
                    IF lOk
                        orderInfo:Order := 1
                        lOk := SELF:OrderListFocus(orderInfo)
                    ENDIF
                    RETURN lOk
                END LOCK

            METHOD _CloseAllIndexes(orderInfo AS DbOrderInfo, lCloseStructural AS LOGIC) AS LOGIC
                RETURN SELF:_indexList:Delete(orderInfo, lCloseStructural)

            VIRTUAL METHOD OrderListDelete(orderInfo AS DbOrderInfo) AS LOGIC
                BEGIN LOCK SELF
                    SELF:GoCold()
                    RETURN SELF:_CloseAllIndexes(orderInfo, FALSE)
                END LOCK

            VIRTUAL METHOD OrderListFocus(orderInfo AS DbOrderInfo) AS LOGIC
                BEGIN LOCK SELF
                    SELF:GoCold()
                    RETURN SELF:_indexList:Focus(orderInfo)
                END LOCK

            VIRTUAL METHOD OrderListRebuild() AS LOGIC
                BEGIN LOCK SELF
                    IF SELF:Shared
                        // Error !! Cannot be written !
                        SELF:_dbfError( ERDD.SHARED, XSharp.Gencode.EG_SHARED )
                        RETURN FALSE
                    ENDIF
                    IF SELF:_ReadOnly
                        SELF:_dbfError( ERDD.READONLY, XSharp.Gencode.EG_READONLY)
                        RETURN FALSE
                    ENDIF

                    SELF:GoCold()
                    RETURN SELF:_indexList:Rebuild()
                END LOCK

            OVERRIDE METHOD OrderInfo(nOrdinal AS DWORD , info AS DbOrderInfo ) AS OBJECT
                LOCAL result AS LONG
                LOCAL isOk := FALSE AS LOGIC
                LOCAL oBag := NULL AS CdxOrderBag
                result := 0
                SELF:_indexList:FindOrder(info, OUT VAR workOrder)
                IF ! String.IsNullOrEmpty(info:BagName)
                     oBag := SELF:_indexList:FindOrderBag(info:BagName)
                ENDIF

                IF workOrder == NULL .AND. info:IsEmpty
                    workOrder := SELF:CurrentOrder
                ENDIF

                BEGIN SWITCH nOrdinal
                CASE DBOI_DEFBAGEXT
                    info:Result := CdxOrderBag.CDX_EXTENSION
                CASE DBOI_CONDITION
                    IF workOrder != NULL
                        info:Result := workOrder:Condition
                    ELSE
                        info:Result := ""
                    ENDIF
                CASE DBOI_EXPRESSION
                    IF workOrder != NULL
                        info:Result := workOrder:Expression
                    ELSE
                        info:Result := ""
                    ENDIF
                CASE DBOI_ORDERCOUNT
                    IF oBag == NULL
                        info:Result := SELF:_indexList:Count
                    ELSE
                        info:Result := oBag:Tags:Count
                    ENDIF
                CASE DBOI_POSITION
                CASE DBOI_RECNO
                    VAR oState := SELF:_GetState()
                    IF workOrder == NULL
                        info:Result := SELF:RecNo
                    ELSE
                        isOk := workOrder:_getRecPos( REF result)
                        IF isOk
                            info:Result := result
                        ENDIF
                    ENDIF
                    SELF:_SetState(oState)
                CASE DBOI_KEYCOUNT
                    result := 0
                    VAR oState := SELF:_GetState()
                    IF workOrder != NULL
                        info:Result := 0
                        isOk := workOrder:_CountRecords(REF result)
                    ELSE
                        isOk := TRUE
                    ENDIF
                    IF isOk
                        info:Result := result
                    ENDIF
                    SELF:_SetState(oState)
                CASE DBOI_NUMBER
                    info:Result := SELF:_indexList:OrderPos(workOrder)
                CASE DBOI_BAGEXT
                    // according to the docs this should always return the default extension and not the actual extension
                    IF workOrder != NULL
                        info:Result := System.IO.Path.GetExtension(workOrder:OrderBag:FullPath)
                    ELSE
                        info:Result := CdxOrderBag.CDX_EXTENSION
                    ENDIF
                CASE DBOI_FULLPATH
                    IF workOrder != NULL
                        info:Result := workOrder:OrderBag:FullPath
                    ELSE
                        info:Result := String.Empty
                    ENDIF
                CASE DBOI_BAGCOUNT
                    info:Result := SELF:_indexList:BagCount
                CASE DBOI_BAGNAME
                    //CASE DBOI_INDEXNAME // alias
                    IF info:Order IS LONG VAR nOrder
                        info:Result := SELF:_indexList:BagName(nOrder)
                    ELSEIF workOrder != NULL
                        info:Result := workOrder:FileName
                    ELSE
                        info:Result :=String.Empty
                    ENDIF

                CASE DBOI_NAME
                    IF workOrder != NULL
                        info:Result := workOrder:_orderName
                    ELSE
                        info:Result := String.Empty
                    ENDIF
                CASE DBOI_COLLATION
                     info:Result := ""
                     IF workOrder != NULL
                        LOCAL collation as VfpCollation
                        collation := workOrder:Collation
                        if collation  != NULL
                            info:Result := collation:Name
                        ENDIF
                    ENDIF

                CASE DBOI_FILEHANDLE
                    IF workOrder != NULL
                        info:Result := workOrder:OrderBag:Handle
                    ELSE
                        info:Result := IntPtr.Zero
                    ENDIF
                CASE DBOI_FILESTREAM
                    IF workOrder != NULL
                        info:Result := workOrder:OrderBag:Stream
                    ELSE
                        info:Result := NULL
                    ENDIF
                CASE DBOI_ISDESC
                    IF workOrder != NULL
                        VAR oldValue  := workOrder:Descending
                        IF info:Result IS LOGIC VAR descend
                            workOrder:Descending := descend
                        ENDIF
                        info:Result := oldValue
                    ELSE
                        info:Result := FALSE
                    ENDIF
                CASE DBOI_ISCOND
                    IF workOrder != NULL
                        info:Result := workOrder:Conditional
                    ELSE
                        info:Result := FALSE
                    ENDIF
                CASE DBOI_KEYTYPE
                    IF workOrder != NULL
                        info:Result := workOrder:KeyType
                    ELSE
                        info:Result := 0
                    ENDIF
                CASE DBOI_KEYSIZE
                    IF workOrder != NULL
                        info:Result := workOrder:KeyLength
                    ELSE
                        info:Result := 0
                    ENDIF
                CASE DBOI_KEYDEC
                    info:Result := 0
                CASE DBOI_HPLOCKING
                    info:Result := FALSE
                CASE DBOI_UNIQUE
                    IF workOrder != NULL
                        info:Result := workOrder:Unique
                    ELSE
                        info:Result := FALSE
                    ENDIF
                CASE DBOI_LOCKOFFSET
                    IF workOrder != NULL
                        info:Result := workOrder:OrderBag:_LockOffSet
                    ELSE
                        info:Result := 0
                    ENDIF
                CASE DBOI_SETCODEBLOCK
                    IF workOrder != NULL
                        info:Result := workOrder:KeyCodeBlock
                    ENDIF
                CASE DBOI_KEYVAL
                    IF workOrder != NULL
                        isOk := TRUE
                        TRY
                            info:Result := SELF:EvalBlock(workOrder:KeyCodeBlock)
                        CATCH ex AS Exception
                            isOk := FALSE
                            SELF:_dbfError(ex, Subcodes.EDB_EXPRESSION, Gencode.EG_SYNTAX, "DBFCDX.OrderInfo")
                        END TRY
                        IF !isOk
                            info:Result := DBNull.Value
                        ENDIF
                    ELSE
                        info:Result := DBNull.Value
                    ENDIF
                CASE DBOI_SCOPETOPCLEAR
                CASE DBOI_SCOPEBOTTOMCLEAR
                    IF workOrder != NULL
                        workOrder:SetOrderScope(info:Result, (DbOrder_Info) nOrdinal)
                    ENDIF
                    info:Result := NULL
                CASE DBOI_SCOPETOP
                CASE DBOI_SCOPEBOTTOM
                    IF workOrder != NULL
                        LOCAL oldValue as OBJECT
                        IF nOrdinal == DBOI_SCOPETOP
                            oldValue := workOrder:TopScope
                        ELSEIF nOrdinal == DBOI_SCOPEBOTTOM
                            oldValue := workOrder:BottomScope
                        ELSE
                            oldValue := DBNull.Value
                        ENDIF
                        IF info:Result != NULL
                            workOrder:SetOrderScope(info:Result, (DbOrder_Info) nOrdinal)
                        ENDIF
                        info:Result := oldValue
                    ELSE
                        info:Result := DBNull.Value
                    ENDIF
                CASE DBOI_KEYADD
                    IF workOrder != NULL
                        info:Result := workOrder:AddKey(SELF:RecNo)
                    ELSE
                        info:Result := FALSE
                    ENDIF
                CASE DBOI_KEYDELETE
                    IF workOrder != NULL
                        info:Result := workOrder:DeleteKey(SELF:RecNo)
                    ELSE
                        info:Result := FALSE
                    ENDIF
                CASE DBOI_CUSTOM
                    IF workOrder != NULL
                        LOCAL lOld AS LOGIC
                        lOld := workOrder:Custom
                        IF info:Result IS LOGIC VAR custom
                            IF custom
                                workOrder:SetCustom()
                            ENDIF
                        ENDIF
                        info:Result := lOld
                    ELSE
                        info:Result := FALSE
                    ENDIF

                CASE DBOI_USER + 42
                CASE DBOI_DUMP
                    // Dump Cdx to Txt file
                    VAR oState := SELF:_GetState()
                    IF workOrder != NULL
                        workOrder:_dump()
                    ENDIF
                    SELF:_SetState(oState)
                CASE DBOI_VALIDATE
                    // Validate integrity of the current Order
                     VAR oState := SELF:_GetState()
                    IF workOrder != NULL
                        info:Result := workOrder:_validate()
                    ENDIF
                    SELF:_SetState(oState)
                OTHERWISE
                    SUPER:OrderInfo(nOrdinal, info)
                END SWITCH
                RETURN info:Result

            #endregion
        #region relations
        METHOD ForceRel() AS LOGIC
            LOCAL isOk    := TRUE AS LOGIC
            IF SELF:_RelInfoPending != NULL
                // Save the current context
                LOCAL currentRelation := SELF:_RelInfoPending AS DbRelInfo
                SELF:_RelInfoPending := NULL
                VAR oParent := (DBFCDX) currentRelation:Parent
                IF oParent:EoF
                    //
                    isOk := SELF:GoTo( 0 )
                ELSE
                    isOk := SELF:RelEval( currentRelation )

                    IF isOk .AND. !((DBFCDX)currentRelation:Parent):EoF
                        TRY
                            LOCAL seekInfo AS DbSeekInfo
                            seekInfo := DbSeekInfo{}
                            seekInfo:Value := SELF:_EvalResult
                            seekInfo:SoftSeek := FALSE
                            isOk := SELF:Seek(seekInfo)

                        CATCH ex AS InvalidCastException
                            SELF:_dbfError(ex, Subcodes.ERDD_DATATYPE,Gencode.EG_DATATYPE,  "DBFNTX.ForceRel")

                        END TRY
                    ENDIF
                ENDIF
            ENDIF

            RETURN isOk
            #endregion
        #region Pack, Zap
        METHOD Pack() AS LOGIC
            LOCAL isOk AS LOGIC

            isOk := SUPER:Pack()
            IF isOk
                isOk := SELF:OrderListRebuild()
            ENDIF
            RETURN isOk

        PUBLIC METHOD Zap() AS LOGIC
            LOCAL isOk AS LOGIC

            isOk := SUPER:Zap()
            IF isOk
                isOk := SELF:OrderListRebuild()
            ENDIF
            RETURN isOk

        #endregion

        #region Open, Close, Create

        PUBLIC OVERRIDE METHOD Close() AS LOGIC
            LOCAL orderInfo AS DbOrderInfo
            BEGIN LOCK SELF
                SELF:GoCold()
                orderInfo := DbOrderInfo{}
                orderInfo:AllTags := TRUE
                SELF:_CloseAllIndexes(orderInfo, TRUE)
                RETURN SUPER:Close()
            END LOCK

        PUBLIC OVERRIDE METHOD Create( openInfo AS DbOpenInfo ) AS LOGIC
            LOCAL isOk AS LOGIC
            LOCAL lMemo := FALSE AS LOGIC
            isOk := SUPER:Create(openInfo)
            IF isOk
                FOREACH VAR fld IN SELF:_Fields
                    IF fld:FieldType:IsMemo()
                        lMemo := TRUE
                        EXIT
                    ENDIF
                NEXT
                LOCAL cIndex AS STRING
                // Delete index because it may have incorrect index expressions
                cIndex := System.IO.Path.ChangeExtension(SELF:FullPath, ".CDX")
                IF System.IO.File.Exists(cIndex)
                    System.IO.File.Delete(cIndex)
                ENDIF
                IF lMemo
                    SELF:_Header:Version := DBFVersion.FoxPro2WithMemo
                ELSE
                    SELF:_Header:Version := DBFVersion.FoxBaseDBase3NoMemo
                ENDIF
            ENDIF
            RETURN isOk

        METHOD Open(info AS DbOpenInfo) AS LOGIC
            LOCAL lOk AS LOGIC
            lOk := SUPER:Open(info)
            IF lOk
                // Open structural index
                IF RuntimeState.AutoOpen
                    SELF:OpenProductionIndex(info)
                ENDIF
                SELF:GoTop()
            ENDIF
            RETURN lOk

        PROTECTED METHOD OpenProductionIndex(info AS DbOpenInfo) AS VOID
            VAR cExt  := CdxOrderBag.GetIndexExtFromDbfExt(info:FileName)
            IF ! String.IsNullOrEmpty(cExt)
                VAR cCdxFileName := System.IO.Path.ChangeExtension(info:FileName, cExt)
                IF System.IO.File.Exists(cCdxFileName)
                    LOCAL orderinfo := DbOrderInfo{} AS DbOrderInfo
                    orderinfo:BagName := cCdxFileName
                    SELF:_indexList:Add(orderinfo, TRUE)
                ENDIF
                SELF:MarkDbfHeader(info:FileName,FALSE)
            ENDIF
         PROTECTED METHOD MarkDbfHeader(cFileName as STRING, lForce as LOGIC) AS VOID
            VAR cExt  := CdxOrderBag.GetIndexExtFromDbfExt(cFileName)
            IF ! String.IsNullOrEmpty(cExt)
                VAR cCdxFileName := System.IO.Path.ChangeExtension(cFileName, cExt)
                var wanted := SELF:Header:TableFlags
                IF System.IO.File.Exists(cCdxFileName)
                    wanted |= DBFTableFlags.HasStructuralCDX
                ELSE
                    wanted &= _NOT(DBFTableFlags.HasStructuralCDX)
                ENDIF
                IF wanted != SELF:Header:TableFlags .or. lForce
                    IF ! SELF:_ReadOnly
                        SELF:Header:TableFlags := wanted
                        SELF:Header:Write()
                    ENDIF
                ENDIF
            ENDIF

        #endregion

        #REGION Move

        INTERNAL METHOD ReadRecord() AS LOGIC
            RETURN SELF:_readRecord()

        PUBLIC METHOD Seek(seekInfo AS DbSeekInfo ) AS LOGIC
            LOCAL isOk AS LOGIC

            isOk := FALSE
            BEGIN LOCK SELF
                VAR index := SELF:CurrentOrder
                IF index != NULL
                    isOk := index:Seek(seekInfo)
                ENDIF
                IF  !isOk
                    SELF:_dbfError(Subcodes.ERDD_DATATYPE, Gencode.EG_NOORDER )
                ENDIF
                SELF:_CheckEofBof()
            END LOCK
            RETURN isOk

        PUBLIC METHOD GoBottom() AS LOGIC
            BEGIN LOCK SELF
                LOCAL result AS LOGIC
                IF SELF:CurrentOrder != NULL
                    result := SELF:CurrentOrder:GoBottom()
                    if (! result)
                        SELF:_SetEOF(TRUE)
                        SELF:_SetBOF(TRUE)
                    ELSE
                        SELF:_CheckEofBof()
                    ENDIF
                ELSE
                    result := SUPER:GoBottom()
                ENDIF
                RETURN result
            END LOCK

        PUBLIC METHOD GoTop() AS LOGIC
            BEGIN LOCK SELF
                LOCAL result AS LOGIC
                IF SELF:CurrentOrder != NULL
                    result := SELF:CurrentOrder:GoTop()
                    if (! result)
                        SELF:_SetEOF(TRUE)
                        SELF:_SetBOF(TRUE)
                        result := TRUE
                    ELSE
                        SELF:_CheckEofBof()
                    ENDIF
                ELSE
                    result := SUPER:GoTop()
                ENDIF
                RETURN result
            END LOCK

        METHOD __Goto(nRec AS LONG) AS LOGIC
            // Skip without reset of topstack
            RETURN SUPER:GoTo(nRec)

        METHOD GoTo(nRec AS LONG) AS LOGIC
            LOCAL result AS LOGIC
            SELF:GoCold()
            IF SELF:CurrentOrder != NULL
                SELF:CurrentOrder:ClearStack() // force to reseek later
            ENDIF
            result := SUPER:GoTo(nRec)
            RETURN result

        PUBLIC METHOD SkipRaw( move AS LONG ) AS LOGIC
            BEGIN LOCK SELF
                LOCAL result AS LOGIC
                IF SELF:CurrentOrder != NULL
                    result := SELF:CurrentOrder:SkipRaw(move)
                    SELF:_CheckEofBof()
                ELSE
                    result := SUPER:SkipRaw(move)
                ENDIF
                RETURN result
            END LOCK

        #ENDREGION

        #REGION GoCold, GoHot, Flush
        PUBLIC OVERRIDE METHOD GoCold() AS LOGIC
            LOCAL isOk AS LOGIC

            isOk := TRUE
            BEGIN LOCK SELF
                IF !SELF:IsHot
                    RETURN isOk
                ENDIF
                isOk := SELF:_indexList:GoCold()
                IF !isOk
                    RETURN isOk
                ENDIF
                RETURN SUPER:GoCold()
            END LOCK

        PUBLIC OVERRIDE METHOD GoHot() AS LOGIC
            LOCAL isOk AS LOGIC

            isOk := TRUE
            BEGIN LOCK SELF
                isOk := SUPER:GoHot()
                IF !isOk
                    RETURN isOk
                ENDIF
                RETURN SELF:_indexList:GoHot()
            END LOCK

        PUBLIC OVERRIDE METHOD Flush() AS LOGIC
            LOCAL isOk AS LOGIC

            isOk := TRUE
            BEGIN LOCK SELF
                isOk := SUPER:Flush()
                RETURN SELF:_indexList:Flush() .AND. isOk
            END LOCK

        #endregion

        INTERNAL METHOD _GetState() AS CdxState
            RETURN CdxState{} {EoF := SELF:EoF, BoF := SELF:BoF, RecNo := SELF:RecNo}

        INTERNAL METHOD _SetState(oState AS CdxState) AS VOID
            SELF:__Goto(oState:RecNo)
            SELF:_SetEOF(oState:EoF)
            SELF:_SetBOF(oState:BoF)

        INTERNAL CLASS CdxState
            PROPERTY EoF AS LOGIC AUTO
            PROPERTY BoF AS LOGIC AUTO
            PROPERTY RecNo AS LONG AUTO
        END CLASS
    END CLASS

END NAMESPACE