// 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 System.Configuration;
using System.Data;
using System.Windows.Forms;
using TERASOLUNA.Fw.Client.Configuration.View;
using TERASOLUNA.Fw.Client.Conversion;
using TERASOLUNA.Fw.Common;
using TERASOLUNA.Fw.Common.Logging;

namespace TERASOLUNA.Fw.Client.Forms
{
    /// <summary>
    /// フォームベースの画面遷移機能を提供するクラスです。
    /// </summary>
    /// <remarks>
    /// このクラスで用いる画面は <see cref="IForwardable"/> を実装した <see cref="Form"/> の
    /// 派生クラスです。
    /// </remarks>
    [DefaultEvent("FormClosed"),
     ToolboxItem(true)]
    public class FormForwarder : ForwarderBase
    {
        /// <summary>
        /// <see cref="ILog"/> 実装クラスのインスタンスです。
        /// </summary>
        /// <remarks>
        /// ログ出力に利用します。
        /// </remarks>
        private static ILog _log = LogFactory.GetLogger(typeof(FormForwarder));
     
        /// <summary>
        /// 遷移先画面のモーダリティを保持します。
        /// </summary>
        /// <remarks>
        /// デフォルト値は <see cref="TERASOLUNA.Fw.Client.Forms.Modality.Modeless"/> 
        /// です。
        /// </remarks>
        private Modality _modality = Modality.Modeless;

        /// <summary>
        /// 遷移先画面に設定するオーナーフォームを保持します。
        /// </summary>
        private Form _ownerForm = null;

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

        /// <summary>
        /// <see cref="FormForwarder"/> クラスの新しいインスタンスを初期化します。
        /// </summary>
        /// <param name="container"><see cref="IContainer"/> 実装クラスのインスタンス。</param>
        public FormForwarder(IContainer container)
        {
            if (container != null)
            {
                container.Add(this);
            }
        }

        /// <summary>
        /// 遷移先画面のモーダリティを取得または設定します。
        /// </summary>
        /// <remarks>
        /// デフォルト値は <see cref="TERASOLUNA.Fw.Client.Forms.Modality.Modeless"/> です。
        /// </remarks>
        /// <value>遷移先画面のモーダリティを表す <see cref="Modality"/> 。</value>
        [Category("画面遷移"),
         Description("遷移先画面のモーダリティを設定します。")]
        public Modality Modality
        {
            get
            {
                return _modality;
            }
            set
            {
                _modality = value;
            }
        }

        /// <summary>
        /// 遷移先画面に設定するオーナーフォームを取得または設定します。
        /// </summary>
        /// <value>オーナーフォーム。</value>
        [Category("画面遷移"),
         Description("遷移先画面のオーナーとなるフォームを設定します。")]
        public Form OwnerForm
        {
            get
            {
                return _ownerForm;
            }
            set
            {
                _ownerForm = value;
            }
        }

