using System;
using System.Security;
using System.Security.Cryptography.X509Certificates;
using System.Net;
using System.Net.Security;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CookComputing.XmlRpc;

namespace BTS.Trac
{
    /// <summary>
    ///オブジェクト
    /// </summary>
    public class Trac
    {
        /// <summary>
        /// XML-RPC I/F
        /// </summary>
        private static TracXmlRpcBase<ITrac> rpc = new TracXmlRpcBase<ITrac>();

        /// <summary>
        /// XML-Trac.rpc I/F
        /// </summary>
        public static ITrac Rpc
        {
            get
            {
                return rpc.Content;
            }
        }

        /// <summary>
        /// URL
        /// </summary>
        public static string Url
        {
            get;
            private set;
        }

        /// <summary>
        /// XML-RPC URL
        /// </summary>
        public static string XmlRpcUrl
        {
            get
            {
                return rpc.Url;
            }
        }

        /// <summary>
        /// ユーザー名
        /// </summary>
        public static string UserName
        {
            get
            {
                return rpc.UserName;
            }
        }

        /// <summary>
        /// パスワード
        /// </summary>
        public static string Password
        {
            get
            {
                return rpc.Password;
            }
        }

        /// <summary>
        /// 接続状態
        /// </summary>
        public static bool IsConnected
        {
            get;
            private set;
        }

        /// <summary>
        /// Trac の URL から XML-RPC の URL を取得
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        public static string GetXmlRpcUrl( string url )
        {
            // URL の取得。終端に'/' が入っていなければ付加し、XML-RPC アドレスを設定
            if ( url[url.Length - 1] != '/' ) {
                url += "/";
            }

            return url + "login/xmlrpc";
        }

        /// <summary>
        /// Trac へ接続
        /// </summary>
        /// <param name="url">URL</param>
        /// <param name="userName">ユーザー名</param>
        /// <param name="password">パスワード</param>
        public static void Connect( string url, string userName, string password )
        {
            rpc.Create( GetXmlRpcUrl( url ), userName, password );

            // 接続確認
            string version = TracXmlRpcSystem.GetAPIVersion();

            Url = url;

            IsConnected = true;
        }

        /// <summary>
        /// 接続テスト
        /// </summary>
        /// <param name="url">URL</param>
        /// <param name="userName">ユーザー名</param>
        /// <param name="password">パスワード</param>
        public static bool TryConnect( string url, string userName, string password )
        {
            bool result = true;
            string currentUrl = Url;
            string currentUserName = rpc.UserName;
            string currentPassword = rpc.Password;

            try {
                // 接続テスト
                Connect( url, userName, password );
            }
            catch {
                // 例外が出たら失敗
                result = false;
            }
            finally {
                // 元のURLがあれば戻す
                if ( !string.IsNullOrEmpty( currentUrl ) ) {
                    Connect( currentUrl, currentUserName, currentPassword );
                }
                // 元のURLがなければ閉じる
                else {
                    Close();
                }
            }

            return result;
        }

        /// <summary>
        /// Trac I/F の終了
        /// </summary>
        public static void Close()
        {
            Url = null;
            rpc.Close();

            IsConnected = false;
        }
    }


    /// <summary>
    ///XML-Trac.rpc インタフェース
    /// </summary>
    public interface ITrac : IXmlRpcProxy
    {
        #region system
        /// <summary>
        /// サポートするメソッドリストを取得する
        /// </summary>
        /// <returns>メソッドリスト</returns>
        [XmlRpcMethod( "system.listMethods" )]
        string[] systemListMethods();

        /// <summary>
        /// 指定されたメソッドのヘルプを取得する
        /// </summary>
        /// <param name="method">メソッド名</param>
        /// <returns>メソッドのヘルプ</returns>
        [XmlRpcMethod( "system.methodHelp" )]
        string systemMethodHelp( string method );

        /// <summary>
        /// 指定されたメソッドのシグネチャを取得する
        /// </summary>
        /// <param name="method">メソッド名</param>
        /// <returns>シグネチャ</returns>
        [XmlRpcMethod( "system.methodSignature" )]
        string[] systemMethodSignature( string method );

        /// <summary>
        /// API バージョンを取得する
        /// </summary>
        /// <returns>API バージョン番号</returns>
        [XmlRpcMethod( "system.getAPIVersion" )]
        int[] systemGetAPIVersion();

        //[XmlRpcMethod( "system.multicall" )]
        //object systemMulticall( array signatures );
        #endregion

        #region ticket
        /// <summary>
        /// 指定されたクエリのチケットIDを取得する
        /// </summary>
        /// <param name="qstr">クエリ</param>
        /// <returns>クエリに合致するチケットID一覧</returns>
        [XmlRpcMethod( "ticket.query" )]
        int[] ticketQuery( string qstr );

