// Copyright (c) 2008, NTT DATA Corporation.
//
// 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.

using System;
using System.ComponentModel;
using Microsoft.Practices.EnterpriseLibrary.Validation;
using Microsoft.Practices.EnterpriseLibrary.Validation.Validators;
using TERASOLUNA.Fw.Common.Logging;

namespace TERASOLUNA.Fw.Common.Validation.Validators
{
    /// <summary>
    /// 型指定範囲チェックを行う <see cref="Microsoft.Practices.EnterpriseLibrary.Validation.Validator"/> 
    /// の基底クラスです。このクラスは抽象クラスです。
    /// </summary>
    /// <typeparam name="T">範囲チェックを行う型。</typeparam>
    /// <remarks>
    /// <para>
    /// 検証対象が型パラメータで渡された型である、または渡された型に変換可能な string の場合、
    /// 範囲チェックを行います。 null または空文字列の場合、検証処理は行われず、検証結果は必ず成功となります。
    /// </para>
    /// <para>
    /// 本クラスは派生クラスによって機能が実装されます。独自に派生クラスを作成する場合、
    /// 検証対象の型を <see cref="Microsoft.Practices.EnterpriseLibrary.Common.Configuration.ConfigurationElementTypeAttribute"/> 属性に指定します。
    /// その際、サブクラスのクラス属性として指定してください。
    /// また、検証対象とする型を型パラメータとして渡すようにしてください。その際、
    /// 属性に指定する型パラメータと同じ型を指定する必要があります。
    /// </para>
    /// </remarks>
    public abstract class TypeRangeValidator<T> : RangeValidator where T : IComparable
    {
        /// <summary>
        /// <see cref="ILog"/> 実装クラスのインスタンスです。
        /// </summary>
        /// <remarks>
        /// ログ出力に利用します。
        /// </remarks>
        private static ILog _log = LogFactory.GetLogger(typeof(TypeRangeValidator<T>));

    　　/// <summary>
        /// 範囲における最小値の扱いを示す列挙値です。
        /// </summary>
        private RangeBoundaryType _lowerBoundType;

        /// <summary>
        /// 範囲における最大値の扱いを示す列挙値です。
        /// </summary>
        private RangeBoundaryType _upperBoundType;

        /// <summary>
        /// 範囲指定を判定するための定数です。
        /// </summary>
        /// <remarks>
        /// この定数の値は "Ignore_Inclusive" です。
        /// </remarks>
        protected const string IGNORE_INCLUSIVE = "Ignore_Inclusive";

        /// <summary>
        /// 範囲指定を判定するための定数です。
        /// </summary>
        /// <remarks>
        /// この定数の値は "Ignore_Exclusive" です。
        /// </remarks>
        protected const string IGNORE_EXCLUSIVE = "Ignore_Exclusive";

        /// <summary>
        /// 範囲指定を判定するための定数です。
        /// </summary>
        /// <remarks>
        /// この定数の値は "Inclusive_Ignore" です。
        /// </remarks>
        protected const string INCLUSIVE_IGNORE = "Inclusive_Ignore";

        /// <summary>
        /// 範囲指定を判定するための定数です。
        /// </summary>
        /// <remarks>
        /// この定数の値は "Inclusive_Inclusive" です。
        /// </remarks>
        protected const string INCLUSIVE_INCLUSIVE = "Inclusive_Inclusive";

        /// <summary>
        /// 範囲指定を判定するための定数です。
        /// </summary>
        /// <remarks>
        /// この定数の値は "Inclusive_Exclusive" です。
        /// </remarks>
        protected const string INCLUSIVE_EXCLUSIVE = "Inclusive_Exclusive";

        /// <summary>
        /// 範囲指定を判定するための定数です。
        /// </summary>
        /// <remarks>
        /// この定数の値は "Exclusive_Ignore" です。
        /// </remarks>
        protected const string EXCLUSIVE_IGNORE = "Exclusive_Ignore";

