#region License

/*
 * Copyright 2002-2006 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.Globalization;
using Common.Logging;

namespace Spring.Context.Support
{
    /// <summary>
    /// Abstract implementation of the <see cref="Spring.Context.IHierarchicalMessageSource"/> interface,
    /// implementing common handling of message variants, making it easy
    /// to implement a specific strategy for a concrete <see cref="Spring.Context.IMessageSource"/>.
    /// </summary>
    /// <remarks>
    /// <p>Subclasses must implement the abstract <code>ResolveObject</code>
    /// method.</p>
    /// <p><b>Note:</b> By default, message texts are only parsed through
    /// String.Format if arguments have been passed in for the message. In case
    /// of no arguments, message texts will be returned as-is. As a consequence,
    /// you should only use String.Format escaping for messages with actual
    /// arguments, and keep all other messages unescaped.
    /// </p>
    /// <p>Supports not only IMessageSourceResolvables as primary messages
    /// but also resolution of message arguments that are in turn
    /// IMessageSourceResolvables themselves.
    /// </p>
    /// <p>This class does not implement caching of messages per code, thus
    /// subclasses can dynamically change messages over time. Subclasses are
    /// encouraged to cache their messages in a modification-aware fashion,
    /// allowing for hot deployment of updated messages.
    /// </p>
    /// </remarks>
    /// <author>Rod Johnson</author>
    /// <author>Juergen Hoeller</author>
    /// <author>Griffin Caprio (.NET)</author>
    /// <author>Harald Radi (.NET)</author>
    /// <seealso cref="Spring.Context.IMessageSourceResolvable"/>
    /// <seealso cref="Spring.Context.IMessageSource"/>
    /// <seealso cref="Spring.Context.IHierarchicalMessageSource"/>
    public abstract class AbstractMessageSource : IHierarchicalMessageSource
    {
        #region Fields

        /// <summary>
        /// holds the logger instance shared with subclasses.
        /// </summary>
        protected readonly ILog log;

        private IMessageSource parentMessageSource;
        private bool useCodeAsDefaultMessage = false;

        #endregion

        #region Constructor

		/// <summary>
		/// Initializes this instance.
		/// </summary>
        protected AbstractMessageSource()
        {
            log = LogManager.GetLogger(GetType());
        }

        #endregion

        #region Properties


        /// <summary>Gets or Sets a value indicating whether to use the message code as 
        /// default message instead of throwing a NoSuchMessageException. 
        /// Useful for development and debugging. Default is "false".
        /// </summary>
        /// <remarks>
        /// <p>Note: In case of a IMessageSourceResolvable with multiple codes
        /// (like a FieldError) and a MessageSource that has a parent MessageSource,
        /// do <i>not</i> activate "UseCodeAsDefaultMessage" in the <i>parent</i>:
        /// Else, you'll get the first code returned as message by the parent,
        /// without attempts to check further codes.</p>
        /// <p>To be able to work with "UseCodeAsDefaultMessage" turned on in the parent,
        /// AbstractMessageSource contains special checks
        /// to delegate to the internal <code>GetMessageInternal</code> method if available.
        /// In general, it is recommended to just use "UseCodeAsDefaultMessage" during
        /// development and not rely on it in production in the first place, though.</p>
        /// <p>Alternatively, consider overriding the <code>GetDefaultMessage</code>
        /// method to return a custom fallback message for an unresolvable code.</p>
        /// </remarks>
        /// <value>
        /// 	<c>true</c> if use the message code as default message instead of 
        /// throwing a NoSuchMessageException; otherwise, <c>false</c>.
        /// </value>
        public bool UseCodeAsDefaultMessage
        {
            get { return useCodeAsDefaultMessage; }
            set { useCodeAsDefaultMessage = value; }
        }

        #endregion

        #region IHierarchicalMessageSource Members

        /// <summary>
        /// The parent message source used to try and resolve messages that
        /// this object can't resolve.
        /// </summary>
        /// <value></value>
        /// <remarks>
        /// 	<p>
        /// If the value of this property is <see langword="null"/> then no
        /// further resolution is possible.
        /// </p>
        /// </remarks>
        public IMessageSource ParentMessageSource
        {
            get { return parentMessageSource; }
            set { parentMessageSource = value; }
        }

        /// <summary>
        /// Resolve the message identified by the supplied
        /// <paramref name="name"/>.
        /// </summary>
        /// <param name="name">The name of the message to resolve.</param>
        /// <returns>
        /// The resolved message if the lookup was successful (see above for
        /// the return value in the case of an unsuccessful lookup).
        /// </returns>
        /// <remarks>
        /// If the lookup is not successful throw NoSuchMessageException
        /// </remarks>
        public string GetMessage(string name)
        {
            return GetMessage(name, CultureInfo.CurrentUICulture, null);
        }

        /// <summary>
        /// Resolve the message identified by the supplied
        /// <paramref name="name"/>.
        /// </summary>
        /// <param name="name">The name of the message to resolve.</param>
        /// <param name="culture">The <see cref="System.Globalization.CultureInfo"/> that represents
        /// the culture for which the resource is localized.</param>
        /// <returns>
        /// The resolved message if the lookup was successful (see above for
        /// the return value in the case of an unsuccessful lookup).
        /// </returns>
        /// <remarks>
        /// Note that the fallback behavior based on CultureInfo seem to
        /// have a bug that is fixed by installed .NET 1.1 Service Pack 1.
        /// <p>
        /// If the lookup is not successful, implementations are permitted to
        /// take one of two actions.
        /// </p>
        /// If the lookup is not successful throw NoSuchMessageException
        /// </remarks>
        public string GetMessage(string name, CultureInfo culture)
        {
            return GetMessage(name, culture, null);
        }

        /// <summary>
        /// Resolve the message identified by the supplied
        /// <paramref name="name"/>.
        /// </summary>
        /// <param name="name">The name of the message to resolve.</param>
        /// <param name="arguments">The array of arguments that will be filled in for parameters within
        /// the message, or <see langword="null"/> if there are no parameters
        /// within the message. Parameters within a message should be
        /// referenced using the same syntax as the format string for the
        /// <see cref="System.String.Format(string,object[])"/> method.</param>
        /// <returns>
        /// The resolved message if the lookup was successful (see above for
        /// the return value in the case of an unsuccessful lookup).
        /// </returns>
        /// <remarks>
        /// If the lookup is not successful throw NoSuchMessageException
        /// </remarks>
        public string GetMessage(string name, params object[] arguments)
        {
            return GetMessage(name, CultureInfo.CurrentUICulture, arguments);
        }

        /// <summary>
        /// Resolve the message identified by the supplied
        /// <paramref name="name"/>.
        /// </summary>
        /// <param name="name">The name of the message to resolve.</param>
        /// <param name="culture">The <see cref="System.Globalization.CultureInfo"/> that represents
        /// the culture for which the resource is localized.</param>
        /// <param name="arguments">The array of arguments that will be filled in for parameters within
        /// the message, or <see langword="null"/> if there are no parameters
        /// within the message. Parameters within a message should be
        /// referenced using the same syntax as the format string for the
        /// <see cref="System.String.Format(string,object[])"/> method.</param>
        /// <returns>
        /// The resolved message if the lookup was successful (see above for
        /// the return value in the case of an unsuccessful lookup).
        /// </returns>
        /// <remarks>
        /// Note that the fallback behavior based on CultureInfo seem to
        /// have a bug that is fixed by installed .NET 1.1 Service Pack 1.
        /// <p>
        /// If the lookup is not successful throw NoSuchMessageException.
        /// </p>
        /// </remarks>        
        public string GetMessage(string name, CultureInfo culture, params object[] arguments)
        {
            string msg = GetMessageInternal(name, arguments, culture);
            if (msg != null) return msg;
            string fallback = GetDefaultMessage(name);
            if (fallback != null) return fallback;
            throw new NoSuchMessageException(name, culture);
        }

        /// <summary>
        /// Resolve the message identified by the supplied
        /// <paramref name="name"/>.
        /// </summary>
        /// <param name="name">The name of the message to resolve.</param>
        /// <param name="defaultMessage">The default message if name is not found.</param>
        /// <param name="culture">The <see cref="System.Globalization.CultureInfo"/> that represents
        /// the culture for which the resource is localized.</param>
        /// <param name="arguments">The array of arguments that will be filled in for parameters within
        /// the message, or <see langword="null"/> if there are no parameters
        /// within the message. Parameters within a message should be
        /// referenced using the same syntax as the format string for the
        /// <see cref="System.String.Format(string,object[])"/> method.</param>
        /// <returns>
        /// The resolved message if the lookup was successful (see above for
        /// the return value in the case of an unsuccessful lookup).
        /// </returns>
        /// <remarks>
        /// Note that the fallback behavior based on CultureInfo seem to
        /// have a bug that is fixed by installed .NET 1.1 Service Pack 1.
        /// <p>
        /// If the lookup is not successful throw NoSuchMessageException
        /// </p>
        /// </remarks>
        public string GetMessage(string name, string defaultMessage, CultureInfo culture, params object[] arguments)
        {
            string msg = GetMessageInternal(name, arguments, culture);
            if (msg != null) return msg;
            if (defaultMessage == null)
            {
                string fallback = GetDefaultMessage(name);
                if (fallback != null) return fallback;
            }
            return RenderDefaultMessage(defaultMessage, arguments, culture);
        }

        /// <summary>
        /// Resolve the message using all of the attributes contained within
        /// the supplied <see cref="Spring.Context.IMessageSourceResolvable"/>
        /// argument.
        /// </summary>
        /// <param name="resolvable">The value object storing those attributes that are required to
        /// properly resolve a message.</param>
        /// <param name="culture">The <see cref="System.Globalization.CultureInfo"/> that represents
        /// the culture for which the resource is localized.</param>
        /// <returns>
        /// The resolved message if the lookup was successful.
        /// </returns>
        /// <exception cref="Spring.Context.NoSuchMessageException">
        /// If the message could not be resolved.
        /// </exception>
        public string GetMessage(IMessageSourceResolvable resolvable, CultureInfo culture)
        {
            string[] codes = resolvable.GetCodes();
            if (codes == null) codes = new string[0];
            for (int i = 0; i < codes.Length; i++)
            {
                string msg = GetMessageInternal(codes[i], resolvable.GetArguments(), culture);
                if (msg != null) return msg;
            }
            if (resolvable.DefaultMessage != null)
                return RenderDefaultMessage(resolvable.DefaultMessage, resolvable.GetArguments(), culture);
            if (codes.Length > 0)
            {
                string fallback = GetDefaultMessage(codes[0]);
                if (fallback != null) return fallback;
            }
            throw new NoSuchMessageException(codes.Length > 0 ? codes[codes.Length - 1] : null, culture);
        }

        /// <summary>
        /// Gets a localized resource object identified by the supplied
        /// <paramref name="name"/>.
        /// </summary>
        /// <param name="name">
        /// The name of the resource object to resolve.
        /// </param>
        /// <returns>
        /// The resolved object, or <see langword="null"/> if not found.
        /// </returns>
        /// <seealso cref="Spring.Context.IMessageSource.GetResourceObject(string)"/>
        public object GetResourceObject(string name)
        {
            object resource = GetResourceInternal(name, CultureInfo.CurrentUICulture);
            if (resource != null) return resource;
            if (ParentMessageSource != null)
                return ParentMessageSource.GetResourceObject(name, CultureInfo.CurrentUICulture);
            return null;
        }

        /// <summary>
        /// Gets a localized resource object identified by the supplied
        /// <paramref name="name"/>.
        /// </summary>
        /// <remarks>
        /// Note that the fallback behavior based on CultureInfo seem to 
        /// have a bug that is fixed by installed .NET 1.1 Service Pack 1.  
        /// </remarks>	    
        /// <param name="name">
        /// The name of the resource object to resolve.
        /// </param>
        /// <param name="culture">
        /// The <see cref="System.Globalization.CultureInfo"/> with which the
        /// resource is associated.
        /// </param>
        /// <returns>
        /// The resolved object, or <see langword="null"/> if not found.  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.
        /// </returns>
        /// <seealso cref="Spring.Context.IMessageSource.GetResourceObject(string, CultureInfo)"/>
        public object GetResourceObject(string name, CultureInfo culture)
        {
            object resource = GetResourceInternal(name, culture);
            if (resource != null) return resource;
            if (ParentMessageSource != null) return ParentMessageSource.GetResourceObject(name, culture);
            return null;
        }

        /// <summary>
        /// Applies resources to object properties.
        /// </summary>
        /// <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 key lookup.
        /// </param>
        /// <param name="culture">
        /// The <see cref="System.Globalization.CultureInfo"/> with which the
        /// resource is associated.
        /// </param>
        /// <seealso cref="Spring.Context.IMessageSource.ApplyResources(object, string, CultureInfo)"/>
        public void ApplyResources(
            object value, string objectName, CultureInfo culture)
        {
            ApplyResourcesInternal(value, objectName, culture);
            if (ParentMessageSource != null) ParentMessageSource.ApplyResources(value, objectName, culture);
        }

        #endregion

        #region Protected Methods

        /// <summary>Resolve the given code and arguments as message in the given culture,
        /// returning null if not found. Does <i>not</i> fall back to the code
        /// as default message. Invoked by GetMessage methods.
        /// </summary>
        /// <param name="code">The code to lookup up, such as 'calculator.noRateSet'.</param>
        /// <param name="args"> array of arguments that will be filled in for params
        /// within the message.</param>
        /// <param name="culture">The <see cref="System.Globalization.CultureInfo"/> with which the
        /// resource is associated.</param>
        /// <returns>
        /// The resolved message if the lookup was successful.
        /// </returns>
        protected string GetMessageInternal(string code, object[] args, CultureInfo culture)
        {
            if (code == null) return null;
            if (culture == null) culture = CultureInfo.CurrentUICulture;

            if ((args != null && args.Length > 0))
            {
                // Resolve arguments eagerly, for the case where the message
                // is defined in a parent MessageSource but resolvable arguments
                // are defined in the child MessageSource.
                args = ResolveArguments(args, culture);
            }

            string message = ResolveMessage(code, culture);

            if (message != null) return FormatMessage(message, args, culture);

            // Not found -> check parent, if any.
            return GetMessageFromParent(code, args, culture);
        }


        /// <summary>
        /// Try to retrieve the given message from the parent MessageSource, if any.
        /// </summary>
        /// <param name="code">The code to lookup up, such as 'calculator.noRateSet'.</param>
        /// <param name="args"> array of arguments that will be filled in for params
        /// within the message.</param>
        /// <param name="culture">The <see cref="System.Globalization.CultureInfo"/> with which the
        /// resource is associated.</param>
        /// <returns>
        /// The resolved message if the lookup was successful.
        /// </returns>
        protected string GetMessageFromParent(string code, object[] args, CultureInfo culture)
        {
            if (ParentMessageSource != null)
            {
                AbstractMessageSource parent = ParentMessageSource as AbstractMessageSource;
                if (parent != null)
                {
                    // Call internal method to avoid getting the default code back
                    // in case of "useCodeAsDefaultMessage" being activated.
                    return parent.GetMessageInternal(code, args, culture);
                }
                else
                {
                    // Check parent MessageSource, returning null if not found there.
                    return ParentMessageSource.GetMessage(code, null, culture, args);
                }
            }
            // Not found in parent either.
            return null;
        }


        /// <summary>
        /// Return a fallback default message for the given code, if any.
        /// </summary>
        /// <remarks>
        /// Default is to return the code itself if "UseCodeAsDefaultMessage"
        /// is activated, or return no fallback else. In case of no fallback,
        /// the caller will usually receive a NoSuchMessageException from GetMessage
        /// </remarks>
        /// <param name="code">The code to lookup up, such as 'calculator.noRateSet'.</param>
        /// <returns>The default message to use, or null if none.</returns>
        protected virtual string GetDefaultMessage(string code)
        {
            if (UseCodeAsDefaultMessage) return code;
            return null;
        }



        /// <summary>
        /// Renders the default message string.  The default message is passed in as specified by the
        /// caller and can be rendered into a fully formatted default message shown to the user.
        /// </summary>
        /// <remarks>Default implementation passed he String for String.Format resolving any 
        /// argument placeholders found in them.  Subclasses may override this method to plug
        /// in custom processing of default messages.
        /// </remarks>
        /// <param name="defaultMessage">The default message.</param>
        /// <param name="args">The array of agruments that will be filled in for parameter
        /// placeholders within the message, or null if none.</param>
        /// <param name="culture">The <see cref="System.Globalization.CultureInfo"/> with which the
        /// resource is associated.</param>
        /// <returns>The rendered default message (with resolved arguments)</returns>
        protected virtual string RenderDefaultMessage(string defaultMessage, object[] args, CultureInfo culture)
        {
            return FormatMessage(defaultMessage, args, culture);
        }

        /// <summary>
        /// Format the given default message String resolving any 
        /// agrument placeholders found in them.
        /// </summary>
        /// <param name="msg">The message to format.</param>
        /// <param name="args">The array of agruments that will be filled in for parameter
        /// placeholders within the message, or null if none.</param>
        /// <param name="culture">The <see cref="System.Globalization.CultureInfo"/> with which the
        /// resource is associated.</param>
        /// <returns>The formatted message (with resolved arguments)</returns>
        protected virtual string FormatMessage(string msg, object[] args, CultureInfo culture)
        {
            if (msg == null || ((args == null || args.Length == 0))) return msg;
            return String.Format(culture, msg, args);
        }


        /// <summary>
        /// Search through the given array of objects, find any
        /// MessageSourceResolvable objects and resolve them.
        /// </summary>
        /// <remarks>
        /// Allows for messages to have MessageSourceResolvables as arguments.
        /// </remarks>
        /// 
        /// <param name="args">The array of arguments for a message.</param>
        /// <param name="culture">The <see cref="System.Globalization.CultureInfo"/> with which the
        /// resource is associated.</param>
        /// <returns>An array of arguments with any IMessageSourceResolvables resolved</returns>
        protected virtual object[] ResolveArguments(object[] args, CultureInfo culture)
        {
            if (args == null) return new object[0];
            object[] resolvedArgs = new object[args.Length];

            for (int i = 0; i < args.Length; i++)
            {
                IMessageSourceResolvable resolvable = args[i] as IMessageSourceResolvable;
                if (resolvable != null) resolvedArgs[i] = GetMessage(resolvable, culture);
                else resolvedArgs[i] = args[i];
            }

            return resolvedArgs;
        }

		/// <summary>
		/// Gets the specified resource (e.g. Icon or Bitmap).
		/// </summary>
		/// <param name="name">The name of the resource to resolve.</param>
		/// <param name="cultureInfo">
		/// The <see cref="System.Globalization.CultureInfo"/> to resolve the
		/// code for.
		/// </param>
		/// <returns>The resource if found. <see langword="null"/> otherwise.</returns>
        protected object GetResourceInternal(string name, CultureInfo cultureInfo)
        {
            if (cultureInfo == null) cultureInfo = CultureInfo.CurrentUICulture;
            if (name == null) return null;
            return ResolveObject(name, cultureInfo);
        }

		/// <summary>
		/// Applies resources from the given name on the specified object.
		/// </summary>
		/// <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 key lookup.
		/// </param>
		/// <param name="cultureInfo">
		/// The <see cref="System.Globalization.CultureInfo"/> with which the
		/// resource is associated.
		/// </param>
		protected void ApplyResourcesInternal(object value, string objectName, CultureInfo cultureInfo)
        {
            if (cultureInfo == null) cultureInfo = CultureInfo.CurrentUICulture;
            ApplyResourcesToObject(value, objectName, cultureInfo);
        }

        #endregion

        #region Protected Abstract Methods

        /// <summary>
        /// Subclasses must implement this method to resolve a message.
        /// </summary>
        /// <param name="code">The code to lookup up, such as 'calculator.noRateSet'.</param>
        /// <param name="cultureInfo">The <see cref="System.Globalization.CultureInfo"/> with which the
        /// resource is associated.</param>
        /// <returns>The resolved message from the backing store of message data.</returns>
        protected abstract string ResolveMessage(string code, CultureInfo cultureInfo);

        /// <summary>
        /// Resolves an object (typically an icon or bitmap).
        /// </summary>
        /// <remarks>
        /// <p>
        /// Subclasses must implement this method to resolve an object.
        /// </p>
        /// </remarks>
        /// <param name="code">The code of the object to resolve.</param>
        /// <param name="cultureInfo">
        /// The <see cref="System.Globalization.CultureInfo"/> to resolve the
        /// code for.
        /// </param>
        /// <returns>
        /// The resolved object or <see langword="null"/> if not found.
        /// </returns>
        protected abstract object ResolveObject(string code, CultureInfo cultureInfo);

        /// <summary>
        /// Applies resources to object properties.
        /// </summary>
        /// <remarks>
        /// <p>
        /// Subclasses must implement this method to apply resources
        /// to an arbitrary object.
        /// </p>
        /// </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 key lookup.
        /// </param>
        /// <param name="cultureInfo">
        /// The <see cref="System.Globalization.CultureInfo"/> with which the
        /// resource is associated.
        /// </param>
        protected abstract void ApplyResourcesToObject(object value, string objectName, CultureInfo cultureInfo);


        #endregion

    }
}