// 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.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Data;
using System.Text;
using System.Windows.Forms;
using TERASOLUNA.Fw.Common;
using TERASOLUNA.Fw.Common.Logging;
using TERASOLUNA.Fw.Common.Validation;

namespace TERASOLUNA.Fw.Client.Forms
{
    /// <summary>
    /// <para><see cref="IErrorHandler"/> 、<see cref="IForwardable"/> を実装した 
    /// <see cref="Form"/> 派生クラスです。</para>
    /// </summary>
    public class FormBase : Form, IErrorHandler, IForwardable
    {
        /// <summary>
        /// <see cref="ILog"/> 実装クラスのインスタンスです。
        /// </summary>
        /// <remarks>
        /// ログ出力に利用します。
        /// </remarks>
        private static ILog _log = LogFactory.GetLogger(typeof(FormBase));

        /// <summary>
        /// 外部からアクセス可能な画面アイテムを保持する 
        /// <see cref="System.Collections.IDictionary"/> を保持します。
        /// </summary>
        private System.Collections.IDictionary _items = new HybridDictionary();

        /// <summary>
        /// 画面 <see cref="DataSet"/> を保持します。
        /// </summary>
        private DataSet _viewData = null;

        /// <summary>
        /// 入力値検証のエラーパスをキーとしてエラーメッセージを保持します。
        /// </summary>
        private IDictionary<string, string> _validationErrors = new Dictionary<string, string>();

        /// <summary>
        /// <see cref="FormBase"/> クラスの新しいインスタンスを初期化します。
        /// </summary>
        /// <remarks>
        /// デフォルトコンストラクタです。
        /// </remarks>
        public FormBase()
        {
        }

        /// <summary>
        /// エラーハンドラのデフォルト実装です。
        /// </summary>
        /// <remarks>
        /// <para>
        /// このクラスを継承してフォームを作る場合は、
        /// <see cref="FormBase.HandleError"/> をオーバーライドしてエラー処理をカスタマイズしてください。
        /// </para>
        /// <para>
        /// <paramref name="resultString"/> が <see cref="Coordinator.VALIDATION_FAILED"/> の場合、
        /// <paramref name="view"/> の入力値検証エラーがあった項目に対してエラーメッセージを設定します。
        /// </para>
        /// </remarks>
        /// <param name="resultString">エラーの識別文字列。</param>
        /// <param name="messages">エラーメッセージを格納した <see cref="MessageInfo"/> の
        /// リスト。</param>
        /// <param name="view">画面 <see cref="DataSet"/> 。</param>
        /// <exception cref="ArgumentNullException">
        /// 以下のような場合に例外をスローします。
        /// <list type="bullet">
        /// <item>
        /// <paramref name="resultString"/> が null 参照です。
        /// </item>
        /// <item>
        /// <paramref name="messages"/> が null 参照です。
        /// </item>
        /// </list>
        /// </exception>
        /// <exception cref="ArgumentException">
        /// <paramref name="resultString"/> が空文字列です。
        /// </exception>
        /// <exception cref="TerasolunaException">
        /// 入力値検証エラーが発生して <paramref name="resultString"/> が 
        /// <see cref="Coordinator.VALIDATION_FAILED"/> であったときに、
        /// <paramref name="view"/> に <see cref="DataSet"/> 以外のオブジェクトを指定した場合。
        /// </exception>
        public virtual void HandleError(string resultString, IList<MessageInfo> messages, object view)
        {
            // 入力チェック
            if (resultString == null)
            {
                ArgumentNullException exception = new ArgumentNullException("resultString");
                if (_log.IsErrorEnabled)
                {
                    _log.Error(string.Format(Properties.Resources.E_NULL_ARGUMENT, "resultString"), exception);
                }
                throw exception;
            }

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

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

            // エラーハンドリングのログ
            if (_log.IsTraceEnabled)
            {
                StringBuilder message = new StringBuilder();
                if (messages.Count > 0)
                {
                    message.AppendLine(string.Format(Properties.Resources.T_HANDLE_ERROR, resultString));
                    foreach (MessageInfo messageInfo in messages)
                    {
                        message.AppendLine(string.Format(
                            Properties.Resources.T_DICTIONARY_KEY_VALUE, messageInfo.Key, messageInfo.Message));
                    }
                }
                else
                {
                    message.AppendLine(string.Format(Properties.Resources.T_HANDLE_ERROR_NO_MESSAGE, resultString));
                }
                _log.Trace(message.ToString().Trim());
            }

            // 画面データセットに対して入力値検証エラーのメッセージを設定
            if (Coordinator.VALIDATION_FAILED.Equals(resultString))
            {
                DataSet dataSet = view as DataSet;
                if (dataSet == null)
                {
                    string message = string.Format(
                        Properties.Resources.E_INVALID_CAST, view.GetType().FullName, typeof(DataSet).FullName);
                    TerasolunaException exception = new TerasolunaException(message);
                    if (_log.IsErrorEnabled)
                    {
                        _log.Error(message, exception);
                    }
                    throw exception;
                }

                HandleValidationError(messages, dataSet);
            }
        }

