using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Xml;
using System.Windows.Forms;
using CookComputing.XmlRpc;

namespace BTS.Trac.Forms
{
    // カラム情報
    using ColumnParams = Dictionary<string, ListViewColumnParams.ListViewColumnParam>;

    public partial class TicketListView : ListView, IMessageFilter
    {
        private const string ColumnHeader_Check = "レ";
        private const string ColumnHeader_ID = "ID";

        private const int ColumnSize_Check = 30;
        private const int ColumnSize_ID = 50;

        /// <summary>
        /// チケットのフィールド一覧
        /// </summary>
        private static XmlRpcStruct[] TicketFields = null;

        /// <summary>
        /// チケット
        /// </summary>
        private static List<Ticket> Tickets = new List<Ticket>();

        /// <summary>
        /// カラム情報
        /// </summary>
        private static ListViewColumnParams columnParams = new ListViewColumnParams();
        private static List<TicketListView> instances = new List<TicketListView>();
        private static List<ColumnHeader> columnHeaders = new List<ColumnHeader>();

        /// <summary>
        /// コンストラクタ
        /// </summary>
        public TicketListView()
        {
            InitializeComponent();

            // 自分を登録
            instances.Add( this );

            InitColumnMenu();

            MouseDoubleClick += new System.Windows.Forms.MouseEventHandler( this.TicketListView_MouseDoubleClick );
            ItemCheck += new System.Windows.Forms.ItemCheckEventHandler( this.TicketListView_ItemCheck );
            MouseDown += new System.Windows.Forms.MouseEventHandler( this.TicketListView_MouseDown );
            VisibleChanged += new System.EventHandler( this.TicketListView_VisibleChanged );
            ColumnWidthChanged += new ColumnWidthChangedEventHandler( TicketListView_ColumnWidthChanged );
        }

        #region カラムの処理
        bool IsColumnWidthChanging = false;

        /// <summary>
        /// カラムのサイズが変更された
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void TicketListView_ColumnWidthChanged( object sender, ColumnWidthChangedEventArgs e )
        {
            // 変更中
            if ( IsColumnWidthChanging ) {
                return;
            }

            try {
                // 変更中にする
                IsColumnWidthChanging = true;

                // チェック（幅は変えられない）
                if ( e.ColumnIndex == 0 ) {
                    Columns[e.ColumnIndex].Width = ColumnSize_Check;
                }
                // ID（幅は変えられない）
                else if ( e.ColumnIndex == 1 ) {
                    Columns[e.ColumnIndex].Width = ColumnSize_ID;
                }
                // そのた
                else {
                    // 実際は"レ"、"ID"の後の２番目から始まる
                    int columnParamIndex = e.ColumnIndex - 2;

                    // チェックされてるものの columnParamIndex 番目のメニューを取得する
                    // メニューとカラムの並びは同期してる
                    int checkedCount = 0;
                    ToolStripMenuItem item = null;
                    for ( int i = 0; i < columnContextMenuStrip.Items.Count; ++i ) {
                        item = (ToolStripMenuItem)columnContextMenuStrip.Items[i];
                        if ( item.Checked ) {
                            if ( checkedCount == columnParamIndex ) {
                                break;
                            }

                            ++checkedCount;
                        }
                    }

                    if ( item != null ) {
                        columnParams.Params[item.Name].Width = Columns[e.ColumnIndex].Width;

                        // サイズの変更
                        foreach ( TicketListView view in instances ) {
                            foreach ( ColumnHeader header in view.Columns ) {
                                if ( header.Name == item.Name ) {
                                    header.Width = columnParams.Params[header.Name].Width;
                                }
                            }
                        }
                    }
                }
            }
            finally {
                // 変更お終い
                IsColumnWidthChanging = false;
            }
        }

        /// <summary>
        /// カラムの更新
        /// </summary>
        public static void UpdateTicketColumn()
        {
            TicketFields = Ticket.GetTicketFields();

            // カラム情報の取得
            if ( columnParams.Params.Count == 0 ) {
                foreach ( XmlRpcStruct field in TicketFields ) {
                    columnParams.Params.Add( field["name"].ToString(), new ListViewColumnParams.ListViewColumnParam() );
                }
            }

            // リストを更新する
            UpdateColumnMenu();
            UpdateColumnHeader();

            // チケットをすべて更新する
            foreach ( TicketListView view in instances ) {
                view.UpdateTicket();
            }
        }

