using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;

namespace NZSDK_CSharpSample
{
    public class DynamicLibraryLoader : IDisposable
    {
        #region Fields

        protected IntPtr ptrLibHandle;
        protected string debugFileName;
        protected string releaseFileName;
        protected string directoryPath;
        protected Dictionary<string, IntPtr> dicProcAddresses = new Dictionary<string, IntPtr>();

        #endregion

        #region Constructors

        public DynamicLibraryLoader()
        {
            if (Assembly.GetExecutingAssembly() != null)
                directoryPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets directory for library.
        /// </summary>
        public string DirectoryPath
        {
            get { return directoryPath; }
            set { directoryPath = value; }
        }

        /// <summary>
        /// Gets file name for debug
        /// </summary>
        public string DebugFileName
        {
            get { return debugFileName; }
            set { debugFileName = value; }
        }

        protected string DebugFilePath
        {
            get
            {
                if (String.IsNullOrEmpty(directoryPath))
                    return debugFileName;

                return Path.Combine(directoryPath, debugFileName);
            }
        }

        /// <summary>
        /// Gets file name for release
        /// </summary>
        public string ReleaseFileName
        {
            get { return releaseFileName; }
            set { releaseFileName = value; }
        }

        protected string ReleaseFilePath
        {
            get
            {
                if (String.IsNullOrEmpty(directoryPath))
                    return releaseFileName;
                return Path.Combine(directoryPath, releaseFileName);
            }
        }

        /// <summary>
        /// Gets handle
        /// </summary>
        public IntPtr LibraryHandle
        {
            get { return ptrLibHandle; }
        }

        /// <summary>
        /// Is loaded?
        /// </summary>
        public bool IsLoaded
        {
            get
            {
                return (ptrLibHandle != IntPtr.Zero);
            }
        }

        #endregion

        #region Private / Protected Methods



        #endregion

        #region Public Methods

        public virtual bool Load()
        {
            if (IsLoaded)
            {
                return true;
            }

            try
            {
                if (!String.IsNullOrEmpty(debugFileName) && File.Exists(DebugFilePath))
                {
                    ptrLibHandle = WinAPI.LoadLibraryEx(DebugFilePath, IntPtr.Zero, 0);
                    if (ptrLibHandle != IntPtr.Zero)
                        return true;
                }

                if (String.IsNullOrEmpty(releaseFileName) || !File.Exists(ReleaseFilePath))
                    return false;

                if (Directory.Exists(directoryPath))
                {
                    WinAPI.SetDllDirectory(DirectoryPath);
                }

                ptrLibHandle = WinAPI.LoadLibraryEx(ReleaseFilePath, IntPtr.Zero, 0);
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.ToString());
                return false;
            }

            if (ptrLibHandle == IntPtr.Zero)
            {
                Debug.WriteLine(String.Format("Error Code : {0}", Marshal.GetLastWin32Error()));
                return false;
            }

            return true;
        }

        public virtual void Free()
        {
            if (IsLoaded == false)
            {
                return;
            }

            try
            {
                WinAPI.FreeLibrary(ptrLibHandle);
                ptrLibHandle = IntPtr.Zero;
                dicProcAddresses.Clear();
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.ToString());
            }
        }

        public IntPtr GetProcAddress(Enum eFunction)
        {
            return GetProcAddress(eFunction.ToString());
        }

        public IntPtr GetProcAddress(string functionName)
        {
            if (!IsLoaded)
            {
                throw new Exception("The library was not loaded.");
            }

            if (dicProcAddresses.ContainsKey(functionName))
                return dicProcAddresses[functionName];

            IntPtr ptrFunction = WinAPI.GetProcAddress(ptrLibHandle, functionName);
            if (ptrFunction == IntPtr.Zero)
                Debug.WriteLine(String.Format("Can't get '{0}' function address. The error code is {1}", functionName, Marshal.GetLastWin32Error()));

            dicProcAddresses[functionName] = ptrFunction;
            return ptrFunction;
        }

        public Delegate GetProcAddress(string functionName, Type t)
        {
            IntPtr ptr = GetProcAddress(functionName);
            if (ptr == IntPtr.Zero)
                return null;

            return Marshal.GetDelegateForFunctionPointer(ptr, t);
        }

        public virtual void Dispose()
        {
            Free();
        }

        #endregion
    }
}