        /// <summary>
        /// 指定日以降に変更されたチケットIDを取得するする
        /// </summary>
        /// <param name="since">取得開始日</param>
        /// <returns>チケットID一覧</returns>
        [XmlRpcMethod( "ticket.getRecentChanges" )]
        int[] ticketGetRecentChanges( DateTime since );

        /// <summary>
        /// 指定されたチケットが遷移できるワークフローの状態を取得する
        /// </summary>
        /// <param name="id">チケットID</param>
        /// <returns>遷移できる状態</returns>
        [XmlRpcMethod( "ticket.getAvailableActions" )]
        string[] ticketGetAvailableActions( int id );

        /// <summary>
        /// 指定されたチケットの詳細を取得する
        /// </summary>
        /// <param name="id">チケットID</param>
        /// <returns>チケットの詳細</returns>
        [XmlRpcMethod( "ticket.get" )]
        object[] ticketGet( int id );

        /// <summary>
        /// チケットを作成する
        /// </summary>
        /// <param name="summary">概要</param>
        /// <param name="description">説明</param>
        /// <param name="attributes">チケットの詳細</param>
        /// <returns>作成されたチケットのID</returns>
        [XmlRpcMethod( "ticket.create" )]
        int ticketCreate( string summary, string description, XmlRpcStruct attributes );

        /// <summary>
        /// 指定されたチケットを更新する
        /// </summary>
        /// <param name="id">チケットID</param>
        /// <param name="comment">更新コメント</param>
        /// <param name="attributes">チケットの詳細</param>
        /// <returns>チケットの詳細（ticket.get と同じ）</returns>
        [XmlRpcMethod( "ticket.update" )]
        object[] ticketUpdate( int id, string comment, XmlRpcStruct attributes );

        /// <summary>
        /// 指定されたチケットを削除する
        /// </summary>
        /// <param name="id">削除するチケットID</param>
        [XmlRpcMethod( "ticket.delete" )]
        void ticketDelete( int id );

        /// <summary>
        /// 指定されたチケットの更新履歴を取得する
        /// </summary>
        /// <param name="id">チケットID</param>
        /// <returns>更新履歴</returns>
        [XmlRpcMethod( "ticket.changeLog" )]
        object[] ticketChangeLog( int id );

        /// <summary>
        /// チケットの添付ファイルリストを取得する
        /// </summary>
        /// <param name="ticket">チケット番号</param>
        /// <returns>添付ファイルリスト</returns>
        [XmlRpcMethod( "ticket.listAttachments" )]
        object[] ticketListAttachments( int ticket );

        /// <summary>
        /// チケットの添付ファイルを取得する
        /// </summary>
        /// <param name="ticket">チケット番号</param>
        /// <param name="filename">添付ファイル名</param>
        /// <returns>バイト列</returns>
        [XmlRpcMethod( "ticket.getAttachment" )]
        byte[] ticketGetAttachment( int ticket, string filename );

        /// <summary>
        /// 添付ファイルを登録する
        /// </summary>
        /// <param name="ticket">チケット番号</param>
        /// <param name="filename">添付ファイル名</param>
        /// <param name="description">詳細</param>
        /// <param name="data">バイト列</param>
        /// <param name="replace">上書き設定</param>
        [XmlRpcMethod( "ticket.putAttachment" )]
        void ticketPutAttachment( int ticket, string filename, string description, byte[] data, bool replace );

        /// <summary>
        /// 添付ファイルを削除する
        /// </summary>
        /// <param name="ticket">チケット番号</param>
        /// <param name="filename">ファイル名</param>
        /// <returns>処理結果</returns>
        [XmlRpcMethod( "ticket.deleteAttachment" )]
        bool ticketDeleteAttachment( int ticket, string filename );

        /// <summary>
        /// チケットの有効なフィールドを取得する
        /// </summary>
        /// <returns>フィールドの一覧</returns>
        [XmlRpcMethod( "ticket.getTicketFields" )]
        XmlRpcStruct[] ticketGetTicketFields();
        #endregion


        #region wiki
        [XmlRpcMethod( "wiki.getRecentChanges" )]
        XmlRpcStruct wikiGetRecentChanges( DateTime since );

        /// <summary>
        /// RPC バージョンを取得する
        /// </summary>
        /// <returns>RPC バージョン番号</returns>
        [XmlRpcMethod( "wiki.getRPCVersionSupported" )]
        int wikiGetRpcVersionSupported();

        /// <summary>
        /// 指定されたページの内容を Wiki 記法で取得する
        /// </summary>
        /// <param name="pagename">ページ名称</param>
        /// <returns>ページの内容</returns>
        [XmlRpcMethod( "wiki.getPage" )]
        string wikiGetPage( string pagename );