        /// <summary>
        /// カラムヘッダの作成
        /// </summary>
        private static void UpdateColumnHeader()
        {
            columnHeaders.Clear();

            // 消去チェックとIDを作成
            columnHeaders.Add( new ColumnHeader()
            {
                Name = "Check",
                Text = ColumnHeader_Check,
                Width = ColumnSize_Check,
            } );
            columnHeaders.Add( new ColumnHeader()
            {
                Name = "ID",
                Text = ColumnHeader_ID,
                Width = ColumnSize_ID,
            } );

            // 共通項目の作成
            foreach ( XmlRpcStruct att in TicketFields ) {
                ToolStripItem[] items = columnContextMenuStrip.Items.Find( att["name"].ToString(), false );
                Debug.Assert( items.Length == 1 );
                ToolStripMenuItem item = (ToolStripMenuItem)items[0];
                if ( item.Checked ) {
                    columnHeaders.Add( new ColumnHeader()
                    {
                        Name = (string)att["name"],
                        Text = (string)att["label"],
                        Width = columnParams.Params[item.Name].Width,
                    } );
                }
            }

            // リストをすべて更新する
            foreach ( TicketListView view in instances ) {
                view.Columns.Clear();
                foreach ( ColumnHeader header in columnHeaders ) {
                    ColumnHeader clone = header.Clone() as ColumnHeader;
                    clone.Name = header.Name;
                    view.Columns.Add( clone );
                }
            }
        }

        /// <summary>
        /// カラムメニューの作成
        /// </summary>
        /// <param name="TicketFields"></param>
        private static void UpdateColumnMenu()
        {
            columnContextMenuStrip.Items.Clear();

            // 項目の作成
            foreach ( XmlRpcStruct att in TicketFields ) {
                ToolStripMenuItem item = new ToolStripMenuItem();
                item.Text = (string)att["label"];
                item.Name = (string)att["name"];
                item.Checked = columnParams.Params[item.Name].IsChecked;
                item.Click += new EventHandler( columnMenu_Click );

                columnContextMenuStrip.Items.Add( item );
            }
        }

        /// <summary>
        /// カラムメニューがクリックされた
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void columnMenu_Click( object sender, EventArgs e )
        {
            // チェック状態を反転してカラムヘッダーを再表示
            ToolStripMenuItem item = sender as ToolStripMenuItem;
            columnParams.Params[item.Name].IsChecked = item.Checked = !item.Checked;

            // 表示更新
            UpdateColumnHeader();

            foreach ( TicketListView view in instances ) {
                view.UpdateTicket();
            }
        }

        /// <summary>
        /// チケットの表示更新
        /// </summary>
        private void UpdateTicket()
        {
            Items.Clear();
            foreach ( Ticket ticket in Tickets ) {
                AddItem( ticket );
            }
        }

        /// <summary>
        /// プロジェクトの変更
        /// </summary>
        /// <param name="ProjectName"></param>
        public static void ChangeProject( string ProjectName )
        {
            columnParams.ProjectName = ProjectName;
            Tickets.Clear();

            // 表示更新
            UpdateTicketColumn();
        }
        #endregion

        /// <summary>
        /// チケットをリストビューに追加する
        /// </summary>
        /// <param name="ticket"></param>
        /// <returns></returns>
        public void Add( Ticket ticket )
        {
            // AddItem内で例外が起こったときのためにリストへの登録はあと
            AddItem( ticket );
            Tickets.Add( ticket );
        }

        /// <summary>
        /// アイテムの追加
        /// </summary>
        /// <param name="ticket"></param>
        /// <returns></returns>
        private void AddItem( Ticket ticket )
        {
            // チェックとIDを作成
            ListViewItem item = new ListViewItem( "" );
            item.SubItems.Add( ticket.ID.ToString() );

            // 残りはカラムの項目順に並べる
            for ( int i = 2; i < Columns.Count; ++i ) {
                item.SubItems.Add( ticket.GetField<string>( Columns[i].Name ) );
            }

            // 一行ごとに色を変える
            if ( (Items.Count & 1) == 0 ) {
                item.BackColor = Color.LightYellow;
            }

            Items.Add( item );
        }

