// 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.Globalization;
using System.Text.RegularExpressions;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using Microsoft.Practices.EnterpriseLibrary.Validation;
using Microsoft.Practices.EnterpriseLibrary.Validation.Validators;
using TERASOLUNA.Fw.Common.Logging;
using TERASOLUNA.Fw.Common.Validation.Configuration;

namespace TERASOLUNA.Fw.Common.Validation.Validators
{
    /// <summary>
    /// 検証ルール"数値チェック"に対応する数値チェックを行う
    /// <see cref="Microsoft.Practices.EnterpriseLibrary.Validation.Validator"/> 継承クラスです。
    /// </summary>
    /// <remarks>
    /// <para>
    /// 検証対象の文字列表現が、整数部と小数部がそれぞれ指定した桁数の数値の形式
    /// であることをチェックする検証機能を提供します。
    /// 桁数のチェックには、指定した値と等しいか調べる一致チェックと、
    /// 指定した以内の範囲に含まれるか調べる範囲チェックを利用することができます。
    ///  <seealso cref="IsAccordedInteger"/> に false が指定される場合、整数部の桁チェックは範囲チェックとなります。
    ///  <seealso cref="IsAccordedScale"/> に false が指定される場合、小数部の桁チェックは範囲チェックとなります。
    /// 範囲チェックの場合、指定した値自体は含まれます。
    /// </para>
    /// <para>
    /// 本クラスを用いて入力値検証を実施するには、入力値検証設定ファイルに記述する
    ///  validator 要素の属性として、以下の設定を行います。
    /// 入力値検証設定ファイル自体の記法については、
    ///  Validation Application Block のドキュメントを参照してください。  
    /// </para>
    /// <para>
    /// <list type="bullet">
    /// <item>
    /// <description>negated</description>
    /// <para>検証ルールを反転するフラグです。true を設定した場合、検証ルールが反転します。デフォルト値は、false です。</para>
    /// </item>
    /// <item>
    /// <description>messageTemplate</description>
    /// <para>検証エラーメッセージのテンプレートとして使用する文字列。
    /// 未設定の場合は、 <see cref="NumberValidator.DefaultNonNegatedMessageTemplate"/> 、 <see cref="NumberValidator.DefaultNegatedMessageTemplate"/> の 
    /// いずれかが使用されます。
    /// </para>
    /// </item>
    /// <item>
    /// <description>messageTemplateResourceName</description>
    /// <para>メッセージテンプレートをリソースから読み込む際の名前。</para>
    /// </item>
    /// <item>
    /// <description>messageTemplateResourceType</description>
    /// <para>メッセージテンプレートをリソースから読み込む際のリソースの型。</para>
    /// </item>
    /// <item>
    /// <description>tag</description>
    /// <para>メッセージテンプレートのプレースホルダ{2}に渡される文字列。
    /// 検証対象項目の論理名を指定します。</para>
    /// </item>
    /// <item>
    /// <description>type</description>
    /// <para>使用する <see cref="Microsoft.Practices.EnterpriseLibrary.Validation.Validator"/> クラスの型を完全修飾型名で指定します。</para>
    /// </item>
    /// <item>
    /// <description>name</description>
    /// <para>使用する <see cref="Microsoft.Practices.EnterpriseLibrary.Validation.Validator"/> の名前です。フレームワークでは利用されません。
    /// 同時に複数の <see cref="Microsoft.Practices.EnterpriseLibrary.Validation.Validator"/> を利用した検証を行う場合、重複した名前を使用する
    /// ことはできません。この属性は必須項目です。</para>
    /// </item>
    /// <item>
    /// <description>integerLength</description>
    /// <para>整数部の桁数を指定します。必須属性です。最小値は 1 です。</para>
    /// </item>
    /// <item>
    /// <description>scale</description>
    /// <para>小数部の桁数を指定します。最小値は 0 です。</para>
    /// </item>
    /// <item>
    /// <description>isAccordedScale</description>
    /// <para>true であれば小数部の桁数一致チェックが行われます。
    ///  false 、または省略時は桁数が範囲内かのチェックです。</para>
    /// </item>
    /// <item>
    /// <description>isAccordedInteger</description>
    /// <para>	true であれば整数部の桁数一致チェックが行われます。
    ///  false、または省略時は桁数が範囲内かのチェックです。</para>
    /// </item>
    /// </list>
    /// </para>
    /// <example>
    /// <code>
    /// &lt;validator negated="false" integerLength="3" isAccordedInteger="false" 
    /// scale="2" messageTemplate=""  isAccordedScale="false" messageTemplateResourceName=""
    /// messageTemplateResourceType="" tag="検証対象項目名" name="number validator"  
    /// type="TERASOLUNA.Fw.Common.Validation.Validators.NumberValidator,TERASOLUNA.Fw.Common"/&gt;
    /// </code>
    /// </example>
    /// </remarks>
    [ConfigurationElementType(typeof(NumberValidatorData))]
    public class NumberValidator : ValueValidator
    {
        /// <summary>
        /// 数値表現をするための <see cref="Regex"/> インスタンスです。
        /// </summary>
        private static readonly Regex DECIMAL_REG_EXP =
                                    new Regex(@"^[+-]?(\d+)(\.(\d+))?$");