        // getPage と同じ？
        //[XmlRpcMethod( "wiki.getPageVersion" )]
        //string wikiGetPageVersion( string pagename );

        /// <summary>
        /// 指定されたページの内容を HTML で取得
        /// </summary>
        /// <param name="pagename">ページ名称</param>
        /// <returns></returns>
        [XmlRpcMethod( "wiki.getPageHTML" )]
        string wikiGetPageHTML( string pagename );

        //[XmlRpcMethod( "wiki.getPageHTMLVersion" )]
        //string wikiGetPageHTMLVersion( string pagename );

        /// <summary>
        /// すべてのページ名を取得
        /// </summary>
        /// <returns>ページ名の一覧</returns>
        [XmlRpcMethod( "wiki.getAllPages" )]
        string[] wikiGetAllPages();

        /// <summary>
        /// 指定されたページの情報を取得する
        /// </summary>
        /// <param name="pagename">取得するページの名称</param>
        [XmlRpcMethod( "wiki.getPageInfo" )]
        XmlRpcStruct wikiGetPageInfo( string pagename );

        // wiki.getPageInfo と同じ？
        //[XmlRpcMethod( "wiki.getPageInfoVersion" )]
        //XmlRpcStruct wikiGetPageInfoVersion( string pagename );

        /// <summary>
        /// 新しいページを作成/更新する
        /// </summary>
        /// <param name="pagename">ページ名称</param>
        /// <param name="content">コンテンツ</param>
        /// <param name="attributes">属性</param>
        /// <returns>処理結果</returns>
        [XmlRpcMethod( "wiki.putPage" )]
        bool wikiPutPage( string pagename, string content, XmlRpcStruct attributes );

        /// <summary>
        /// 指定されたページを削除する
        /// </summary>
        /// <param name="name">削除するページの名称</param>
        /// <returns>処理結果</returns>
        [XmlRpcMethod( "wiki.deletePage" )]
        bool wikiDeletePage( string name );

        /// <summary>
        /// 添付ファイルのパス一覧を取得する
        /// </summary>
        /// <param name="pagename">ページ名称</param>
        /// <returns>添付ファイルのパス一覧</returns>
        [XmlRpcMethod( "wiki.listAttachments" )]
        string[] wikiListAttachments( string pagename );

        /// <summary>
        /// 指定されたパスの添付ファイルを取得する
        /// </summary>
        /// <param name="path">取得するパスの名称</param>
        /// <returns>バイト列</returns>
        [XmlRpcMethod( "wiki.getAttachment" )]
        byte[] wikiGetAttachment( string path );

        /// <summary>
        /// 指定されたパスの添付ファイルを更新する
        /// </summary>
        /// <param name="path">更新するパス</param>
        /// <param name="data">バイト列</param>
        /// <returns>処理結果</returns>
        [XmlRpcMethod( "wiki.putAttachment" )]
        bool wikiPutAttachment( string path, byte[] data );

        /// <summary>
        /// 指定されたページに添付ファイルを設定する
        /// </summary>
        /// <param name="pagename">添付するページ</param>
        /// <param name="filename">添付するファイル名</param>
        /// <param name="description">説明</param>
        /// <param name="data">バイト列</param>
        /// <returns>ファイル名称</returns>
        [XmlRpcMethod( "wiki.putAttachmentEx" )]
        string wikiPutAttachmentEx( string pagename, string filename, string description, byte[] data );

        /// <summary>
        /// 指定されたページの添付ファイルを削除する
        /// </summary>
        /// <param name="path">削除するパス</param>
        /// <returns>処理結果</returns>
        [XmlRpcMethod( "wiki.deleteAttachment" )]
        bool wikiDeleteAttachment( string path );

        // Not implemented
        //[XmlRpcMethod( "wiki.listLinks" )]
        //object wikiListLinks( string pagename );

        /// <summary>
        /// Wiki 記法のページを HTML に変換する
        /// </summary>
        /// <param name="text">変換する Wiki 記法のテキスト</param>
        /// <returns>HTML のテキスト</returns>
        /// <note>GetPageHTML の HTML とは違った</note>
        [XmlRpcMethod( "wiki.wikiToHtml" )]
        string wikiWikiToHtml( string text );
        #endregion


        #region search
        [XmlRpcMethod( "search.getSearchFilters" )]
        object searchGetSearchFilters();

        [XmlRpcMethod( "search.performSearch" )]
        object searchPerformSearch( string query, object filters );

        [XmlRpcMethod( "search.performSearch" )]
        object searchPerformSearch( string query );
        #endregion


        #region ticket.milestone
        /// <summary>
        /// マイルストーン名称一覧を取得する
        /// </summary>
        /// <returns>マイルストーン名称一覧</returns>
        [XmlRpcMethod( "ticket.milestone.getAll" )]
        string[] ticketMilestoneGetAll();

