// 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;
using System.Collections.Generic;
using System.Configuration;
using TERASOLUNA.Fw.Client.Configuration.View;
using TERASOLUNA.Fw.Common;
using TERASOLUNA.Fw.Common.Logging;

namespace TERASOLUNA.Fw.Client.Forms
{
    /// <summary>
    /// <see cref="IForwardable"/> 実装クラスのインスタンスを生成するファクトリクラスです。
    /// </summary>
    public class ViewFactory
    {
        /// <summary>
        /// <see cref="ILog"/> 実装クラスのインスタンスです。
        /// </summary>
        /// <remarks>
        /// ログ出力に利用します。
        /// </remarks>
        private static ILog _log = LogFactory.GetLogger(typeof(ViewFactory));

        /// <summary>
        /// 利用する <see cref="ViewFactory"/> クラスの型名をアプリケーション構成
        /// ファイルから取得する際に指定する文字列です。
        /// </summary>
        /// <remarks>
        /// <para>
        /// この定数の値は "ViewFactoryTypeName" です。
        /// </para>
        /// </remarks>
        protected static readonly string VIEW_FACTORY_TYPENAME = "ViewFactoryTypeName";

        /// <summary>
        /// ロック用のオブジェクトです。
        /// </summary>
        private static object _syncRoot = new Object();

        /// <summary>
        /// <see cref="ViewFactory"/> のシングルトンインスタンスです。
        /// </summary>
        private static volatile ViewFactory _factory = null;

        /// <summary>
        /// <see cref="IForwardable"/> を実装した画面の型のキャッシュです。
        /// </summary>
        private IDictionary<string, Type> _forwardableTypes = new Dictionary<string, Type>();

        /// <summary>
        /// <see cref="ViewFactory"/> を取得または設定します。
        /// </summary>
        /// <value><see cref="ViewFactory"/> のシングルトンインスタンス。</value>
        protected static ViewFactory Factory
        {
            get
            {
                if (_factory == null)
                {
                    lock (_syncRoot)
                    {
                        if (_factory == null)
                        {
                            _factory = ClassUtil.CreateInstanceFromAppSettings<ViewFactory>(
                              VIEW_FACTORY_TYPENAME, typeof(ViewFactory));
                        }
                    }
                }
                return _factory;
            }
        }

        /// <summary>
        /// <see cref="IForwardable"/> を実装した画面の型のキャッシュのコレクションを取得します。
        /// </summary>
        /// <value><see cref="IForwardable"/> を実装した画面の型のキャッシュのコレクション。</value>
        protected IDictionary<string, Type> ForwardableTypes
        {
            get
            {
                return _forwardableTypes;
            }
        }

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

        /// <summary>
        /// <see cref="IForwardable"/> 実装クラスのインスタンスを生成します。
        /// </summary>
        /// <param name="viewId">生成しようとする <see cref="IForwardable"/> 実装クラスを
        /// 識別するための文字列。</param>
        /// <returns><see cref="IForwardable"/> 実装クラスのインスタンス。
        /// 生成に失敗した場合は、例外をスローし、 null は返しません。</returns>
        /// <remarks>
        /// <para>
        /// このメソッドは、<paramref name="viewId"/> を元に、画面遷移設定ファイルに定義された 
        /// <see cref="IForwardable"/> 実装クラスの型名を取得し、対応するインスタンスを生成します。
        /// </para>
        /// </remarks>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="viewId"/> が null の場合に例外をスローします。
        /// </exception>
        /// <exception cref="ArgumentException">
        /// <paramref name="viewId"/> が空文字列の場合に例外をスローします。
        /// </exception>
        /// <exception cref="ConfigurationErrorsException">
        /// アプリケーション構成ファイル、または、画面遷移設定ファイルの内容が不正のため、
        /// <see cref="ViewConfiguration"/> 設定情報を取得できません。
        /// </exception>
        /// <exception cref="TerasolunaException">
        /// 以下のような場合にスローします。
        /// <list type="bullet">
        /// <item>
        /// <description>
        /// 画面遷移設定ファイルに、<paramref name="viewId"/> に対応する <see cref="IForwardable"/> 
        /// 実装クラスの型名が存在しない場合。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// 画面遷移設定ファイルに、<paramref name="viewId"/> に対応する <see cref="IForwardable"/> 
        /// 実装クラスの型名が空文字列の場合。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// <paramref name="viewId"/> に対応する <see cref="IForwardable"/> 実装クラスの型名が
        /// 不正の場合。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// 生成されたインスタンスが <see cref="IForwardable"/> を実装したクラスのインスタンス
        /// ではない場合。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// <paramref name="viewId"/> に対応する <see cref="IForwardable"/> 実装クラスが引数なしの
        /// パブリックなコンストラクタを持たない場合。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// <paramref name="viewId"/> に対応する <see cref="IForwardable"/> 実装クラスのコンストラクタで
        /// 例外が発生した場合。
        /// </description>
        /// </item>
        /// </list>
        /// </exception>
        /// <example>
        /// 画面遷移設定ファイルの設定例を以下に示します。
        /// <code>
        /// &lt;viewConfiguration xmlns="http://www.terasoluna.jp/schema/ViewSchema"&gt;
        ///   &lt;view id="logon"
        ///              type="TERASOLUNA.Sample.LogonForm, TERASOLUNA.Sample" /&gt;
        ///   &lt;view id="menu" 
        ///              type="TERASOLUNA.Sample.Menu, TERASOLUNA.Sample" /&gt;
        /// &lt;/viewConfiguration&gt;
        /// </code>
        /// </example>
        public static IForwardable CreateView(string viewId)
        {
            // 入力チェック
            if (viewId == null)
            {
                ArgumentNullException exception = new ArgumentNullException("viewId");
                if (_log.IsErrorEnabled)
                {
                    _log.Error(string.Format(Properties.Resources.E_NULL_ARGUMENT, "viewId"), exception);
                }
                throw exception;
            }

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

            IForwardable forwardable = Factory.CreateInstance(viewId);

            if (_log.IsDebugEnabled)
            {
                _log.Debug(string.Format(Properties.Resources.D_CREATE_INSTANCE_SUCCESS,
                    typeof(IForwardable).FullName, forwardable.GetType().FullName));
            }

            return forwardable;
        }

