/*
 * AssemblyOps.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.IO;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.Security.Policy;
using System.Text;
using Eagle._Attributes;
using Eagle._Components.Public;

namespace Eagle._Components.Private
{
    [ObjectId("adb2230c-58c9-4950-991d-d2e83931ad47")]
    internal static class AssemblyOps
    {
        #region Assembly Support Methods
        public static Assembly FindInAppDomain(
            AppDomain appDomain,
            AssemblyName assemblyName
            )
        {
            if (assemblyName != null)
            {
                if (appDomain == null)
                    appDomain = AppDomain.CurrentDomain;

                Assembly[] assemblies = appDomain.GetAssemblies();

                foreach (Assembly assembly in assemblies)
                {
                    if (assembly != null)
                    {
                        AssemblyName localAssemblyName = assembly.GetName();

                        if (localAssemblyName != null)
                        {
                            if (Object.ReferenceEquals(
                                    localAssemblyName, assemblyName) ||
                                String.Equals(
                                    localAssemblyName.FullName,
                                    assemblyName.FullName,
                                    StringOps.SystemStringComparisonType))
                            {
                                return assembly;
                            }
                        }
                    }
                }
            }

            return null;
        }

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

        public static Assembly FindInAppDomain(
            AppDomain appDomain,
            string name,
            Version version,
            byte[] publicKeyToken,
            ref Result error
            )
        {
            if (appDomain == null)
                appDomain = AppDomain.CurrentDomain;

            Assembly[] assemblies = appDomain.GetAssemblies();

            foreach (Assembly assembly in assemblies)
            {
                if (assembly == null)
                    continue;

                AssemblyName assemblyName = assembly.GetName();

                if (assemblyName == null)
                    continue;

                if ((name != null) &&
                    !String.Equals(
                        assemblyName.Name, name,
                        StringOps.SystemStringComparisonType))
                {
                    continue;
                }

                if ((version != null) &&
                    (PackageOps.VersionCompare(assemblyName.Version, version) != 0))
                {
                    continue;
                }

                if ((publicKeyToken != null) &&
                    !ArrayOps.Equals(
                        assemblyName.GetPublicKeyToken(), publicKeyToken))
                {
                    continue;
                }

                return assembly;
            }

            error = "assembly not found in application domain";
            return null;
        }

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

        public static Assembly LoadFromStream(
            Stream stream,
            ref Result error
            )
        {
            if (stream == null)
            {
                error = "invalid stream";
                return null;
            }

            if (!stream.CanRead)
            {
                error = "stream is not readable";
                return null;
            }

            if (!stream.CanSeek)
            {
                error = "stream is not seekable";
                return null;
            }

            try
            {
                using (BinaryReader binaryReader = new BinaryReader(stream))
                {
                    byte[] bytes = binaryReader.ReadBytes(
                        (int)stream.Length); /* throw */

                    return Assembly.Load(bytes); /* throw */
                }
            }
            catch (Exception e)
            {
                error = e;
            }

            return null;
        }
        #endregion

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

        #region Assembly Property Helper Methods
        public static string GetFullName(
            Type type
            )
        {
            if (type != null)
            {
                try
                {
                    Assembly assembly = type.Assembly;

                    if (assembly != null)
                    {
                        AssemblyName assemblyName = assembly.GetName();

                        if (assemblyName != null)
                            return assemblyName.FullName;
                    }
                }
                catch
                {
                    // do nothing.
                }
            }

            return null;
        }

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

        public static string GetPublicKey(
            AssemblyName assemblyName
            )
        {
            if (assemblyName != null)
            {
                try
                {
                    return ArrayOps.ToHexadecimalString(
                        assemblyName.GetPublicKey());
                }
                catch
                {
                    // do nothing.
                }
            }

            return null;
        }

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

        public static string GetPublicKeyToken(
            AssemblyName assemblyName
            )
        {
            if (assemblyName != null)
            {
                try
                {
                    return ArrayOps.ToHexadecimalString(
                        assemblyName.GetPublicKeyToken());
                }
                catch
                {
                    // do nothing.
                }
            }

            return null;
        }

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

        public static Version GetVersion(
            Assembly assembly
            )
        {
            if (assembly != null)
            {
                try
                {
                    AssemblyName assemblyName = assembly.GetName();

                    if (assemblyName != null)
                        return assemblyName.Version;
                }
                catch
                {
                    // do nothing.
                }
            }

            return null;
        }

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

        public static string GetImageRuntimeVersion(
            Assembly assembly
            )
        {
            if (assembly != null)
            {
                try
                {
                    return assembly.ImageRuntimeVersion;
                }
                catch
                {
                    // do nothing.
                }
            }

            return null;
        }

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

        public static Guid GetModuleVersionId(
            Assembly assembly
            )
        {
            if (assembly != null)
            {
                try
                {
                    Module module = assembly.ManifestModule;

                    if (module != null)
                        return module.ModuleVersionId;
                }
                catch
                {
                    // do nothing.
                }
            }

            return Guid.Empty;
        }

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

        public static string GetPath(
            Assembly assembly
            )
        {
            if (assembly != null)
            {
                try
                {
                    string location = assembly.Location;

                    if (!String.IsNullOrEmpty(location))
                        return Path.GetDirectoryName(location);
                }
                catch
                {
                    // do nothing.
                }
            }

            return null;
        }
        #endregion

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

        #region Assembly Resource Helper Methods
        public static Stream GetResourceStream(
            Assembly assembly,
            string name
            )
        {
            Result error = null;

            return GetResourceStream(assembly, name, ref error);
        }

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

        public static Stream GetResourceStream(
            Assembly assembly,
            string name,
            ref Result error
            )
        {
            if (assembly != null)
            {
                try
                {
                    return assembly.GetManifestResourceStream(name);
                }
                catch (Exception e)
                {
                    error = e;
                }
            }
            else
            {
                error = "invalid assembly";
            }

            return null;
        }
        #endregion

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

        #region Assembly StrongName Helper Methods
        public static StrongName GetStrongName(
            Assembly assembly
            )
        {
            if (assembly != null)
            {
                StrongName strongName = null;

                if (GetStrongName(assembly,
                        ref strongName) == ReturnCode.Ok)
                {
                    return strongName;
                }
            }

            return null;
        }

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

        public static ReturnCode GetStrongName(
            Assembly assembly,
            ref StrongName strongName
            )
        {
            Result error = null;

            return GetStrongName(assembly, ref strongName, ref error);
        }

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

        public static ReturnCode GetStrongName(
            Assembly assembly,
            ref StrongName strongName,
            ref Result error
            )
        {
            if (assembly != null)
            {
                Evidence evidence = assembly.Evidence;

                if (evidence != null)
                {
                    try
                    {
#if MONO_BUILD
#pragma warning disable 612
#endif
                        foreach (object item in evidence)
#if MONO_BUILD
#pragma warning restore 612
#endif
                        {
                            if (item is StrongName)
                            {
                                strongName = (StrongName)item;
                                return ReturnCode.Ok;
                            }
                        }

                        error = "no strong name found";
                        return ReturnCode.Error;
                    }
                    catch (Exception e)
                    {
                        error = e;
                    }
                }
                else
                {
                    error = "invalid evidence";
                }
            }
            else
            {
                error = "invalid assembly";
            }

            TraceOps.DebugTrace(String.Format(
                "{0}: GetStrongName: assembly {1} query failure, error = {2}{3}",
                GlobalState.GetCurrentThreadId(), FormatOps.WrapOrNull(assembly),
                FormatOps.WrapOrNull(true, true, error), Environment.NewLine),
                typeof(AssemblyOps).Name);

            return ReturnCode.Error;
        }
        #endregion

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

        #region Assembly Hash Helper Methods
        public static Hash GetHash(
            Assembly assembly
            )
        {
            if (assembly != null)
            {
                Hash hash = null;

                if (GetHash(assembly, ref hash) == ReturnCode.Ok)
                    return hash;
            }

            return null;
        }

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

        public static ReturnCode GetHash(
            Assembly assembly,
            ref Hash hash
            )
        {
            Result error = null;

            return GetHash(assembly, ref hash, ref error);
        }

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

        public static ReturnCode GetHash(
            Assembly assembly,
            ref Hash hash,
            ref Result error
            )
        {
            if (assembly != null)
            {
                Evidence evidence = assembly.Evidence;

                if (evidence != null)
                {
                    try
                    {
#if MONO_BUILD
#pragma warning disable 612
#endif
                        foreach (object item in evidence)
#if MONO_BUILD
#pragma warning restore 612
#endif
                        {
                            if (item is Hash)
                            {
                                hash = (Hash)item;
                                return ReturnCode.Ok;
                            }
                        }

                        error = "no hash found";
                        return ReturnCode.Error;
                    }
                    catch (Exception e)
                    {
                        error = e;
                    }
                }
                else
                {
                    error = "invalid evidence";
                }
            }
            else
            {
                error = "invalid assembly";
            }

            TraceOps.DebugTrace(String.Format(
                "{0}: GetHash: assembly {1} query failure, error = {2}{3}",
                GlobalState.GetCurrentThreadId(), FormatOps.WrapOrNull(assembly),
                FormatOps.WrapOrNull(true, true, error), Environment.NewLine),
                typeof(AssemblyOps).Name);

            return ReturnCode.Error;
        }
        #endregion

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

        #region Assembly Certificate Helper Methods
        public static X509Certificate GetCertificate(
            Assembly assembly
            )
        {
            if (assembly != null)
            {
                X509Certificate certificate = null;

                if (GetCertificate(assembly,
                        ref certificate) == ReturnCode.Ok)
                {
                    return certificate;
                }
            }

            return null;
        }

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

        public static ReturnCode GetCertificate(
            Assembly assembly,
            ref X509Certificate certificate
            )
        {
            Result error = null;

            return GetCertificate(assembly, ref certificate, ref error);
        }

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

        public static ReturnCode GetCertificate(
            Assembly assembly,
            ref X509Certificate certificate,
            ref Result error
            )
        {
            if (assembly != null)
            {
                Module module = assembly.ManifestModule;

                if (module != null)
                {
                    try
                    {
                        certificate = module.GetSignerCertificate();

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

            TraceOps.DebugTrace(String.Format(
                "{0}: GetCertificate: assembly {1} query failure, error = {2}{3}",
                GlobalState.GetCurrentThreadId(), FormatOps.WrapOrNull(assembly),
                FormatOps.WrapOrNull(true, true, error), Environment.NewLine),
                typeof(AssemblyOps).Name);

            return ReturnCode.Error;
        }

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

        public static ReturnCode GetCertificate(
            byte[] assemblyBytes,
            ref X509Certificate certificate
            )
        {
            Result error = null;

            return GetCertificate(assemblyBytes, ref certificate, ref error);
        }

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

        public static ReturnCode GetCertificate(
            byte[] assemblyBytes,
            ref X509Certificate certificate,
            ref Result error
            )
        {
            if (assemblyBytes != null)
            {
                try
                {
                    certificate = new X509Certificate(assemblyBytes);
                    return ReturnCode.Ok;
                }
                catch (Exception e)
                {
                    error = e;
                }
            }
            else
            {
                error = "invalid assembly bytes";
            }

            TraceOps.DebugTrace(String.Format(
                "{0}: GetCertificate: query failure, error = {1}{2}",
                GlobalState.GetCurrentThreadId(), FormatOps.WrapOrNull(
                true, true, error), Environment.NewLine),
                typeof(AssemblyOps).Name);

            return ReturnCode.Error;
        }

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

        public static X509Certificate2 GetCertificate2(
            Assembly assembly
            )
        {
            if (assembly != null)
            {
                X509Certificate certificate = null;

                if (GetCertificate(assembly,
                        ref certificate) == ReturnCode.Ok)
                {
                    try
                    {
                        return (certificate != null) ?
                            new X509Certificate2(certificate) : null;
                    }
                    catch (Exception e)
                    {
                        //
                        // NOTE: Nothing we can do here except log the failure.
                        //
                        TraceOps.DebugTrace(String.Format(
                            "{0}: {1}{2}",
                            GlobalState.GetCurrentThreadId(),
                            e, Environment.NewLine),
                            typeof(AssemblyOps).Name);
                    }
                }
            }

            return null;
        }

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

        public static ReturnCode GetCertificate2(
            Assembly assembly,
            bool strict,
            ref X509Certificate2 certificate,
            ref Result error
            )
        {
            X509Certificate localCertificate = null;

            if (GetCertificate(assembly, ref localCertificate,
                    ref error) == ReturnCode.Ok)
            {
                if (localCertificate != null)
                {
                    try
                    {
                        certificate = new X509Certificate2(localCertificate);

                        return ReturnCode.Ok;
                    }
                    catch (Exception e)
                    {
                        error = e;
                    }
                }
                else if (!strict)
                {
                    certificate = null;

                    return ReturnCode.Ok;
                }
                else
                {
                    error = "invalid certificate";
                }
            }

            TraceOps.DebugTrace(String.Format(
                "{0}: GetCertificate2: assembly {1} query failure, error = {2}{3}",
                GlobalState.GetCurrentThreadId(), FormatOps.WrapOrNull(assembly),
                FormatOps.WrapOrNull(true, true, error), Environment.NewLine),
                typeof(AssemblyOps).Name);

            return ReturnCode.Error;
        }
        #endregion
    }
}