        /// <summary>
        /// マイルストーン情報を取得する
        /// </summary>
        /// <param name="name">マイルストーン名称</param>
        /// <returns>マイルストーン情報</returns>
        [XmlRpcMethod( "ticket.milestone.get" )]
        XmlRpcStruct ticketMilestoneGet( string name );

        /// <summary>
        /// マイルストーンを削除する
        /// </summary>
        /// <param name="name">マイルストーン名称</param>
        /// <returns>処理結果</returns>
        [XmlRpcMethod( "ticket.milestone.delete" )]
        int ticketMilestoneDelete( string name );

        /// <summary>
        /// マイルストーンの作成
        /// </summary>
        /// <param name="name">マイルストーン名称</param>
        /// <param name="attributes">マイルストーン名称</param>
        /// <returns>処理結果</returns>
        [XmlRpcMethod( "ticket.milestone.create" )]
        int ticketMilestoneCreate( string name, XmlRpcStruct attributes );

        /// <summary>
        /// マイルストーンの更新
        /// </summary>
        /// <param name="name">マイルストーン名称</param>
        /// <param name="attributes">更新情報</param>
        /// <returns></returns>
        [XmlRpcMethod( "ticket.milestone.update" )]
        int ticketMilestoneUpdate( string name, XmlRpcStruct attributes );
        #endregion

        #region ticket.component
        /// <summary>
        /// コンポーネント名称の一覧を取得する
        /// </summary>
        /// <returns>コンポーネント名称一覧</returns>
        [XmlRpcMethod( "ticket.component.getAll" )]
        string[] ticketComponentGetAll();

        /// <summary>
        /// 指定されたコンポーネントの詳細を取得する
        /// </summary>
        /// <param name="name">取得するコンポーネント名称</param>
        /// <returns>コンポーネントの詳細</returns>
        [XmlRpcMethod( "ticket.component.get" )]
        XmlRpcStruct ticketComponentGet( string name );

        /// <summary>
        /// 指定されたコンポーネントを削除する
        /// </summary>
        /// <param name="name">削除するコンポーネント名称</param>
        /// <returns>処理結果</returns>
        [XmlRpcMethod( "ticket.component.delete" )]
        int ticketComponentDelete( string name );

        /// <summary>
        /// 新しいコンポーネントを作成する
        /// </summary>
        /// <param name="name">新しく作成するコンポーネント名称</param>
        /// <param name="attributes">作成する詳細情報</param>
        /// <returns>処理結果</returns>
        [XmlRpcMethod( "ticket.component.create" )]
        int ticketComponentCreate( string name, XmlRpcStruct attributes );

        /// <summary>
        /// 指定されたコンポーネントを更新する
        /// </summary>
        /// <param name="name">更新するコンポーネント名称</param>
        /// <param name="attributes">更新する詳細情報</param>
        /// <returns></returns>
        [XmlRpcMethod( "ticket.component.update" )]
        int ticketComponentUpdate( string name, XmlRpcStruct attributes );
        #endregion


        #region ticket.version
        /// <summary>
        /// バージョン名称の一覧を取得する
        /// </summary>
        /// <returns>バージョン名称の一覧</returns>
        [XmlRpcMethod( "ticket.version.getAll" )]
        string[] ticketVersionGetAll();

        /// <summary>
        /// 指定されたバージョンの詳細を取得する
        /// </summary>
        /// <param name="name">詳細を取得するバージョン名称</param>
        /// <returns>詳細情報</returns>
        [XmlRpcMethod( "ticket.version.get" )]
        XmlRpcStruct ticketVersionGet( string name );

        /// <summary>
        /// 指定されたバージョンを削除する
        /// </summary>
        /// <param name="name">削除するバージョンの名称</param>
        /// <returns>処理結果</returns>
        [XmlRpcMethod( "ticket.version.delete" )]
        int ticketVersionDelete( string name );

        /// <summary>
        /// 新しいバージョン情報を作成する
        /// </summary>
        /// <param name="name">作成するバージョンの名称</param>
        /// <param name="attributes">作成するバージョンの詳細情報</param>
        /// <returns>処理結果</returns>
        [XmlRpcMethod( "ticket.version.create" )]
        int ticketVersionCreate( string name, XmlRpcStruct attributes );

        /// <summary>
        /// 指定されたバージョンを更新する
        /// </summary>
        /// <param name="name">更新するバージョンの名称</param>
        /// <param name="attributes">更新するバージョンの詳細情報</param>
        /// <returns>処理結果</returns>
        [XmlRpcMethod( "ticket.version.update" )]
        int ticketVersionUpdate( string name, XmlRpcStruct attributes );
        #endregion
    }
}
