/*
 * AppDomainOps.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;

#if ISOLATED_PLUGINS
using System.Collections.Generic;
using System.Reflection;
#endif

#if REMOTING
using System.Runtime.Remoting;
#endif

#if CAS_POLICY
using System.Security.Policy;
#endif

using Eagle._Attributes;
using Eagle._Components.Public;

#if ISOLATED_PLUGINS
using Eagle._Containers.Public;
#endif

using Eagle._Interfaces.Public;

namespace Eagle._Components.Private
{
    [ObjectId("592a1298-b491-48ca-b66d-0a5ef3c7a1ae")]
    internal static class AppDomainOps
    {
        #region AppDomain / Remoting Support Methods
        #region Dead Code
#if DEAD_CODE
        private static bool IsPrimary(
            Interpreter interpreter,
            AppDomain appDomain
            )
        {
            return (interpreter != null) && (appDomain != null) &&
                Object.ReferenceEquals(appDomain, interpreter.GetAppDomain());
        }

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

        private static bool IsPrimary(
            AppDomain appDomain
            )
        {
            return (appDomain != null) &&
                Object.ReferenceEquals(appDomain, AppDomain.CurrentDomain);
        }
#endif
        #endregion

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

        public static bool IsTransparentProxy(
            object proxy
            )
        {
#if REMOTING
            return RemotingServices.IsTransparentProxy(proxy);
#else
            return false;
#endif
        }

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

        public static bool IsDefault()
        {
            return IsDefault(AppDomain.CurrentDomain);
        }

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

        public static bool IsDefault(
            AppDomain appDomain
            )
        {
            return ((appDomain != null) && appDomain.IsDefaultAppDomain());
        }

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

        public static bool IsSame(
            Interpreter interpreter
            )
        {
            if (interpreter == null)
                return true;

            AppDomain appDomain = interpreter.GetAppDomain();

            if ((appDomain == null) ||
                (appDomain.Id == AppDomain.CurrentDomain.Id))
            {
                return true;
            }

            return false;
        }

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

        public static bool IsFinalizing()
        {
            return IsFinalizing(AppDomain.CurrentDomain);
        }

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

        private static bool IsFinalizing(
            AppDomain appDomain
            )
        {
            return ((appDomain != null) && appDomain.IsFinalizingForUnload());
        }

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

#if ISOLATED_PLUGINS
        public static bool IsIsolated(
            IPlugin plugin
            )
        {
            return (plugin != null) &&
                FlagOps.HasFlags(plugin.Flags, PluginFlags.Isolated, true);
        }

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

        private static OptionFlags GetEnumOptionFlags(
            TypeCode typeCode,
            bool strict
            )
        {
            switch (typeCode)
            {
                case TypeCode.Byte:
                case TypeCode.SByte:
                case TypeCode.Int16:
                case TypeCode.UInt16:
                case TypeCode.Int32:
                case TypeCode.UInt32:
                    return OptionFlags.MustBeInteger;
                case TypeCode.Int64:
                case TypeCode.UInt64:
                    return OptionFlags.MustBeWideInteger;
                default:
                    return strict ? OptionFlags.None :
                        OptionFlags.MustBeWideInteger;
            }
        }

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

        //
        // HACK: This is a horrible hack to workaround the issue with not being
        //       able to use plugin enumerated types from the "primary"
        //       application domain when the plugin has been loaded in isolated
        //       mode.
        //
        public static ReturnCode FixupOptions(
            IPlugin plugin,
            OptionDictionary options,
            bool strict,
            ref Result error
            )
        {
            if (plugin == null)
            {
                if (strict)
                {
                    error = "invalid plugin";
                    return ReturnCode.Error;
                }

                return ReturnCode.Ok;
            }

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

                return ReturnCode.Ok;
            }

            if (!IsIsolated(plugin))
                return ReturnCode.Ok;

            Assembly assembly = plugin.Assembly;

            foreach (KeyValuePair<string, IOption> pair in options)
            {
                IOption option = pair.Value;

                if (option == null)
                    continue;

                //
                // HACK: Skip options that do not have enumerated types.
                //       For now, these are the only options we really have to
                //       worry about because they are the only ones that can
                //       directly refer to user-defined types [of any kind].
                //
                if (!option.HasFlags(OptionFlags.MustBeEnum, true))
                    continue;

                //
                // NOTE: Grab the enumerated (?) type and figure out if it
                //       came from the plugin assembly.  If not, ignore it and
                //       continue.
                //
                Type type = option.Type;

                if ((type == null) || !type.IsEnum ||
                    !Object.ReferenceEquals(type.Assembly, assembly))
                {
                    continue;
                }

                //
                // NOTE: Get the current value of the option.
                //
                object oldValue = option.InnerValue;
                TypeCode typeCode = TypeCode.Empty;

                //
                // NOTE: Attempt to get the new value for the integral type for
                //       the enumeration value of this option, if any.  We must
                //       do this even if the original value is null because we
                //       must have the type code to properly reset the option
                //       flags.
                //
                object newValue = EnumOps.ConvertToTypeCodeValue(
                    type, (oldValue != null) ? oldValue : 0, ref typeCode,
                    ref error);

                if (newValue == null)
                    return ReturnCode.Error;

                //
                // NOTE: Get the option flags required for the integral type.
                //
                OptionFlags flags = GetEnumOptionFlags(typeCode, strict);

                if (flags == OptionFlags.None)
                {
                    error = String.Format(
                        "unsupported type code for enumerated type \"{0}\"",
                        type);

                    return ReturnCode.Error;
                }

                //
                // NOTE: Special handling for "flags" enumerations here.
                //
                if (type.IsDefined(typeof(FlagsAttribute), false))
                {
                    //
                    // HACK: Substitute our placeholder flags enumerated type.
                    //       It does not know about the textual values provided
                    //       by the actual enumerated type; however, at least
                    //       they can use the custom flags enumeration handling
                    //       (i.e. the "+" and "-" operators, etc).
                    //
                    option.Type = typeof(StubFlagsEnum);
                }
                else
                {
                    //
                    // NOTE: Remove the MustBeEnum flag for this option and add
                    //       the flag(s) needed for its integral type.
                    //
                    option.Flags &= ~OptionFlags.MustBeEnum;
                    option.Flags |= flags;

                    //
                    // NOTE: Clear the type for the option.  The type property
                    //       is only meaningful for enumeration-based options
                    //       and we are converting this option to use some kind
                    //       of integral type.
                    //
                    option.Type = null;
                }

                //
                // NOTE: If necessary, set the new [default] value for this
                //       option to the one we converted to an integral type
                //       value above.  If the old (original) value was null, we
                //       just discard the new value which will be zero anyhow.
                //
                option.Value = (oldValue != null) ? new Variant(newValue) : null;
            }

            return ReturnCode.Ok;
        }
#endif

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

        public static ReturnCode GetOrCreate(
            Interpreter interpreter,
            string friendlyName,
            string packagePath,
#if CAS_POLICY
            Evidence evidence,
#endif
            IClientData clientData,
            bool isolated,
            bool useBasePath,
            ref AppDomain appDomain,
            ref Result error
            )
        {
            //
            // NOTE: Use an isolated application domain or the current one?
            //
            if (isolated)
            {
#if ISOLATED_PLUGINS
                //
                // BUGBUG: This feature does not currently work due to
                //         cross-domain marshalling issues.
                //
                return Create(
                    interpreter, friendlyName, packagePath,
#if CAS_POLICY
                    evidence,
#endif
                    clientData, useBasePath, ref appDomain, ref error);
#else
                error = "not implemented";
#endif
            }
            else if (interpreter != null)
            {
                //
                // NOTE: Get the application domain configured for this
                //       interpreter.
                //
                appDomain = interpreter.GetAppDomain();

                if (appDomain != null)
                {
                    return ReturnCode.Ok;
                }
                else
                {
                    error = "invalid application domain";
                }
            }
            else
            {
                error = "invalid interpreter";
            }

            return ReturnCode.Error;
        }

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

#if ISOLATED_INTERPRETERS
        public static ReturnCode CreateForTest(
            Interpreter interpreter,
            string friendlyName,
            string packagePath,
            IClientData clientData, /* NOT USED */
            ref AppDomain appDomain,
            ref Result error
            )
        {
            return Create(
                interpreter, friendlyName, packagePath,
#if CAS_POLICY
                null,
#endif
                clientData, true, ref appDomain, ref error);
        }