        /// <summary>
        /// 範囲指定を判定するための定数です。
        /// </summary>
        /// <remarks>
        /// この定数の値は "Exclusive_Inclusive" です。
        /// </remarks>
        protected const string EXCLUSIVE_INCLUSIVE = "Exclusive_Inclusive";

        /// <summary>
        /// 範囲指定を判定するための定数です。
        /// </summary>
        /// <remarks>
        /// この定数の値は "Exclusive_Exclusive" です。
        /// </remarks>
        protected const string EXCLUSIVE_EXCLUSIVE = "Exclusive_Exclusive";

        /// <summary>
        /// コンストラクタ。派生クラスから呼び出されます。
        /// </summary>
        /// <param name="lowerBound">型パラメータで与えられた型の最小値。</param>
        /// <param name="lowerBoundType">範囲における最小値の扱いを示す <see cref="RangeBoundaryType"/> 列挙の値。
        /// 指定しない場合、<seealso cref="RangeBoundaryType.Ignore"/>　が使用されます。</param>
        /// <param name="upperBound">型パラメータで与えられた型の最大値。</param>
        /// <param name="upperBoundType">範囲における最大値の扱いを示す <see cref="RangeBoundaryType"/> 列挙の値。
        /// 指定しない場合、<see cref="RangeBoundaryType.Inclusive"/> が利用されます。</param>
        /// <param name="messageTemplate">検証失敗の際、メッセージを生成するために利用するテンプレート文字列。
        /// 検証に利用するパラメータがプレースホルダに渡されます。
        /// パラメータとそのプレースホルダ番号は以下の通りです。
        /// <list type="table">
        /// <listheader>
        /// <term>プレースホルダ</term>
        /// <description>内容</description>
        /// </listheader>
        /// <item>
        /// <term>{0}</term>
        /// <description>検証対象文字列</description>
        /// </item>
        /// <item>
        /// <term>{1}</term>
        /// <description>key(プロパティ・メソッド名)</description>
        /// </item>
        /// <item>
        /// <term>{2}</term>
        /// <description>Tag文字列</description>
        /// </item>
        /// <item>
        /// <term>{3}</term>
        /// <description>LowerBound</description>
        /// </item>
        /// <item>
        /// <term>{4}</term>
        /// <description>LowerBoundType</description>
        /// </item>
        /// <item>
        /// <term>{5}</term>
        /// <description>UpperBound</description>
        /// </item>
        /// <item>
        /// <term>{6}</term>
        /// <description>UpperBoundType</description>
        /// </item>
        /// </list>
        /// </param>
        /// <param name="negated">検証条件の反転を示すフラグ。
        /// 反転した条件で検証を実施する場合には true を指定します。</param>
        protected TypeRangeValidator(T lowerBound,
                                 RangeBoundaryType lowerBoundType,
                                 T upperBound,
                                 RangeBoundaryType upperBoundType,
                                 string messageTemplate,
                                 bool negated)
            : base(lowerBound, lowerBoundType, upperBound, upperBoundType, messageTemplate, negated)
        {
            _lowerBoundType = lowerBoundType;
            _upperBoundType = upperBoundType;
        }

        /// <summary>
        /// 下限値のタイプ(値を含む、値を含まない、無視)を取得します。
        /// </summary>
        protected RangeBoundaryType LowerBoundType
        {
            get { return _lowerBoundType; }
        }

        /// <summary>
        /// 上限値のタイプ(値を含む、値を含まない、無視)を取得します。
        /// </summary>
        protected RangeBoundaryType UpperBoundType
        {
            get { return _upperBoundType; }
        }


