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

namespace TERASOLUNA.Fw.Client.Communication
{
    /// <summary>
    /// XML 送信機能を提供するクラスです。
    /// </summary>
    public class DataSetXmlSender : SenderBase<DataSet>
    {
        /// <summary>
        /// <see cref="ILog"/> 実装クラスのインスタンスです。
        /// </summary>
        /// <remarks>
        /// ログ出力に利用します。
        /// </remarks>
        private static ILog _log = LogFactory.GetLogger(typeof(DataSetXmlSender));

        /// <summary>
        /// 送信時に利用する Content-Typeヘッダの値です。
        /// </summary>
        /// <remarks>
        /// この定数の値は "text/xml;charset=utf-8" です。
        /// </remarks>
        protected static readonly string CONTENT_TYPE = "text/xml;charset=utf-8";

        /// <summary>
        /// 送信時に XML データを書き込むモードです。
        /// </summary>
        private XmlWriteMode _writeMode = XmlWriteMode.IgnoreSchema;

        /// <summary>
        /// 送信時に XML データを書き込むモードを表す <see cref="XmlWriteMode"/> 値を取得または設定します。
        /// </summary>
        /// <remarks>
        /// このプロパティのデフォルト値は "IgnoreSchema" です。
        /// </remarks>
        public XmlWriteMode WriteMode
        {
            get
            {
                return _writeMode;
            }
            set
            {
                _writeMode = value;
            }
        }

        /// <summary>
        /// 送信時に利用する Content-Typeヘッダの値を取得します。
        /// </summary>
        /// <remarks>
        /// 常に "text/xml;charset=utf-8" を返します。
        /// </remarks>
        public override string ContentType
        {
            get
            {
                return CONTENT_TYPE;
            }
        }

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

        /// <summary>
        /// <paramref name="request"/> で渡された <see cref="HttpWebRequest"/> を用いて、
        /// <paramref name="paramData"/> と <paramref name="headerCollection"/> の情報を
        /// 送信します。
        /// </summary>
        /// <remarks>
        /// <para>
        /// 送信されるHTTP リクエストボディは <paramref name="paramData"/> の <see cref="DataSet.WriteXml(Stream)"/> 
        /// メソッドで書き出されるXMLデータです 
        /// </para>
        /// <para>
        /// HTTP リクエストヘッダには <paramref name="headerCollection"/> に格納されたキー/値の情報が全て追加されます。
        /// <paramref name="headerCollection"/> に格納するキー/値はそれぞれ HTTP リクエストヘッダのキー/値として利用
        /// 可能な形式を満足する必要があります。それらは規定は、RFC 2616 "request-header" で定義されています。
        /// </para>
        /// <para>
        /// XML データは <see cref="DataSet.WriteXml(Stream, XmlWriteMode)"/> メソッドを用いて書き込まれます。このとき、
        /// スキーマ情報を利用するかどうかは <see cref="WriteMode"/> プロパティの値によって決まります。
        /// </para>
        /// <para>
        /// <paramref name="reporter"/> が <c>null</c> 参照ではない場合、
        /// <see cref="SenderBase{TParam}.BufferSize"/> プロパティに設定されたバッファサイズ分のデータ（デフォルトは8 KB ）をサーバに送信する毎に、
        /// <see cref="IProgressChangeReporter.ReportProgressChanged"/> イベントを発生させます。
        /// </para>
        /// </remarks>
        /// <param name="request">送信処理を行うリクエストオブジェクト。</param>
        /// <param name="paramData">送信データを格納したオブジェクト。</param>
        /// <param name="headerCollection">送信時にHTTPヘッダとして利用するヘッダ文字列のコレクション。</param>
        /// <param name="reporter">進行状況通知を行う <see cref="IProgressChangeReporter"/> インスタンス。 </param> 
        /// <exception cref="WebException">
        /// 通信エラーが発生しました。または、リクエストがキャンセルされました。
        /// </exception>
        protected override void SendRequest(HttpWebRequest request,
                                            DataSet paramData,
                                            IDictionary<string, string> headerCollection,
                                            IProgressChangeReporter reporter)
        {
            byte[] buffer = new byte[BufferSize];
            using (Stream stream = new MemoryStream())
            {
                // DataSetのデータをXML形式でStreamに書き込む
                paramData.WriteXml(stream, WriteMode);

                stream.Position = 0;

                long sendSize = 0L;
                long totalSize = stream.Length;

                // ContentLengthの設定
                request.ContentLength = totalSize;
                int percentage = 0;
                using (Stream reqStream = request.GetRequestStream())
                {
                    // 送信情報のログ出力
                    if (_log.IsTraceEnabled)
                    {
                        // URL
                        _log.Trace(string.Format(
                            Properties.Resources.T_REQUEST_SEND_ADDRESS, request.Address));

                        // HTTPヘッダ
                        StringBuilder requestHeaders = new StringBuilder();
                        requestHeaders.AppendLine(Properties.Resources.T_REQUEST_SEND_HEADER);
                        foreach (string key in request.Headers.AllKeys)
                        {
                            requestHeaders.AppendLine(string.Format(
                            Properties.Resources.T_DICTIONARY_KEY_VALUE, key, request.Headers[key]));
                        }
                        _log.Trace(requestHeaders.ToString().Trim());

                        // HTTPボディ
                        StringBuilder requestBody = new StringBuilder();
                        requestBody.AppendLine(Properties.Resources.T_REQUEST_SEND_BODY);
                        requestBody.Append(paramData.GetXml());
                        _log.Trace(requestBody.ToString());
                    }

                    int readSize = 0;

                    // バッファサイズ分送信する毎にイベント通知を行う
                    readSize = stream.Read(buffer, 0, BufferSize);
                    while (readSize > 0)
                    {
                        reqStream.Write(buffer, 0, readSize);
                        sendSize += readSize;
                        if (reporter != null)
                        {
                            if (totalSize > 0)
                            {
                                percentage = CalcSendPercentage(sendSize, totalSize);
                            }
                            reporter.ReportProgressChanged(new ExecuteProgressChangedEventArgs(percentage));
                        }
                        readSize = stream.Read(buffer, 0, BufferSize);
                    }
                }
            }
        }
    }
}