#endif

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

#if APPDOMAINS || ISOLATED_INTERPRETERS || ISOLATED_PLUGINS
        private static string GetBasePath()
        {
            return GlobalState.GetRawBinaryBasePath();
        }

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

        private static AppDomainSetup CreateSetup(
            Interpreter interpreter,
            string packagePath,
            bool useBasePath,
            ref Result error
            )
        {
            string basePath = useBasePath ? GetBasePath() : null;

            if (!useBasePath || !String.IsNullOrEmpty(basePath))
            {
                //
                // NOTE: Check if the package path is located under the base
                //       path.
                //
                bool packageUnderBasePath = (packagePath != null) ?
                    PathOps.IsUnderPath(interpreter, packagePath,
                        basePath) : false;

                //
                // NOTE: Verify that the package path is either usable or
                //       superfluous.
                //
                if ((packagePath == null) ||
                    !useBasePath || packageUnderBasePath)
                {
                    //
                    // NOTE: Grab the full path for the Eagle core library
                    //       assembly.
                    //
                    string assemblyPath = GlobalState.GetAssemblyPath();

                    //
                    // NOTE: Check if the assembly path is located under
                    //       the base path.
                    //
                    bool assemblyUnderBasePath = (assemblyPath != null) ?
                        PathOps.IsUnderPath(interpreter, assemblyPath,
                            basePath) : false;

                    //
                    // NOTE: Verify that the assembly path is either usable
                    //       or superfluous.
                    //
                    if ((assemblyPath == null) ||
                        !useBasePath || assemblyUnderBasePath)
                    {
                        AppDomainSetup appDomainSetup = new AppDomainSetup();

                        //
                        // NOTE: Use the base directory of the Eagle install
                        //       as the base directory for the new isolated
                        //       application domain.
                        //
                        appDomainSetup.ApplicationBase = useBasePath ?
                            basePath : (packagePath != null) ?
                                packagePath : assemblyPath;

                        //
                        // NOTE: If we are using the base path of the Eagle
                        //       core library assembly, then we need to modify
                        //       the private binary path so that it includes
                        //       both the directory containing that assembly
                        //       and the directory containing the package;
                        //       otherwise, we can simply skip this step.
                        //
                        if (useBasePath)
                        {
                            //
                            // TODO: May need to add more options here.
                            //
                            string relativeAssemblyPath =
                                (assemblyPath != null) && assemblyUnderBasePath ?
                                    assemblyPath.Remove(0, basePath.Length).Trim(
                                        PathOps.DirectoryChars) : null;

                            string privateBinPath = relativeAssemblyPath;

                            string relativePackagePath =
                                (packagePath != null) && packageUnderBasePath ?
                                    packagePath.Remove(0, basePath.Length).Trim(
                                        PathOps.DirectoryChars) : null;

                            if (!String.IsNullOrEmpty(relativePackagePath))
                            {
                                if (!String.IsNullOrEmpty(privateBinPath))
                                    privateBinPath += Characters.SemiColon;

                                privateBinPath += relativePackagePath;
                            }

                            appDomainSetup.PrivateBinPath = privateBinPath;
                        }

                        return appDomainSetup;
                    }
                    else
                    {
                        error = "assembly path is not under base path";
                    }
                }
                else
                {
                    error = "package path is not under base path";
                }
            }
            else
            {
                error = "invalid base path";
            }

            return null;
        }

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

        public static ReturnCode Create(
            Interpreter interpreter,
            string friendlyName,
            string packagePath,
#if CAS_POLICY
            Evidence evidence,
#endif
            IClientData clientData, /* NOT USED */
            bool useBasePath,
            ref AppDomain appDomain,
            ref Result error
            )
        {
            //
            // NOTE: *WARNING* Empty application domain names are allowed,
            //       please do not change this to "!String.IsNullOrEmpty".
            //
            if (friendlyName != null)
            {
                try
                {
                    AppDomainSetup appDomainSetup = CreateSetup(
                        interpreter, packagePath, useBasePath, ref error);

                    if (appDomainSetup != null)
                    {
                        appDomain = AppDomain.CreateDomain(
                            friendlyName,
#if CAS_POLICY
                            evidence,
#else
                            null,
#endif
                            appDomainSetup);

                        return ReturnCode.Ok;
                    }
                }
                catch (Exception e)
                {
                    error = e;
                }
            }
            else
            {
                error = "invalid friendly name";
            }

            return ReturnCode.Error;
        }

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

        public static ReturnCode Unload(
            AppDomain appDomain,
            IClientData clientData, /* NOT USED */
            ref Result error
            )
        {
            if (appDomain != null)
            {
                try
                {
                    AppDomain.Unload(appDomain); /* throw */

                    return ReturnCode.Ok;
                }
                catch (Exception e)
                {
                    error = e;
                }
            }
            else
            {
                error = "invalid application domain";
            }

            //
            // NOTE: We do not really expect this method to fail;
            //       therefore, output a complaint about it.
            //
            DebugOps.Complain(ReturnCode.Error, error);

            return ReturnCode.Error;
        }
#endif
        #endregion
    }
}
