// 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.Data;
using System.IO;
using System.Net;
using System.Text;
using System.Xml;
using TERASOLUNA.Fw.Common.Logging;

namespace TERASOLUNA.Fw.Client.Communication
{
    /// <summary>
    /// XML 受信機能を提供するクラスです。
    /// </summary>
    /// <typeparam name="TResult">
    /// 受信した XML データを格納するためのデータセット型。
    /// </typeparam>
    public class DataSetXmlReceiver<TResult> : ReceiverBase
        where TResult : DataSet, new()
    {
        /// <summary>
        /// <see cref="ILog"/> 実装クラスのインスタンスです。
        /// </summary>
        /// <remarks>
        /// ログ出力に利用します。
        /// </remarks>
        private ILog _log = LogFactory.GetLogger(typeof(DataSetXmlReceiver<TResult>));

        /// <summary>
        /// 受信時に XML データを読み込むモードです。
        /// </summary>
        private XmlReadMode _readMode = XmlReadMode.Auto;

        /// <summary>
        /// 受信時に XML データを読み込むモードを表す <see cref="XmlReadMode"/> 値を取得または設定します。
        /// </summary>
        /// <remarks>
        /// このプロパティのデフォルト値は "Auto" です。
        /// </remarks>
        public XmlReadMode ReadMode
        {
            get
            {
                return _readMode;
            }
            set
            {
                _readMode = value;
            }
        }

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

        /// <summary>
        /// <paramref name="request"/> で渡された <see cref="HttpWebResponse"/>を用いて XML データ受信処理を行います。
        /// </summary>
        /// <remarks>
        /// <para>
        /// <paramref name="request"/> で渡された <see cref="HttpWebResponse"/> から HTTP レスポンスボディを取得し、
        /// <typeparamref name="TResult"/> で指定された型のデータセットに読み込みます。
        /// </para>
        /// <para>戻り値となる <see cref="CommunicationResult"/> の各プロパティは以下の通りです。</para>
        /// <para>
        /// <list type="table">
        /// <listheader>
        ///     <term>CommunicationResultのプロパティ名</term>
        ///     <description>設定される値</description>
        /// </listheader>
        /// <item>
        ///     <term>Headers</term>
        ///     <description>HTTP レスポンスヘッダのキー/値が格納されます。</description>
        /// </item>
        /// <item>
        ///     <term>ResultData</term>
        ///     <description>受信した XML データを格納したデータセット。</description>
        /// </item>
        /// </list>
        /// </para>
        /// <para>
        /// XML データを正常に受信していても、<typeparamref name="TResult"/> で指定されたデータセットの型と
        /// 受信した XML データの形式が一致しない場合、<see cref="CommunicationResult"/> にはデータは読み込
        /// まれません。
        /// </para>
        /// <para>
        /// サーバから送られる Cookie などのヘッダは <see cref="CommunicationResult.ResponseHeaders"/>　に格納されます。
        /// 利用者は必要に応じてそれらの情報を利用することができます。
        /// </para>
        /// <para>
        /// XML データは <see cref="DataSet.ReadXml(XmlReader)"/> メソッドを用いて読み込まれます。このとき、
        /// スキーマ情報を利用するかどうかは <see cref="ReadMode"/> プロパティの値によって決まります。
        /// </para>
        /// <para>
        /// <paramref name="reporter"/> が <c>null</c> 参照ではない場合、
        /// <see cref="ReceiverBase.BufferSize"/> プロパティに設定されたバッファサイズ分のデータ（デフォルトは8 KB ）をサーバに送信する毎に、
        /// <see cref="IProgressChangeReporter.ReportProgressChanged"/> イベントを発生させます。
        /// </para>
        /// </remarks>
        /// <param name="response">受信処理を行う HTTP レスポンスオブジェクト。</param>
        /// <param name="reporter">進行状況通知を行う<see cref="IProgressChangeReporter"/>インスタンス。</param>
        /// <returns>受信結果を保持した <see cref="CommunicationResult"/>インスタンス。</returns>
        /// <exception cref="WebException">
        /// 通信エラーが発生しました。または、リクエストがキャンセルされました。
        /// </exception>
        /// <exception cref="CommunicationException">
        /// 以下のような場合に例外をスローします。
        /// <list type="bullet">
        /// <item>
        /// <description>
        /// 受信データがXML電文ではありません。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// 受信したXML電文にルートノードがありません。
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// 通信処理中にエラーが発生しました。
        /// </description>
        /// </item>
        /// </list>
        /// </exception>
        /// <exception cref="ServerException">
        /// サーバで業務エラーが発生しました。サーバでの業務エラーは HTTP レスポンスヘッダに "exception" 
        /// ヘッダがあるかどうかで判断します。エラー情報のハンドリングについては <see cref="ReceiverBase.HandleErrorResult"/> 
        /// を参照します。
        /// </exception>
        protected override CommunicationResult ReceiveResponse(HttpWebResponse response, IProgressChangeReporter reporter)
        {
            // XMLデータを読み込み
            XmlDocument receiveXml = GetXmlFromResponseBody(response, reporter);

            // 受信情報のログ出力
            if (_log.IsTraceEnabled)
            {
                // HTTPヘッダ
                StringBuilder responseHeader = new StringBuilder();
                responseHeader.AppendLine(Properties.Resources.T_RESPONSE_RECEIVE_HEADER);
                foreach (string key in response.Headers.AllKeys)
                {
                    responseHeader.AppendLine(string.Format(
                    Properties.Resources.T_DICTIONARY_KEY_VALUE,
                    key, response.Headers[key]));
                }
                _log.Trace(responseHeader.ToString().Trim());

                // HTTPボディ
                StringBuilder responseBody = new StringBuilder();
                responseBody.AppendLine(Properties.Resources.T_RESPONSE_RECEIVE_BODY);
                using (Stream st = new MemoryStream())
                {
                    receiveXml.Save(st);
                    st.Position = 0;
                    StreamReader reader = new StreamReader(st);
                    responseBody.Append(reader.ReadToEnd());
                }
                _log.Trace(responseBody.ToString());
            }

            // サーバで業務エラーが発生していれば、ServerExceptionとする
            string errorType = response.Headers[ERROR_TYPE];
            if (errorType != null)
            {
                // サーバで発生した例外の内容をServerExceptionにラップして再スロー
                ServerException exception = HandleErrorResult(errorType, receiveXml);
                if (_log.IsErrorEnabled)
                {
                    _log.Error(exception.Message, exception);
                }
                throw exception;
            }

            // XMLからDataSetに変換する
            TResult receiveDataSet = null;
            using (XmlReader xmlReader = new XmlNodeReader(receiveXml))
            {
                receiveDataSet = new TResult();
                receiveDataSet.ReadXml(xmlReader);
            }

            // 結果を CommunicationResult オブジェクトに格納する
            CommunicationResult communicationResult = CreateCommunicationResult(response, receiveDataSet);

            return communicationResult;
        }

        /// <summary>
        /// HTTP レスポンスと受信したデータを格納した<paramref name="receiveData"/>基に、
        /// 通信結果を表す <see cref="CommunicationResult"/> インスタンスを作成します。
        /// </summary>
        /// <remarks>
        /// <para>
        /// 通信結果を表す <see cref="CommunicationResult"/> を生成します。<see cref="CommunicationResult.ResultData"/>
        ///  プロパティに <paramref name="receiveData"/> を格納し、<see cref="CommunicationResult.ResponseHeaders"/>プロパティに
        /// HTTP レスポンスヘッダのキー/値を全て追加します。
        /// </para>
        /// <para>
        /// このメソッドは <c>null</c> 参照を返しません。
        /// </para>
        /// </remarks>
        /// <param name="response">受信処理を行った HTTP レスポンスオブジェクト。</param>
        /// <param name="receiveData">受信した XML データを格納したデータセット</param>
        /// <returns>通信結果を表す <see cref="CommunicationResult"/> インスタンス。</returns>
        protected virtual CommunicationResult CreateCommunicationResult(WebResponse response, TResult receiveData)
        {
            CommunicationResult result = new CommunicationResult();
            result.ResultData = receiveData;
            foreach (string key in response.Headers.Keys)
            {
                result.ResponseHeaders.Add(key, response.Headers[key]);
            }
            return result;
        }
    }
}