        /// <summary>
        /// 画面遷移を実行します。
        /// </summary>
        /// <remarks>
        /// <para>以下の手順で画面遷移処理を実行します。</para>
        /// <list type="number">
        /// <item>
        /// <description>
        /// 画面IDより遷移先の画面インスタンスを生成。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// 遷移元画面のインスタンスから遷移先画面のインスタンスへ画面の保持する
        /// コレクションの内容をコピー。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// 遷移元画面のインスタンスから遷移先画面のインスタンスへ画面 
        /// <see cref="DataSet"/> をコンバート。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// 遷移先画面を所有するフォームを設定。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// 遷移先画面の終了イベントハンドラを設定。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// 遷移先画面の <see cref="IForwardable.Init"/> メソッドを用いて初期化。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// 遷移先画面を表示する。
        /// </description>
        /// </item>
        /// </list>
        /// <para>遷移元画面が設定されていない場合、「画面のコレクションのコピー」、
        /// 「 <see cref="DataSet"/> のコンバート」は実行されません。</para>
        /// <para>データセット変換情報が設定されていない場合、「 <see cref="DataSet"/> のコンバート」
        /// は実行されません。</para>
        /// <para><see cref="FormForwarder.Modality"/> に設定されている値によって画面の表示形式が
        /// 決定されます。</para>
        /// <list type="bullet">
        /// <item>
        /// <description>
        /// <para><see cref="TERASOLUNA.Fw.Client.Forms.Modality.Modeless"/> :モードレスに
        /// 遷移先画面を表示します。これは通常の挙動です。</para>
        /// <para>遷移先画面と遷移元画面は互いに影響を受けません。 (<see cref="Form.Owner"/> の
        /// 影響は受けます)</para>
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// <para><see cref="TERASOLUNA.Fw.Client.Forms.Modality.FormModal"/> :擬似
        /// フォームモーダルなダイアログとして遷移先画面を表示します。</para>
        /// <para>遷移先画面が閉じられるまで、遷移元画面は Disable となります。遷移元画面
        /// 以外の画面には影響を与えません。</para>
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// <para><see cref="TERASOLUNA.Fw.Client.Forms.Modality.ApplicationModal"/> :　
        /// モーダルなダイアログとして遷移先画面を表示します。遷移先画面が閉じられるまで、
        /// <see cref="FormForwarder.Execute"/> メソッドの処理は完了しません。</para>
        /// <para><see cref="TERASOLUNA.Fw.Client.Forms.Modality.ApplicationModal"/> で
        /// 画面を開いた場合、ユーザがウィンドウ右上の×ボタンを押すなどしてウィンドウを閉じた
        /// 際、 <see cref="ForwardableFormCloseEventArgs.DialogResult"/> に
        /// <see cref="DialogResult.Cancel"/> が設定されます。</para></description>
        /// </item>
        /// </list>
        /// </remarks>
        /// <returns>
        /// <list type="bullet">
        /// <item>
        /// <description>
        /// 遷移先画面の初期化 (<see cref="IForwardable.Init"/>) が true を返却した場合、
        /// <see cref="ForwardResult.None"/> を返す。 false を返した場合、
        /// <see cref="ForwardResult.Abort"/> を返す。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// <para>
        /// アプリケーションモーダルで表示した場合、戻り値に対応した 
        /// <see cref="ForwardResult"/> の値を返却する。
        /// </para>
        /// </description>
        /// </item>
        /// </list>
        /// </returns>
        /// <exception cref="ConfigurationErrorsException">
        /// アプリケーション構成ファイル、または、画面遷移設定ファイルの内容が不正のため、
        /// <see cref="ViewConfiguration"/> 設定情報を取得できません。
        /// </exception>
        /// <exception cref="TerasolunaException">
        /// 以下のような場合に例外をスローします。
        /// <list type="bullet">
        /// <item>
        /// <description>
        /// <see cref="ForwarderBase.ViewId"/> プロパティが null 参照または空文字に設定されています。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// <see cref="IForwardable"/> 実装クラスのインスタンスを生成できません。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// <see cref="IForwardable"/> 実装クラスのインスタンスが、 <see cref="Form"/> にキャストできません。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// <see cref="ForwarderBase.ConvertId"/> が指定されていますが、画面遷移元の 
        /// <see cref="IForwardable.ViewData"/> 、または、画面遷移先の <see cref="IForwardable.ViewData"/>
        /// が null 参照です。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// <see cref="IConverter"/> 実装クラスのインスタンスを生成できません。
        /// </description>
        /// </item>
        /// </list>
        /// </exception>
        /// <exception cref="ConversionException">
        /// データセット変換中にエラーが発生しました。
        /// </exception>
        public override ForwardResult Execute()
        {
            // 画面を生成するためのViewIdプロパティのチェック
            if (string.IsNullOrEmpty(ViewId))
            {
                string message = string.Format(
                    Properties.Resources.E_NULL_OR_EMPTY_PROPERTY_STRING, "Forwarder", "ViewId");
                TerasolunaException exception = new TerasolunaException(message);
                if (_log.IsErrorEnabled)
                {
                    _log.Error(message, exception);
                }
                throw exception;
            }

            // 遷移対象ビューを生成
            IForwardable forwardTarget = ViewFactory.CreateView(ViewId);

            Form targetForm = forwardTarget as Form;
            if (targetForm == null)
            {
                string message = string.Format(
                    Properties.Resources.E_INVALID_CAST,
                    forwardTarget.GetType().FullName,
                    typeof(Form).FullName);
                TerasolunaException exception = new TerasolunaException(message);
                if (_log.IsErrorEnabled)
                {
                    _log.Error(message, exception);
                }
                throw exception;
            }

            if (ForwardHost != null)
            {
                // Itemsをコピー
                CopyViewItems(ForwardHost, forwardTarget);

                if (!string.IsNullOrEmpty(ConvertId))
                {

                    if (ForwardHost.ViewData == null)
                    {
                        string message = string.Format(
                            Properties.Resources.E_FORMS_NULL_HOST_OBJECT, "ConvertId", "ViewData");
                        TerasolunaException exception = new TerasolunaException(message);
                        if (_log.IsErrorEnabled)
                        {
                            _log.Error(message, exception);
                        }
                        throw exception;
                    }
                    if (forwardTarget.ViewData == null)
                    {
                        string message = string.Format(
                            Properties.Resources.E_FORMS_NULL_TARGET_OBJECT, "ConvertId", "ViewData");
                        TerasolunaException exception = new TerasolunaException(message);
                        if (_log.IsErrorEnabled)
                        {
                            _log.Error(message, exception);
                        }
                        throw exception;
                    }

                    // DataSetを変換して設定
                    ConvertViewData(ForwardHost.ViewData, forwardTarget.ViewData);
                }
            }

            // OwnerFormを遷移先に追加
            targetForm.Owner = _ownerForm;

            ForwardResult result = ForwardResult.None;

            // 初期化
            // Initメソッドの引数をIForwardableをとるよう修正
            if (forwardTarget.Init(ForwardHost))
            {
                // Initがtrueのときのみ、Closing、Closedイベントを追加する
                // Closingイベント設定
                targetForm.FormClosing += new FormClosingEventHandler(DoClosing);

                // ModalityプロパティがFormModalであれば、終了時に親を有効化するイベントを設定
                if (Modality == Modality.FormModal && ForwardHost is Form)
                {
                    targetForm.FormClosed += new FormClosedEventHandler(EnableParent);
                }

                // Closedイベント設定
                targetForm.FormClosed += new FormClosedEventHandler(DoClosed);

                if (Modality == Modality.ApplicationModal)
                {
                    if(_log.IsInfoEnabled)
                    {
                        if (ForwardHost != null)
                        {
                            _log.Info(string.Format(
                                Properties.Resources.I_FORM_TRANSITION,
                                Modality.ToString(),
                                ForwardHost.GetType().FullName,
                                targetForm.GetType().FullName));
                        }
                        else
                        {
                            _log.Info(string.Format(
                                Properties.Resources.I_FORM_TRANSITION_NO_FORWARD_HOST,
                                Modality.ToString(),
                                targetForm.GetType().FullName));
                        }
                    }

                    // ビューをダイアログとして表示
                    DialogResult dResult = targetForm.ShowDialog();
                    switch (dResult)
                    {
                        case DialogResult.Abort:
                            result = ForwardResult.Abort;
                            break;

                        case DialogResult.Cancel:
                            result = ForwardResult.Cancel;
                            break;

                        case DialogResult.Ignore:
                            result = ForwardResult.Ignore;
                            break;

                        case DialogResult.No:
                            result = ForwardResult.No;
                            break;

                        case DialogResult.OK:
                            result = ForwardResult.OK;
                            break;

                        case DialogResult.Retry:
                            result = ForwardResult.Retry;
                            break;

                        case DialogResult.Yes:
                            result = ForwardResult.Yes;
                            break;

                        default:
                            throw new TerasolunaException(string.Format(
                                Properties.Resources.E_UNEXPECTED_VALUE, "DialogResult"));
                    }
                    targetForm.Dispose();
                }
                else
                {
                    if (Modality == Modality.FormModal && ForwardHost is Form)
                    {
                        Form formHost = ForwardHost as Form;
                        formHost.Enabled = false;
                    }
                    if(_log.IsInfoEnabled)
                    {
                        if (ForwardHost != null)
                        {
                            _log.Info(string.Format(
                                Properties.Resources.I_FORM_TRANSITION,
                                Modality.ToString(),
                                ForwardHost.GetType().FullName,
                                targetForm.GetType().FullName));
                        }
                        else
                        {
                            _log.Info(string.Format(
                                Properties.Resources.I_FORM_TRANSITION_NO_FORWARD_HOST,
                                Modality.ToString(),
                                targetForm.GetType().FullName));
                        }
                    }
                    // ビューを表示
                    targetForm.Show();
                }
            }
            else
            {
                result = ForwardResult.Abort;
            }

            return result;
        }