        /// <summary>
        /// <see cref="IForwardable"/> 実装クラスのインスタンスを生成します。
        /// </summary>
        /// <param name="viewId">生成しようとする <see cref="IForwardable"/> 実装クラスを
        /// 識別するための文字列。</param>
        /// <returns><see cref="IForwardable"/> 実装クラスのインスタンス。
        /// 生成に失敗した場合は、例外をスローし、 null は返しません。</returns>
        /// <remarks>
        /// <para>
        /// このメソッドは、 <paramref name="viewId"/> を元に、画面遷移設定ファイルに定義された 
        /// <see cref="IForwardable"/> 実装クラスの型名を取得し、対応するインスタンスを生成します。
        /// </para>
        /// </remarks>
        /// <exception cref="ConfigurationErrorsException">
        /// アプリケーション構成ファイル、または、画面遷移設定ファイルの内容が不正のため、
        /// <see cref="ViewConfiguration"/> 設定情報を取得できません。
        /// </exception>
        /// <exception cref="TerasolunaException">
        /// 以下のような場合に例外をスローします。
        /// <list type="bullet">
        /// <item>
        /// <description>
        /// 画面遷移設定ファイルに、 <paramref name="viewId"/> に対応する <see cref="IForwardable"/> 
        /// 実装クラスの型名が存在しない場合。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// 画面遷移設定ファイルに、 <paramref name="viewId"/> に対応する <see cref="IForwardable"/> 
        /// 実装クラスの型名が空文字列の場合。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// <paramref name="viewId"/> に対応する <see cref="IForwardable"/> 実装クラスの型名が
        /// 不正の場合。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// 生成されたインスタンスが <see cref="IForwardable"/> を実装したクラスのインスタンス
        /// ではない場合。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// <paramref name="viewId"/> に対応する <see cref="IForwardable"/> 実装クラスが引数なしの
        /// パブリックなコンストラクタを持たない場合。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// <paramref name="viewId"/> に対応する <see cref="IForwardable"/> 実装クラスのコンストラクタで
        /// 例外が発生した場合。
        /// </description>
        /// </item>
        /// </list>
        /// </exception>
        /// <example>
        /// 画面遷移設定ファイルの設定例を以下に示します。
        /// <code>
        /// &lt;viewConfiguration xmlns="http://www.terasoluna.jp/schema/ViewSchema"&gt;
        ///   &lt;view id="logon"
        ///              type="TERASOLUNA.Sample.LogonForm, TERASOLUNA.Sample" /&gt;
        ///   &lt;view id="menu" 
        ///              type="TERASOLUNA.Sample.Menu, TERASOLUNA.Sample" /&gt;
        /// &lt;/viewConfiguration&gt;
        /// </code>
        /// </example>
        protected IForwardable CreateInstance(string viewId)
        {
            IForwardable forwardable = null;
            
            Type forwardableType = null;
            bool canGetValue = false;
            ICollection forwardableTypesCollection = (ICollection)_forwardableTypes;
            lock (forwardableTypesCollection.SyncRoot)
            {
                canGetValue = _forwardableTypes.TryGetValue(viewId, out forwardableType);
            }

            if (canGetValue)
            {
                // viewIdをキーとして型がキャッシュされていれば、その型のインスタンスを生成
                forwardable = ClassUtil.CreateInstanceByType<IForwardable>(forwardableType);
            }
            else
            {
                // viewIdをキーとして型がキャッシュされていなければ、設定ファイルから型名を取得する
                string typeName = ViewConfiguration.GetViewTypeName(viewId);

                // 型名が取得できなければエラー
                if (string.IsNullOrEmpty(typeName))
                {
                    string message = string.Format(Properties.Resources.E_FORMS_FORM_TYPENAME_NOT_FOUND, viewId);
                    TerasolunaException exception = new TerasolunaException(message);
                    if (_log.IsErrorEnabled)
                    {
                        _log.Error(message, exception);
                    }
                    throw exception;
                }

                // インスタンスを生成し、成功すれば型をキャッシュ
                forwardable = ClassUtil.CreateInstanceByTypeName<IForwardable>(typeName);

                lock (forwardableTypesCollection.SyncRoot)
                {
                    _forwardableTypes[viewId] = forwardable.GetType();
                }
            }

            return forwardable;
        }
    }
}
