/*
 * NativeModule.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.Runtime.InteropServices;
using System.Threading;
using Eagle._Attributes;
using Eagle._Components.Public;
using Eagle._Interfaces.Private;
using Eagle._Interfaces.Public;
using _Public = Eagle._Components.Public;

namespace Eagle._Components.Private
{
    [ObjectId("20e0292a-25ad-4817-9ca3-2b86d7f4f002")]
    internal sealed class NativeModule : IModule, IDisposable
    {
        #region Private Data
        private readonly object syncRoot = new object();
        private Interpreter interpreter;
        #endregion

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

        #region Public Constructors
        public NativeModule(
            string name,
            string group,
            string description,
            IClientData clientData,
            Interpreter interpreter,
            ModuleFlags flags,
            string fileName,
            long token
            )
        {
            this.kind = IdentifierKind.NativeModule;
            this.id = Guid.Empty;
            this.name = name;
            this.group = group;
            this.description = description;
            this.clientData = clientData;
            this.interpreter = interpreter;
            this.flags = flags;
            this.fileName = fileName;
            this.token = token;
        }
        #endregion

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

        #region IIdentifier Members
        private IdentifierKind kind;
        public IdentifierKind Kind
        {
            get { CheckDisposed(); return kind; }
            set { CheckDisposed(); kind = value; }
        }

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

        private Guid id;
        public Guid Id
        {
            get { CheckDisposed(); return id; }
            set { CheckDisposed(); id = value; }
        }

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

        private string name;
        public string Name
        {
            get { CheckDisposed(); return name; }
            set { CheckDisposed(); name = value; }
        }

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

        private string group;
        public string Group
        {
            get { CheckDisposed(); return group; }
            set { CheckDisposed(); Group = value; }
        }

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

        private string description;
        public string Description
        {
            get { CheckDisposed(); return description; }
            set { CheckDisposed(); description = value; }
        }
        #endregion

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

        #region IHaveClientData Members
        private IClientData clientData;
        public IClientData ClientData
        {
            get { CheckDisposed(); return clientData; }
            set { CheckDisposed(); clientData = value; }
        }
        #endregion

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

        #region IWrapperData Members
        private long token;
        public long Token
        {
            get { CheckDisposed(); return token; }
            set { CheckDisposed(); token = value; }
        }
        #endregion

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

        #region IModule Members
        private ModuleFlags flags;
        public ModuleFlags Flags
        {
            get
            {
                CheckDisposed();

                lock (syncRoot)
                {
                    return flags;
                }
            }
        }

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

        private string fileName;
        public string FileName
        {
            get
            {
                CheckDisposed();

                lock (syncRoot)
                {
                    return fileName;
                }
            }
        }

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

        private IntPtr module;
        public IntPtr Module
        {
            get
            {
                CheckDisposed();

                lock (syncRoot)
                {
                    return module;
                }
            }
        }

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

        private int referenceCount;
        public int ReferenceCount
        {
            get
            {
                CheckDisposed();

                lock (syncRoot)
                {
                    return referenceCount;
                }
            }
        }

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

        public ReturnCode Load(
            ref Result error
            )
        {
            CheckDisposed();

            int loaded = 0;

            return Load(ref loaded, ref error);
        }

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

        public ReturnCode Load(
            ref int loaded,
            ref Result error
            )
        {
            CheckDisposed();

            ReturnCode code;

            lock (syncRoot)
            {
                if (module == IntPtr.Zero)
                {
                    try
                    {
                        if (!String.IsNullOrEmpty(fileName))
                        {
                            module = NativeOps.LoadLibrary(fileName); /* throw */

                            if (NativeOps.IsValidHandle(module))
                            {
                                Interlocked.Increment(ref loaded);

                                code = ReturnCode.Ok;
                            }
                            else
                            {
                                int lastError = Marshal.GetLastWin32Error();

                                error = String.Format(
                                    "LoadLibrary({1}) failed with error {0}: {2}",
                                    lastError, FormatOps.WrapOrNull(fileName),
                                    NativeOps.GetDynamicLoadingError(lastError));

                                code = ReturnCode.Error;
                            }
                        }
                        else
                        {
                            error = "invalid file name";
                            code = ReturnCode.Error;
                        }
                    }
                    catch (Exception e)
                    {
                        error = e;
                        code = ReturnCode.Error;
                    }
                }
                else
                {
                    //
                    // NOTE: The module was already loaded previously.
                    //
                    code = ReturnCode.Ok;
                }

                //
                // NOTE: If the module handle is valid then we know the module
                //       was loaded successfully or was already loaded; therefore,
                //       increment the reference count.
                //
                if (module != IntPtr.Zero)
                    Interlocked.Increment(ref referenceCount);
            }

            return code;
        }

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

        public ReturnCode Unload(
            ref Result error
            )
        {
            CheckDisposed();

            int loaded = 1;

            return Unload(ref loaded, ref error);
        }

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

        public ReturnCode Unload(
            ref int loaded,
            ref Result error
            )
        {
            CheckDisposed();

            ReturnCode code;

            lock (syncRoot)
            {
                if (module != IntPtr.Zero)
                {
                    if (Interlocked.Decrement(ref referenceCount) <= 0)
                    {
                        if (!FlagOps.HasFlags(flags, ModuleFlags.Locked, true))
                        {
                            try
                            {
                                if (NativeOps.FreeLibrary(module)) /* throw */
                                {
                                    Interlocked.Decrement(ref loaded);

                                    module = IntPtr.Zero;
                                    code = ReturnCode.Ok;
                                }
                                else
                                {
                                    int lastError = Marshal.GetLastWin32Error();

                                    error = String.Format(
                                        "FreeLibrary(0x{1:X}) failed with error {0}: {2}",
                                        lastError, module, NativeOps.GetDynamicLoadingError(
                                        lastError));

                                    code = ReturnCode.Error;
                                }
                            }
                            catch (Exception e)
                            {
                                error = e;
                                code = ReturnCode.Error;
                            }
                        }
                        else
                        {
                            //
                            // NOTE: The native module has been locked in place
                            //       (probably because it cannot be cleanly
                            //       unloaded); therefore, leave it alone.
                            //
                            code = ReturnCode.Ok;
                        }
                    }
                    else
                    {
                        //
                        // NOTE: There are still outstanding references to the
                        //       native module, do nothing.
                        //
                        code = ReturnCode.Ok;
                    }
                }
                else
                {
                    //
                    // NOTE: The module was already unloaded previously.
                    //
                    code = ReturnCode.Ok;
                }
            }

            return code;
        }
        #endregion

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

        #region Static "Factory" Members
        public static ReturnCode Load(
            Interpreter interpreter,
            string name,
            ModuleFlags flags,
            string fileName,
            ref int loaded,
            ref IModule module,
            ref Result error
            )
        {
            ReturnCode code;

            if (!String.IsNullOrEmpty(fileName))
            {
                if (module == null)
                {
                    NativeModule newModule = new NativeModule(
                        name, null, null, _Public.ClientData.Empty,
                        interpreter, flags, fileName, 0);

                    code = newModule.Load(ref loaded, ref error);

                    if (code == ReturnCode.Ok)
                        module = newModule;
                    else
                        newModule.Dispose();
                }
                else
                {
                    error = "cannot overwrite valid native module";
                    code = ReturnCode.Error;
                }
            }
            else
            {
                error = "invalid file name";
                code = ReturnCode.Error;
            }

            return code;
        }
        #endregion

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

        #region Destructor
        ~NativeModule()
        {
            Dispose(false);
        }
        #endregion

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

        #region System.Object Overrides
        public override string ToString()
        {
            CheckDisposed();

            return (fileName != null) ? fileName : String.Empty;
        }
        #endregion

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

        #region IDisposable "Pattern" Members
        private bool disposed;
        private void CheckDisposed() /* throw */
        {
#if THROW_ON_DISPOSED
            if (disposed && Engine.IsThrowOnDisposed(interpreter, true))
                throw new ObjectDisposedException(typeof(NativeModule).Name);
#endif
        }

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

        private /* protected virtual */ void Dispose(bool disposing)
        {
            lock (syncRoot)
            {
                if (!disposed)
                {
                    if (disposing)
                    {
                        ////////////////////////////////////
                        // dispose managed resources here...
                        ////////////////////////////////////

                        //
                        // NOTE: Get rid of the module file name.
                        //
                        fileName = null;

                        //
                        // NOTE: Get rid of other stuff...
                        //
                        clientData = null;
                        description = null;

                        //
                        // NOTE: We do not own the interpreter, just clear our
                        //       reference to it.
                        //
                        interpreter = null;
                    }

                    //////////////////////////////////////
                    // release unmanaged resources here...
                    //////////////////////////////////////

                    ReturnCode unloadCode;
                    Result unloadError = null;

                    unloadCode = Unload(ref unloadError);

                    if (unloadCode != ReturnCode.Ok)
                        DebugOps.Complain(interpreter, unloadCode, unloadError);

                    //
                    // NOTE: This object is now disposed.
                    //
                    disposed = true;
                }
            }
        }
        #endregion

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

        #region IDisposable Members
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        #endregion
    }
}