        /// <summary>
        /// 遷移元の画面を有効化するイベントハンドラです。
        /// </summary>
        /// <remarks>
        /// <para>遷移元の画面を有効化します。</para>
        /// <para><see cref="FormForwarder.Modality"/> に 
        /// <see cref="TERASOLUNA.Fw.Client.Forms.Modality.FormModal"/> が設定
        /// されている場合、遷移先画面の <see cref="Form.Closed"/> イベント発生時に呼び出
        /// されます。</para>
        /// </remarks>
        /// <param name="sender">イベントのソース。</param>
        /// <param name="e">イベントデータを格納している <see cref="FormClosedEventArgs"/> 。</param>
        protected virtual void EnableParent(object sender, FormClosedEventArgs e)
        {
            Form form = ForwardHost as Form;
            if (form != null)
            {
                form.Enabled = true;
            }
        }

        /// <summary>
        /// <see cref="FormForwarder.FormClosing"/> に登録したイベントハンドラを呼び出します。
        /// </summary>
        /// <param name="e"> イベントデータを格納している 
        /// <see cref="ForwardableFormCloseEventArgs"/> 。</param>
        protected virtual void OnClosing(ForwardableFormCloseEventArgs e)
        {
            if (FormClosing != null)
            {
                if (_log.IsDebugEnabled)
                {
                    _log.Debug(string.Format(Properties.Resources.D_BEGIN_EVENT, "FormClosing"));
                }

                FormClosing(this, e);

                if (_log.IsDebugEnabled)
                {
                    _log.Debug(string.Format(Properties.Resources.D_BEGIN_EVENT, "FormClosing"));
                }
            }
        }