        /// <summary>
        /// エラーパスに従い <paramref name="dataSet"/> にエラーメッセージを設定します。
        /// </summary>
        /// <remarks>
        /// <para>
        /// <paramref name="messages"/> 中の <see cref="ValidationMessageInfo"/> から
        /// エラーパスを取得し、画面 <see cref="DataSet"/> の対応する項目に対してエラーメッセージを設定します。
        /// </para>
        /// </remarks>
        /// <param name="messages"><see cref="MessageInfo"/> のリスト。</param>
        /// <param name="dataSet">画面 <see cref="DataSet"/> 。</param>
        protected virtual void HandleValidationError(IList<MessageInfo> messages, DataSet dataSet)
        {
            // エラーパスに従いSetColumnErrorを設定する
            foreach (MessageInfo message in messages)
            {
                ValidationMessageInfo validationMessage = message as ValidationMessageInfo;
                if (validationMessage != null)
                {
                    string errPath = validationMessage.ErrorPath;

                    // エラーパスが含まれていない場合には何もしない
                    if (!string.IsNullOrEmpty(errPath))
                    {
                        string errMessage = validationMessage.Message;
                        IList<DataRow> rowList = ValidationUtils.GetRowList(dataSet, errPath);

                        if (_validationErrors.ContainsKey(errPath))
                        {
                            if (!string.IsNullOrEmpty(_validationErrors[errPath]))
                            {
                                errMessage = _validationErrors[errPath] + Environment.NewLine + errMessage;
                            }
                        }

                        foreach (DataRow row in rowList)
                        {
                            DataTable table = row.Table;
                            string lastPath = ValidationUtils.GetLastPath(errPath);
                            if (table.Columns.Contains(lastPath))
                            {
                                row.SetColumnError(lastPath, errMessage);
                                _validationErrors[errPath] = errMessage;
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// エラークリア処理を行います。
        /// </summary>
        /// <remarks>
        /// <see cref="FormBase.HandleError"/> で設定したエラーに対し、 
        /// XPath でエラー箇所を指定し、エラーの解除を行います。
        /// </remarks>
        /// <param name="view">画面 <see cref="DataSet"/> 。</param>
        /// <exception cref="TerasolunaException">
        /// <paramref name="view"/> に <see cref="DataSet"/> 以外のオブジェクトを指定した場合。
        /// </exception>
        public virtual void ClearError(object view)
        {
            if (view == null)
            {
                return;
            }

            DataSet dataSet = view as DataSet;

            if (dataSet == null)
            {
                string message = string.Format(Properties.Resources.E_INVALID_CAST,
                        view.GetType().FullName,
                        typeof(DataSet).FullName);
                TerasolunaException exception = new TerasolunaException(message);
                if (_log.IsErrorEnabled)
                {
                    _log.Error(message, exception);
                }
                throw exception;
            }

            foreach (string errPath in _validationErrors.Keys)
            {
                // エラーパスがnullまたは空文字であれば何もしない
                if (!string.IsNullOrEmpty(errPath))
                {
                    IList<DataRow> rowList = ValidationUtils.GetRowList(dataSet, errPath);

                    foreach (DataRow row in rowList)
                    {
                        if (row.HasErrors)
                        {
                            DataTable table = row.Table;
                            string lastPath = ValidationUtils.GetLastPath(errPath);
                            if (table.Columns.Contains(lastPath))
                            {
                                row.SetColumnError(lastPath, string.Empty);
                            }
                        }
                    }
                }
            }
            _validationErrors.Clear();
        }

        /// <summary>
        /// 画面アイテムを保持する <see cref="System.Collections.IDictionary"/> を
        /// 取得します。
        /// </summary>
        [Category("画面情報"),
         Description("画面アイテムを取得します。"),
         ReadOnly(true)]
        public virtual System.Collections.IDictionary Items
        {
            get
            {
                return _items;
            }
        }

        /// <summary>
        /// 画面 <see cref="DataSet"/> を取得または設定します。
        /// </summary>
        [Category("画面情報"),
        Description("画面データセットを取得または設定します。")]
        public virtual DataSet ViewData
        {
            get
            {
                return _viewData;
            }
            set
            {
                _viewData = value;
            }
        }

        /// <summary>
        /// 入力値検証のエラー情報を取得または設定します。
        /// </summary>
        public virtual IDictionary<string, string> ValidationErrors
        {
            get
            {
                return _validationErrors;
            }
        }

        /// <summary>
        /// 初期化処理を行います。
        /// </summary>
        /// <remarks>
        /// <para>入力情報を用いて画面を初期化する必要がある場合には、このメソッドを
        /// オーバーライドします。</para>
        /// <para>遷移元の画面が <see cref="IForwardable"/> を実装している場合、画面そのものが渡されます。
        /// その場合でも、遷移元の画面の実装クラスに依存する処理を記述することは避けてください。</para>
        /// </remarks>
        /// <param name="forwardableHost">
        /// 遷移元となる画面を表します。
        /// 遷移元が <see cref="IForwardable"/> インスタンスでない場合、 null を渡します。
        /// </param>
        /// <returns>
        /// 初期化成功時に true 、初期化失敗時に false を返却します。
        /// </returns>
        public virtual bool Init(IForwardable forwardableHost)
        {
            return true;
        }
    }
}
