using System;
using System.Windows.Forms;
using System.Collections;
using Microsoft.VisualStudio.Tools.Applications.Runtime;
using Excel = Microsoft.Office.Interop.Excel;
using Office = Microsoft.Office.Core;
using CookComputing.XmlRpc;
using TracProxy;
using TracLoginInformation;
using ImageConverter;


namespace ExcelTracAddIn
{
    public partial class ThisAddIn
    {
        private const string commandBarName = "TracTicketImpotTemplete";

        private Office.CommandBarButton btnCreateNewWookbook = null;
        //private Office.CommandBarButton btnOption = null;
        private Office.CommandBarButton btnDownload = null;
        private Office.CommandBarButton btnUpload = null;
        private Office.CommandBar menubar = null;

        private delegate string[] GetAllitem();

        private Excel.Worksheet TracData = null;
        private Excel.Worksheet ImportData = null;

        private string url;
        private string user;
        private string password;

        TracProxy.TicketManager trac = null;
        XmlRpcStruct[] FieldNames = null;

        bool enableid;

        /// <summary>
        /// アドインが開始されるときに呼ばれる
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ThisAddIn_Startup( object sender, System.EventArgs e )
        {
            #region VSTO で生成されたコード

            this.Application = (Excel.Application)Microsoft.Office.Tools.Excel.ExcelLocale1033Proxy.Wrap( typeof( Excel.Application ), this.Application );

            #endregion

            try {
                // メニューバーを追加
                menubar = Application.CommandBars.Add( commandBarName, missing, missing, false );
                menubar.Visible = true;

                // コマンドバーを上部にドッキングさせる
                Application.CommandBars[commandBarName].Position = Microsoft.Office.Core.MsoBarPosition.msoBarTop;

                // 作成ボタン
                btnCreateNewWookbook = (Office.CommandBarButton)menubar.Controls.Add( 1, missing, missing, missing, missing );
                btnCreateNewWookbook.Style = Office.MsoButtonStyle.msoButtonIcon;
                btnCreateNewWookbook.FaceId = 1;
                btnCreateNewWookbook.Tag = "ExcelTracAddIn1";
                btnCreateNewWookbook.Picture = ImageConverter.ImageConverter.getImage( Properties.Resources.file_add );
                btnCreateNewWookbook.TooltipText = "雛形を作成";
                btnCreateNewWookbook.Click += new Office._CommandBarButtonEvents_ClickEventHandler( CommandBarButton_CreateClick );


                // 設定ボタン
                //btnOption = (Office.CommandBarButton)menubar.Controls.Add( 1, missing, missing, missing, missing );
                //btnOption.Style = Office.MsoButtonStyle.msoButtonIcon;
                //btnOption.FaceId = 2;
                //btnOption.Tag = "ExcelTracAddIn2";
                //btnOption.Picture = getImage( Properties.Resources.options );
                //btnOption.TooltipText = "設定";
                //btnOption.Click += new Office._CommandBarButtonEvents_ClickEventHandler( CommandBarButton_OptionClick );


                // ダウンロードボタン
                btnDownload = (Office.CommandBarButton)menubar.Controls.Add( 1, missing, missing, missing, missing );
                btnDownload.Style = Office.MsoButtonStyle.msoButtonIcon;
                btnDownload.FaceId = 3;
                btnDownload.Tag = "ExcelTracAddIn3";
                btnDownload.Picture = ImageConverter.ImageConverter.getImage( Properties.Resources.download );
                btnDownload.TooltipText = "チケットの一覧を Trac から取得";
                btnDownload.Click += new Office._CommandBarButtonEvents_ClickEventHandler( CommandBarButton_DownloadClick );

                // アップロードボタン
                btnUpload = (Office.CommandBarButton)menubar.Controls.Add( 1, missing, missing, missing, missing );
                btnUpload.Style = Office.MsoButtonStyle.msoButtonIcon;
                btnUpload.FaceId = 4;
                btnUpload.Tag = "ExcelTracAddIn4";
                btnUpload.Picture = ImageConverter.ImageConverter.getImage( Properties.Resources.upload );
                btnUpload.TooltipText = "チケットの一覧を Trac へ送信";
                btnUpload.Click += new Office._CommandBarButtonEvents_ClickEventHandler( CommandBarButton_UploadClick );
            }
            catch ( Exception ex ) {
                MessageBox.Show( ex.Message );
            }
        }

