//
// Redmine.cs - Utility class to access Redmine
//
// Copyright (C) 2009 Kaoru Nakamura
//  mailto:kaorun55@gmail.com
//  blogto:http://d.hatena.ne.jp/kaorun55/
//
// Licensed under the MIT License.
//
// 本プログラムは farend-redmine-tools を参考にしています
//  http://code.google.com/p/farend-redmine-tools/
//
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Text;
using CDO;
using ADODB;
using MSXML2;

namespace BTS.Redmine
{
    /// <summary>
    /// チケット
    /// </summary>
    public class Issue
    {
        public string Project = null;       // プロジェクト識別子
        public string Tracker = null;       // トラッカー
        public string Subject = null;       // 題名
        public string Priority = null;      // 優先度
        public string Category = null;      // カテゴリ
        public string Description  = null;  // 説明
        public string AttachFile = null;    // ファイル（フルパス）
    }

    /// <summary>
    /// Redmine へのリモート操作
    /// </summary>
    public class Redmine
    {
        private string RedmineUrl = null;       // Post する URL
        private string ApiKey = null;           // API キー
        private string FromAddress = null;      // 発起者メールアドレス

        public const string PostURL = "mail_handler";   // Post する URL のおしり

        /// <summary>
        /// エラーコード
        /// </summary>
        public enum ErrorCode : int
        {
            NOERROR = 201,              // 登録成功
            INVALID_KEY = 403,          // APIキーが誤っている
            UNPROCESSABLE_ENTITY = 422, // メッセージが不正(Projectキーワードがない、メールアドレスが誤っている等)
        }

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="RedmineUrl">RedmineのトップページURL</param>
        /// <param name="ApiKey">APIキー</param>
        /// <param name="FromAddress">発起者メールアドレス</param>
        public Redmine( string RedmineUrl, string ApiKey, string FromAddress )
        {
            this.ApiKey = ApiKey;
            this.FromAddress = FromAddress;

            // URLの終端が "mail_handler" でない場合は付け足す
            this.RedmineUrl = RedmineUrl;
            if ( RedmineUrl.Substring( RedmineUrl.Length - PostURL.Length, PostURL.Length ) != PostURL ) {
                // URLの終端に "/" がない場合はそれも付け足す
                if ( RedmineUrl.Substring( RedmineUrl.Length - 1, 1 ) != "/" ) {
                    this.RedmineUrl += "/";
                }

                this.RedmineUrl += PostURL;
            }
        }

        /// <summary>
        /// チケットの登録
        /// </summary>
        /// <param name="issue">チケット</param>
        public void PostIssue( Issue issue )
        {
            // プロジェクト識別子と題名がないと登録できない
            if ( issue.Project == null ) {
                throw new ArgumentException( "プロジェクト識別子が未設定です" );
            }

            if ( issue.Subject == null ) {
                throw new ArgumentException( "題名が未設定です" );
            }

            
            // CDO.Messageオブジェクトを使用してRFC822形式のデータを作成。
		    Configuration iConf = new Configuration();
            iConf.Load( CdoConfigSource.cdoDefaults, null );

            Message iMsg = new Message();
		    iMsg.Configuration = iConf;

		    iMsg.From = FromAddress;
            iMsg.Subject = issue.Subject;
            iMsg.TextBody = MakeBody( issue );

            IBodyPart iBodyPart = iMsg.TextBodyPart;
            iBodyPart.Charset = "utf-8";
		    iBodyPart.ContentTransferEncoding = "base64";
            iMsg.AddAttachment( issue.AttachFile, null, null );

            Stream stream = iMsg.GetStream();
		    string postdata = "key=" + ApiKey +
                              "&allow_override=tracker,category,priority&email=";
		    while ( true ) {
                string RawBodyPart = stream.ReadText( 1024 * 1024 );
			    if (RawBodyPart.Length == 0) {
				    break;
			    }

			    postdata += HttpUtility.UrlEncode( RawBodyPart );
		    }

		    // RedmineにPOST
            IXMLHTTPRequest xhr = new XMLHTTP30();
            xhr.open( "POST", RedmineUrl, false, null, null );
		    xhr.setRequestHeader( "Content-Type", "application/x-www-form-urlencoded" );
            xhr.send( postdata );

            // エラーの場合、エラーコードを文字列にして例外送出
            if ( xhr.status != (int)ErrorCode.NOERROR ) {
                throw new Exception( xhr.status.ToString() );
            }
        }

        /// <summary>
        /// Post する文字列を作成
        /// </summary>
        /// <param name="issue">チケット情報</param>
        /// <returns>Post する文字列</returns>
        private string MakeBody( Issue issue )
        {
            string body = "Project: " + issue.Project +"\r\n";
            if ( issue.Tracker != null ) {
                body += "Tracker: " + issue.Tracker + "\r\n";
            }

            if ( issue.Priority != null ) {
                body += "Priority: " + issue.Priority + "\r\n";
            }

            if ( issue.Category != null ) {
                body += "Category: " + issue.Category + "\r\n";
            }

            if ( issue.Description != null ) {
                body += issue.Description + "\r\n";
            }

            return body;
        }
    }
}