        /// <summary>
        /// アイテムのクリア
        /// </summary>
        public new void Clear()
        {
            Items.Clear();
            Tickets.Clear();
        }

        #region リストビューのダブルクリックを制御して不正なチェックを抑止する
        bool IsTicketListDoubleClick = false;

        /// <summary>
        /// リストがダブルクリックされた
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void TicketListView_MouseDoubleClick( object sender, MouseEventArgs e )
        {
            IsTicketListDoubleClick = false;
        }

        /// <summary>
        /// アイテムがチェックされた
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void TicketListView_ItemCheck( object sender, ItemCheckEventArgs e )
        {
            // ダブルクリックならチェックさせない
            if ( IsTicketListDoubleClick ) {
                e.NewValue = e.NewValue == CheckState.Unchecked ? CheckState.Checked : CheckState.Unchecked;
            }
        }

        /// <summary>
        /// マウスがクリックされると呼ばれる
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void TicketListView_MouseDown( object sender, MouseEventArgs e )
        {
            if ( e.Clicks == 2 ) {
                IsTicketListDoubleClick = true;
            }
        }
        #endregion

        #region カラム変更処理 see http://witchnest.seesaa.net/article/51659294.html
        /// <summary>
        /// 右ボタンUPメッセージ
        /// </summary>
        private const int WM_RBUTTONUP = 0x0205;

        /// <summary>
        /// カラムのウィンドウハンドルを取得するコマンド
        /// </summary>
        private const int LVM_GETHEADER = 4127;

        /// <summary>
        /// カラムのハンドル
        /// </summary>
        private IntPtr columnHeaderHandle = new IntPtr();

        /// <summary>
        /// メニュー
        /// </summary>
        private static ContextMenuStrip columnContextMenuStrip = new ContextMenuStrip();

        /// <summary>
        /// SendMessage のインポート
        /// </summary>
        /// <param name="hWnd"></param>
        /// <param name="msg"></param>
        /// <param name="wp"></param>
        /// <param name="lp"></param>
        /// <returns></returns>
        [DllImport( "USER32.dll" )]
        private static extern IntPtr SendMessage( IntPtr hWnd, int msg, IntPtr wp, IntPtr lp );

        /// <summary>
        /// ClientToScreen のインポート
        /// </summary>
        /// <param name="hWnd"></param>
        /// <param name="pt"></param>
        /// <returns></returns>
        [DllImport( "USER32.dll" )]
        private static extern bool ClientToScreen( IntPtr hWnd, ref Point pt );

        /// <summary>
        /// カラムメニューの初期化
        /// </summary>
        private void InitColumnMenu()
        {
            // メッセージフィルタに登録
            Application.AddMessageFilter( this );
        }

        /// <summary>
        /// カラムメニューの解放
        /// </summary>
        private void ReleaseColumnMenu()
        {
            // メッセージフィルタから削除
            Application.RemoveMessageFilter( this ); 
        }

        /// <summary>
        /// ウィンドウメッセージのフィルタ
        /// </summary>
        /// <param name="m"></param>
        /// <returns></returns>
        public bool PreFilterMessage(ref Message m)
        {
            // マウスの右ボタンが押されて離されて、このイベントが起きたのはカラムの上だった
            if ( (m.Msg == WM_RBUTTONUP) && (m.HWnd == columnHeaderHandle) ) {
                // マウス座標はLParamに入っていて、下位16ビットがX座標・上位16ビットがY座標
                int lParam = m.LParam.ToInt32();
                Point point = new Point( lParam & 0xFFFF, (lParam >> 16) & 0xFFFF );

                // クライアント座標をスクリーン座標に変換できたら
                // コンテキストメニューをマウスの位置に表示させる
                if ( ClientToScreen( columnHeaderHandle, ref point ) ) {
                    columnContextMenuStrip.Show( point );
                }

                // このメッセージをフィルタリングして終了
                return true;
            }
        
            return false;
        }

        /// <summary>
        /// 表示状態の変更
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void TicketListView_VisibleChanged( object sender, EventArgs e )
        {
            // カラムのハンドルが取得されていなければ取得する
            if ( columnHeaderHandle.ToInt32() == 0 ) {
                columnHeaderHandle = SendMessage( Handle, LVM_GETHEADER, IntPtr.Zero, IntPtr.Zero );
            }
        }
        #endregion