        /// <summary>
        /// アドインが終了するときに呼ばれる
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ThisAddIn_Shutdown( object sender, System.EventArgs e )
        {
            // メニューバーを削除
            menubar.Delete();
            menubar = null;
        }

        /// <summary>
        /// 設定ボタン
        /// </summary>
        /// <param name="Ctrl"></param>
        /// <param name="CancelDefault"></param>
        void CommandBarButton_OptionClick( Office.CommandBarButton Ctrl, ref bool CancelDefault )
        {
        }

        /// <summary>
        /// ひな形を作成ボタン
        /// </summary>
        /// <param name="Ctrl"></param>
        /// <param name="CancelDefault"></param>
        void CommandBarButton_CreateClick( Office.CommandBarButton Ctrl, ref bool CancelDefault )
        {
            try {
                // サーバに接続
                bool ret = Login();
                if ( !ret ) {
                    return;
                }

                // 新規ワークブックを作成
                CreateNewWookbooks( false );
            }
            catch ( Exception ex ) {
                trac = null;
                MessageBox.Show( ex.Message );
            }
        }

        /// <summary>
        /// Tracからダウンロードボタン
        /// </summary>
        /// <param name="Ctrl"></param>
        /// <param name="CancelDefault"></param>
        void CommandBarButton_DownloadClick( Office.CommandBarButton Ctrl, ref bool CancelDefault )
        {
            try {
                // サーバに接続
                bool ret = Login();
                if ( !ret ) {
                    return;
                }

                // 新規ワークブックを作成し、チケット一覧を取得
                CreateNewWookbooks( true );
                DownloadTicket( "status!=closed" );
            }
            catch ( Exception ex ) {
                trac = null;
                MessageBox.Show( ex.Message );
            }
        }

        /// <summary>
        /// Tracへ送信ボタン
        /// </summary>
        /// <param name="Ctrl"></param>
        /// <param name="CancelDefault"></param>
        void CommandBarButton_UploadClick( Office.CommandBarButton Ctrl, ref bool CancelDefault )
        {
            try {
                // サーバに接続
                bool ret = Login();
                if ( !ret ) {
                    return;
                }

                // チケット一覧を Trac へ送信 - ID あり -> 更新
                if ( enableid ) {
                    UpdateTicket();
                }
                // チケット一覧を Trac へ送信 - ID なし -> 新規作成
                else {
                    CreateTicket();
                }
            }
            catch ( Exception ex ) {
                trac = null;
                MessageBox.Show( ex.Message );
            }
        }

        /// <summary>
        /// ログイン
        /// </summary>
        /// <returns></returns>
        private bool Login()
        {
            if ( trac == null ) {
                // サーバ情報の取得
                LoginInformation logicInfo = new LoginInformation();
                logicInfo.StartPosition = FormStartPosition.CenterParent;
                DialogResult ret = logicInfo.ShowDialog( null );
                if ( ret != DialogResult.OK ) {
                    return false;
                }

                user = logicInfo.UserName;
                password = logicInfo.Password;

                // URL の取得。終端に'/' が入っていなければ付加する
                url = logicInfo.ServerURL;
                if ( url[url.Length - 1] != '/' ) {
                    url += "/";
                }

                // Trac へのアクセス I/F を作成
                trac = new TracProxy.TicketManager();
                trac.Connect( url + "login/xmlrpc", user, password );
            }

            return true;
        }

