#region License

/*
 * Copyright © 2002-2007 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#endregion

#region Imports

using System;
using System.Collections;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Reflection;
using System.Reflection.Emit;
using Spring.Collections;

#endregion

namespace Spring.Util
{
    /// <summary>
    /// Use this class for obtaining <see cref="ModuleBuilder"/> instances for dynamic code generation.
    /// </summary>
    /// <remarks>
    /// <p>
    /// The purpose of this class is to provide a simple abstraction for creating and managing dynamic assemblies. 
    /// </p>
    /// <note>
    /// Using this factory you can't define several modules within a single dynamic assembly - only a simple one2one relation between assembly/module is used.
    /// </note>
    /// </remarks>
    /// <example>
    /// <p>The following excerpt from <see cref="Spring.Proxy.DynamicProxyManager"/> demonstrates usage:</p>
    /// <code language="c#">
    /// public class DynamicProxyManager
    /// {
    ///   public const string PROXY_ASSEMBLY_NAME = "Spring.Proxy";
    ///
    ///   public static TypeBuilder CreateTypeBuilder(string name, Type baseType)
    ///   {
    ///     // Generates type name
    ///     string typeName = String.Format("{0}.{1}_{2}", PROXY_ASSEMBLY_NAME, name, Guid.NewGuid().ToString("N"));
    ///     ModuleBuilder module = DynamicCodeManager.GetModuleBuilder(PROXY_ASSEMBLY_NAME);
    ///     return module.DefineType(typeName, PROXY_TYPE_ATTRIBUTES);
    ///   }
    /// }
    /// </code>
    /// </example>
    /// <author>Erich Eichinger</author>
    /// <seealso cref="Spring.Reflection.Dynamic.DynamicReflectionManager"/>
    /// <seealso cref="Spring.Proxy.DynamicProxyManager"/>
    /// <seealso cref="Spring.Objects.Factory.Support.MethodInjectingInstantiationStrategy"/>
    public sealed class DynamicCodeManager
    {
        private static readonly Hashtable s_moduleCache = new CaseInsensitiveHashtable(); //CollectionsUtil.CreateCaseInsensitiveHashtable();
        
        /// <summary>
        /// prevent instantiation
        /// </summary>
        private DynamicCodeManager()
        {
            throw new InvalidOperationException();
        }
        
        /// <summary>
        /// Returns the <see cref="ModuleBuilder"/> for the dynamic module within the specified assembly.
        /// </summary>
        /// <remarks>
        /// If the assembly does not exist yet, it will be created.<br/>
        /// This factory caches any dynamic assembly it creates - calling GetModule() twice with 
        /// the same name will *not* create 2 distinct modules!
        /// </remarks>
        /// <param name="assemblyName">The assembly-name of the module to be returned</param>
        /// <returns>the <see cref="ModuleBuilder"/> that can be used to define new types within the specified assembly</returns>
        public static ModuleBuilder GetModuleBuilder( string assemblyName )
        {
            lock(s_moduleCache.SyncRoot)
            {
                ModuleBuilder module = (ModuleBuilder) s_moduleCache[assemblyName];
                if (module == null)
                {
                    AssemblyName an = new AssemblyName();
                    an.Name = assemblyName;
                    
                    AssemblyBuilder assembly;
#if DEBUG_DYNAMIC
                    assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.RunAndSave, null, null, null, null,null, true );
                    module = assembly.DefineDynamicModule(an.Name, an.Name + ".dll", true);
#else
                    assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run, null, null, null, null,null, true );
#if DEBUG                    
                    module = assembly.DefineDynamicModule(an.Name, true);
#else
			        module = assembly.DefineDynamicModule(an.Name, false);
#endif
#endif
                    s_moduleCache[assemblyName] = module;
                }
                return module;
            }
        }
        
        /// <summary>
        /// Persists the specified dynamic assembly to the file-system
        /// </summary>
        /// <param name="assemblyName">the name of the dynamic assembly to persist</param>
        /// <remarks>
        /// Can only be called in DEBUG_DYNAMIC mode, per ConditionalAttribute rules.
        /// </remarks>
        [Conditional("DEBUG_DYNAMIC")]        
        public static void SaveAssembly( string assemblyName )
        {
            AssertUtils.ArgumentHasText(assemblyName, "assemblyName");
            
            ModuleBuilder module = null;
            lock(s_moduleCache.SyncRoot)
            {
                module = (ModuleBuilder) s_moduleCache[assemblyName];
            }
            
            if(module == null)
            {
                throw new ArgumentException(string.Format("'{0}' is not a valid dynamic assembly name", assemblyName), "assemblyName");
            }

            AssemblyBuilder assembly = (AssemblyBuilder) module.Assembly;
            assembly.Save(assembly.GetName().Name + ".dll");            
        }

        /// <summary>
        /// Removes all registered <see cref="ModuleBuilder"/>s.
        /// </summary>
        public static void Clear()
        {
            lock (s_moduleCache.SyncRoot)
            {
                s_moduleCache.Clear();
            }
        }
    }
}