// 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;
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>
    /// 検証ルール" byte 列長範囲チェック"に対応するバイト長範囲チェックを行う <see cref="Microsoft.Practices.EnterpriseLibrary.Validation.Validator"/> 継承クラスです。
    /// </summary>
    /// <remarks>
    /// <para>
    /// 検証対象の文字列を指定したエンコードでバイト列に変換し、バイト長が指定した範囲
    /// に含まれることをチェックする検証機能を提供します。
    /// </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="ByteRangeValidator.DefaultNonNegatedMessageTemplate"/> 、 <see cref="ByteRangeValidator.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>minByteLength</description>
    /// <para>バイト長の最小値を示します。範囲には指定した数値自身が含まれます。
    ///  0 未満の値を指定した場合、実行時例外となります。</para>
    /// </item>
    /// <item>
    /// <description>maxByteLength</description>
    /// <para>バイト長の最大値を示します。範囲には指定した数値自身が含まれます。
    ///  2,147,483,647 を超える値を指定した場合、実行時例外となります。</para>
    /// </item>
    /// <item>
    /// <description>encoding</description>
    /// <para>文字列をバイト列に変換する際に利用するエンコード形式を指定します。
    /// コードページでの指定はできません。
    /// 利用できるエンコード形式の名称については、<see cref="System.Text.Encoding"/> の
    /// ドキュメントを参照してください。</para>
    /// </item>
    /// </list>
    /// </para>
    /// <example>
    /// <code>
    /// &lt;validator negated="false" minByteLength="10" maxByteLength="200" encoding="shift_jis"
    /// messageTemplate="" messageTemplateResourceName=""
    /// messageTemplateResourceType="" tag="検証対象項目名" name="byte range validator" 
    /// type="TERASOLUNA.Fw.Common.Validation.Validators.ByteRangeValidator,TERASOLUNA.Fw.Common"/&gt;
    /// </code>
    /// </example>
    /// </remarks>
    [ConfigurationElementType(typeof(ByteRangeValidatorData))]
    public class ByteRangeValidator : TypeRangeValidator<int>
    {
        /// <summary>
        /// バイト長の最小値です。
        /// </summary>
        /// <remarks>
        /// デフォルトの値は、 0 です。
        /// </remarks>
        private int _minByteLength = 0;

        /// <summary>
        /// バイト長の最大値です。
        /// </summary>
        /// <remarks>
        /// デフォルトの値は、 0 です。
        /// </remarks>
        private int _maxByteLength = 0;

        /// <summary>
        /// エンコーディング。
        /// </summary>
        /// <remarks>
        /// デフォルトの値は、 null です。
        /// </remarks>
        private Encoding _encoding = null;

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

        /// <summary>
        /// コンストラクタです。新しい <see cref="ByteRangeValidator"/> のインスタンスを初期化します。
        /// </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">検証対象の項目名。</param>
        /// <param name="negated">検証条件の反転を示すフラグ。</param>
        /// <param name="encodingName">文字列をバイト列に変換する際利用するエンコード名。</param>
        /// <param name="minByteLength">バイト長の最小値。</param>
        /// <param name="maxByteLength">バイト長の最大値。</param>
        /// <exception cref="ArgumentException">
        /// <list type="bullet">
        /// <item>
        /// <paramref name="minByteLength"/> が 0 以下です。
        /// </item>
        /// <item>
        /// <paramref name="maxByteLength"/> が <paramref name="minByteLength"/> 以下です。
        /// </item>
        /// <item>
        /// <paramref name="encodingName"/> が空文字列です。
        /// </item>
        /// <item>
        /// <paramref name="encodingName"/> が有効なエンコード名でない場合。
        /// </item>
        /// </list>
        /// </exception>
        /// <exception cref="ArgumentNullException">
        /// <list type="bullet">
        /// <item>
        /// <paramref name="encodingName"/> が null 参照です。
        /// </item>
        /// </list>
        /// </exception>
        public ByteRangeValidator(int minByteLength,
                                  int maxByteLength,
                                  string encodingName,
                                  string messageTemplate,
                                  string tag,
                                  bool negated)
            : base(minByteLength, RangeBoundaryType.Inclusive,
                   maxByteLength, RangeBoundaryType.Inclusive, messageTemplate, negated)
        {
            if (minByteLength < 0)
            {
                string message = string.Format(Properties.Resources.E_NEGATIVE_NUMBER, "minByteLength");
                ArgumentException exception = new ArgumentException(message);
                if (_log.IsErrorEnabled)
                {
                    _log.Error(message, exception);
                }
                throw exception;
            }

            if (encodingName == null)
            {
                ArgumentNullException exception = new ArgumentNullException("encodingName");
                if (_log.IsErrorEnabled)
                {
                    _log.Error(string.Format(
                        Properties.Resources.E_NULL_ARGUMENT, "encodingName"), exception);
                }
                throw exception;
            }

            if (encodingName.Length == 0)
            {
                string message = string.Format(Properties.Resources.E_EMPTY_STRING, "encodingName");
                ArgumentException exception = new ArgumentException(message);
                if (_log.IsErrorEnabled)
                {
                    _log.Error(message, exception);
                }
                throw exception;
            }

            try
            {
                _encoding = Encoding.GetEncoding(encodingName);
            }
            catch (ArgumentException e)
            {
                string message = string.Format(Properties.Resources.E_INVALID_ARGUMENT, "encodingName");
                ArgumentException exception = new ArgumentException(message, e);
                if (_log.IsErrorEnabled)
                {
                    _log.Error(message, exception);
                }
                throw exception;
            }
            if (!string.IsNullOrEmpty(tag))
            {
                this.Tag = tag;
            }
            _minByteLength = minByteLength;
            _maxByteLength = maxByteLength;
        }


        /// <summary>
        /// バイト長の最小値を表す int 値を取得します。
        /// </summary>
        /// <value>バイト長の最小値。</value>
        public int MinByteLength
        {
            get { return _minByteLength; }
        }


        /// <summary>
        /// バイト長の最大値を表す int 値を取得します。
        /// </summary>
        /// <value>バイト長の最大値。</value>
        public int MaxByteLength
        {
            get { return _maxByteLength; }
        }

        /// <summary>
        /// 文字列をバイト配列に変換する際利用するエンコード名を取得します。
        /// </summary>
        /// <value>エンコード名。</value>
        public Encoding Encoding
        {
            get { return _encoding; }
        }

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

        /// <summary>
        /// <seealso cref="ValueValidator.Negated"/> が false である場合に使用されるデフォルトのメッセージテンプレートを取得します。
        /// </summary>
        /// <value>デフォルトメッセージテンプレート文字列。</value>
        protected override string DefaultNonNegatedMessageTemplate
        {
            get
            {
                return Properties.Resources.BYTE_RANGE_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>
        /// <item>
        /// <term>{5}</term>
        /// <description>エンコード名</description>
        /// </item>
        /// </list>
        /// </para>
        /// </remarks>
        /// <exception cref="FormatException">
        /// <seealso cref="Validator.MessageTemplate"/> のプレースホルダが 6 以上です。
        /// </exception>
        /// <param name="objectToValidate">検証対象のオブジェクト。</param>
        /// <param name="key">オブジェクトが取得されるプロパティまたはメソッド名。</param>
        /// <returns>検証エラーメッセージ。</returns>
        protected override string GetMessage(object objectToValidate, string key)
        {
            return string.Format(CultureInfo.CurrentUICulture,
                this.MessageTemplate,
                objectToValidate,
                key,
                this.Tag,
                MinByteLength,
                MaxByteLength,
                this.Encoding.WebName);
        }

        /// <summary>
        /// byte 列長範囲チェックを行うメソッドです。検証対象値の文字列表現が、
        /// バイト配列長が指定範囲内に収まっていない場合、検証失敗となります。
        /// </summary>
        /// <remarks>
        /// <para>
        /// <paramref name="objectToValidate"/> が null の場合、検証処理は行われません。
        /// </para>
        /// </remarks>
        /// <exception cref="ArgumentNullException">
        /// <list type="bullet">
        /// <item>
        /// <paramref name="validationResults"/> が null 参照です。
        /// </item>
        /// </list>
        /// </exception>
        /// <param name="objectToValidate">検証対象の値。</param>
        /// <param name="currentTarget">検証対象のオブジェクト。</param>
        /// <param name="key">検証対象のプロパティ名またはメソッド名。</param>
        /// <param name="validationResults">検証結果を格納する <see cref="ValidationResults"/> 。</param>
        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 objectToValidateString = objectToValidate as string;
            if (objectToValidateString == null)
            {
                LogValidationResult(validationResults, GetMessage(objectToValidate, key), currentTarget, key);
            }
            else
            {
                byte[] bytes = _encoding.GetBytes(objectToValidateString);
                base.DoValidate(bytes.Length, currentTarget, key, validationResults);
            }
        }
    }
}