        /// <summary>
        /// ひな形を作成
        /// </summary>
        /// <param name="enableid">IDを有効にするかどうか</param>
        private void CreateNewWookbooks( bool enableid )
        {
            // ワークブックの作成
            Excel.Workbook wb = this.Application.Workbooks.Add( Excel.XlWBATemplate.xlWBATWorksheet );
            wb.Sheets.Add( missing, missing, 1, Excel.XlWBATemplate.xlWBATWorksheet );


            TracData = (Excel.Worksheet)wb.Sheets[1];
            TracData.Name = "インポートデータ";
            TracData.Cells.Clear();

            ImportData = (Excel.Worksheet)wb.Sheets[2];
            ImportData.Name = "Tracデータ";
            ImportData.Cells.Clear();

            // 日付用ダブルクリックイベント設定
            TracData.BeforeDoubleClick += new Excel.DocEvents_BeforeDoubleClickEventHandler( Sheet1_BeforeDoubleClick );

            // Ticket i/F を作成
            TracProxy.ITicket ticket = trac.Ticket;
            object[] obj = ticket.GetTicketFields();
            FieldNames = new XmlRpcStruct[obj.Length];
            for ( int i = 0; i < FieldNames.Length; ++i ) {
                FieldNames[i] = (XmlRpcStruct)obj[i];
            }

            // ID のスペースを作成
            this.enableid = enableid;
            int index = 0;
            if ( enableid ) {
                index = 1;
                TracData.Cells[1, 1] = ImportData.Cells[1, 1] = TracProxy.TicketAttributes.Id;
            }

            for ( int i = 0; i < FieldNames.Length; ++i ) {
                XmlRpcStruct st = (XmlRpcStruct)FieldNames[i];

                // カラムは1から開始
                int columnBase = i + index;
                int column = columnBase + 1;

                // 表示名を設定
                string name = (string)st["name"];
                TracData.Cells[1, column] = ImportData.Cells[1, column] = name;

                // コメント設定
                Excel.Range cell = (Excel.Range)TracData.Cells[1, column];
                cell.AddComment( (string)st["label"] );

                // @@@ この条件でデフォルト入力設定
                //if ( name != "resolution" ) {
                //    XmlRpcStruct st2 = (XmlRpcStruct)components[name];
                //    if ( (string)st2["value"] != null && (string)st2["value"] != "" ) {
                //        MessageBox.Show( st["name"] + "," + st["label"] + "," + st["value"] );
                //    }
                //}

                // 表示名の要素を取得
                GetAllitem getAllItem = null;
                if ( name == TracProxy.TicketAttributes.Milestone ) {
                    getAllItem = new GetAllitem( ticket.GetAllMilestones );
                }
                else if ( name == TracProxy.TicketAttributes.Component ) {
                    getAllItem = new GetAllitem( ticket.GetAllComponents );
                }
                else if ( name == TracProxy.TicketAttributes.Priority ) {
                    getAllItem = new GetAllitem( ticket.GetAllPriorities );
                }
                else if ( name == TracProxy.TicketAttributes.Resolution ) {
                    getAllItem = new GetAllitem( ticket.GetAllResolutions );
                }
                else if ( name == TracProxy.TicketAttributes.Type ) {
                    getAllItem = new GetAllitem( ticket.GetAllTypes );
                }
                else if ( name == TracProxy.TicketAttributes.Version ) {
                    getAllItem = new GetAllitem( ticket.GetAllVersions );
                }
                else if ( name == TracProxy.TicketAttributes.Status ) {
                    getAllItem = new GetAllitem( ticket.GetAllStatus );
                }

                // 幅を文字数（半角？）で指定
                if ( name == TracProxy.TicketAttributes.Summary ) {
                    cell.ColumnWidth = 30;
                }
                else if ( name == TracProxy.TicketAttributes.Description ) {
                    cell.ColumnWidth = 50;
                }
                else if ( name == TracProxy.TicketAttributes.DueAssign ) {
                    cell.ColumnWidth = 12;
                }
                else if ( name == TracProxy.TicketAttributes.DueClose ) {
                    cell.ColumnWidth = 12;
                }

                // 入力規則の作成
                if ( getAllItem != null ) {
                    string[] items = getAllItem();
                    for ( int j = 0; j < items.Length; ++j ) {
                        ImportData.Cells[j + 2, column] = items[j];
                    }

                    // シートをまたぐ入力規則用に複数セルに名前をつける
                    string dataName = "データ" + columnBase.ToString();
                    ImportData.get_Range( ImportData.Cells[2, column], ImportData.Cells[(items.Length + 1).ToString(), column] ).Name = dataName;

                    // コンボボックスの作成
                    char cellName = (char)('A' + columnBase);
                    string start = cellName + "2";
                    string end = cellName + "100";

                    //// 別シートの一覧を元に入力規則を作る
                    TracData.get_Range( start, end ).Validation.Add( Excel.XlDVType.xlValidateList, Excel.XlDVAlertStyle.xlValidAlertStop,
                            Excel.XlFormatConditionOperator.xlEqual, "=" + dataName, 0 );
                }
            }
        }