        /// <summary>
        /// <see cref="ILog"/> 実装クラスのインスタンスです。
        /// </summary>
        /// <remarks>
        /// ログ出力に利用します。
        /// </remarks>
        private static ILog _log = LogFactory.GetLogger(typeof(NumberValidator));

        /// <summary>
        /// 整数部の桁数です。
        /// </summary>
        /// <remarks>
        /// デフォルトの値は、 0 です。
        /// </remarks>
        private int _integerLength = 0;

        /// <summary>
        /// 小数部の桁数です。
        /// </summary>
        /// <remarks>
        /// デフォルトの値は、 0 です。
        /// </remarks>
        private int _scale = 0;

        /// <summary>
        /// 整数部の桁数の一致チェックを行うかどうかのフラグです。
        /// </summary>
        /// <remarks>
        /// デフォルトの値は、 false です。
        /// </remarks>
        private bool _isAccordedInteger = false;

        /// <summary>
        /// 小数部の桁数の一致チェックを行うかどうかのフラグです。
        /// </summary>
        /// <remarks>
        /// デフォルトの値は、 false です。
        /// </remarks>
        private bool _isAccordedScale = false;

        /// <summary>
        /// 新しい <see cref="NumberValidator"/> のインスタンスを初期化します。
        /// </summary>
        /// <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>整数部の桁数</description>
        /// </item>
        /// <item>
        /// <term>{4}</term>
        /// <description>小数部の桁数</description>
        /// </item>
        /// </list>
        /// </param>
        /// <param name="tag">タグ文字列。メッセージテンプレートを用いてメッセージを生成する際、
        /// プレースホルダの 2 番目に渡されます。</param>
        /// <param name="negated">検証条件の反転を示すフラグ。
        /// 反転した条件で検証を実施する場合には true を指定します。</param>
        /// <param name="integerLength">整数部の桁数。</param>
        /// <param name="isAccordedInteger">整数部の桁数を一致チェックとするかどうかのフラグ。
        ///  true であれば整数部の桁数一致チェックが行われます。
        ///  false を指定した場合、桁数の範囲内チェックとなります。</param>
        /// <param name="scale">小数部の桁数。</param>
        /// <param name="isAccordedScale">小数部の桁数を一致チェックとするかどうかのフラグ。
        ///  true であれば小数部の桁数一致チェックが行われます。
        ///  false を指定した場合、桁数の範囲内チェックとなります。</param>
        /// <exception cref="ArgumentException">
        /// <list type="bullet">
        /// <item>
        /// <paramref name="integerLength"/> が 0 以下です。
        /// </item>
        /// <item>
        /// <paramref name="scale"/> が0未満です。
        /// </item>
        /// </list>
        /// </exception>  
        public NumberValidator(int integerLength,
                               bool isAccordedInteger,
                               int scale,
                               bool isAccordedScale,
                               string messageTemplate,
                               string tag,
                               bool negated)
            : base(messageTemplate, tag, negated)
        {
            if (integerLength <= 0)
            {
                string message = string.Format(Properties.Resources.E_ZERO_AND_NEGATIVE_NUMBER, "integerLength");
                ArgumentException exception = new ArgumentException(message);
                if (_log.IsErrorEnabled)
                {
                    _log.Error(message, exception);
                }
                throw exception;
            }

            if (scale < 0)
            {
                string message = string.Format(Properties.Resources.E_NEGATIVE_NUMBER, "scale");
                ArgumentException exception = new ArgumentException(message);
                if (_log.IsErrorEnabled)
                {
                    _log.Error(message, exception);
                }
                throw exception;
            }
            _integerLength = integerLength;
            _scale = scale;
            _isAccordedScale = isAccordedScale;
            _isAccordedInteger = isAccordedInteger;
        }

        /// <summary>
        /// 小数部の桁数を一致チェックするかどうかを示す bool 値を取得します。
        /// </summary>
        /// <value>小数部の桁数を一致チェックするかどうかを示す bool 値。</value>
        public bool IsAccordedScale
        {
            get { return _isAccordedScale; }
        }

        /// <summary>
        /// 整数部の桁数を一致チェックするかどうかを示す bool 値を取得します。
        /// </summary>
        /// <value>整数部の桁数を一致チェックするかどうかを示す bool 値。</value>
        public bool IsAccordedInteger
        {
            get { return _isAccordedInteger; }
        }