        /// <summary>
        /// 遷移先画面の <see cref="Form.FormClosing"/> イベントに登録される
        /// イベントハンドラです。
        /// </summary>
        /// <remarks>
        /// <para>遷移先画面の <see cref="Form.Closing"/> イベント発生時に実行されます。</para>
        /// <para>
        /// 遷移先画面の情報を取得して新しい <see cref="ForwardableFormCloseEventArgs"/> 
        /// インスタンスを生成し、 <see cref="FormForwarder.OnClosing"/> メソッドを呼び出します。
        /// </para>
        /// </remarks>
        /// <param name="sender">イベントのソース。</param>
        /// <param name="e">イベントデータを格納している 
        /// <see cref="FormClosingEventArgs"/> 。</param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="sender"/> が null である場合。</exception>
        /// <exception cref="TerasolunaException">
        /// <paramref name="sender"/> が <see cref="IForwardable"/> を実装していない場合。
        /// </exception>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
        protected virtual void DoClosing(object sender, FormClosingEventArgs e)
        {
            if (sender == null)
            {
                ArgumentNullException exception = new ArgumentNullException("sender");
                if (_log.IsErrorEnabled)
                {
                    _log.Error(string.Format(
                        Properties.Resources.E_NULL_ARGUMENT, "sender"), exception);
                }
                throw exception;
            }

            IForwardable forwardable = sender as IForwardable;

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

            ForwardableFormCloseEventArgs arg =
                new ForwardableFormCloseEventArgs(forwardable, e);

            OnClosing(arg);
        }

        /// <summary>
        /// <para>
        /// <see cref="FormForwarder.FormClosed"/> に登録されたイベントハンドラを呼び出します。
        /// </para>
        /// </summary>
        /// <param name="e">イベントデータを格納している
        /// <see cref="ForwardableFormCloseEventArgs"/> 。</param>
        protected virtual void OnClosed(ForwardableFormCloseEventArgs e)
        {
            if (FormClosed != null)
            {
                if (_log.IsDebugEnabled)
                {
                    _log.Debug(string.Format(Properties.Resources.D_BEGIN_EVENT, "FormClosed"));
                }

                FormClosed(this, e); 
                
                if (_log.IsDebugEnabled)
                {
                    _log.Debug(string.Format(Properties.Resources.D_END_EVENT, "FormClosed"));
                }
            }
        }