        #region カラム情報の保存と復元
        class XML_TAG
        {
            public const string ROOT = "root";

                public const string COLUMN_HEADER = "column-header";
                    public const string PROJECT_NAME = "project-name";
                    public const string COLUMN = "column";
                        public const string NAME  = "name";
                        public const string CHECKED = "checked";
                        public const string WIDTH = "width";
        }

        /// <summary>
        /// カラム情報の保存
        /// </summary>
        /// <param name="path"></param>
        public static void Save( string path )
        {
            // XML として保存
            XmlDocument xml = new XmlDocument();
            xml.AppendChild( xml.CreateXmlDeclaration( "1.0", "utf-8", null ) );

            XmlNode root = xml.CreateElement( XML_TAG.ROOT );

            // プロジェクトごとのカラムを保存
            foreach ( KeyValuePair<string, ColumnParams> projectColumn in columnParams.Columns ) {
                XmlNode header = XmlHelper.AppendChild( xml, root, XML_TAG.COLUMN_HEADER );
                XmlHelper.AppendChild( xml, header, XML_TAG.PROJECT_NAME, projectColumn.Key );

                // カラムの情報の保存
                foreach ( KeyValuePair<string, ListViewColumnParams.ListViewColumnParam> column in projectColumn.Value ) {
                    XmlNode columnNode = XmlHelper.AppendChild( xml, header, XML_TAG.COLUMN );

                    XmlHelper.AppendChild( xml, columnNode, XML_TAG.NAME, column.Key );
                    XmlHelper.AppendChild( xml, columnNode, XML_TAG.CHECKED, column.Value.IsChecked.ToString() );
                    XmlHelper.AppendChild( xml, columnNode, XML_TAG.WIDTH, column.Value.Width.ToString() );
                }
            }

            xml.AppendChild( root );

            // 保存
            xml.Save( path );
        }

        /// <summary>
        /// カラム情報の復元
        /// </summary>
        /// <param name="path"></param>
        public static void Load( string path )
        {
            XmlDocument xml = new XmlDocument();
            xml.Load( path );

            columnParams.Columns.Clear();

            XmlNodeList headerNodes = xml.SelectNodes( "//" + XML_TAG.COLUMN_HEADER );
            foreach ( XmlNode node in headerNodes ) {
                ColumnParams Params = new ColumnParams();

                XmlNodeList columnNodes = xml.SelectNodes( ".//" + XML_TAG.COLUMN );
                foreach ( XmlNode columnNode in columnNodes ) {
                    ListViewColumnParams.ListViewColumnParam param = new ListViewColumnParams.ListViewColumnParam();
                    param.IsChecked = bool.Parse( columnNode.SelectNodes( "./" + XML_TAG.CHECKED )[0].InnerText );
                    param.Width = int.Parse( columnNode.SelectNodes( "./" + XML_TAG.WIDTH )[0].InnerText );

                    // カラムを追加
                    string name = columnNode.SelectNodes( "./" + XML_TAG.NAME )[0].InnerText;
                    Params.Add( name, param );
                }

                // プロジェクトごとのカラム情報を追加
                string projectName = node.SelectNodes( "./" + XML_TAG.PROJECT_NAME )[0].InnerText;
                columnParams.Columns.Add( projectName, Params );
            }
        }
        #endregion
    }

    /// <summary>
    /// カラム情報
    /// </summary>
    public class ListViewColumnParams
    {
        /// <summary>
        /// カラム情報
        /// </summary>
        public class ListViewColumnParam
        {
            public bool IsChecked = true;
            public int Width = 100;
        }


        /// <summary>
        /// プロジェクト名
        /// </summary>
        public string ProjectName;

        /// <summary>
        /// カラム情報
        /// </summary>
        public Dictionary<string, ColumnParams> Columns = new Dictionary<string, ColumnParams>();

        /// <summary>
        /// パラメータの取得
        /// </summary>
        public ColumnParams Params
        {
            get
            {
                ColumnParams value = new ColumnParams();
                bool ret = Columns.TryGetValue( ProjectName, out value );
                if ( !ret ) {
                    value = new ColumnParams();
                    Columns.Add( ProjectName, value );
                }

                return value;
            }
        }
    }
}
