/*
 * Package.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._Constants;
using Eagle._Containers.Private;
using Eagle._Containers.Public;
using Eagle._Interfaces.Public;

namespace Eagle._Commands
{
    [ObjectId("c8fd57c0-20b3-4594-a5a7-919d6f9a8272")]
    /* 
     * POLICY: We allow certain "safe" sub-commands.
     */
    [CommandFlags(CommandFlags.Unsafe | CommandFlags.Standard)]
    [ObjectGroup("scriptEnvironment")]
    internal sealed class Package : Core
    {
        public Package(
            ICommandData commandData
            )
            : base(commandData)
        {
            // do nothing.
        }

        #region IEnsemble Members
        private static readonly EnsembleDictionary subCommands = new EnsembleDictionary(new string[] { 
            "forget", "ifneeded", "indexes", "info", "loaded",
            "names", "present", "provide", "relativefilename", "require",
            "reset", "scan", "unknown", "vcompare", "versions",
            "vsatisfies", "withdraw"
        });

        public override EnsembleDictionary SubCommands
        {
            get { return subCommands; }
        }
        #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)
                {
                    if (arguments.Count >= 2)
                    {
                        string subCommand = arguments[1];
                        bool tried = false;

                        code = ScriptOps.TryExecuteSubCommandFromEnsemble(
                            this, interpreter, clientData, arguments, true,
                            false, ref subCommand, ref tried, ref result);

                        if ((code == ReturnCode.Ok) && !tried)
                        {
                            switch (subCommand)
                            {
                                case "forget":
                                    {
                                        if (arguments.Count >= 2)
                                        {
                                            code = interpreter.PkgForget(
                                                new StringList(arguments, 2), ref result);
                                        }
                                        else
                                        {
                                            result = "wrong # args: should be \"package forget ?package package ...?\"";
                                            code = ReturnCode.Error;
                                        }
                                        break;
                                    }
                                case "ifneeded":
                                    {
                                        if ((arguments.Count == 4) || (arguments.Count == 5))
                                        {
                                            Version version = null;

                                            code = Value.GetVersion(
                                                arguments[3], interpreter.CultureInfo,
                                                ref version, ref result);

                                            if (code == ReturnCode.Ok)
                                            {
                                                string text = null;

                                                if (arguments.Count == 5)
                                                    text = arguments[4];

                                                code = interpreter.PkgIfNeeded(
                                                    arguments[2], version, text, ref result);
                                            }
                                        }
                                        else
                                        {
                                            result = "wrong # args: should be \"package ifneeded package version ?script?\"";
                                            code = ReturnCode.Error;
                                        }
                                        break;
                                    }
                                case "indexes":
                                    {
                                        if ((arguments.Count == 2) || (arguments.Count == 3))
                                        {
                                            string pattern = null;

                                            if (arguments.Count == 3)
                                                pattern = arguments[2];

                                            code = interpreter.PkgIndexes(
                                                pattern, false, ref result);
                                        }
                                        else
                                        {
                                            result = "wrong # args: should be \"package indexes ?pattern?\"";
                                            code = ReturnCode.Error;
                                        }
                                        break;
                                    }
                                case "info":
                                    {
                                        if (arguments.Count == 3)
                                        {
                                            IPackage package = null;

                                            code = interpreter.GetPackage(
                                                arguments[2], LookupFlags.Default,
                                                ref package, ref result);

                                            if (code == ReturnCode.Ok)
                                            {
                                                PackageFlags flags = package.Flags;
                                                Guid id = AttributeOps.GetObjectId(package);

                                                result = StringList.MakeList(
                                                    "kind", package.Kind,
                                                    "id", package.Id.Equals(Guid.Empty) ? id : package.Id,
                                                    "name", package.Name,
                                                    "description", package.Description,
                                                    "indexFileName", package.IndexFileName,
                                                    "provideFileName", package.ProvideFileName,
                                                    "flags", flags,
                                                    "loaded", (package.Loaded != null) ? package.Loaded : null,
                                                    "ifNeeded", (package.IfNeeded != null) ?
                                                        package.IfNeeded.KeysAndValuesToString(null, false) : null);
                                            }
                                        }
                                        else
                                        {
                                            result = "wrong # args: should be \"package info name\"";
                                            code = ReturnCode.Error;
                                        }
                                        break;
                                    }
                                case "loaded":
                                    {
                                        if ((arguments.Count == 2) || (arguments.Count == 3))
                                        {
                                            string pattern = null;

                                            if (arguments.Count == 3)
                                                pattern = arguments[2];

                                            code = interpreter.PkgLoaded(
                                                pattern, false, ref result);
                                        }
                                        else
                                        {
                                            result = "wrong # args: should be \"package loaded ?pattern?\"";
                                            code = ReturnCode.Error;
                                        }
                                        break;
                                    }
                                case "names":
                                    {
                                        if ((arguments.Count == 2) || (arguments.Count == 3))
                                        {
                                            string pattern = null;

                                            if (arguments.Count == 3)
                                                pattern = arguments[2];

                                            code = interpreter.PkgNames(
                                                pattern, false, ref result);
                                        }
                                        else
                                        {
                                            result = "wrong # args: should be \"package names ?pattern?\"";
                                            code = ReturnCode.Error;
                                        }
                                        break;
                                    }
                                case "present":
                                    {
                                        if (arguments.Count >= 3)
                                        {
                                            OptionDictionary options = new OptionDictionary(
                                                new Option[] { 
                                                new Option(null, OptionFlags.None, Index.Invalid, Index.Invalid, "-exact", null),
                                                new Option(null, OptionFlags.None, Index.Invalid, Index.Invalid, Option.EndOfOptions, null)
                                            });

                                            int argumentIndex = Index.Invalid;

                                            code = interpreter.GetOptions(options, arguments, 0, 2, Index.Invalid, false, ref argumentIndex, ref result);

                                            if (code == ReturnCode.Ok)
                                            {
                                                if ((argumentIndex != Index.Invalid) &&
                                                    ((argumentIndex + 2) >= arguments.Count))
                                                {
                                                    bool exact = false;

                                                    if (options.IsPresent("-exact"))
                                                        exact = true;

                                                    Version version = null;

                                                    if ((argumentIndex + 1) < arguments.Count)
                                                        code = Value.GetVersion(
                                                            arguments[argumentIndex + 1], interpreter.CultureInfo,
                                                            ref version, ref result);

                                                    if (code == ReturnCode.Ok)
                                                        code = interpreter.PresentPackage(
                                                            arguments[argumentIndex], version, exact, ref result);
                                                }
                                                else
                                                {
                                                    if ((argumentIndex != Index.Invalid) &&
                                                        Option.LooksLikeOption(arguments[argumentIndex]))
                                                    {
                                                        result = OptionDictionary.BadOption(options, arguments[argumentIndex]);
                                                    }
                                                    else
                                                    {
                                                        result = "wrong # args: should be \"package present ?-exact? package ?version?\"";
                                                    }

                                                    code = ReturnCode.Error;
                                                }
                                            }
                                        }
                                        else
                                        {
                                            result = "wrong # args: should be \"package present ?-exact? package ?version?\"";
                                            code = ReturnCode.Error;
                                        }
                                        break;
                                    }
                                case "provide":
                                    {
                                        if ((arguments.Count == 3) || (arguments.Count == 4))
                                        {
                                            Version version = null;

                                            if (arguments.Count == 4)
                                                code = Value.GetVersion(arguments[3], interpreter.CultureInfo, ref version, ref result);

                                            if (code == ReturnCode.Ok)
                                                code = interpreter.ProvidePackage(arguments[2], version, ref result);
                                        }
                                        else
                                        {
                                            result = "wrong # args: should be \"package provide package ?version?\"";
                                            code = ReturnCode.Error;
                                        }
                                        break;
                                    }
                                case "relativefilename":
                                    {
                                        if ((arguments.Count == 3) || (arguments.Count == 4))
                                        {
                                            PathComparisonType pathComparisonType = PathComparisonType.Default;

                                            if (arguments.Count == 4)
                                            {
                                                object enumValue = EnumOps.TryParseFlagsEnum(
                                                    interpreter, typeof(PathComparisonType),
                                                    pathComparisonType.ToString(), arguments[3],
                                                    interpreter.CultureInfo, true, true, ref result);

                                                if (enumValue is EventFlags)
                                                    pathComparisonType = (PathComparisonType)enumValue;
                                                else
                                                    code = ReturnCode.Error;
                                            }

                                            if (code == ReturnCode.Ok)
                                            {
                                                string fileName = null;

                                                code = PackageOps.GetRelativeFileName(
                                                    interpreter, arguments[2], pathComparisonType,
                                                    ref fileName, ref result);

                                                if (code == ReturnCode.Ok)
                                                    result = fileName;
                                            }
                                        }
                                        else
                                        {
                                            result = "wrong # args: should be \"package relativefilename fileName ?type?\"";
                                            code = ReturnCode.Error;
                                        }
                                        break;
                                    }
                                case "require":
                                    {
                                        if (arguments.Count >= 3)
                                        {
                                            OptionDictionary options = new OptionDictionary(
                                                new Option[] { 
                                                new Option(null, OptionFlags.None, Index.Invalid, Index.Invalid, "-exact", null),
                                                new Option(null, OptionFlags.None, Index.Invalid, Index.Invalid, Option.EndOfOptions, null)
                                            });

                                            int argumentIndex = Index.Invalid;

                                            code = interpreter.GetOptions(options, arguments, 0, 2, Index.Invalid, false, ref argumentIndex, ref result);

                                            if (code == ReturnCode.Ok)
                                            {
                                                if ((argumentIndex != Index.Invalid) && ((argumentIndex + 2) >= arguments.Count))
                                                {
                                                    bool exact = false;

                                                    if (options.IsPresent("-exact"))
                                                        exact = true;

                                                    Version version = null;

                                                    if ((argumentIndex + 1) < arguments.Count)
                                                        code = Value.GetVersion(
                                                            arguments[argumentIndex + 1], interpreter.CultureInfo,
                                                            ref version, ref result);

                                                    if (code == ReturnCode.Ok)
                                                        code = interpreter.RequirePackage(
                                                            arguments[argumentIndex], version, exact, ref result);

                                                    //
                                                    // BUGFIX: This is really a new feature.  In the event of a failure
                                                    //         here, we now fallback to the "unknown package handler",
                                                    //         just like Tcl does.
                                                    //
                                                    if ((code != ReturnCode.Ok) && !FlagOps.HasFlags(
                                                            interpreter.InterpreterFlags, InterpreterFlags.NoPackageUnknown, true))
                                                    {
                                                        string text = interpreter.PackageUnknown + Characters.Space +
                                                            Parser.Quote(arguments[argumentIndex]);

                                                        if (version != null)
                                                            text += Characters.Space + Parser.Quote(version.ToString());

                                                        code = interpreter.EvaluateScript(text, ref result); /* EXEMPT */

                                                        if (code == ReturnCode.Ok)
                                                            code = interpreter.RequirePackage(
                                                                arguments[argumentIndex], version, exact, ref result);
                                                    }
                                                }
                                                else
                                                {
                                                    if ((argumentIndex != Index.Invalid) &&
                                                        Option.LooksLikeOption(arguments[argumentIndex]))
                                                    {
                                                        result = OptionDictionary.BadOption(options, arguments[argumentIndex]);
                                                    }
                                                    else
                                                    {
                                                        result = "wrong # args: should be \"package require ?-exact? package ?version?\"";
                                                    }

                                                    code = ReturnCode.Error;
                                                }
                                            }
                                        }
                                        else
                                        {
                                            result = "wrong # args: should be \"package require ?-exact? package ?version?\"";
                                            code = ReturnCode.Error;
                                        }
                                        break;
                                    }
                                case "reset":
                                    {
                                        if (arguments.Count == 2)
                                        {
                                            code = interpreter.ResetPkgIndexes(ref result);
                                        }
                                        else
                                        {
                                            result = "wrong # args: should be \"package reset\"";
                                            code = ReturnCode.Error;
                                        }
                                        break;
                                    }
                                case "scan":
                                    {
                                        if (arguments.Count >= 2)
                                        {
                                            OptionDictionary options = new OptionDictionary(
                                                new Option[] {
                                                new Option(null, OptionFlags.None, Index.Invalid, Index.Invalid, "-host", null),
                                                new Option(null, OptionFlags.None, Index.Invalid, Index.Invalid, "-normal", null),
                                                new Option(null, OptionFlags.None, Index.Invalid, Index.Invalid, "-resolve", null),
                                                new Option(null, OptionFlags.None, Index.Invalid, Index.Invalid, "-refresh", null),
                                                new Option(null, OptionFlags.None, Index.Invalid, Index.Invalid, "-autopath", null),
                                                new Option(null, OptionFlags.None, Index.Invalid, Index.Invalid, Option.EndOfOptions, null)
                                            });

                                            int argumentIndex = Index.Invalid;

                                            if (arguments.Count > 2)
                                                code = interpreter.GetOptions(options, arguments, 0, 2, Index.Invalid, true, ref argumentIndex, ref result);
                                            else
                                                code = ReturnCode.Ok;

                                            if (code == ReturnCode.Ok)
                                            {
                                                lock (interpreter.SyncRoot) /* TRANSACTIONAL */
                                                {
                                                    bool host = false;

                                                    if (options.IsPresent("-host"))
                                                        host = true;

                                                    bool normal = false;

                                                    if (options.IsPresent("-normal"))
                                                        normal = true;

                                                    bool resolve = false;

                                                    if (options.IsPresent("-resolve"))
                                                        resolve = true;

                                                    bool refresh = false;

                                                    if (options.IsPresent("-refresh"))
                                                        refresh = true;

                                                    bool autoPath = false;

                                                    if (options.IsPresent("-autopath"))
                                                        autoPath = true;

                                                    StringList paths;

                                                    if (argumentIndex != Index.Invalid)
                                                    {
                                                        //
                                                        // NOTE: Refresh the specified path list.
                                                        //
                                                        paths = new StringList(arguments, argumentIndex);
                                                    }
                                                    else
                                                    {
                                                        //
                                                        // NOTE: Refresh the default path list.
                                                        //
                                                        paths = GlobalState.GetAutoPathList(interpreter, autoPath);

                                                        //
                                                        // NOTE: Did they request the auto-path be rebuilt?
                                                        //
                                                        if (autoPath)
                                                        {
                                                            //
                                                            // NOTE: Since the actual auto-path may have changed,
                                                            //       update the variable now.  We disable traces
                                                            //       here because we manually rescan, if necessary,
                                                            //       below.
                                                            //
                                                            code = interpreter.SetLibraryVariableValue(
                                                                VariableFlags.SkipTrace, TclVars.AutoPath,
                                                                (paths != null) ? paths.ToString() : null,
                                                                ref result);
                                                        }
                                                    }

                                                    if (code == ReturnCode.Ok)
                                                    {
                                                        PackageIndexDictionary packageIndexes = interpreter.CopyPackageIndexes();

                                                        if (code == ReturnCode.Ok)
                                                            code = PackageOps.FindAll(
                                                                interpreter, paths, host, normal, resolve,
                                                                refresh, ref packageIndexes, ref result);

                                                        if (code == ReturnCode.Ok)
                                                        {
                                                            interpreter.PackageIndexes = packageIndexes;
                                                            result = String.Empty;
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                        else
                                        {
                                            result = "wrong # args: should be \"package scan ?options? ?dir dir ...?\"";
                                            code = ReturnCode.Error;
                                        }
                                        break;
                                    }
                                case "unknown":
                                    {
                                        if ((arguments.Count == 2) || (arguments.Count == 3))
                                        {
                                            if (arguments.Count == 3)
                                            {
                                                interpreter.PackageUnknown = arguments[2];
                                                result = String.Empty;
                                            }
                                            else
                                            {
                                                result = interpreter.PackageUnknown;
                                            }

                                            code = ReturnCode.Ok;
                                        }
                                        else
                                        {
                                            result = "wrong # args: should be \"package unknown ?command?\"";
                                            code = ReturnCode.Error;
                                        }
                                        break;
                                    }
                                case "vcompare":
                                    {
                                        if (arguments.Count == 4)
                                        {
                                            Version version1 = null;

                                            code = Value.GetVersion(
                                                arguments[2], interpreter.CultureInfo,
                                                ref version1, ref result);

                                            Version version2 = null;

                                            if (code == ReturnCode.Ok)
                                                code = Value.GetVersion(
                                                    arguments[3], interpreter.CultureInfo,
                                                    ref version2, ref result);

                                            if (code == ReturnCode.Ok)
                                                result = PackageOps.VersionCompare(version1, version2);
                                        }
                                        else
                                        {
                                            result = "wrong # args: should be \"package vcompare version1 version2\"";
                                            code = ReturnCode.Error;
                                        }
                                        break;
                                    }
                                case "versions":
                                    {
                                        if (arguments.Count == 3)
                                        {
                                            code = interpreter.PkgVersions(
                                                arguments[2], ref result);
                                        }
                                        else
                                        {
                                            result = "wrong # args: should be \"package versions package\"";
                                            code = ReturnCode.Error;
                                        }
                                        break;
                                    }
                                case "vsatisfies":
                                    {
                                        if (arguments.Count == 4)
                                        {
                                            Version version1 = null;

                                            code = Value.GetVersion(arguments[2], interpreter.CultureInfo, ref version1, ref result);

                                            Version version2 = null;

                                            if (code == ReturnCode.Ok)
                                                code = Value.GetVersion(
                                                    arguments[3], interpreter.CultureInfo,
                                                    ref version2, ref result);

                                            if (code == ReturnCode.Ok)
                                                result = PackageOps.VersionSatisfies(
                                                    version1, version2, false);
                                        }
                                        else
                                        {
                                            result = "wrong # args: should be \"package vsatisfies version1 version2\"";
                                            code = ReturnCode.Error;
                                        }
                                        break;
                                    }
                                case "withdraw":
                                    {
                                        if ((arguments.Count == 3) || (arguments.Count == 4))
                                        {
                                            Version version = null;

                                            if (arguments.Count == 4)
                                                code = Value.GetVersion(
                                                    arguments[3], interpreter.CultureInfo,
                                                    ref version, ref result);

                                            if (code == ReturnCode.Ok)
                                                code = interpreter.WithdrawPackage(
                                                    arguments[2], version, ref result);
                                        }
                                        else
                                        {
                                            result = "wrong # args: should be \"package withdraw package ?version?\"";
                                            code = ReturnCode.Error;
                                        }
                                        break;
                                    }
                                default:
                                    {
                                        result = ScriptOps.BadSubCommand(null, null, subCommand, this, null, null);
                                        code = ReturnCode.Error;
                                        break;
                                    }
                            }
                        }
                    }
                    else
                    {
                        result = "wrong # args: should be \"package arg ?arg ...?\"";
                        code = ReturnCode.Error;
                    }
                }
                else
                {
                    result = "invalid argument list";
                    code = ReturnCode.Error;
                }
            }
            else
            {
                result = "invalid interpreter";
                code = ReturnCode.Error;
            }

            return code;
        }
        #endregion
    }
}
