#region License

/*
 * Copyright © 2002-2005 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.ComponentModel;
using System.Globalization;
using System.Resources;
using System.Text;

using Spring.Objects.Factory;
using Spring.Core.TypeConversion;

#endregion

namespace Spring.Context.Support
{
	/// <summary>
	/// An <see cref="Spring.Context.IMessageSource"/> implementation that
	/// accesses resources from .resx / .resource files.
	/// </summary>
    /// <remarks>Note that for the method 
    /// GetResourceObject if the resource name resolves to null, then in 
    /// .NET 1.1 the return value will be String.Empty whereas 
    /// in .NET 2.0 it will return null.</remarks>
	/// <author>Griffin Caprio (.NET)</author>
	/// <author>Mark Pollack (.NET)</author>
	/// <author>Aleksandar Seovic (.NET)</author>
	public class ResourceSetMessageSource : AbstractMessageSource, IInitializingObject
	{
		#region Fields

		private Hashtable _cachedResources;
		private IList _resourceManagers;

		#endregion

		/// <summary>
		/// Creates a new instance of the
		/// <see cref="Spring.Context.Support.ResourceSetMessageSource"/> class.
		/// </summary>
		public ResourceSetMessageSource()
		{
			_cachedResources = new Hashtable();
			_resourceManagers = new ArrayList();
		}

		/// <summary>
		/// The collection of <see cref="System.Resources.ResourceManager"/>s
		/// in this <see cref="Spring.Context.Support.ResourceSetMessageSource"/>.
		/// </summary>
		public IList ResourceManagers
		{
			get { return _resourceManagers; }
			set { _resourceManagers = value; }
		}

		/// <summary>
		/// Resolves a given code by searching through each assembly name in
		/// the base names array.
		/// </summary>
		/// <param name="code">The code to resolve.</param>      
		/// <param name="cultureInfo">
		/// The <see cref="System.Globalization.CultureInfo"/> to use for lookups.
		/// </param>
		/// <returns>The message from the resource set.</returns>
		protected override string ResolveMessage(
			string code, CultureInfo cultureInfo)
		{
			string message = null;
			for (int i = 0; message == null & i < _resourceManagers.Count; i++)
			{
				message = ResolveObject((ResourceManager) _resourceManagers[i], code, cultureInfo) as string;
			}
			return message;
		}

		/// <summary>
		/// Resolves a given code by searching through each assembly name in the array.
		/// </summary>
		/// <param name="code">The code to resolve.</param>      
		/// <param name="cultureInfo">
		/// The <see cref="System.Globalization.CultureInfo"/> to use for lookups.
		/// </param>
		/// <returns>The object from the resource set.</returns>
		protected override object ResolveObject(string code, CultureInfo cultureInfo)
		{
			object obj = null;

			for (int i = 0; obj == null & i < _resourceManagers.Count; i++)
			{
				obj = ResolveObject((ResourceManager) _resourceManagers[i], code, cultureInfo);
			}
			return obj;
		}

		// *** NOTE Don't use cref for ComponentResourceManager as it doesn't
		//          exist on 1.0
		//

		/// <summary>
		/// Uses a System.ComponentModel.ComponentResourceManager
		/// to apply resources to object properties.
		/// Resource key names are of the form objectName.propertyName
		/// </summary>
		/// <remarks>
        /// This feature is not currently supported on version 1.0 of the .NET platform.
		/// </remarks>
		/// <param name="value">
		/// An object that contains the property values to be applied.
		/// </param>
		/// <param name="objectName">
		/// The base name of the object to use for the key lookup.
		/// </param>      
		/// <param name="culture">
		/// The <see cref="System.Globalization.CultureInfo"/> to use for lookups.
		/// If <cref lang="null"/>, uses the
		/// <see cref="System.Globalization.CultureInfo.CurrentUICulture"/> value.
		/// </param>
		/// <exception cref="System.NotSupportedException">
		/// This feature is not currently supported on version 1.0 of the .NET platform.
		/// </exception>
		protected override void ApplyResourcesToObject(object value, string objectName, CultureInfo culture)
		{
#if !NET_1_0
		    if(value != null) 
		    {
		        ComponentResourceManager crm = new ComponentResourceManager(value.GetType());
		        crm.ApplyResources(value, objectName, culture);
		    }
#else
		    throw new NotSupportedException("Operation not supported in .NET 1.0 Release.");
#endif 
		}

	    /// <summary>
	    /// Resolves a code into an object given a base name.
	    /// </summary>
	    /// <param name="resourceManager">The <see cref="System.Resources.ResourceManager"/> to search.</param>
	    /// <param name="code">The code to resolve.</param>      
	    /// <param name="cultureInfo">
	    /// The <see cref="System.Globalization.CultureInfo"/> to use for lookups.
	    /// </param>
	    /// <returns>The object from the resource file.</returns>
	    protected object ResolveObject(ResourceManager resourceManager, string code, CultureInfo cultureInfo)
	    {
            string cacheKey = code + "." + cultureInfo.Name;
            object resource = _cachedResources[cacheKey];

            if (resource == null)
            {
                resource = resourceManager.GetObject(code, cultureInfo);
                if (resource != null)
                {
                    lock (_cachedResources)
                    {
                        _cachedResources[cacheKey] = resource;
                    }
                }
            }

            return resource;
	    }

	    /// <summary>
	    /// Returns a <see cref="System.String"/> representation of the
	    /// <see cref="Spring.Context.Support.ResourceSetMessageSource"/>.
	    /// </summary>
	    /// <returns>A <see cref="System.String"/> representation of the
	    /// <see cref="Spring.Context.Support.ResourceSetMessageSource"/>.</returns>
	    public override string ToString()
	    {
	        StringBuilder sb = new StringBuilder();
	        sb.Append(GetType().Name);
	        sb.Append(" with ResourceManagers of base names = [");
	        foreach (ResourceManager rm in ResourceManagers)
	        {
	            sb.Append(rm.BaseName);
	            sb.Append(",");
	        }
	        string s = sb.ToString();
	        return s.TrimEnd(new char[] {','}) + "]";
	    }

	    /// <summary>
	    /// Invoked by an <see cref="Spring.Objects.Factory.IObjectFactory"/>
	    /// after it has set all object properties supplied.
	    /// </summary>
	    /// <remarks>
	    /// <p>
	    /// The list may contain objects of type <see cref="System.String"/> or
	    /// <see cref="System.Resources.ResourceManager"/>. <see cref="System.String"/> types
	    /// are converted to <see cref="System.Resources.ResourceManager"/> instances using the notation
	    /// resourcename, assembly partial name.
	    /// </p>
	    /// </remarks>
	    /// <exception cref="System.ArgumentException">
	    /// If the conversion from a <see cref="System.String"/> to a
	    /// <see cref="System.Resources.ResourceManager"/> can't be performed.
	    /// </exception>
	    public void AfterPropertiesSet()
	    {
	        ResourceManagerConverter cvt = new ResourceManagerConverter();
	        for (int i = 0; i < _resourceManagers.Count; i++)
	        {
	            object o = _resourceManagers[i];
	            if (o is String)
	            {
	                _resourceManagers[i] = cvt.ConvertFrom((string) o);
	            }
	            else if (!(o is ResourceManager))
	            {
	                throw new ArgumentException("Only Types of string and ResourceManager are allowed.  Type " + o.GetType() + " was set instead.");
	            }
	        }
	    }
	}
}