/*
 * ScriptBinder.cs --
 *
 * Copyright (c) 2007-2012 by Joe Mistachkin.  All rights reserved.
 *
 * See the file "license.terms" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * RCS: @(#) $Id: $
 */

using System;
using System.Globalization;
using System.Reflection;
using Eagle._Attributes;
using Eagle._Components.Public;
using Eagle._Components.Public.Delegates;
using Eagle._Containers.Private;
using Eagle._Containers.Public;
using Eagle._Interfaces.Public;

namespace Eagle._Components.Private
{
    //
    // HACK: This class derives from the Binder type solely for the purpose
    //       of allowing other components to pass it around in place of an
    //       actual Binder object.  The .NET Framework should have defined
    //       a formal IBinder interface and had the built-in Binder classes
    //       implement it.  The methods of the base class are never called.
    //       When this class needs to fallback to "default" binding behavior,
    //       it uses the Binder object contained in the "binder" field of
    //       this class, not the methods of the base class.
    //
    [ObjectId("0e087802-e964-4900-b687-79bbc4332079")]
    internal sealed class ScriptBinder : Binder, IScriptBinder, IHaveInterpreter
    {
        #region Private Data
        //
        // NOTE: What interpreter do we belong to?
        //
        private Interpreter interpreter;

        //
        // NOTE: What is our fallback binder?
        //
        private IBinder fallbackBinder;

        //
        // NOTE: What is our parent binder?  This will almost always be null
        //       for the default IScriptBinder implementation.
        //
        private IScriptBinder parentBinder;

        //
        // NOTE: Is this binder operating in "debug" mode?
        //
        private bool debug;

        //
        // NOTE: What dynamic string-to-type conversions do we support?
        //
        private TypeChangeTypeCallbackDictionary changeTypes;

        //
        // NOTE: What dynamic type-to-string conversions do we support?
        //
        private TypeToStringCallbackDictionary toStringTypes;
        #endregion

        ///////////////////////////////////////////////////////////////////////////////////////////////

        #region Private Constructors
        private ScriptBinder()
        {
            changeTypes = new TypeChangeTypeCallbackDictionary(
                ConversionOps.Dynamic.ChangeTypes);

            toStringTypes = new TypeToStringCallbackDictionary(
                ConversionOps.Dynamic.ToStringTypes);
        }
        #endregion

        ///////////////////////////////////////////////////////////////////////////////////////////////

        #region Public Constructors
        public ScriptBinder(
            Interpreter interpreter,
            IBinder fallbackBinder, /* MAY BE NULL */
            bool debug
            )
            : this()
        {
            this.interpreter = interpreter;
            this.fallbackBinder = fallbackBinder;
            this.debug = debug;
        }
        #endregion

        ///////////////////////////////////////////////////////////////////////////////////////////////