        /// <summary>
        /// 指定されたカラムを探す
        /// </summary>
        /// <param name="columnName">探すカラム名</param>
        /// <returns>カラムのインデックス</returns>
        private int FindColumn( string columnName )
        {
            string name;

            int column = 1;
            do {
                name = (string)((Excel.Range)TracData.Cells[1, column]).Value2;
                if ( name == columnName ) {
                    break;
                }

                ++column;
            } while ( name != null );
            if ( name == null ) {
                throw new Exception( columnName + "が見つかりません" );
            }

            return column;
        }

        /// <summary>
        /// チケットのダウンロード
        /// </summary>
        /// <param name="quiery">チケット取得のためのクエリ</param>
        private void DownloadTicket( string quiery )
        {
            string name;

            // 概要を探す(列は1から開始)
            int column = FindColumn( TracProxy.TicketAttributes.Summary );

            // 概要の空白（挿入位置）を探す(行は2から開始)
            int raw = 2;
            while ( true ) {
                name = (string)((Excel.Range)TracData.Cells[raw, column]).Value2;
                if ( name == null ) {
                    break;
                }

                ++raw;
            }

            TracProxy.ITicket ticket = trac.Ticket;
            // アクティブなチケット一覧を取得して表示
            int[] query = ticket.Query( quiery );
            using ( ProgressDlg progress = new ProgressDlg( 0, query.Length ) ) {
                progress.Show();

                foreach ( int i in query ) {
                    ++progress.Value;
                    progress.Text = "Trac からダウンロード中 (" + progress.Value +"/" + progress.Maximum +")";

                    // チケットの詳細を取得して表示
                    object[] ret = ticket.GetTicket( i );

                    // ID を設定
                    if ( enableid ) {
                        Excel.Range cell = (Excel.Range)TracData.Cells[raw, FindColumn( TracProxy.TicketAttributes.Id )];
                        //cell.NumberFormatLocal = "@";
                        cell.set_Value( null, ret[0] );
                    }

                    // その他の項目を設定
                    XmlRpcStruct attributes = (XmlRpcStruct)ret[3];
                    foreach ( DictionaryEntry obj in attributes ) {
                        // セルの書式を"文字列"にし、取得した情報を設定
                        Excel.Range cell = (Excel.Range)TracData.Cells[raw, FindColumn( (string)obj.Key )];
                        cell.NumberFormatLocal = "@";
                        cell.set_Value( null, obj.Value );
                    }

                    // 次の行
                    ++raw;
                }
            }
        }

        /// <summary>
        /// チケットの新規登録
        /// </summary>
        /// <param name="cells">セル</param>
        /// <param name="raw">登録する行</param>
        private void CreateTicket( Excel.Range cells, int raw )
        {
            // 概要と説明を取得
            string summary = ((Excel.Range)TracData.Cells[raw, FindColumn( TracProxy.TicketAttributes.Summary )]).Text.ToString();
            string discription = ((Excel.Range)TracData.Cells[raw, FindColumn( TracProxy.TicketAttributes.Description )]).Text.ToString();

            // 概要と説明以外のフィールドを取得
            XmlRpcStruct attributes = new XmlRpcStruct();
            foreach ( XmlRpcStruct Field in FieldNames ) {
                string key = Field["name"].ToString();
                if ( (key != TracProxy.TicketAttributes.Summary) && (key != TracProxy.TicketAttributes.Description) ) {
                    attributes[key] = ((Excel.Range)TracData.Cells[raw, FindColumn( key )]).Text.ToString();
                }
            }

            // 登録
            trac.Ticket.Create( summary, discription, attributes );
        }

        /// <summary>
        /// チケットの更新
        /// </summary>
        /// <param name="cells">セル</param>
        /// <param name="raw">登録する行</param>
        private void UpdateTicket( Excel.Range cells, int raw, string comment )
        {
            // ID から現在登録されているチケットを取得
            int id = int.Parse( ((Excel.Range)TracData.Cells[raw, FindColumn( TracProxy.TicketAttributes.Id )]).Text.ToString() );
            XmlRpcStruct currentTicket = (XmlRpcStruct)trac.Ticket.GetTicket( id )[3];

            // ID以外のフィールドに対して変更があれば登録する
            XmlRpcStruct attributes = null;
            foreach ( XmlRpcStruct Field in FieldNames ) {
                // キーを取得
                string key = Field["name"].ToString();
                if ( key != TracProxy.TicketAttributes.Id ) {
                    // キーに対応する値を取得し、変更があれば設定属性として登録する
                    string value = ((Excel.Range)TracData.Cells[raw, FindColumn( key )]).Text.ToString();
                    if ( value != currentTicket[key].ToString() ) {
                        if ( attributes == null ) {
                            attributes = new XmlRpcStruct();
                        }

                        attributes[key] = value;
                    }
                }
            }

            // 更新
            if ( attributes != null ) {
                trac.Ticket.Update( id, comment, attributes );
            }
        }

