using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Runtime.InteropServices;
using System.IO;
using System.Windows.Forms;

namespace Dialect
{
    /// <summary>
    /// Imports Mosaic plugin DLLs into the application.
    /// </summary>
    public class PluginManager
    {
        /// <summary>
        /// Delegate to get the DLL pointer.
        /// </summary>
        /// <returns>The pointer.</returns>
        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        internal delegate IntPtr GetInModulePtr();

        public PluginManager()
        {
            Plugins = new List<Plugin>();
        }

        /// <summary>
        /// The list of imported plugins.
        /// </summary>
        public List<Plugin> Plugins;

        /// <summary>
        /// Loads the plugins from a list of supplied paths.
        /// </summary>
        /// <param name="paths">The plugins to load.</param>
        public void Load(params String[] paths)
        {
            // Bail if no paths.
            if (paths.Length <= 0)
                return;

            bool foundExisting = false;

            for (int i = 0; i < paths.Length; i++)
            {
                // Load the library using the WinAPI.
                IntPtr pDll = NativeMethods.LoadLibrary(paths[i]);

                // Skip if pointer is borked.
                if (pDll == IntPtr.Zero)
                    continue;

                // Find the main 'Plugin' method.
                GetInModulePtr getPlugin = (GetInModulePtr)GetMethod(pDll, typeof(GetInModulePtr), "Plugin");

                // Skip if pointer is borked.
                if (getPlugin == null)
                    continue;

                // Marshal the attributes structure from the method pointer.
                IntPtr pModule = getPlugin();
                PluginAttributes pluginAttributes = (PluginAttributes)Marshal.PtrToStructure(pModule, typeof(PluginAttributes));
                Marshal.StructureToPtr(pluginAttributes, pModule, false);

                // Create a new plugin from the attributes.
                Plugin newPlugin = new Plugin(Plugins.Count, pluginAttributes.Name);
                newPlugin.Version = pluginAttributes.Version;
                newPlugin.Description = pluginAttributes.Description;

                // Check if the plugin name already exists.
                foundExisting = false;
                foreach (Plugin Plugin in Plugins)
                {
                    if (Plugin.Name == newPlugin.Name)
                    {
                        foundExisting = true;
                        break;
                    }
                }

                if (foundExisting)
                    continue;
               
                // Add the plugin to the list.
                Plugins.Add(newPlugin);
            }
        }

        /// <summary>
        /// Finds a plugin in the list from a supplied name.
        /// </summary>
        /// <param name="name">The name to find.</param>
        /// <returns>The found plugin.</returns>
        public Plugin FindPlugin(String name)
        {
            return Plugins.Find(delegate(Plugin findPlugin) { return findPlugin.Name == name; });
        }

        /// <summary>
        /// Calls a function pointer.
        /// </summary>
        /// <param name="pDll">The pointer to the DLL.</param>
        /// <param name="type">The marshal structure.</param>
        /// <param name="methodName">The method name to call.</param>
        /// <returns>The return value from the function pointer.</returns>
        private Object GetMethod(IntPtr pDll, Type type, String methodName)
        {
            // Use the WinAPI to get the procedure address.
            IntPtr pAddressOfFunctionToCall = NativeMethods.GetProcAddress(pDll, methodName);
            Object functionPointerDelegate;

            try
            {
                // Get the function pointer.
                functionPointerDelegate = Marshal.GetDelegateForFunctionPointer(pAddressOfFunctionToCall, type);
                return functionPointerDelegate;
            }
            catch (ArgumentNullException)
            {
                // Do nothing if the function failed (an invalid plugin).
                return null;
            }
        }
    }

    /// <summary>
    /// A selection of WinAPI calls needed to import the DLLs
    /// </summary>
    static class NativeMethods
    {
        /// <summary>
        /// Loads a DLL into memory.
        /// </summary>
        /// <param name="dllToLoad">The path to the DLL.</param>
        /// <returns>The pointer to the DLL.</returns>
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr LoadLibrary(string dllToLoad);

        /// <summary>
        /// Gets the procedure address of a module.
        /// </summary>
        /// <param name="hModule">The module to find.</param>
        /// <param name="procedureName">The procedure name.</param>
        /// <returns>A pointer to the procedure.</returns>
        [DllImport("kernel32.dll")]
        public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);

        /// <summary>
        /// Releases a library from memory.
        /// </summary>
        /// <param name="hModule">The module to release.</param>
        /// <returns>If the release was a success.</returns>
        [DllImport("kernel32.dll")]
        public static extern bool FreeLibrary(IntPtr hModule);

        /// <summary>
        /// Gets a handle to a module.
        /// </summary>
        /// <param name="lpModuleName">The module name.</param>
        /// <returns>The pointer to the module.</returns>
        [DllImport("kernel32.dll")]
        private static extern IntPtr GetModuleHandle(string lpModuleName);
    }

    /// <summary>
    /// The plugin structure inside the Mosaic DLL.
    /// </summary>
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Size = 152)]
    public class PluginAttributes
    {
        /// <summary>
        /// The plugin version.
        /// </summary>
        public int Version;
        
        /// <summary>
        /// The plugin name.
        /// </summary>
        [MarshalAs(UnmanagedType.LPStr)]
        public String Name;

        /// <summary>
        /// The plugin description.
        /// </summary>
        [MarshalAs(UnmanagedType.LPStr)]
        public String Description;
    }
}