        #region Private Static Methods
        private static IClientData GetBinderClientData()
        {
            IAnyPair<Interpreter, IClientData> pair =
                Interpreter.GetActivePair(typeof(BinderClientData));

            return (pair != null) ? pair.Y : null;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        private static void GetBinderClientData(
            out OptionDictionary options,
            out IClientData clientData
            )
        {
            BinderClientData binderClientData =
                GetBinderClientData() as BinderClientData;

            if (binderClientData != null)
            {
                options = binderClientData.Options;
                clientData = binderClientData.ClientData;
            }
            else
            {
                options = null;
                clientData = null;
            }
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        private static ReturnCode InvokeToStringCallback(
            ToStringCallback callback,
            Interpreter interpreter,
            Type type,
            object value,
            OptionDictionary options,
            CultureInfo cultureInfo,
            IClientData clientData,
            ref string text,
            ref Result error
            )
        {
            try
            {
                if (callback != null)
                    return callback.Invoke(
                        interpreter, type, value, options, cultureInfo,
                        clientData, ref text, ref error);
                else
                    error = "invalid callback";
            }
            catch (Exception e)
            {
                error = e;
            }

            return ReturnCode.Error;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        private static ReturnCode InvokeChangeTypeCallback(
            ChangeTypeCallback callback,
            Interpreter interpreter,
            Type type,
            string text,
            OptionDictionary options,
            CultureInfo cultureInfo,
            IClientData clientData,
            ref object value,
            ref Result error
            )
        {
            try
            {
                if (callback != null)
                    return callback.Invoke(
                        interpreter, type, text, options, cultureInfo,
                        clientData, ref value, ref error);
                else
                    error = "invalid callback";
            }
            catch (Exception e)
            {
                error = e;
            }

            return ReturnCode.Error;
        }
        #endregion

        ///////////////////////////////////////////////////////////////////////////////////////////////

        #region Private Properties
        private static Binder DefaultBinder /* CANNOT RETURN NULL */
        {
            get { return Type.DefaultBinder; }
        }
        #endregion

        ///////////////////////////////////////////////////////////////////////////////////////////////

        #region IGetInterpreter / ISetInterpreter Members
        public Interpreter Interpreter
        {
            get { return interpreter; }
            set { interpreter = value; }
        }
        #endregion

        ///////////////////////////////////////////////////////////////////////////////////////////////

        #region IScriptBinder Members
        public IBinder FallbackBinder
        {
            get { return fallbackBinder; }
            set { fallbackBinder = value; }
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public IScriptBinder ParentBinder
        {
            get { return parentBinder; }
            set { parentBinder = value; }
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public bool Debug
        {
            get { return debug; }
            set { debug = value; }
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public bool DoesMatchType(
            object value,
            Type type
            )
        {
            if (type != null)
            {
                if (value != null)
                {
                    Type objectType = value.GetType();

                    return MarshalOps.IsSameReferenceType(objectType, type) ||
                        MarshalOps.IsSameValueType(objectType, type);
                }
                else if (!MarshalOps.IsValueType(type) || MarshalOps.IsNullableType(type))
                {
                    return true;
                }
            }

            return false;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public bool IsCoreCallback(
            Delegate callback
            )
        {
            return IsCoreToStringCallback(callback as ToStringCallback) ||
                IsCoreChangeTypeCallback(callback as ChangeTypeCallback);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public bool IsCoreStringListToStringCallback(
            ToStringCallback callback
            )
        {
            //
            // HACK: There are currently no custom ToString callbacks defined
            //       (i.e. in the ConversionOps.Dynamic._ToString class).
            //
            return false;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public bool IsCoreStringListChangeTypeCallback(
            ChangeTypeCallback callback
            )
        {
            //
            // HACK: There is only one method that handles the conversion to a
            //       StringList.
            //
            return (callback == ConversionOps.Dynamic.ChangeType.ToStringList);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public bool HasToStringTypes()
        {
            return (toStringTypes != null);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public ReturnCode ListToStrings(
            ref TypeList types,
            ref Result error
            )
        {
            if (this.toStringTypes == null)
            {
                error = "types not available";
                return ReturnCode.Error;
            }

            types = new TypeList(this.toStringTypes.Keys);
            return ReturnCode.Ok;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public bool IsCoreToStringCallback(
            ToStringCallback callback
            )
        {
            if (callback != null)
            {
                MethodInfo methodInfo = callback.Method;

                if (methodInfo != null)
                {
                    Type type = methodInfo.DeclaringType;

                    if (type == typeof(ConversionOps.Dynamic._ToString))
                        return true;
                }
            }

            return false;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public bool HasToStringCallback(
            Type type,
            bool primitive
            )
        {
            ToStringCallback callback = null;

            return HasToStringCallback(type, primitive, ref callback);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public bool HasToStringCallback(
            Type type,
            bool primitive,
            ref ToStringCallback callback
            )
        {
            if (type != null)
            {
                if (toStringTypes != null)
                {
                    if (toStringTypes.TryGetValue(type, out callback))
                    {
                        if (primitive)
                        {
                            //
                            // NOTE: If the callback is null then the type entry is
                            //       simply ignored (i.e. because it is invalid).
                            //       Also, if the callback is not implemented by the
                            //       built-in dynamic type conversions class, it is
                            //       not considered to be a primitive type.
                            //
                            if ((callback != null) &&
                                IsCoreToStringCallback(callback))
                            {
                                return true;
                            }
                        }
                        else if (callback != null)
                        {
                            return true;
                        }
                    }
                }
            }

            return false;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public ReturnCode AddToStringCallback(
            Type type,
            ToStringCallback callback,
            ref Result error
            )
        {
            if (toStringTypes == null)
            {
                error = "types not available";
                return ReturnCode.Error;
            }

            if (type == null)
            {
                error = "invalid type";
                return ReturnCode.Error;
            }

            if (callback == null)
            {
                error = "invalid callback";
                return ReturnCode.Error;
            }

            if (toStringTypes.ContainsKey(type))
            {
                error = "type already exists";
                return ReturnCode.Error;
            }

            toStringTypes.Add(type, callback);
            return ReturnCode.Ok;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public ReturnCode RemoveToStringCallback(
            Type type,
            ref Result error
            )
        {
            if (toStringTypes == null)
            {
                error = "types not available";
                return ReturnCode.Error;
            }

            if (type == null)
            {
                error = "invalid type";
                return ReturnCode.Error;
            }

            if (!toStringTypes.ContainsKey(type))
            {
                error = "type not found";
                return ReturnCode.Error;
            }

            toStringTypes.Remove(type);
            return ReturnCode.Ok;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public ReturnCode InvokeToStringCallback(
            ToStringCallback callback,
            Type type,
            object value,
            OptionDictionary options,
            CultureInfo cultureInfo,
            IClientData clientData,
            ref string text,
            ref Result error
            )
        {
            return InvokeToStringCallback(
                callback, interpreter, type, value, options, cultureInfo,
                clientData, ref text, ref error);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public ReturnCode ToString(
            IChangeTypeData changeTypeData,
            ref Result error
            )
        {
            ReturnCode code = ReturnCode.Ok;

            if (changeTypeData != null)
            {
                //
                // NOTE: Grab the primary data we need from the helper object.
                //
                Type type = changeTypeData.Type;
                object value = changeTypeData.OldValue;
                OptionDictionary options = changeTypeData.Options;
                CultureInfo cultureInfo = changeTypeData.CultureInfo;
                IClientData clientData = changeTypeData.ClientData;

                //
                // NOTE: This will be reused whenever we need to lookup and/or
                //       invoke a ToString callback.
                //
                ToStringCallback callback = null;

                //
                // NOTE: See if there is a ToString callback for the specified
                //       type.  If so, invoke it.
                //
                if ((code == ReturnCode.Ok) &&
                    HasToStringCallback(type, false, ref callback))
                {
                    changeTypeData.Attempted = true;

                    string text = null;

                    code = InvokeToStringCallback(
                        callback, type, value, options, cultureInfo,
                        clientData, ref text, ref error);

                    if (code == ReturnCode.Ok)
                    {
                        value = text;
                        changeTypeData.Converted = true;
                    }
                }

                //
                // NOTE: Store the new value back into the helper object.
                //
                changeTypeData.NewValue = value;

                //
                // NOTE: Do we consider the type conversions performed, if any, to
                //       be a success (i.e. do the types more-or-less match now)?
                //
                changeTypeData.DoesMatch = DoesMatchType(value, typeof(string));

                //
                // NOTE: In debug mode, show some diagnostic output.
                //
                if (debug)
                {
                    object oldValue = changeTypeData.OldValue;
                    object newValue = changeTypeData.NewValue;

                    TraceOps.DebugTrace(String.Format(
                        "{0}: ToString: caller = {1}, oldValue = {2}, " +
                        "oldType = {3}, newValue = {4}, newType = {5}, " +
                        "fromType = {6}, options = {7}, cultureInfo = {8}, " +
                        "clientData = {9}, callback = {10}, " +
                        "wasObject = {11}, attempted = {12}, " +
                        "converted = {13}, doesMatch = {14}, code = {15}, " +
                        "error = {16}{17}", GlobalState.GetCurrentThreadId(),
                        FormatOps.DisplayString(changeTypeData.Caller),
                        FormatOps.WrapOrNull(true, true, oldValue),
                        FormatOps.WrapOrNull((oldValue != null) ? oldValue.GetType() : null),
                        FormatOps.WrapOrNull(true, true, newValue),
                        FormatOps.WrapOrNull((newValue != null) ? newValue.GetType() : null),
                        FormatOps.WrapOrNull(type), FormatOps.WrapOrNull(true, true, options),
                        FormatOps.WrapOrNull(cultureInfo), FormatOps.WrapOrNull(clientData),
                        FormatOps.WrapOrNull(FormatOps.DelegateName(callback)),
                        changeTypeData.WasObject, changeTypeData.Attempted,
                        changeTypeData.Converted, changeTypeData.DoesMatch, code,
                        FormatOps.WrapOrNull(true, true, error), Environment.NewLine),
                        typeof(ScriptBinder).Name);
                }
            }
            else
            {
                error = "invalid change type data";
                code = ReturnCode.Error;
            }

            return code;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public bool HasChangeTypes()
        {
            return (changeTypes != null);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public ReturnCode ListChangeTypes(
            ref TypeList types,
            ref Result error
            )
        {
            if (this.changeTypes == null)
            {
                error = "types not available";
                return ReturnCode.Error;
            }

            types = new TypeList(this.changeTypes.Keys);
            return ReturnCode.Ok;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public bool IsCoreChangeTypeCallback(
            ChangeTypeCallback callback
            )
        {
            if (callback != null)
            {
                MethodInfo methodInfo = callback.Method;

                if (methodInfo != null)
                {
                    Type type = methodInfo.DeclaringType;

                    if (type == typeof(ConversionOps.Dynamic.ChangeType))
                        return true;
                }
            }

            return false;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public bool HasChangeTypeCallback(
            Type type,
            bool primitive
            )
        {
            ChangeTypeCallback callback = null;

            return HasChangeTypeCallback(type, primitive, ref callback);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public bool HasChangeTypeCallback(
            Type type,
            bool primitive,
            ref ChangeTypeCallback callback
            )
        {
            if (type != null)
            {
                if (changeTypes != null)
                {
                    if (changeTypes.TryGetValue(type, out callback))
                    {
                        if (primitive)
                        {
                            //
                            // NOTE: If the callback is null then the type entry is
                            //       simply ignored (i.e. because it is invalid).
                            //       Also, if the callback deals with opaque object
                            //       or interpreter handles, or it is not implemented
                            //       by the built-in dynamic type conversion class,
                            //       it is not considered to be a primitive type.
                            //
                            if ((callback != null) &&
                                (callback != ConversionOps.Dynamic.ChangeType.ToObject) &&
                                (callback != ConversionOps.Dynamic.ChangeType.ToInterpreter) &&
                                IsCoreChangeTypeCallback(callback))
                            {
                                return true;
                            }
                        }
                        else if (callback != null)
                        {
                            return true;
                        }
                    }
                }
            }

            return false;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public ReturnCode AddChangeTypeCallback(
            Type type,
            ChangeTypeCallback callback,
            ref Result error
            )
        {
            if (changeTypes == null)
            {
                error = "types not available";
                return ReturnCode.Error;
            }

            if (type == null)
            {
                error = "invalid type";
                return ReturnCode.Error;
            }

            if (callback == null)
            {
                error = "invalid callback";
                return ReturnCode.Error;
            }

            if (changeTypes.ContainsKey(type))
            {
                error = "type already exists";
                return ReturnCode.Error;
            }

            changeTypes.Add(type, callback);
            return ReturnCode.Ok;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public ReturnCode RemoveChangeTypeCallback(
            Type type,
            ref Result error
            )
        {
            if (changeTypes == null)
            {
                error = "types not available";
                return ReturnCode.Error;
            }

            if (type == null)
            {
                error = "invalid type";
                return ReturnCode.Error;
            }

            if (!changeTypes.ContainsKey(type))
            {
                error = "type not found";
                return ReturnCode.Error;
            }

            changeTypes.Remove(type);
            return ReturnCode.Ok;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public ReturnCode InvokeChangeTypeCallback(
            ChangeTypeCallback callback,
            Type type,
            string text,
            OptionDictionary options,
            CultureInfo cultureInfo,
            IClientData clientData,
            ref object value,
            ref Result error
            )
        {
            return InvokeChangeTypeCallback(
                callback, interpreter, type, text, options, cultureInfo,
                clientData, ref value, ref error);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public ReturnCode ChangeType(
            IChangeTypeData changeTypeData, /* in, out */
            ref Result error                /* out */
            )
        {
            ReturnCode code = ReturnCode.Ok;

            if (changeTypeData != null)
            {
                //
                // NOTE: Grab the primary data we need from the helper object.
                //
                Type type = changeTypeData.Type;
                object value = changeTypeData.OldValue;
                OptionDictionary options = changeTypeData.Options;
                CultureInfo cultureInfo = changeTypeData.CultureInfo;
                IClientData clientData = changeTypeData.ClientData;

                //
                // NOTE: This will be reused whenever we need to lookup and/or invoke
                //       a ChangeType callback.
                //
                ChangeTypeCallback callback = null;

                //
                // NOTE: First, try to find an underlying object based on the string,
                //       which may be an opaque object handle.
                //
                if ((code == ReturnCode.Ok) && (value is string) &&
                    !changeTypeData.NoHandle &&
                    HasChangeTypeCallback(typeof(object), false, ref callback) &&
                    (interpreter != null) &&
                    (interpreter.DoesObjectExist((string)value) == ReturnCode.Ok))
                {
                    changeTypeData.Attempted = true;

                    code = InvokeChangeTypeCallback(
                        callback, type, (string)value, options, cultureInfo,
                        clientData, ref value, ref error);

                    if (code == ReturnCode.Ok)
                        changeTypeData.WasObject = true;
                }

                //
                // NOTE: Next, see if the underlying object value requires further
                //       conversion.
                //
                if ((code == ReturnCode.Ok) && (value is string) &&
                    (type != typeof(object)) &&
                    HasChangeTypeCallback(type, false, ref callback))
                {
                    changeTypeData.Attempted = true;

                    code = InvokeChangeTypeCallback(
                        callback, type, (string)value, options, cultureInfo,
                        clientData, ref value, ref error);

                    if (code == ReturnCode.Ok)
                        changeTypeData.Converted = true;
                }
                else if ((code == ReturnCode.Ok) && (value is string) &&
                    (type != null) && MarshalOps.IsEnumType(type, true, true) &&
                    HasChangeTypeCallback(typeof(Enum), false, ref callback))
                {
                    changeTypeData.Attempted = true;

                    code = InvokeChangeTypeCallback(
                        callback, type, (string)value, options, cultureInfo,
                        clientData, ref value, ref error);

                    if (code == ReturnCode.Ok)
                        changeTypeData.Converted = true;
                }
                else if ((code == ReturnCode.Ok) && (value is string) &&
                    (type != null) && MarshalOps.IsPrimitiveType(type, true) &&
                    HasChangeTypeCallback(typeof(ValueType), false, ref callback))
                {
                    changeTypeData.Attempted = true;

                    code = InvokeChangeTypeCallback(
                        callback, type, (string)value, options, cultureInfo,
                        clientData, ref value, ref error);

                    if (code == ReturnCode.Ok)
                        changeTypeData.Converted = true;
                }
                else if ((code == ReturnCode.Ok) && (value is string) &&
                    ConversionOps.IsDelegateType(type, false) &&
                    HasChangeTypeCallback(typeof(Delegate), false, ref callback))
                {
                    changeTypeData.Attempted = true;

                    code = InvokeChangeTypeCallback(
                        callback, type, (string)value, options, cultureInfo,
                        clientData, ref value, ref error);

                    if (code == ReturnCode.Ok)
                        changeTypeData.Converted = true;
                }
                else if ((code == ReturnCode.Ok) && (value is string) &&
                    (type != null))
                {
                    try
                    {
                        //
                        // NOTE: Try looking up an implicit operator that will
                        //       convert the string to the requested type.
                        //
                        MethodInfo methodInfo = type.GetMethod(
                            MarshalOps.ImplicitOperatorMethodName,
                            new Type[] { typeof(string) });

                        //
                        // NOTE: Failing that, try looking up an explicit operator
                        //       that will convert the string to the requested type.
                        //
                        if (methodInfo == null)
                            methodInfo = type.GetMethod(
                                MarshalOps.ExplicitOperatorMethodName,
                                new Type[] { typeof(string) });

                        //
                        // NOTE: Failing that, if this is a reference type, get the
                        //       "element type" and try to lookup an implicit and/or
                        //       explicit operator in that context.
                        //
                        if ((methodInfo == null) && type.IsByRef)
                        {
                            Type byRefElementType = type.GetElementType();

                            if (byRefElementType != null)
                            {
                                methodInfo = byRefElementType.GetMethod(
                                    MarshalOps.ImplicitOperatorMethodName,
                                    new Type[] { typeof(string) });

                                if (methodInfo == null)
                                    methodInfo = byRefElementType.GetMethod(
                                        MarshalOps.ExplicitOperatorMethodName,
                                        new Type[] { typeof(string) });
                            }
                        }

                        //
                        // NOTE: Did we find an operator method to use?
                        //
                        if (methodInfo != null)
                        {
                            changeTypeData.Attempted = true;

                            value = methodInfo.Invoke(
                                null /* static */, new object[] { value });

                            changeTypeData.Converted = true;
                        }
                    }
                    catch (Exception e)
                    {
                        error = e;
                        code = ReturnCode.Error;
                    }
                }

                //
                // NOTE: Store the new value back into the helper object.
                //
                changeTypeData.NewValue = value;

                //
                // NOTE: Do we consider the type conversions performed, if any, to
                //       be a success (i.e. do the types more-or-less match now)?
                //
                changeTypeData.DoesMatch = DoesMatchType(value, type);

                //
                // NOTE: In debug mode, show some diagnostic output.
                //
                if (debug)
                {
                    object oldValue = changeTypeData.OldValue;
                    object newValue = changeTypeData.NewValue;

                    TraceOps.DebugTrace(String.Format(
                        "{0}: ChangeType: caller = {1}, oldValue = {2}, " +
                        "oldType = {3}, newValue = {4}, newType = {5}, " +
                        "toType = {6}, options = {7}, cultureInfo = {8}, " +
                        "clientData = {9}, callback = {10}, " +
                        "wasObject = {11}, attempted = {12}, " +
                        "converted = {13}, doesMatch = {14}, code = {15}, " +
                        "error = {16}{17}", GlobalState.GetCurrentThreadId(),
                        FormatOps.DisplayString(changeTypeData.Caller),
                        FormatOps.WrapOrNull(true, true, oldValue),
                        FormatOps.WrapOrNull((oldValue != null) ? oldValue.GetType() : null),
                        FormatOps.WrapOrNull(true, true, newValue),
                        FormatOps.WrapOrNull((newValue != null) ? newValue.GetType() : null),
                        FormatOps.WrapOrNull(type), FormatOps.WrapOrNull(true, true, options),
                        FormatOps.WrapOrNull(cultureInfo), FormatOps.WrapOrNull(clientData),
                        FormatOps.WrapOrNull(FormatOps.DelegateName(callback)),
                        changeTypeData.WasObject, changeTypeData.Attempted,
                        changeTypeData.Converted, changeTypeData.DoesMatch, code,
                        FormatOps.WrapOrNull(true, true, error), Environment.NewLine),
                        typeof(ScriptBinder).Name);
                }
            }
            else
            {
                error = "invalid change type data";
                code = ReturnCode.Error;
            }

            return code;
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public ReturnCode SelectMethodIndex(
            Type type,
            CultureInfo cultureInfo,
            TypeList parameterTypes,
            MethodBase[] methods,
            object[] args,
            IntList methodIndexList,
            ObjectArrayList argsList,
            ref int methodIndex,
            ref Result error
            )
        {
            //
            // FIXME: For now, always fallback to the default behavior, which is to
            //        select the first method that matches.  More sophisticated logic
            //        may need to be added here later.
            //
            return ReturnCode.Continue;
        }
        #endregion

        ///////////////////////////////////////////////////////////////////////////////////////////////

        #region Binder / IScriptBinder Members
        public override FieldInfo BindToField(
            BindingFlags bindingAttr,
            FieldInfo[] match,
            object value,
            CultureInfo culture
            )
        {
            //
            // HACK: Mono may not be passing something valid to us.
            //
            if (match != null)
            {
                FieldInfo fieldInfo = null;
                ReturnCode code = ReturnCode.Ok;
                Result error = null;

                //
                // NOTE: These are the options and client data for the
                //       current [object invoke] (etc) call.
                //
                OptionDictionary options;
                IClientData clientData;

                //
                // NOTE: Attempt to fetch the binder data from the active
                //       interpreter stack.
                //
                GetBinderClientData(out options, out clientData);

                //
                // NOTE: Check each potential field for a match with a
                //       compatible type.
                //
                for (int matchIndex = 0; matchIndex < match.Length; matchIndex++)
                {
                    FieldInfo thisFieldInfo = match[matchIndex];

                    if (thisFieldInfo != null)
                    {
                        //
                        // NOTE: Create our helper object to hold all the necessary
                        //       input and output parameters necessary for the type
                        //       conversions.
                        //
                        IChangeTypeData changeTypeData = new ChangeTypeData(
                            "IBinder.BindToField", thisFieldInfo.FieldType, value,
                            options, culture, clientData, false);

                        //
                        // NOTE: Try to change the type of the value.
                        //
                        code = ChangeType(changeTypeData, ref error);

                        //
                        // NOTE: Did we succeed AND did we actually do something?
                        //
                        if ((code == ReturnCode.Ok) &&
                            (changeTypeData.WasObject || changeTypeData.Converted))
                        {
                            //
                            // NOTE: If we translated an opaque object handle or
                            //       converted the object value to another type,
                            //       we must count that as a matched field.
                            //
                            fieldInfo = thisFieldInfo;
                            break;
                        }
                        else if (code != ReturnCode.Ok)
                        {
                            break;
                        }
                    }
                }

                //
                // NOTE: Did we find a matching field?
                //
                if ((code == ReturnCode.Ok) && (fieldInfo != null))
                {
                    return fieldInfo;
                }
                else if (code != ReturnCode.Ok)
                {
                    throw new ScriptException(code, error);
                }
            }

            IBinder binder = FallbackBinder;

            if (binder != null)
                return binder.BindToField(bindingAttr, match, value, culture);
            else
                return DefaultBinder.BindToField(bindingAttr, match, value, culture);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public override MethodBase BindToMethod(
            BindingFlags bindingAttr,
            MethodBase[] match,
            ref object[] args,
            ParameterModifier[] modifiers,
            CultureInfo culture,
            string[] names,
            out object state
            )
        {
            //
            // HACK: Mono may not be passing something valid to us.
            //
            if ((match != null) && (args != null))
            {
                MethodBase methodBase = null;
                ReturnCode code = ReturnCode.Ok;
                Result error = null;

                //
                // NOTE: These are the options and client data for the
                //       current [object invoke] (etc) call.
                //
                OptionDictionary options;
                IClientData clientData;

                //
                // NOTE: Attempt to fetch the binder data from the active
                //       interpreter stack.
                //
                GetBinderClientData(out options, out clientData);

                //
                // NOTE: Create a new argument array for working storage while
                //       we perform the type conversions.  This is critical for
                //       the transactional (all-or-nothing) semantics of this
                //       method.
                //
                object[] newArgs = new object[args.Length];

                for (int matchIndex = 0; matchIndex < match.Length; matchIndex++)
                {
                    MethodBase thisMethodBase = match[matchIndex];

                    if (thisMethodBase != null)
                    {
                        ParameterInfo[] parameterInfo = thisMethodBase.GetParameters();

                        if (parameterInfo != null)
                        {
                            int count = parameterInfo.Length;

                            for (int index = 0; index < args.Length; index++)
                            {
                                //
                                // NOTE: Match up index with Position from ParameterInfo.
                                //
                                //       Check destination type and in/out attributes
                                //       (actually, this must be done in the [object]
                                //       command itself, not here).
                                //
                                //       Use temporary array of arguments until ready to
                                //       call base class method (below).
                                //
                                foreach (ParameterInfo thisParameterInfo in parameterInfo)
                                {
                                    if ((thisParameterInfo != null) &&
                                        (thisParameterInfo.Position == index))
                                    {
                                        //
                                        // NOTE: Create our helper object to hold all the necessary
                                        //       input and output parameters necessary for the type
                                        //       conversions.
                                        //
                                        IChangeTypeData changeTypeData = new ChangeTypeData(
                                            "IBinder.BindToMethod", thisParameterInfo.ParameterType,
                                            args[index], options, culture, clientData, false);

                                        //
                                        // NOTE: Try to change the type of the value.
                                        //
                                        code = ChangeType(changeTypeData, ref error);

                                        //
                                        // NOTE: Did we succeed AND did we actually do something?
                                        //
                                        if ((code == ReturnCode.Ok) &&
                                            (changeTypeData.WasObject || changeTypeData.Converted))
                                        {
                                            //
                                            // NOTE: If we translated an opaque object handle or
                                            //       converted the object value to another type,
                                            //       we must store the new value and count that
                                            //       as a converted parameter.
                                            //
                                            newArgs[index] = changeTypeData.NewValue;
                                            count--;
                                        }

                                        break;
                                    }
                                }

                                if (code != ReturnCode.Ok)
                                    break;
                            }

                            if (code != ReturnCode.Ok)
                                break;

                            //
                            // NOTE: Is this method a match for all the parameters?
                            //
                            if (count == 0)
                            {
                                methodBase = thisMethodBase;
                                break;
                            }
                        }
                    }
                }

                //
                // NOTE: Did we find a matching method?
                //
                if ((code == ReturnCode.Ok) && (methodBase != null))
                {
                    //
                    // NOTE: Ok, commit changes to args array.
                    //
                    for (int index = 0; index < args.Length; index++)
                        args[index] = newArgs[index];

                    state = null; // NOTE: Or maybe "new object();"?
                    return methodBase;
                }
                else if (code != ReturnCode.Ok)
                {
                    throw new ScriptException(code, error);
                }
            }
#if DEBUG && MONO
            else
            {
                //
                // FIXME: Remove this when Mono fixes this bug.
                //
                TraceOps.DebugTrace(String.Format(
                    "{0}: BindToMethod: null arguments (?), match = {1}, " +
                    "args = {2}{3}", GlobalState.GetCurrentThreadId(),
                    (match == null), (args == null), Environment.NewLine),
                    typeof(ScriptBinder).Name);
            }
#endif

            IBinder binder = FallbackBinder;

            if (binder != null)
                return binder.BindToMethod(
                    bindingAttr, match, ref args, modifiers, culture, names, out state);
            else
                return DefaultBinder.BindToMethod(
                    bindingAttr, match, ref args, modifiers, culture, names, out state);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public override object ChangeType(
            object value,
            Type type,
            CultureInfo culture
            ) /* throw */
        {
            if (debug)
            {
                TraceOps.DebugTrace(String.Format(
                    "{0}: ChangeType: value = {1}, valueType = {2}, type = {3}, "+
                    "cultureInfo = {4}{5}", GlobalState.GetCurrentThreadId(),
                    FormatOps.WrapOrNull(true, true, value),
                    FormatOps.WrapOrNull((value != null) ? value.GetType() : null),
                    FormatOps.WrapOrNull(type), FormatOps.WrapOrNull(culture),
                    Environment.NewLine), typeof(ScriptBinder).Name);
            }

#if MONO || MONO_HACKS
            //
            // HACK: *MONO* As of Mono 2.8.0, it seems that Mono will call
            //       the ChangeType method of a custom binder even if the
            //       value type and the desired type are identical.  This
            //       is a problem for us; therefore, prevent it.
            //       https://bugzilla.novell.com/show_bug.cgi?id=471359
            //
            // BUGFIX: Everything is assignable to System.Object and we always
            //         want to lookup opaque object handles; therefore, skip
            //         this hack if the destination type is System.Object.
            //
            if ((type != typeof(object)) && CommonOps.Runtime.IsMono() &&
                DoesMatchType(value, type))
            {
                return value;
            }
#endif

            ReturnCode code = ReturnCode.Ok;
            Result error = null;

            //
            // NOTE: These are the options and client data for the
            //       current [object invoke] (etc) call.
            //
            OptionDictionary options;
            IClientData clientData;

            //
            // NOTE: Attempt to fetch the binder data from the active
            //       interpreter stack.
            //
            GetBinderClientData(out options, out clientData);

            //
            // NOTE: Create our helper object to hold all the necessary
            //       input and output parameters necessary for the type
            //       conversions.
            //
            IChangeTypeData changeTypeData = new ChangeTypeData(
                "IBinder.ChangeType", type, value, options, culture,
                clientData, false);

            //
            // NOTE: Try to change the type of the value.
            //
            code = ChangeType(changeTypeData, ref error);

            //
            // NOTE: Did we succeed AND did we actually do something?
            //
            if ((code == ReturnCode.Ok) &&
                (changeTypeData.WasObject || changeTypeData.Converted))
            {
                return changeTypeData.NewValue;
            }
            else if (code != ReturnCode.Ok)
            {
                throw new ScriptException(code, error);
            }

            //
            // WARNING: Per MSDN, the default ChangeType does not do
            //          anything except throw exceptions (i.e. it
            //          does not actually convert or change anything).
            //
            IBinder binder = FallbackBinder;

            if (binder != null)
                return binder.ChangeType(value, type, culture);
            else
                return DefaultBinder.ChangeType(value, type, culture);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public override void ReorderArgumentArray(
            ref object[] args,
            object state
            )
        {
            IBinder binder = FallbackBinder;

            if (binder != null)
                binder.ReorderArgumentArray(ref args, state);
            else
                DefaultBinder.ReorderArgumentArray(ref args, state);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public override MethodBase SelectMethod(
            BindingFlags bindingAttr,
            MethodBase[] match,
            Type[] types,
            ParameterModifier[] modifiers
            )
        {
            IBinder binder = FallbackBinder;

            if (binder != null)
                return binder.SelectMethod(bindingAttr, match, types, modifiers);
            else
                return DefaultBinder.SelectMethod(bindingAttr, match, types, modifiers);
        }

        ///////////////////////////////////////////////////////////////////////////////////////////////

        public override PropertyInfo SelectProperty(
            BindingFlags bindingAttr,
            PropertyInfo[] match,
            Type returnType,
            Type[] indexes,
            ParameterModifier[] modifiers
            )
        {
            IBinder binder = FallbackBinder;

            if (binder != null)
                return binder.SelectProperty(
                    bindingAttr, match, returnType, indexes, modifiers);
            else
                return DefaultBinder.SelectProperty(
                    bindingAttr, match, returnType, indexes, modifiers);
        }
        #endregion
    }
}
