/*
 * CommandCallback.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.Reflection;
using System.Threading;
using Eagle._Attributes;
using Eagle._Components.Public;
using Eagle._Containers.Public;
using Eagle._Interfaces.Public;

namespace Eagle._Components.Private
{
    [ObjectId("ee26fe9d-bbe4-42af-be2d-d52952acc9a6")]
    internal sealed class CommandCallback :
            IGetInterpreter, ICallback, IExecute
    {
        #region Private Data
        private Interpreter interpreter;
        #endregion

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

        #region Private Constructors
        private CommandCallback(
            string name,
            string group,
            string description,
            IClientData clientData,
            CallbackFlags callbackFlags,
            ObjectFlags objectFlags,
            Interpreter interpreter,
            StringList arguments
            )
        {
            this.kind = IdentifierKind.Callback;
            this.id = AttributeOps.GetObjectId(this);
            this.name = name;
            this.group = group;
            this.description = description;
            this.clientData = clientData;
            this.callbackFlags = callbackFlags;
            this.objectFlags = objectFlags;
            this.interpreter = interpreter;
            this.arguments = arguments;
        }
        #endregion

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

        #region Static "Factory" Methods
        public static ICallback Create(
            CallbackFlags callbackFlags,
            ObjectFlags objectFlags,
            Interpreter interpreter,
            IClientData clientData,
            StringList arguments,
            ref Result error
            )
        {
            ICallback callback = null;

            if (CheckInterpreter(interpreter, ref error))
            {
                //
                // NOTE: The name of the callback is the string representation
                //       of the argument list.  We normalize null to empty
                //       string here because the underlying dictionary of
                //       callbacks in the interpreter cannot handle a null key.
                //
                string name = (arguments != null) ?
                    arguments.ToString() : String.Empty;

                //
                // NOTE: Attempt to locate the matching callback object in the
                //       interpreter.  If it is not found, create a new one
                //       based on the specified arguments, add it to the
                //       interpreter, and return it.
                //
                if (interpreter.GetCallback(name, LookupFlags.Exists,
                        ref callback) != ReturnCode.Ok)
                {
                    callback = new CommandCallback(
                        name, null, null, clientData, callbackFlags,
                        objectFlags, interpreter, arguments);

                    if (interpreter.AddCallback(callback, clientData,
                            ref error) != ReturnCode.Ok)
                    {
                        callback = null;
                    }
                }
            }

            return callback;
        }
        #endregion

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

        #region Private Static Methods
        private static bool CheckInterpreter(
            Interpreter interpreter,
            ref Result error
            )
        {
            if (interpreter == null)
            {
                error = "invalid interpreter";
                return false;
            }

            if (Interpreter.IsDeletedOrDisposed(interpreter))
            {
                error = "interpreter is deleted or disposed";
                return false;
            }

            return true;
        }
        #endregion

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

        #region Private Methods
        private ReturnCode Invoke(
            Interpreter interpreter,
            StringList arguments,
            ref Result result
            )
        {
            ReturnCode code;

            if (CheckInterpreter(interpreter, ref result))
            {
                //
                // NOTE: Construct a new local argument list.
                //
                StringList localArguments = new StringList();

                //
                // NOTE: Start with the original arguments, if any.
                //
                if (this.arguments != null)
                    foreach (string argument in this.arguments)
                        localArguments.Add(argument);

                //
                // NOTE: Append the arguments for this invocation, if any.
                //
                if (arguments != null)
                    foreach (string argument in arguments)
                        localArguments.Add(argument);

                //
                // NOTE: Evaluate the resulting command as a script (with
                //       proper list quoting).
                //
                code = interpreter.EvaluateScript(
                    localArguments.ToString(), ref result); /* EXEMPT */
            }
            else
            {
                code = ReturnCode.Error;
            }

            return code;
        }
        #endregion

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

        #region IGetInterpreter Members
        public Interpreter Interpreter
        {
            get { return interpreter; }
        }
        #endregion

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

        #region IIdentifier Members
        private IdentifierKind kind;
        public IdentifierKind Kind
        {
            get { return kind; }
            set { kind = value; }
        }

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

        private Guid id;
        public Guid Id
        {
            get { return id; }
            set { id = value; }
        }

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

        private string name;
        public string Name
        {
            get { return name; }
            set { name = value; }
        }

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

        private string group;
        public string Group
        {
            get { return group; }
            set { group = value; }
        }

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

        private string description;
        public string Description
        {
            get { return description; }
            set { description = value; }
        }
        #endregion

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

        #region IHaveClientData Members
        private IClientData clientData;
        public IClientData ClientData
        {
            get { return clientData; }
            set { clientData = value; }
        }
        #endregion

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

        #region ICallback Members
        private CallbackFlags callbackFlags;
        public CallbackFlags CallbackFlags
        {
            get { return callbackFlags; }
            set { callbackFlags = value; }
        }

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

        private ObjectFlags objectFlags;
        public ObjectFlags ObjectFlags
        {
            get { return objectFlags; }
            set { objectFlags = value; }
        }

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

        private StringList arguments;
        public StringList Arguments
        {
            get { return arguments; }
        }

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

        private Delegate @delegate;
        public Delegate Delegate
        {
            get { return @delegate; }
        }

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

        private Type type;
        public Type Type
        {
            get { return type; }
        }

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

        private AsyncCallback asyncCallback;
        public AsyncCallback AsyncCallback
        {
            get { return asyncCallback; }
        }

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

        private EventHandler eventHandler;
        public EventHandler EventHandler
        {
            get { return eventHandler; }
        }

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

        private ThreadStart threadStart;
        public ThreadStart ThreadStart
        {
            get { return threadStart; }
        }

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

        private ParameterizedThreadStart parameterizedThreadStart;
        public ParameterizedThreadStart ParameterizedThreadStart
        {
            get { return parameterizedThreadStart; }
        }

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

        public AsyncCallback GetAsyncCallback()
        {
            if (asyncCallback == null)
                asyncCallback = new AsyncCallback(FireAsyncCallback);

            return asyncCallback;
        }

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

        public EventHandler GetEventHandler()
        {
            if (eventHandler == null)
                eventHandler = new EventHandler(FireEventHandler);

            return eventHandler;
        }

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

        public ThreadStart GetThreadStart()
        {
            if (threadStart == null)
                threadStart = new ThreadStart(FireThreadStart);

            return threadStart;
        }

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

        public ParameterizedThreadStart GetParameterizedThreadStart()
        {
            if (parameterizedThreadStart == null)
                parameterizedThreadStart =
                    new ParameterizedThreadStart(FireParameterizedThreadStart);

            return parameterizedThreadStart;
        }

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

        public Delegate GetDelegate(
            Type type,
            bool throwOnBindFailure,
            ref Result error
            )
        {
            if ((@delegate == null) || (this.type != type))
            {
                bool isDelegate = ConversionOps.IsDelegate(type);
                MethodInfo methodInfo = null;

                if (ConversionOps.IsAsyncCallback(type))
                {
                    if (asyncCallback == null)
                        asyncCallback = new AsyncCallback(FireAsyncCallback);

                    methodInfo = asyncCallback.Method;
                }
                else if (ConversionOps.IsEventHandler(type))
                {
                    if (eventHandler == null)
                        eventHandler = new EventHandler(FireEventHandler);

                    methodInfo = eventHandler.Method;
                }
                //
                // HACK: Fake that "System.Delegate" really means they want a
                //       delegate with no parameters and no return value (i.e.
                //       the same signature as ThreadStart).
                //
                else if (isDelegate || ConversionOps.IsThreadStart(type))
                {
                    if (threadStart == null)
                        threadStart = new ThreadStart(FireThreadStart);

                    methodInfo = threadStart.Method;
                }
                else if (ConversionOps.IsParameterizedThreadStart(type))
                {
                    if (parameterizedThreadStart == null)
                        parameterizedThreadStart =
                            new ParameterizedThreadStart(FireParameterizedThreadStart);

                    methodInfo = parameterizedThreadStart.Method;
                }

                if (methodInfo != null)
                {
                    @delegate = Delegate.CreateDelegate(
                        isDelegate ? typeof(ThreadStart) : type, this,
                        methodInfo, throwOnBindFailure); /* throw */

                    this.type = type;
                }
                else
                {
                    error = ScriptOps.BadValue(
                        "unsupported", "delegate type", type.FullName,
                        new string[] {
                            typeof(Delegate).FullName,
                            typeof(AsyncCallback).FullName,
                            typeof(EventHandler).FullName,
                            typeof(ThreadStart).FullName,
                            typeof(ParameterizedThreadStart).FullName
                        }, null, null);
                }
            }

            return @delegate;
        }

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

        public void FireAsyncCallback(
            IAsyncResult ar
            ) /* System.AsyncCallback */
        {
            FireAsyncCallback(ar, null);
        }

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

        public void FireAsyncCallback(
            IAsyncResult ar,
            StringList arguments
            )
        {
            ReturnCode code = ReturnCode.Ok;
            Result result = null;

            if (CheckInterpreter(interpreter, ref result))
            {
                StringList localArguments = null;

                //
                // NOTE: Do we want to create opaque object handles for the
                //       event arguments?
                //
                if (FlagOps.HasFlags(callbackFlags, CallbackFlags.Arguments, true))
                {
                    //
                    // NOTE: Construct a new local argument list.
                    //
                    localArguments = new StringList();

                    //
                    // NOTE: Add an opaque object handle to the interpreter
                    //       for the sender of the event.
                    //
                    bool create = FlagOps.HasFlags(callbackFlags,
                        CallbackFlags.Create, true);

                    bool dispose = FlagOps.HasFlags(callbackFlags,
                        CallbackFlags.Dispose, true);

                    bool alias = FlagOps.HasFlags(callbackFlags,
                        CallbackFlags.Alias, true);

                    bool aliasRaw = FlagOps.HasFlags(callbackFlags,
                        CallbackFlags.AliasRaw, true);

                    bool aliasAll = FlagOps.HasFlags(callbackFlags,
                        CallbackFlags.AliasAll, true);

                    bool aliasReference = FlagOps.HasFlags(callbackFlags,
                        CallbackFlags.AliasReference, true);

                    bool toString = FlagOps.HasFlags(callbackFlags,
                        CallbackFlags.ToString, true);

                    ObjectOptionType objectOptionType = ObjectOptionType.Callback |
                        ObjectOps.GetOptionType(aliasRaw, aliasAll);

                    code = MarshalOps.FixupReturnValue(interpreter,
                        interpreter.Binder, interpreter.CultureInfo, null,
                        objectFlags, ObjectOps.GetInvokeOptions(objectOptionType),
                        objectOptionType, null, null, ar, create, dispose, alias,
                        aliasReference, toString, ref result);

                    if (code == ReturnCode.Ok)
                    {
                        //
                        // NOTE: Add the "ar" argument to the list.
                        //
                        localArguments.Add(result);
                    }
                }

                //
                // NOTE: Did we succeed in adding all the opaque object handles
                //       for the arguments, if necessary?
                //
                if (code == ReturnCode.Ok)
                {
                    //
                    // NOTE: Were any extra arguments supplied by the caller?
                    //       If so, add them now.
                    //
                    if (arguments != null)
                    {
                        if (localArguments == null)
                            localArguments = new StringList();

                        localArguments.Add(arguments);
                    }

                    //
                    // NOTE: Invoke the callback (i.e. evaluate the script).
                    //
                    code = Invoke(interpreter, localArguments, ref result);
                }
            }
            else
            {
                code = ReturnCode.Error;
            }

            if ((code != ReturnCode.Ok) &&
                FlagOps.HasFlags(callbackFlags, CallbackFlags.Complain, true))
            {
                DebugOps.Complain(interpreter, code, result);
            }
        }

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

        public void FireEventHandler(
            object sender,
            EventArgs e
            ) /* System.EventHandler */
        {
            FireEventHandler(sender, e, null);
        }

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

        public void FireEventHandler(
            object sender,
            EventArgs e,
            StringList arguments
            )
        {
            ReturnCode code = ReturnCode.Ok;
            Result result = null;

            if (CheckInterpreter(interpreter, ref result))
            {
                StringList localArguments = null;

                //
                // NOTE: Do we want to create opaque object handles for the
                //       event arguments?
                //
                if (FlagOps.HasFlags(callbackFlags, CallbackFlags.Arguments, true))
                {
                    //
                    // NOTE: Construct a new local argument list.
                    //
                    localArguments = new StringList();

                    //
                    // NOTE: Add an opaque object handle to the interpreter
                    //       for the sender of the event.
                    //
                    bool create = FlagOps.HasFlags(callbackFlags,
                        CallbackFlags.Create, true);

                    bool dispose = FlagOps.HasFlags(callbackFlags,
                        CallbackFlags.Dispose, true);

                    bool alias = FlagOps.HasFlags(callbackFlags,
                        CallbackFlags.Alias, true);

                    bool aliasRaw = FlagOps.HasFlags(callbackFlags,
                        CallbackFlags.AliasRaw, true);

                    bool aliasAll = FlagOps.HasFlags(callbackFlags,
                        CallbackFlags.AliasAll, true);

                    bool aliasReference = FlagOps.HasFlags(callbackFlags,
                        CallbackFlags.AliasReference, true);

                    bool toString = FlagOps.HasFlags(callbackFlags,
                        CallbackFlags.ToString, true);

                    ObjectOptionType objectOptionType = ObjectOptionType.Callback |
                        ObjectOps.GetOptionType(aliasRaw, aliasAll);

                    code = MarshalOps.FixupReturnValue(interpreter,
                        interpreter.Binder, interpreter.CultureInfo, null,
                        objectFlags, ObjectOps.GetInvokeOptions(objectOptionType),
                        objectOptionType, null, null, sender, create, dispose,
                        alias, aliasReference, toString, ref result);

                    if (code == ReturnCode.Ok)
                    {
                        //
                        // NOTE: Add the "sender" argument to the list.
                        //
                        localArguments.Add(result);

                        //
                        // NOTE: Add an opaque object handle to the interpreter
                        //       for the data of the event.
                        //
                        code = MarshalOps.FixupReturnValue(interpreter,
                            interpreter.Binder, interpreter.CultureInfo, null,
                            objectFlags, ObjectOps.GetInvokeOptions(objectOptionType),
                            objectOptionType, null, null, e, create, dispose, alias,
                            aliasReference, toString, ref result);

                        if (code == ReturnCode.Ok)
                            //
                            // NOTE: Add the "e" argument to the list.
                            //
                            localArguments.Add(result);
                    }
                }

                //
                // NOTE: Did we succeed in adding all the opaque object handles
                //       for the arguments, if necessary?
                //
                if (code == ReturnCode.Ok)
                {
                    //
                    // NOTE: Were any extra arguments supplied by the caller?
                    //       If so, add them now.
                    //
                    if (arguments != null)
                    {
                        if (localArguments == null)
                            localArguments = new StringList();

                        localArguments.Add(arguments);
                    }

                    //
                    // NOTE: Invoke the callback (i.e. evaluate the script).
                    //
                    code = Invoke(interpreter, localArguments, ref result);
                }
            }
            else
            {
                code = ReturnCode.Error;
            }

            if ((code != ReturnCode.Ok) &&
                FlagOps.HasFlags(callbackFlags, CallbackFlags.Complain, true))
            {
                DebugOps.Complain(interpreter, code, result);
            }
        }

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

        public void FireThreadStart() /* System.Threading.ThreadStart */
        {
            FireThreadStart(null);
        }

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

        public void FireThreadStart(
            StringList arguments
            )
        {
            ReturnCode code;
            Result result = null;

            if (CheckInterpreter(interpreter, ref result))
            {
                StringList localArguments = null;

                //
                // NOTE: Were any extra arguments supplied by the caller?
                //       If so, add them now.
                //
                if (arguments != null)
                {
                    if (localArguments == null)
                        localArguments = new StringList();

                    localArguments.Add(arguments);
                }

                //
                // NOTE: Invoke the callback (i.e. evaluate the script).
                //
                code = Invoke(interpreter, localArguments, ref result);

                //
                // NOTE: Cleanup all the thread-specific data for the current
                //       interpreter (for this thread, which is now exiting)
                //       unless this is the primary thread for the interpreter
                //       -OR- it is actively in use elsewhere in this thread.
                //
                /* IGNORED */
                interpreter.MaybeDisposeThread();
            }
            else
            {
                code = ReturnCode.Error;
            }

            if ((code != ReturnCode.Ok) &&
                FlagOps.HasFlags(callbackFlags, CallbackFlags.Complain, true))
            {
                DebugOps.Complain(interpreter, code, result);
            }
        }

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

        public void FireParameterizedThreadStart(
            object obj
            ) /* System.Threading.ParameterizedThreadStart */
        {
            FireParameterizedThreadStart(obj, null);
        }

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

        public void FireParameterizedThreadStart(
            object obj,
            StringList arguments
            )
        {
            ReturnCode code = ReturnCode.Ok;
            Result result = null;

            if (CheckInterpreter(interpreter, ref result))
            {
                StringList localArguments = null;

                //
                // NOTE: Do we want to create opaque object handles for the
                //       event arguments?
                //
                if (FlagOps.HasFlags(callbackFlags, CallbackFlags.Arguments, true))
                {
                    //
                    // NOTE: Construct a new local argument list.
                    //
                    localArguments = new StringList();

                    //
                    // NOTE: Add an opaque object handle to the interpreter
                    //       for the object parameter.
                    //
                    bool create = FlagOps.HasFlags(
                        callbackFlags, CallbackFlags.Create, true);

                    bool dispose = FlagOps.HasFlags(
                        callbackFlags, CallbackFlags.Dispose, true);

                    bool alias = FlagOps.HasFlags(
                        callbackFlags, CallbackFlags.Alias, true);

                    bool aliasRaw = FlagOps.HasFlags(callbackFlags,
                        CallbackFlags.AliasRaw, true);

                    bool aliasAll = FlagOps.HasFlags(callbackFlags,
                        CallbackFlags.AliasAll, true);

                    bool aliasReference = FlagOps.HasFlags(callbackFlags,
                        CallbackFlags.AliasReference, true);

                    bool toString = FlagOps.HasFlags(callbackFlags,
                        CallbackFlags.ToString, true);

                    ObjectOptionType objectOptionType = ObjectOptionType.Callback |
                        ObjectOps.GetOptionType(aliasRaw, aliasAll);

                    code = MarshalOps.FixupReturnValue(interpreter,
                        interpreter.Binder, interpreter.CultureInfo, null,
                        objectFlags, ObjectOps.GetInvokeOptions(objectOptionType),
                        objectOptionType, null, null, obj, create, dispose, alias,
                        aliasReference, toString, ref result);

                    if (code == ReturnCode.Ok)
                        //
                        // NOTE: Add the "obj" argument to the list.
                        //
                        localArguments.Add(result);
                }

                //
                // NOTE: Did we succeed in adding all the opaque object handles
                //       for the arguments, if necessary?
                //
                if (code == ReturnCode.Ok)
                {
                    //
                    // NOTE: Were any extra arguments supplied by the caller?
                    //       If so, add them now.
                    //
                    if (arguments != null)
                    {
                        if (localArguments == null)
                            localArguments = new StringList();

                        localArguments.Add(arguments);
                    }

                    //
                    // NOTE: Invoke the callback (i.e. evaluate the script).
                    //
                    code = Invoke(interpreter, localArguments, ref result);

                    //
                    // NOTE: Cleanup all the thread-specific data for the current
                    //       interpreter (for this thread, which is now exiting)
                    //       unless this is the primary thread for the interpreter
                    //       -OR- it is actively in use elsewhere in this thread.
                    //
                    /* IGNORED */
                    interpreter.MaybeDisposeThread();
                }
            }
            else
            {
                code = ReturnCode.Error;
            }

            if ((code != ReturnCode.Ok) &&
                FlagOps.HasFlags(callbackFlags, CallbackFlags.Complain, true))
            {
                DebugOps.Complain(interpreter, code, result);
            }
        }

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

        public ReturnCode Invoke(
            StringList arguments,
            ref Result result
            )
        {
            return Invoke(interpreter, arguments, ref result);
        }
        #endregion

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

        #region IExecute Members
        public ReturnCode Execute(
            Interpreter interpreter,
            IClientData clientData, /* NOT USED */
            ArgumentList arguments,
            ref Result result
            )
        {
            return Invoke(interpreter, (arguments != null) ?
                new StringList(arguments) : null, ref result);
        }
        #endregion

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

        #region System.Object Overrides
        public override string ToString()
        {
            return (arguments != null) ? arguments.ToString() : String.Empty;
        }
        #endregion
    }
}
