#region License

/*
 * Copyright 2002-2009 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

using System;
using System.Collections;
using Common.Logging;
using Spring.Context;
using Spring.Context.Support;
using Spring.Util;

namespace Spring.Testing.Microsoft
{
    /// <summary>
    /// Superclass for NUnit test cases using a Spring context.
    /// </summary>
    /// <remarks>
    /// <p>Maintains a cache of contexts by key. This has significant performance
    /// benefit if initializing the context would take time. While initializing a 
    /// Spring context itself is very quick, some objects in a context, such as
    /// a LocalSessionFactoryObject for working with NHibernate, may take time to
    /// initialize. Hence it often makes sense to do that initializing once.</p>
    /// <p>Normally you won't extend this class directly but rather extend one
    /// of its subclasses.</p>
    /// </remarks>
    /// <author>Rod Johnson</author>
    /// <author>Aleksandar Seovic (.NET)</author>
    public abstract class AbstractSpringContextTests 
    {
        /// <summary>
        /// Map of context keys returned by subclasses of this class, to
        /// Spring contexts.
        /// </summary>
	    private static readonly IDictionary contextKeyToContextMap;

        /// <summary>
        /// Static ctor to avoid "beforeFieldInit" problem.
        /// </summary>
        static AbstractSpringContextTests()
        {
            contextKeyToContextMap = new Hashtable();
        }

        /// <summary>
        /// Disposes any cached context instance and removes it from cache.
        /// </summary>
        public static void ClearContextCache()
        {
            foreach(IApplicationContext ctx in contextKeyToContextMap.Values)
            {
                ctx.Dispose();
            }
            contextKeyToContextMap.Clear();
        }

        /// <summary>
        /// Indicates, whether context instances should be automatically registered with the global <see cref="ContextRegistry"/>.
        /// </summary>
        private bool registerContextWithContextRegistry = true;

        /// <summary>
        /// Logger available to subclasses.
        /// </summary>
        protected readonly ILog logger;

        /// <summary>
        /// Default constructor for AbstractSpringContextTests.
        /// </summary>
	    protected AbstractSpringContextTests()
        {
            logger = LogManager.GetLogger(GetType());
        }

        /// <summary>
        /// Controls, whether application context instances will
        /// be registered/unregistered with the global <see cref="ContextRegistry"/>.
        /// Defaults to <c>true</c>.
        /// </summary>
        public bool RegisterContextWithContextRegistry
        {
            get { return registerContextWithContextRegistry; }
            set { registerContextWithContextRegistry = value; }
        }

        /// <summary>
        /// Set custom locations dirty. This will cause them to be reloaded
        /// from the cache before the next test case is executed.
        /// </summary>
        /// <remarks>
        /// Call this method only if you change the state of a singleton
        /// object, potentially affecting future tests.
        /// </remarks>
        /// <param name="locations">Locations </param>
	    protected void SetDirty(string[] locations) 
        {
		    String keyString = ContextKeyString(locations);
		    IConfigurableApplicationContext ctx =
				    (IConfigurableApplicationContext) contextKeyToContextMap[keyString];
	        contextKeyToContextMap.Remove(keyString);

            if (ctx != null) 
            {
			    ctx.Dispose();
		    }
	    }

        /// <summary>
        /// Returns <c>true</c> if context for the specified 
        /// <paramref name="contextKey"/> is cached.
        /// </summary>
        /// <param name="contextKey">Context key to check.</param>
        /// <returns>
        /// <c>true</c> if context for the specified 
        /// <paramref name="contextKey"/> is cached, 
        /// <c>false</c> otherwise.
        /// </returns>
	    protected bool HasCachedContext(object contextKey) 
        {
            string keyString = ContextKeyString(contextKey);
            return contextKeyToContextMap.Contains(keyString);
	    }

        /// <summary>
        /// Converts context key to string.
        /// </summary>
        /// <remarks>
        /// Subclasses can override this to return a string representation of
        /// their contextKey for use in logging.
        /// </remarks>
        /// <param name="contextKey">Context key to convert.</param>
        /// <returns>
        /// String representation of the specified <paramref name="contextKey"/>.  Null if 
        /// contextKey is null.
        /// </returns>
	    protected virtual string ContextKeyString(object contextKey) 
        {
            if (contextKey == null)
            {
                return null;
            }
		    if (contextKey is string[]) 
            {
			    return StringUtils.ArrayToCommaDelimitedString((string[]) contextKey);
		    }
		    else 
            {
			    return contextKey.ToString();
		    }
	    }

        /// <summary>
        /// Caches application context.
        /// </summary>
        /// <param name="key">Key to use.</param>
        /// <param name="context">Context to cache.</param>
	    public void AddContext(object key, IConfigurableApplicationContext context) 
        {
            AssertUtils.ArgumentNotNull(context, "context", "ApplicationContext must not be null");
            string keyString = ContextKeyString(key);
            contextKeyToContextMap.Add(keyString, context);

            if (RegisterContextWithContextRegistry
                && !ContextRegistry.IsContextRegistered(context.Name))
            {
                ContextRegistry.RegisterContext(context);
            }
	    }

        /// <summary>
        /// Returns cached context if present, or loads it if not.
        /// </summary>
        /// <param name="key">Context key.</param>
        /// <returns>Spring application context associated with the specified key.</returns>
	    protected IConfigurableApplicationContext GetContext(object key) 
        {
		    string keyString = ContextKeyString(key);
		    IConfigurableApplicationContext ctx =
				    (IConfigurableApplicationContext) contextKeyToContextMap[keyString];
		    if (ctx == null) 
            {
			    if (key is string[]) 
                {
				    ctx = LoadContextLocations((string[]) key);
			    }
			    else 
                {
				    ctx = LoadContext(key);
			    }
			    AddContext(key, ctx);
		    }
		    return ctx;
	    }


	    /// <summary>
	    /// Loads application context from the specified resource locations.
	    /// </summary>
	    /// <param name="locations">Resources to load object definitions from.</param>
	    protected virtual IConfigurableApplicationContext LoadContextLocations(string[] locations) 
        {
		    if (logger.IsInfoEnabled) 
            {
			    logger.Info("Loading config for: " + StringUtils.ArrayToCommaDelimitedString(locations));
		    }
		    return new XmlApplicationContext(locations);
	    }

        /// <summary>
        /// Loads application context based on user-defined key.
        /// </summary>
        /// <remarks>
        /// Unless overriden by the user, this method will alway throw 
        /// a <see cref="NotSupportedException"/>. 
        /// </remarks>
        /// <param name="key">User-defined key.</param>
        protected virtual IConfigurableApplicationContext LoadContext(object key) 
        {
		    throw new NotSupportedException("Subclasses may override this");
	    }

    }
}