/*
 * Core.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 Eagle._Attributes;
using Eagle._Components.Private;
using Eagle._Components.Public;
using Eagle._Containers.Public;
using Eagle._Interfaces.Public;

namespace Eagle._Procedures
{
    [ObjectId("5765ee79-add6-444a-a4e5-d6f80d501125")]
    public class Core : Default
    {
        #region Public Constructors
        public Core(
            IProcedureData procedureData
            )
            : base(procedureData)
        {
            // do nothing.
        }
        #endregion

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

        #region IExecute Members
        public override ReturnCode Execute(
            Interpreter interpreter,
            IClientData clientData,
            ArgumentList arguments,
            ref Result result
            )
        {
            ReturnCode code = ReturnCode.Ok;

            if (interpreter != null)
            {
                if (arguments != null)
                {
                    IScriptLocation location = null;

                    code = ScriptOps.GetAndCheckProcedureLocation(
                        interpreter, this, ref location, ref result);

                    if (code == ReturnCode.Ok)
                    {
                        string procedureName = this.Name;
                        ArgumentList procedureArguments = this.Arguments;

                        if (procedureArguments != null)
                        {
                            bool hasArgs = procedureArguments.IsVariadic(true);
                            int totalArgs = hasArgs ? procedureArguments.Count - 1 : procedureArguments.Count;
                            int optionalArgs = procedureArguments.GetOptionalCount();

                            if ((arguments.Count > 0) &&
                                ((((arguments.Count - 1) >= (totalArgs - optionalArgs)) &&
                                ((arguments.Count - 1) <= totalArgs)) ||
                                (hasArgs && ((arguments.Count - 1) >= (totalArgs - optionalArgs)))))
                            {
                                ICallFrame frame = interpreter.NewProcedureCallFrame(
                                    procedureName, CallFrameFlags.Procedure, this, arguments);

                                VariableDictionary variables = frame.Variables;

                                frame.ProcedureArguments = new ArgumentList(arguments[0]);

                                for (int argumentIndex = 0; argumentIndex < procedureArguments.Count; argumentIndex++)
                                {
                                    string varName = procedureArguments[argumentIndex].Name;

                                    if (!variables.ContainsKey(varName))
                                    {
                                        ArgumentFlags flags = ArgumentFlags.None;
                                        string varValue;

                                        if (hasArgs && (argumentIndex == (procedureArguments.Count - 1)))
                                        {
                                            //
                                            // NOTE: This argument is part of an argument list.
                                            //
                                            flags |= ArgumentFlags.ArgumentList;

                                            //
                                            // NOTE: Build the list for the final formal argument value,
                                            //       which consists of all the remaining argument values.
                                            //
                                            StringList args = new StringList();

                                            for (int argsArgumentIndex = argumentIndex + 1;
                                                argsArgumentIndex < arguments.Count; argsArgumentIndex++)
                                            {
                                                //
                                                // NOTE: Sync up argument names/flags for debugging (below).
                                                //
                                                arguments[argsArgumentIndex].Name = varName + Characters.Space + args.Count;
                                                arguments[argsArgumentIndex].Flags |= flags;

                                                args.Add(arguments[argsArgumentIndex]);
                                            }

                                            varValue = args.ToString();
                                        }
                                        else
                                        {
                                            if ((argumentIndex + 1) < arguments.Count)
                                            {
                                                //
                                                // NOTE: Sync up argument name for debugging (below).
                                                //
                                                arguments[argumentIndex + 1].Name = varName;

                                                //
                                                // NOTE: Use the value supplied by the caller.
                                                //
                                                varValue = arguments[argumentIndex + 1];
                                            }
                                            else
                                            {
                                                //
                                                // NOTE: We cannot sync up the argument name here
                                                //       because we are out-of-bounds on that list
                                                //       and it cannot be extended (i.e. it would
                                                //       break [info level]); therefore, we punt
                                                //       on that for now.  Use the default value
                                                //       for this argument, if any; otherwise, use
                                                //       an empty string.
                                                //
                                                object @default = procedureArguments[argumentIndex].Default;

                                                varValue = (@default != null) ?
                                                    @default.ToString() : Argument.NoValue;
                                            }
                                        }

                                        code = interpreter.SetVariableValue2(VariableFlags.Argument, frame,
                                            varName, null, varValue, null, ref result);

                                        if (code != ReturnCode.Ok)
                                            break;

                                        //
                                        // BUGFIX: Now, also keep track of this argument in the procedure
                                        //         arguments list.  Primarily because we do not want to
                                        //         have to redo this logic later (i.e. for [scope]).
                                        //
                                        frame.ProcedureArguments.Add(new Argument(flags, varName, varValue));
                                    }
                                }

                                //
                                // NOTE: Make sure we succeeded in creating the call frame.
                                //
                                if (code == ReturnCode.Ok)
                                {
                                    ICallFrame savedFrame = null;
                                    interpreter.PushProcedureCallFrame(frame, true, ref savedFrame);

#if DEBUGGER && DEBUGGER_EXECUTE
                                    if (DebuggerOps.CanHitBreakpoints(EngineFlags.None, interpreter))
                                        code = interpreter.CheckBreakpoints(
                                            code, BreakpointType.BeforeProcedureBody, procedureName, null, null,
                                            this, null, clientData, arguments, ref result);
#endif

                                    if (code == ReturnCode.Ok)
                                    {
                                        string body = this.Body;

                                        interpreter.ReturnCode = ReturnCode.Ok;

                                        code = interpreter.EvaluateScript(body, location, ref result);

#if DEBUGGER && DEBUGGER_EXECUTE
                                        if (DebuggerOps.CanHitBreakpoints(EngineFlags.None, interpreter))
                                            code = interpreter.CheckBreakpoints(
                                                code, BreakpointType.AfterProcedureBody, procedureName, null, null,
                                                this, null, clientData, arguments, ref result);
#endif

                                        //
                                        // BUGFIX: If an opaque object handle is being returned, add
                                        //         a reference to it now.
                                        //
                                        if ((code == ReturnCode.Ok) || (code == ReturnCode.Return))
                                            code = interpreter.AddObjectReference(code, result, ref result);

                                        if (code == ReturnCode.Return)
                                            code = Engine.UpdateReturnInformation(interpreter);
                                        else if (code == ReturnCode.Error)
                                            Engine.AddErrorInformation(interpreter, result,
                                                String.Format("{0}    (procedure \"{1}\" line {2})",
                                                    Environment.NewLine, FormatOps.Ellipsis(procedureName),
                                                    Interpreter.GetErrorLine(interpreter)));
                                    }

                                    /* IGNORED */
                                    interpreter.PopProcedureCallFrame(frame, ref savedFrame);
                                }
                            }
                            else
                            {
                                if (procedureArguments.Count > 0)
                                    result = String.Format(
                                        "wrong # args: should be \"{0} {1}\"",
                                        Parser.Quote(procedureName),
                                        procedureArguments.ToRawString(ToStringFlags.Decorated,
                                            Characters.Space.ToString()));
                                else
                                    result = String.Format(
                                        "wrong # args: should be \"{0}\"",
                                        Parser.Quote(procedureName));

                                code = ReturnCode.Error;
                            }
                        }
                        else
                        {
                            result = "invalid procedure argument list";
                            code = ReturnCode.Error;
                        }
                    }
                }
                else
                {
                    result = "invalid argument list";
                    code = ReturnCode.Error;
                }
            }
            else
            {
                result = "invalid interpreter";
                code = ReturnCode.Error;
            }

            return code;
        }
        #endregion
    }
}