        /// <summary>
        /// 型指定範囲チェックを行うメソッドです。型パラメータで与えられた型で、
        /// 指定された範囲に検証対象値が含まれるかをチェックします。
        /// </summary>
        /// <remarks>
        /// <para>
        /// <paramref name="objectToValidate"/> が null または空文字列の場合、
        /// 検証処理は行われず、検証結果は必ず成功となります。
        ///  negated が true の場合、検証条件が反転します。
        /// </para>
        /// </remarks>
        /// <param name="objectToValidate">検証対象の値。</param>
        /// <param name="currentTarget">検証対象のオブジェク。</param>
        /// <param name="key">検証対象のプロパティ名またはメソッド名。</param>
        /// <param name="validationResults">検証結果を格納する <see cref="ValidationResults"/> 。</param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="validationResults"/> が null 参照です。
        /// </exception> 
        protected override void DoValidate(IComparable objectToValidate,
                                           object currentTarget,
                                           string key,
                                           ValidationResults validationResults)
        {
            if (validationResults == null)
            {
                ArgumentNullException exception = new ArgumentNullException("validationResults");
                if (_log.IsErrorEnabled)
                {
                    _log.Error(string.Format(
                        Properties.Resources.E_NULL_ARGUMENT, "validationResults"), exception);
                }
                throw exception;
            }

            if (objectToValidate == null || string.IsNullOrEmpty(objectToValidate.ToString()))
            {
                return;
            }

            bool isLogError = false;

            T targetObject = default(T);
            Type tp = objectToValidate.GetType();
            if (tp == typeof(T))
            {
                targetObject = (T)objectToValidate;
            }
            else if (tp == typeof(String))
            {
                string targetString = Normalize((string)objectToValidate);

                if (!TryConvertFromString(targetString, out targetObject))
                {
                    isLogError = true;
                }
            }
            else
            {
                isLogError = true;
            }


            if (!isLogError)
            {
                base.DoValidate(targetObject, currentTarget, key, validationResults);
            }
            else
            {
                LogValidationResult(validationResults,
                                    GetMessage(objectToValidate, key),
                                    currentTarget,
                                    key);
            }
        }

        /// <summary>
        /// 文字列を正規化するメソッドです。派生クラスでオーバーライドされます。
        /// 拡張ポイントとして用意されており、デフォルトでは特に何も処理しません。
        /// 文字列を型変換する際に、前処理を実施する場合、このメソッドを利用します。
        /// デフォルトではこのメソッドが null または空文字列を返却した場合、検証失敗となります。
        /// </summary>
        /// <param name="str">正規化対象の文字列。</param>
        /// <returns>正規化された文字列。</returns>
        protected virtual string Normalize(string str)
        {
            return str;
        }

        /// <summary>
        /// 文字列から検証対象の型に変換を試みます。成功した場合には true を返し、 <paramref name="output"/> の値を
        /// 変換後のオブジェクトに書き換えます。
        /// </summary>
        /// <param name="str">変換する値を格納する文字列。</param>
        /// <param name="output">変換が成功した場合、このメソッドが返されるときに、
        /// <paramref name="str"/>に格納された文字列を変換した型パラメータで与えられた型のオブジェクト。
        /// 変換に失敗した場合は default 値を格納します。このパラメータは初期化せずに渡すことができます。</param>
        /// <returns><paramref name="str"/> が正常に変換された場合は true 。それ以外の場合は false 。</returns>
        protected virtual bool TryConvertFromString(string str, out T output)
        {
            bool result = false;
            output = default(T);
            TypeConverter typeConverter = null;
            typeConverter = TypeDescriptor.GetConverter(typeof(T));
            try
            {
                output = (T)typeConverter.ConvertFromString(str);
                result = true;
            }
            catch (Exception exception)
            {
                // ConvertFromStringがSystem.ExceptionをスローするのでExceptionでcatchしている。
                // 例外が発生するのは変換失敗時なので何もしない
                if(_log.IsWarnEnabled)
                {
                    _log.Warn(string.Format(
                        Properties.Resources.W_VALIDATOR_EXCEPTION, str, typeof(T).FullName), exception);
                }
            }
            return result;
        }
    }
}
