/*
 * ShellOps.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.Collections.Generic;
using System.IO;
using System.Text;
using Eagle._Attributes;
using Eagle._Components.Public;
using Eagle._Constants;
using Eagle._Containers.Public;
using Eagle._Interfaces.Public;

namespace Eagle._Components.Private
{
    [ObjectId("d9374375-f3bb-402f-8c43-354168741995")]
    internal static class ShellOps
    {
        #region Private Constants
        #region Interactive Command Prefix
        internal static readonly string InteractiveCommandPrefix =
            Characters.NumberSign.ToString();

        internal static readonly string InteractiveSystemCommandPrefix =
            Characters.NumberSign.ToString() + Characters.NumberSign.ToString();

        internal static readonly string DefaultInteractiveCommandPrefix =
            InteractiveCommandPrefix;
        #endregion

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

        #region Script Update Checking
        //
        // NOTE: These procedure names are all RESERVED; however, they may
        //       legally be redefined to do nothing.
        //
        private const string CheckForUpdateScript = "checkForUpdate";
        private const string FetchUpdateScript = "fetchUpdate";
        private const string RunUpdateAndExitScript = "runUpdateAndExit";
        #endregion
        #endregion

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

        #region Shell Support Methods
        public static bool ShouldUseArgumentsFileName(
            IList<string> argv
            )
        {
            if (argv == null)
                return true;

            for (int index = 0; index < argv.Count; index++)
            {
                string arg = argv[index];
                int count = 0;

                arg = StringOps.TrimSwitchChars(arg, ref count);

                if ((count > 0) && StringOps.MatchSwitch(arg,
                        CommandLineOption.NoArgumentsFileName))
                {
                    return false;
                }
            }

            return true;
        }

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

        public static string GetArgumentsFileName(
            string fileName
            )
        {
            if (String.IsNullOrEmpty(fileName))
                return null;

            return String.Format("{0}{1}", fileName, FileExtension.Arguments);
        }

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

        public static TextReader GetTextReaderForFile(
            Encoding encoding,
            string fileName,
            bool console,
            ref Result error
            )
        {
            try
            {
#if CONSOLE
                if (console && (String.Equals(fileName,
                        CommandLineArgument.StandardInput,
                        StringOps.SystemNoCaseStringComparisonType) ||
                    String.Equals(fileName, StandardChannel.Input,
                        StringOps.SystemNoCaseStringComparisonType)))
                {
                    //
                    // TODO: Allow the interpreter host (if available) to be
                    //       used here instead?
                    //
                    return Console.In;
                }
                else
#endif
                {
                    return (encoding != null) ?
                        new StreamReader(fileName, encoding) :
                        new StreamReader(fileName);
                }
            }
            catch (Exception e)
            {
                error = e;
            }

            return null;
        }

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

        public static ReturnCode ReadArgumentsFromFile(
            Interpreter interpreter,
            TextReader textReader,
            int count,
            bool append,
            ref IList<string> argv,
            ref Result error
            )
        {
            if (textReader == null)
            {
                error = "invalid stream";
                return ReturnCode.Error;
            }

            StringList newArgv = new StringList();

            while (true)
            {
                string line = textReader.ReadLine();

                if (line == null) // NOTE: End-of-file?
                    break;

                string trimLine = line.Trim();

                if (!String.IsNullOrEmpty(trimLine))
                {
                    if ((trimLine[0] != Characters.Comment) &&
                        (trimLine[0] != Characters.AltComment))
                    {
                        StringList list = null;

                        if (Parser.SplitList(
                                interpreter, trimLine, 0, Length.Invalid,
                                true, ref list, ref error) == ReturnCode.Ok)
                        {
                            newArgv.Add(list);
                        }
                        else
                        {
                            //
                            // NOTE: The line read from the file cannot be
                            //       parsed as a list, fail now.
                            //
                            return ReturnCode.Error;
                        }
                    }
                }
            }

            //
            // NOTE: *WARNING* This assumes that the arguments that need to be
            //       removed are at the start of the list provided by the
            //       caller; the ShellMainCore method can guarantee that will
            //       be the case and other callers should do so as well.
            //
            while ((argv != null) && (count-- > 0))
                GenericOps<string>.PopFirstArgument(ref argv);

            //
            // NOTE: If we used up all the arguments (i.e. there were only
            //       "count" arguments in the list), the original argument list
            //       (i.e. "argv") will now be null.  If that is the case, use
            //       a new list.
            //
            if (argv == null)
                argv = new StringList();

            //
            // NOTE: The count may already be zero at this point (if the above
            //       loop was actually fully executed); however, we must be 100%
            //       sure that it is zero beyond this point (for the loop below).
            //
            count = 0;

            //
            // NOTE: Insert each argument read from the file, in order, where the
            //       original argument(s) was/were removed.
            //
            foreach (string arg in newArgv)
            {
                if (append)
                    argv.Add(arg);
                else
                    argv.Insert(count++, arg);
            }

            return ReturnCode.Ok;
        }

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

        //
        // NOTE: These methods are private because it currently seems unlikely
        //       that they will be useful to any external callers (i.e. those
        //       other than ShellMainCore).
        //
        public static void ShellMainCoreError( /* FOR ShellMain USE ONLY. */
            Interpreter interpreter,
            string savedArg,
            string arg,
            ReturnCode code,
            Result result
            )
        {
            IInteractiveHost interactiveHost = null;
            bool quiet = false;

            if (interpreter != null)
            {
                interactiveHost = interpreter.Host;
                quiet = interpreter.ShouldBeQuiet();
            }

            ShellMainCoreError(
                interpreter, savedArg, arg, code, result,
                ref interactiveHost, ref quiet);
        }

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

        public static void ShellMainCoreError(
            Interpreter interpreter,
            string savedArg,
            string arg,
            ReturnCode code,
            Result result,
            ref IInteractiveHost interactiveHost,
            ref bool quiet
            )
        {
            //
            // NOTE: This method overload is for non-API generated errors only.
            //       No error line info, script stack trace, or return code is
            //       needed.
            //
            IList<string> argv = null;

            ShellMainCoreError(interpreter, savedArg, arg, code, result,
                ref argv, ref interactiveHost, ref quiet);
        }

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

        public static void ShellMainCoreError(
            Interpreter interpreter,
            string savedArg,
            string arg,
            Result result,
            ref IList<string> argv,
            ref IInteractiveHost interactiveHost,
            ref bool quiet
            )
        {
            //
            // NOTE: This method overload is for non-API generated errors only.
            //       No error line info, script stack trace, or return code is
            //       needed.
            //
            ShellMainCoreError(interpreter, savedArg, arg, ReturnCode.Error,
                result, ref argv, ref interactiveHost, ref quiet);
        }

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

        public static void ShellMainCoreError(
            Interpreter interpreter,
            string savedArg,
            string arg,
            ReturnCode code,
            Result result,
            ref IList<string> argv,
            ref IInteractiveHost interactiveHost,
            ref bool quiet
            )
        {
            //
            // NOTE: This method overload is for [non-script] API generated
            //       errors only.  No error line info or script stack trace is
            //       required.
            //
            ShellMainCoreError(interpreter, savedArg, arg, code, result, 0,
                false, false, ref argv, ref interactiveHost, ref quiet);
        }

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

        public static void ShellMainCoreError(
            Interpreter interpreter,
            string savedArg,
            string arg,
            ReturnCode code,
            Result result,
            int errorLine,
            bool errorInfo,
            bool strict,
            ref IList<string> argv,
            ref IInteractiveHost interactiveHost,
            ref bool quiet
            )
        {
            TraceOps.DebugTrace(String.Format(
                "{0}: ShellMainCoreError: interpreter = {1}, " +
                "savedArg = {2}, arg = {3}, code = {4}, result = {5}, " +
                "errorLine = {6}, errorInfo = {7}, strict = {8}, " +
                "argv = {9}, interactiveHost = {10}, quiet = {11}{12}",
                GlobalState.GetCurrentThreadId(),
                FormatOps.WrapOrNull(interpreter),
                FormatOps.WrapOrNull(true, true, savedArg),
                FormatOps.WrapOrNull(true, true, arg), code,
                FormatOps.WrapOrNull(true, true, result), errorLine,
                errorInfo, strict, FormatOps.WrapOrNull(true, true, argv),
                FormatOps.WrapOrNull(interactiveHost), quiet,
                Environment.NewLine), typeof(ShellOps).Name);

            if (interpreter == null)
            {
                if (strict)
                {
                    //
                    // NOTE: Interpreter is invalid; therefore, no interpreter
                    //       host is available.
                    //
                    HostOps.WriteConsoleOrComplain(code, result, errorLine);
                }

                //
                // NOTE: Nothing else to do, return now.
                //
                return;
            }

            //
            // NOTE: Always grab the interpreter host fresh as it can change
            //       after any user code has been evaluated or executed.
            //
            interactiveHost = interpreter.Host;

            //
            // NOTE: See if quiet mode has been enabled for the interpreter.
            //       If so, skip doing any output because MSBuild may be
            //       watching us (i.e. it can cause the build to fail).
            //
            quiet = interpreter.ShouldBeQuiet();

            if (quiet)
                return;

            //
            // NOTE: Is the interpreter host unavailable now?
            //
            if (interactiveHost == null)
            {
                if (strict)
                {
                    //
                    // NOTE: No interpreter host is available.
                    //
                    HostOps.WriteConsoleOrComplain(code, result, errorLine);
                }

                //
                // NOTE: Nothing else to do, return now.
                //
                return;
            }

            //
            // NOTE: Write the result to the interpreter host.  If the error
            //       line is zero, it will not actually be output.
            //
            interactiveHost.WriteResultLine(code, result, errorLine);

            //
            // NOTE: Do we want to report the script stack trace as well?
            //       First, see if debug mode has been enabled for the
            //       interpreter.
            //
            if (errorInfo && interpreter.Debug)
            {
                Result error = null;

                if (interpreter.CopyErrorInformation(
                        VariableFlags.None, ref error) == ReturnCode.Ok)
                {
                    interactiveHost.WriteResultLine(code, error.ErrorInfo);
                }
            }
        }
        #endregion

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

        #region Interactive Loop Support Methods
        public static bool LooksLikeInteractiveCommand(
            string text
            )
        {
            int nextIndex = 0;

            return LooksLikeInteractiveCommand(text, ref nextIndex);
        }

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

        public static bool LooksLikeInteractiveCommand(
            string text,
            ref int nextIndex
            )
        {
            if (!String.IsNullOrEmpty(text))
            {
                string prefix = InteractiveCommandPrefix;

                if (!String.IsNullOrEmpty(prefix))
                {
                    int prefixLength = prefix.Length;
                    string localText = text.Trim();

                    if (localText.StartsWith(prefix,
                            StringOps.SystemNoCaseStringComparisonType))
                    {
                        int localIndex = text.IndexOf(prefix,
                            StringOps.SystemNoCaseStringComparisonType);

                        if (localIndex != Index.Invalid)
                            nextIndex = localIndex + prefixLength;

                        return true;
                    }
                }
            }

            return false;
        }

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

        public static bool LooksLikeInteractiveSystemCommand(
            string text
            )
        {
            int nextIndex = 0;

            return LooksLikeInteractiveSystemCommand(text, ref nextIndex);
        }

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

        public static bool LooksLikeInteractiveSystemCommand(
            string text,
            ref int nextIndex
            )
        {
            if (!String.IsNullOrEmpty(text))
            {
                string prefix = InteractiveSystemCommandPrefix;

                if (!String.IsNullOrEmpty(prefix))
                {
                    int prefixLength = prefix.Length;
                    string localText = text.Trim();

                    if (localText.StartsWith(prefix,
                            StringOps.SystemNoCaseStringComparisonType))
                    {
                        int localIndex = text.IndexOf(prefix,
                            StringOps.SystemNoCaseStringComparisonType);

                        if (localIndex != Index.Invalid)
                            nextIndex = localIndex + prefixLength;

                        return true;
                    }
                }
            }

            return false;
        }

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

        public static string StripInteractiveCommand(
            string text
            )
        {
            if (String.IsNullOrEmpty(text))
                return text;

            int nextIndex = 0;

            if (!LooksLikeInteractiveSystemCommand(text, ref nextIndex))
                return text;
            else if (!LooksLikeInteractiveCommand(text, ref nextIndex))
                return text;

            int index = text.IndexOfAny(Characters.WhiteSpaceChars, nextIndex);

            if (index == Index.Invalid)
                return text;

            return text.Substring(index + 1).Trim();
        }

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

        public static ReturnCode CheckForUpdate(
            Interpreter interpreter,
            ActionType actionType,
            ReleaseType releaseType,
            bool wantScripts,
            bool quiet,
            bool prompt,
            bool automatic,
            EngineFlags engineFlags,
            SubstitutionFlags substitutionFlags,
            EventFlags eventFlags,
            ExpressionFlags expressionFlags,
            ref int errorLine,
            ref Result result
            )
        {
            ReturnCode code;

            //
            // NOTE: Evaluate the script we use to check for updates to the
            //       script engine.  If the proc has been redefined, this may
            //       not actually do anything.
            //
            errorLine = 0;

            code = Engine.EvaluateScript(
                interpreter, StringList.MakeList(CheckForUpdateScript,
                wantScripts, quiet, prompt, automatic), engineFlags,
                substitutionFlags, eventFlags, expressionFlags, ref result,
                ref errorLine);

            //
            // NOTE: Evaluate the script we use to fetch an update to the
            //       script engine, if necessary.  If the proc has been
            //       redefined, this may not actually do anything.
            //
            if (code == ReturnCode.Ok)
            {
                //
                // NOTE: Attempt to parse the result of the check-for-update
                //       script as a list.
                //
                StringList list = null;

                code = Parser.SplitList(
                    interpreter, result, 0, Length.Invalid, true,
                    ref list, ref result);

                if (code == ReturnCode.Ok)
                {
                    //
                    // NOTE: If the specified action is greater than zero, we
                    //       need to actively interpret the result and then
                    //       fetch the update if necessary; otherwise, we do
                    //       nothing but simply reporting the result of the
                    //       check-for-update script.
                    //
                    if ((actionType != ActionType.None) &&
                        (actionType != ActionType.CheckForUpdate))
                    {
                        //
                        // NOTE: If the result from the check-for-update script
                        //       is a list with more than one element, then a
                        //       download must be necessary.
                        //
                        if (list.Count > 1)
                        {
                            //
                            // NOTE: Parse the second element of the list as a
                            //       nested list containing [most of] the
                            //       arguments to pass to the fetch-an-update
                            //       script.
                            //
                            StringList list2 = null;

                            code = Parser.SplitList(
                                interpreter, list[1], 0, Length.Invalid, true,
                                ref list2, ref result);

                            if ((code == ReturnCode.Ok) && (list2.Count == 2))
                            {
                                string actionScript = null;

                                if (actionType == ActionType.RunUpdateAndExit)
                                {
                                    actionScript = StringList.MakeList(
                                        RunUpdateAndExitScript, automatic);
                                }
                                else if (actionType == ActionType.FetchUpdate)
                                {
                                    actionScript = StringList.MakeList(
                                        FetchUpdateScript, list2[0], list2[1],
                                        releaseType, PathOps.GetUnixPath(
                                        Path.GetTempPath()));
                                }
                                else
                                {
                                    result = String.Format(
                                        "unsupported action type {0}",
                                        actionType);

                                    code = ReturnCode.Error;
                                }

                                if ((code == ReturnCode.Ok) &&
                                    (actionScript != null))
                                {
                                    errorLine = 0;

                                    code = Engine.EvaluateScript(
                                        interpreter, actionScript, engineFlags,
                                        substitutionFlags, eventFlags,
                                        expressionFlags, ref result,
                                        ref errorLine);

                                    //
                                    // NOTE: Return just the message itself as
                                    //       the result.
                                    //
                                    result = String.Format(
                                        "{0}{1}{2}", list[0], Environment.NewLine,
                                        result);
                                }
                            }
                        }
                    }
                    else
                    {
                        //
                        // NOTE: Return just the informational message itself
                        //       as the result or an error if none was provided.
                        //
                        result = (list.Count > 0) ? list[0] :
                            "invalid result from the check-for-update script";
                    }
                }
            }

            return code;
        }
        #endregion
    }
}