        /// <summary>
        /// チケットの新規登録
        /// </summary>
        private void CreateTicket()
        {
            // 行のインデックスは2から（1originで1は項目名称）
            //  登録する個数を数える
            int count = 0;
            for ( int raw = 2; true; ++raw, ++count ) {
                // 概要が空白になるまで有効なチケットとみなす
                Excel.Range cell = (Excel.Range)TracData.Cells[raw, FindColumn( TracProxy.TicketAttributes.Summary )];
                if ( cell.Text.ToString() == "" ) {
                    break;
                }
            }

            // チケットを登録する
            using ( ProgressDlg progress = new ProgressDlg( 0, count ) ) {
                progress.Show();
                for ( int i = 0, raw = 2; i < count; ++i, ++raw ) {
                    ++progress.Value;
                    progress.Text = "Trac からへアップロード中 (" + progress.Value +"/" + progress.Maximum +")";

                    // チケットを新規に登録して、次のチケットへ
                    CreateTicket( TracData.Cells, raw );
                }
            }
        }

        /// <summary>
        /// チケットの更新登録
        /// </summary>
        private void UpdateTicket()
        {
            // 行のインデックスは2から（1originで1は項目名称）
            //  登録する個数を数える
            int count = 0;
            for ( int raw = 2; true; ++raw, ++count ) {
                // 概要が空白になるまで有効なチケットとみなす
                Excel.Range cell = (Excel.Range)TracData.Cells[raw, FindColumn( TracProxy.TicketAttributes.Summary )];
                if ( cell.Text.ToString() == "" ) {
                    break;
                }
            }

            // チケットを登録する
            using ( ProgressDlg progress = new ProgressDlg( 0, count ) ) {
                progress.Show();
                for ( int i = 0, raw = 2; i < count; ++i, ++raw ) {
                    ++progress.Value;
                    progress.Text = "Trac からへアップロード中 (" + progress.Value +"/" + progress.Maximum +")";

                    // ID が空のチケットは新規作成
                    Excel.Range cell = (Excel.Range)TracData.Cells[raw, FindColumn( TracProxy.TicketAttributes.Id )];
                    if ( cell.Text.ToString() == "" ) {
                        CreateTicket( TracData.Cells, raw );
                    }
                    // ID が設定されているチケットは更新
                    else {
                        UpdateTicket( TracData.Cells, raw, "ExcelTracAddIn から更新" );
                    }
                }
            }
        }

        /// <summary>
        /// セルのダブルクリックイベント
        /// </summary>
        /// <param name="Target"></param>
        /// <param name="Cancel"></param>
        private void Sheet1_BeforeDoubleClick( Excel.Range Target, ref bool Cancel )
        {
            int raw = Target.Row;
            int col = Target.Column;

            Excel.Range title = (Excel.Range)ImportData.Cells[1, Target.Column];
            // "開始日"と"終了日"の場合はカレンダーを表示する
            if ( (title.Text.ToString() == TracProxy.TicketAttributes.DueAssign) ||
                 (title.Text.ToString() == TracProxy.TicketAttributes.DueClose) ) {
                Calendar cal = new Calendar();
                cal.StartPosition = FormStartPosition.Manual;
                cal.Location = Cursor.Position;
                DialogResult ret = cal.ShowDialog( null );
                if ( ret == DialogResult.OK ) {
                    // セルの書式を"文字列"にし、選択された日付を設定
                    Target.NumberFormatLocal = "@";
                    Target.set_Value( null, cal.SelectDate.ToShortDateString() );
                }

                // セルの入力はさせたくないので、キャンセル扱いにする
                Cancel = true;
            }
        }

        #region VSTO で生成されたコード

        /// <summary>
        /// デザイナ サポートに必要なメソッドです。このメソッドの内容を
        /// コード エディタで変更しないでください。
        /// </summary>
        private void InternalStartup()
        {
            this.Startup += new System.EventHandler( ThisAddIn_Startup );
            this.Shutdown += new System.EventHandler( ThisAddIn_Shutdown );
        }

        #endregion
    }
}