        /// <summary>
        /// 遷移先画面の <see cref="Form.FormClosed"/> イベントに登録される
        /// イベントハンドラです。
        /// </summary>
        /// <remarks>
        /// <para>遷移先画面の <see cref="Form.Closed"/> イベント発生時に実行されます。</para>
        /// <para>
        /// イベント発生元の遷移可能画面が、 <see cref="Form"/> のインスタンスであり、
        /// <see cref="Form.DialogResult"/> プロパティの値が <see cref="DialogResult.OK"/> である場合、
        /// <see cref="ForwarderBase.ReverseViewData"/> を用いて <see cref="DataSet"/> の反映処理
        /// を行います。
        /// </para>
        /// <para>
        /// 遷移先画面の情報を取得して新しい <see cref="ForwardableFormCloseEventArgs"/> 
        /// インスタンスを生成し、 <see cref="FormForwarder.OnClosed"/> メソッドを呼び出します。
        /// </para>
        /// </remarks>
        /// <param name="sender">イベントのソース。</param>
        /// <param name="e">イベントデータを格納している 
        /// <see cref="FormClosingEventArgs"/> 。</param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="sender"/> が null 参照です。
        /// </exception>
        /// <exception cref="TerasolunaException">
        /// 以下のような場合に例外をスローします。
        /// <list type="bullet">
        /// <item>
        /// <description>
        /// <paramref name="sender"/> が <see cref="IForwardable"/> を実装していません。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// <see cref="ForwarderBase.ConvertId"/> が指定されていますが、画面遷移元の <see cref="IForwardable.ViewData"/> 、 
        /// または、画面遷移先の <see cref="IForwardable.ViewData"/> が null 参照です。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// <see cref="IConverter"/> 実装クラスのインスタンスを生成できません。
        /// </description>
        /// </item>
        /// </list>
        /// </exception>
        /// <exception cref="ConversionException">
        /// データセット変換中にエラーが発生しました。
        /// </exception>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
        protected virtual void DoClosed(object sender, FormClosedEventArgs e)
        {
            if (sender == null)
            {
                ArgumentNullException exception = new ArgumentNullException("sender");
                if (_log.IsErrorEnabled)
                {
                    _log.Error(string.Format(
                        Properties.Resources.E_NULL_ARGUMENT, "sender"), exception);
                }
                throw exception;
            }
            
            IForwardable forwardable = sender as IForwardable;

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

            ForwardableFormCloseEventArgs arg =
                new ForwardableFormCloseEventArgs(forwardable, e);

            // イベント発生元の画面がFormのインスタンスであり、
            // DialogResultプロパティにOKが格納されている場合のみ、反映処理を行う。
            Form tempForm = sender as Form;
            if (tempForm != null && tempForm.DialogResult == DialogResult.OK)
            {
                // 反映処理
                if (ForwardHost != null)
                {

                    if (!string.IsNullOrEmpty(ConvertId))
                    {

                        if (ForwardHost.ViewData == null)
                        {
                            string message = string.Format(
                                Properties.Resources.E_FORMS_NULL_HOST_OBJECT, "ConvertId", "ViewData");
                            TerasolunaException exception = new TerasolunaException(message);
                            if (_log.IsErrorEnabled)
                            {
                                _log.Error(message, exception);
                            }
                            throw exception;
                        }
                        if (forwardable.ViewData == null)
                        {
                            string message = string.Format(
                                Properties.Resources.E_FORMS_NULL_TARGET_OBJECT, "ConvertId", "ViewData");
                            TerasolunaException exception = new TerasolunaException(message);
                            if (_log.IsErrorEnabled)
                            {
                                _log.Error(message, exception);
                            }
                            throw exception;
                        }

                        ReverseViewData(ForwardHost.ViewData, forwardable.ViewData);
                    }
                }
            }
            OnClosed(arg);
        }


        /// <summary>
        /// 遷移先画面が閉じるときに発生するイベントです。
        /// </summary>
        [Category("動作"),
         Description("遷移先画面が閉じる前に発生します。")]
        public event ForwardableFormCloseEventHandler FormClosing;

        /// <summary>
        /// 遷移先画面が閉じた後に発生するイベントです。
        /// </summary>
        [Category("動作"),
         Description("遷移先画面が閉じた後に発生します。")]
        public event ForwardableFormCloseEventHandler FormClosed;
    }

    /// <summary>
    /// <see cref="Form"/> が閉じるときに発生するイベントハンドラの型です。
    /// </summary>
    /// <param name="sender">イベントソース。</param>
    /// <param name="e">閉じている対象の <see cref="IForwardable"/> の情報とオリジナルの
    /// イベントオブジェクトを格納している <see cref="ForwardableFormCloseEventArgs"/> 。</param>
    public delegate void ForwardableFormCloseEventHandler(object sender,
                                                            ForwardableFormCloseEventArgs e);

}