        /// <summary>
        /// 小数部の桁数を取得します。
        /// </summary>
        /// <value>小数部の桁数。</value>
        public int Scale
        {
            get { return _scale; }
        }

        /// <summary>
        /// 整数部の桁数を取得します。
        /// </summary>
        /// <value>整数部の桁数。</value>
        public int IntegerLength
        {
            get { return _integerLength; }
        }

        /// <summary>
        /// <seealso cref="ValueValidator.Negated"/> が true である場合に使用されるデフォルトのメッセージテンプレートを取得します。
        /// </summary>
        /// <value>検証ルール反転時のデフォルトメッセージテンプレート文字列。</value>
        protected override string DefaultNegatedMessageTemplate
        {
            get
            {
                return Properties.Resources.NUMBER_VALIDATOR_NEGATED_MESSAGETEMPLATE;
            }
        }

        /// <summary>
        /// <seealso cref="ValueValidator.Negated"/> が false である場合に使用されるデフォルトのメッセージテンプレートを取得します。
        /// </summary>
        /// <value>デフォルトメッセージテンプレート文字列です。</value>
        protected override string DefaultNonNegatedMessageTemplate
        {
            get
            {
                return Properties.Resources.NUMBER_VALIDATOR_NONNEGATED_MESSAGETEMPLATE;
            }
        }

        /// <summary>
        /// 検証エラーメッセージを生成するメソッドです。 <seealso cref="Validator.MessageTemplate"/> をテンプレートとし、
        /// 現在のカルチャに従って文字列をフォーマットします。
        /// </summary>
        /// <remarks>
        /// <para>フォーマット文字列に含まれるプレースホルダに渡される情報は、以下の通りです。
        /// <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>整数部の桁数</description>
        /// </item>
        /// <item>
        /// <term>{4}</term>
        /// <description>小数部の桁数</description>
        /// </item>
        /// </list>
        /// </para>
        /// </remarks>
        /// <param name="objectToValidate">検証対象のオブジェクト。</param>
        /// <param name="key">オブジェクトが取得されるプロパティまたはメソッド名。</param>
        /// <returns>検証エラーメッセージ。</returns>
        /// <exception cref="FormatException">
        /// 以下のメンバを参照。
        /// <list type="bullet">
        /// <item>
        /// format の書式項目が無効です。または書式設定する引数を示す数が 0 より小さいか、書式設定する指定されたオブジェクトの数以上です。 
        /// </item>
        /// </list>
        /// </exception>
        protected override string GetMessage(object objectToValidate, string key)
        {
            return string.Format(CultureInfo.CurrentUICulture,
                this.MessageTemplate,
                objectToValidate,
                key,
                this.Tag,
                IntegerLength,
                Scale);
        }

        /// <summary>
        /// <paramref name="objectToValidate"/>が数値に変換可能な文字列であり、
        /// 整数桁・小数桁がそれぞれ指定した桁数に一致、または範囲内に収まるかを検証するメソッドです。
        /// </summary>
        /// <remarks>
        /// <para>
        /// <paramref name="objectToValidate"/>が null または空文字列の場合、検証処理は行われません。
        /// </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> 
        /// <exception cref="FormatException">
        /// 以下のメンバを参照。
        /// <list type="bullet">
        /// <item><seealso cref="GetMessage"/></item>
        /// </list>
        /// </exception>
        protected override void DoValidate(object 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)
            {
                return;
            }
            string targetData = objectToValidate.ToString();
            if (targetData.Length == 0)
            {
                return;
            }

            int[] targetArray = GetIntAntScaleLength(targetData);

            bool isError = false;
            if (targetArray.Length == 0)
            {
                isError = true;
            }
            else
            {
                // 整数部の比較
                if ((IsAccordedInteger && targetArray[0] != _integerLength)
                    || (!IsAccordedInteger && targetArray[0] > _integerLength))
                {
                    isError = true;

                }

                if ((IsAccordedScale && targetArray[1] != _scale)
                    || (!IsAccordedScale && targetArray[1] > _scale))
                {
                    isError = true;
                }
            }

            if (!isError == Negated)
            {
                LogValidationResult(validationResults,
                    GetMessage(targetData, key),
                    currentTarget,
                    key);
            }
        }

        /// <summary>
        /// 引数の文字列の整数部・小数部の桁数を格納した int 配列を返す内部メソッドです。
        /// </summary>
        /// <param name="str">解析対象の文字列。</param>
        /// <returns>整数部・小数部の桁数を格納した int 配列。</returns>
        protected virtual int[] GetIntAntScaleLength(string str)
        {

            Match match = DECIMAL_REG_EXP.Match(str);
            if (!match.Success || match.Groups.Count != 4)
            {
                return new int[0];
            }

            int[] result = new int[2];
            result[0] = match.Groups[1].Length;
            result[1] = match.Groups[3].Length;

            return result;
        }
    }
}
