/*
 * SetupOps.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 Microsoft.Win32;
using Eagle._Attributes;
using Eagle._Components.Public;
using Eagle._Containers.Public;

namespace Eagle._Components.Private
{
    [ObjectId("bd1dfe4f-116f-4780-9bbf-4c8c80d94873")]
    internal static class SetupOps
    {
        #region Private Constants
#if NATIVE && WINDOWS
        private static readonly string mutexName =
            GlobalState.GetPackageName(null, "_Setup", false);

        private static readonly string globalMutexName =
            "Global\\" + mutexName;
#endif

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

        private static readonly string libraryKeyName =
            GlobalState.GetPackageName("Software\\", null, false);

        private const string assemblyValueName = "Assembly";
        private const string appIdValueName = "AppId";
        private const string compileInfoValueName = "CompileInfo";
        private const string pathValueName = "Path";
        #endregion

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

        #region Private Data
#if NATIVE && WINDOWS
        //
        // NOTE: Used to serialize access to the mutex handles below.
        //
        private static readonly object syncRoot = new object();

        //
        // NOTE: *NEVER CLOSED* Prevents our setup from installing.
        //
        private static IntPtr globalMutex = IntPtr.Zero;
        private static IntPtr mutex = IntPtr.Zero;
#endif
        #endregion

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

        public static string GetPath(
            Version version
            )
        {
            RegistryKey[] rootKeys = new RegistryKey[] {
                Registry.CurrentUser,
                Registry.LocalMachine
            };

            foreach (RegistryKey rootKey in rootKeys)
            {
                if (rootKey == null)
                    continue;

                string path = GetPath(rootKey, version);

                if (path != null)
                    return path;
            }

            return null;
        }

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

        private static string GetPath(
            RegistryKey rootKey,
            Version version
            )
        {
            string path = null;
            Result error = null;

            if (!GetPath(rootKey, version, ref path, ref error))
            {
                DebugOps.Complain(ReturnCode.Error, error);
                return null;
            }

            return path;
        }

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

        private static bool GetPath(
            RegistryKey rootKey,
            Version version,
            ref string path,
            ref Result error
            )
        {
            if (rootKey == null)
            {
                error = "invalid root registry key";
                return false;
            }

            if (version == null)
            {
                error = "invalid version";
                return false;
            }

            if (String.IsNullOrEmpty(libraryKeyName))
            {
                error = "invalid library registry key name";
                return false;
            }

            try
            {
                string keyName = String.Format(
                    "{0}\\{1}", libraryKeyName, version);

                using (RegistryKey key = rootKey.OpenSubKey(keyName))
                {
                    if (key != null)
                    {
                        path = key.GetValue(pathValueName) as string;
                        return true;
                    }
                    else
                    {
                        error = String.Format(
                            "setup instance not found: {0}\\{1}",
                            rootKey, keyName);
                    }
                }
            }
            catch (Exception e)
            {
                error = e;
            }

            return false;
        }

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

        public static ReturnCode GetInstances(
            ref Result result
            )
        {
            return GetInstances(Registry.LocalMachine, ref result);
        }

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

        private static ReturnCode GetInstances(
            RegistryKey rootKey,
            ref Result result
            )
        {
            if (rootKey == null)
            {
                result = "invalid root registry key";
                return ReturnCode.Error;
            }

            if (String.IsNullOrEmpty(libraryKeyName))
            {
                result = "invalid library registry key name";
                return ReturnCode.Error;
            }

            try
            {
                using (RegistryKey key = rootKey.OpenSubKey(libraryKeyName))
                {
                    if (key == null)
                    {
                        result = "no setup instances found";
                        return ReturnCode.Error;
                    }

                    StringList list = new StringList();

                    foreach (string subKeyName in key.GetSubKeyNames())
                    {
                        StringList subList = new StringList("Name",
                            subKeyName);

                        try
                        {
                            using (RegistryKey subKey = key.OpenSubKey(
                                    subKeyName))
                            {
                                if (subKey != null)
                                {
                                    subList.Add(appIdValueName,
                                        subKey.GetValue(appIdValueName) as string);

                                    subList.Add(pathValueName,
                                        subKey.GetValue(pathValueName) as string);

                                    subList.Add(assemblyValueName,
                                        subKey.GetValue(assemblyValueName) as string);

                                    subList.Add(compileInfoValueName,
                                        subKey.GetValue(compileInfoValueName) as string);
                                }
                                else
                                {
                                    subList.Add("Error",
                                        String.Format("could not open sub-key " +
                                        "\"{0}\" of registry key \"{1}\"",
                                        subKeyName, key));
                                }
                            }
                        }
                        catch (Exception e)
                        {
                            subList.Add("Exception", e.ToString());

                            subList.Add("Error",
                                String.Format("could not open sub-key " +
                                "\"{0}\" of registry key \"{1}\"",
                                subKeyName, key));
                        }

                        list.Add(subList.ToString());
                    }

                    result = list;
                    return ReturnCode.Ok;
                }
            }
            catch (Exception e)
            {
                result = e;
            }

            return ReturnCode.Error;
        }

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

#if NATIVE && WINDOWS
        private static IntPtr CreateMutex(
            string name
            )
        {
            return NativeOps.UnsafeNativeMethods.CreateMutex(
                IntPtr.Zero, true, name);
        }

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

        public static void CreateMutexes()
        {
            ReturnCode mutexCode;
            Result mutexError = null;

            mutexCode = CreateMutexes(ref mutexError);

            if (mutexCode != ReturnCode.Ok)
                DebugOps.Complain(null, mutexCode, mutexError);
        }

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

        public static ReturnCode CreateMutexes(
            ref Result error
            )
        {
            if (!PlatformOps.IsWindowsOperatingSystem())
            {
                error = "not supported on this operating system";
                return ReturnCode.Error;
            }

            try
            {
                lock (syncRoot)
                {
                    ResultList errors = null;

                    //
                    // NOTE: *NEVER CLOSED* This should prevent our setup
                    //       from running while the library is in use
                    //       (even if the setup is running in a different
                    //       user session).
                    //
                    if (!String.IsNullOrEmpty(globalMutexName))
                    {
                        if (globalMutex == IntPtr.Zero)
                            globalMutex = CreateMutex(globalMutexName);

                        if (globalMutex == IntPtr.Zero)
                        {
                            if (errors == null)
                                errors = new ResultList();

                            Result localError = String.Format(
                                "could not create global mutex \"{0}\": {1}",
                                globalMutexName, NativeOps.GetErrorMessage());

                            errors.Add(localError);

                            DebugOps.Complain(ReturnCode.Error, localError);
                        }
                    }

                    //
                    // NOTE: *NEVER CLOSED* This should prevent our setup
                    //       from running while the library is in use.
                    //
                    if (!String.IsNullOrEmpty(mutexName))
                    {
                        if (mutex == IntPtr.Zero)
                            mutex = CreateMutex(mutexName);

                        if (mutex == IntPtr.Zero)
                        {
                            if (errors == null)
                                errors = new ResultList();

                            Result localError = String.Format(
                                "could not create mutex \"{0}\": {1}",
                                mutexName, NativeOps.GetErrorMessage());

                            errors.Add(localError);

                            DebugOps.Complain(ReturnCode.Error, localError);
                        }
                    }

                    //
                    // NOTE: Pass any errors back to the caller.
                    //
                    if (errors != null)
                        error = errors;
                }

                return ReturnCode.Ok;
            }
            catch (Exception e)
            {
                error = e;
            }

            return ReturnCode.Error;
        }
#endif
    }
}
