using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Text;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Windows.Forms;
using ChaKi.Common;
using ChaKi.Entity;
using ChaKi.Entity.Corpora;
using ChaKi.Entity.Search;
using ChaKi.GUICommon;
using ChaKi.Service.Database;
using ChaKi.Service.Export;
using ChaKi.ToolDialogs;
using Crownwood.DotNetMagic.Controls;
using System.Text;
using ChaKi.Entity.Readers;
using ChaKi.Service.Readers;
using ChaKi.Common.Wdigets;

namespace ChaKi.Panels.ConditionsPanes
{
    public partial class CorpusPane : UserControl
    {
        private SentenceSearchCondition m_Model;
        private List<Image> m_ImageList;
        private bool m_bSuppressUpdate;
        private ProgressDialog m_Dlg;
        private BackgroundWorker m_Worker;  // Export Corpusワーク
        private ManualResetEvent m_WaitDone;
        private bool m_CancelFlag;

        public List<Corpus> CorpusList
        {
            get
            {
                return m_Model.Corpora;
            }
        }

        public CorpusPane(SentenceSearchCondition model)
        {
            InitializeComponent();

            // 標準のListViewでSmallImageListを使用すると、XPで画質が悪くなるので、
            // DotNetMagicのTreeView(List Style)を使用する。
            treeControl1.SetTreeControlStyle(TreeControlStyles.List);
            treeControl1.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;

            m_ImageList = new List<Image>();
            using (Stream st = Assembly.GetExecutingAssembly().GetManifestResourceStream("ChaKi.Resources.SQLite_S.png"))
            {
                m_ImageList.Add(new Bitmap(st));
            }
            using (Stream st = Assembly.GetExecutingAssembly().GetManifestResourceStream("ChaKi.Resources.MySQL_S.png"))
            {
                m_ImageList.Add(new Bitmap(st));
            }
            using (Stream st = Assembly.GetExecutingAssembly().GetManifestResourceStream("ChaKi.Resources.MSSQL_S.png"))
            {
                m_ImageList.Add(new Bitmap(st));
            }

            m_Model = model;
            m_bSuppressUpdate = false;
            m_Model.OnModelChanged += new EventHandler(this.ModelChangedHandler);

            UpdateView();
        }
#if false
        public CorpusPane(IContainer container)
        {
            container.Add(this);
            InitializeComponent();

            m_Model = model;
            m_Model.OnModelChanged += this.ModelChangedHandler;

            UpdateView();
        }
#endif
        public void SetCondition(SentenceSearchCondition cond)
        {
            m_Model = cond;
            m_Model.OnModelChanged += new EventHandler(this.ModelChangedHandler);
            UpdateView();
        }

        public SentenceSearchCondition GetCondition()
        {
            return m_Model;
        }

        private void UpdateView()
        {
            treeControl1.SuspendLayout();
            treeControl1.Nodes.Clear();
            foreach (Corpus c in m_Model.Corpora)
            {
                string name = c.Name;
                Node node = new Node(name);
                if (c.DBParam.DBType.Equals("SQLite"))
                {
                    node.Image = m_ImageList[0];
                }
                else if (c.DBParam.DBType.Equals("MySQL"))
                {
                    node.Image = m_ImageList[1];
                }
                else if (c.DBParam.DBType.Equals("SQLServer"))
                {
                    node.Image = m_ImageList[2];
                }
                treeControl1.Nodes.Add(node);
                if (c == ChaKiModel.CurrentCorpus)
                {
                    m_bSuppressUpdate = true;   // AfterSelectイベント誘発による再帰的Updateを抑制
                    this.treeControl1.SelectedNode = node;
                    m_bSuppressUpdate = false;
                }
            }
            treeControl1.ResumeLayout();
        }

        public void ModelChangedHandler(object sender, EventArgs e)
        {
            UpdateView();
        }

        /// <summary>
        /// コーパスが選択状態にされたとき、ChaKiModel.CurrentCorpusを更新する.
        /// m_bSuppressUpdateがセットされていれば何も行わない.
        /// </summary>
        /// <param name="tc"></param>
        /// <param name="e"></param>
        private void treeControl1_AfterSelect(Crownwood.DotNetMagic.Controls.TreeControl tc, Crownwood.DotNetMagic.Controls.NodeEventArgs e)
        {
            if (m_bSuppressUpdate || e.Node == null)
            {
                return;
            }
            int index = e.Node.Index;
            if (index < 0 || index >= m_Model.Corpora.Count)
            {
                return;
            }
            ChaKiModel.CurrentCorpus = m_Model.Corpora[index];
        }

        /// <summary>
        /// Addボタンが押されたとき、新たなコーパスを一覧に追加する処理
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button1_Click(object sender, EventArgs e)
        {
            using (OpenFileDialog dlg = new OpenFileDialog())
            {
                dlg.Filter = "Database files (*.db;*.def;*.dblist)|*.db;*.def;*.dblist|SQLite database files (*.db)|*.db|Corpus definition files (*.def)|*.def|DB List (*.dblist)|*.dbs|All files (*.*)|*.*";
                dlg.Title = "Select Corpus to Open";
                dlg.CheckFileExists = true;
                dlg.Multiselect = true;
                if (dlg.ShowDialog() == DialogResult.OK)
                {
                    Cursor oldCursor = this.Cursor;
                    this.Cursor = Cursors.WaitCursor;
                    foreach (string path in dlg.FileNames)
                    {
                        if (Path.GetExtension(path).ToUpper() == ".DBLIST")
                        {
                            try
                            {
                                using (StreamReader rdr = new StreamReader(path))
                                {
                                    string s;
                                    while ((s = rdr.ReadLine()) != null)
                                    {
                                        LoadCorpus(s);
                                    }
                                }
                            }
                            catch (Exception ex)
                            {
                                ErrorReportDialog err = new ErrorReportDialog(string.Format("Cannot load corpus list file {0}", path), ex);
                                err.ShowDialog();
                            }
                        }
                        else
                        {
                            LoadCorpus(path);
                        }
                    }
                    this.Cursor = oldCursor;
                }
                if (m_Model.Corpora.Count > 0)
                {
                    ChaKiModel.CurrentCorpus = m_Model.Corpora[0];
                }
                else
                {
                    ChaKiModel.CurrentCorpus = null;
                }
            }
            UpdateView();
        }

        private void LoadCorpus(string path)
        {
            try
            {
                Corpus c = Corpus.CreateFromFile(path);
                // コーパスの基本情報をロードしておく
                DBService dbs = DBService.Create(c.DBParam);

                // TagSelectorの内容をLoadMandatoryCorpusInfo()のcallbackによって設定する
                TagSelector.ClearAllTags();

                dbs.LoadSchemaVersion(c);

                // Schemaチェック
                if (c.Schema.Version != CorpusSchema.CurrentVersion)
                {
                    DBSchemaConversion dlg = new DBSchemaConversion(dbs, c);
                    dlg.DoConversion();
                }

                dbs.LoadMandatoryCorpusInfo(c, (s, t) => { TagSelector.PreparedSelectors[s].AddTag(t); });

                m_Model.Corpora.Add(c);
            }
            catch (Exception ex)
            {
                ErrorReportDialog err = new ErrorReportDialog(string.Format("Cannot add corpus {0}", path), ex);
                err.ShowDialog();
            }
        }

        /// <summary>
        /// Delボタンが押されたとき、選択状態のコーパスを一覧から消す処理
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button2_Click(object sender, EventArgs e)
        {
            Node n = treeControl1.SelectedNode;
            if (n == null)
            {
                return;
            }
            int index = n.Index;
            if (index < 0 || index >= m_Model.Corpora.Count)
            {
                return;
            }
            try
            {
                m_Model.Corpora.RemoveAt(index);
            }
            catch (ArgumentOutOfRangeException)
            {
                MessageBox.Show("CorpusPane - Invalid Model");
            }
            UpdateView();
        }

        /// <summary>
        /// "Del All"
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button6_Click(object sender, EventArgs e)
        {
            try
            {
                m_Model.Corpora.Clear();
            }
            catch (ArgumentOutOfRangeException)
            {
                MessageBox.Show("CorpusPane - Invalid Model");
            }
            UpdateView();
        }


        /// <summary>
        /// 「↑」ボタンが押されたとき、選択状態にあるコーパスの優先度を上げる処理
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button4_Click(object sender, EventArgs e)
        {
            Node n = treeControl1.SelectedNode;
            if (n == null)
            {
                return;
            }
            int index = n.Index;
            if (index <= 0 || m_Model.Corpora.Count == 0 || index >= m_Model.Corpora.Count)
            {
                return;
            }
            Corpus c = m_Model.Corpora[index];
            m_Model.Corpora.RemoveAt(index);
            m_Model.Corpora.Insert(index - 1, c);
            UpdateView();
        }

        /// <summary>
        /// 「↓」ボタンが押されたとき、選択状態にあるコーパスの優先度を下げる処理
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button5_Click(object sender, EventArgs e)
        {
            Node n = treeControl1.SelectedNode;
            if (n == null)
            {
                return;
            }
            int index = n.Index;
            if (index < 0 || m_Model.Corpora.Count == 0 || index >= m_Model.Corpora.Count - 1)
            {
                return;
            }
            Corpus c = m_Model.Corpora[index];
            m_Model.Corpora.RemoveAt(index);
            m_Model.Corpora.Insert(index + 1, c);
            UpdateView();
        }

        /// <summary>
        /// Showボタンが押されたとき、選択状態にあるコーパスの詳細を表示する処理
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button3_Click(object sender, EventArgs e)
        {
            Node n = treeControl1.SelectedNode;
            if (n == null)
            {
                return;
            }
            int index = n.Index;
            if (index < 0 || index >= m_Model.Corpora.Count)
            {
                return;
            }

            // Corpus Information ダイアログを表示
            CorpusInfo cdlg = new CorpusInfo();
            MainForm.Instance.AddOwnedForm(cdlg);
            cdlg.Show();
            cdlg.LoadCorpusInfo(m_Model.Corpora[index]);
        }

        private void button7_Click(object sender, EventArgs e)
        {
            ExportCurrent();
        }

        /// <summary>
        /// 選択状態にあるコーパスをCabochaEx形式でエクスポート
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        public void ExportCurrent()
        {
            Node n = treeControl1.SelectedNode;
            if (n == null)
            {
                return;
            }
            int index = n.Index;
            if (index < 0 || index >= m_Model.Corpora.Count)
            {
                return;
            }
            Corpus crps = m_Model.Corpora[index];

            CorpusSourceReaderFactory factory = CorpusSourceReaderFactory.Instance;
            FileDialog dlg = new SaveFileDialog();
            StringBuilder filterStr = new StringBuilder();
            foreach (ReaderDef def in factory.ReaderDefs.ReaderDef)
            {
                filterStr.AppendFormat("{0}|*.cabocha|", def.Name.Replace('|', '-'));
            }
            dlg.Filter = filterStr.ToString().TrimEnd('|');
            dlg.CheckPathExists = true;
            dlg.Title = string.Format("Export \"{0}\" as CabochaEx", crps.Name);
            if (dlg.ShowDialog() == DialogResult.OK)
            {
                if (dlg.FilterIndex <= 0 || dlg.FilterIndex > factory.ReaderDefs.ReaderDef.Length)
                {
                    MessageBox.Show("Invalid ReaderDef index");
                    return;
                }
                ReaderDef def = factory.ReaderDefs.ReaderDef[dlg.FilterIndex-1];
                m_CancelFlag = false;
                m_WaitDone = new ManualResetEvent(false);
                m_Dlg = new ProgressDialog();
                m_Dlg.Text = "Querying...";
                m_Dlg.ProgressMax = 100;
                m_Dlg.ProgressReset();
                m_Dlg.WorkerCancelled += new EventHandler(OnCancelled);
                m_Worker = new BackgroundWorker();
                m_Worker.WorkerReportsProgress = true;
                m_Worker.DoWork += new DoWorkEventHandler(OnExportCorpus);
                m_Worker.ProgressChanged += new ProgressChangedEventHandler(OnWorkerProgressChanged);
                m_Worker.RunWorkerAsync(new object[] { dlg.FileName, crps, def });
                m_Dlg.ShowDialog();
                m_WaitDone.WaitOne();
                m_Dlg.Dispose();
                m_Worker.Dispose();
            }
        }

        void OnExportCorpus(object sender, DoWorkEventArgs e)
        {
            object[] args = (object[])e.Argument;
            string filename = (string)args[0];
            Corpus crps = (Corpus)args[1];
            ReaderDef def = (ReaderDef)args[2];
            try
            {
                using (TextWriter wr = new StreamWriter(filename, false))
                {
                    IExportService svc = new ExportServiceCabocha(wr, def);
                    svc.ExportCorpus(crps, ref m_CancelFlag, new Action<int>((int v) => { m_Worker.ReportProgress(v); }));
                }
            }
            catch (Exception ex)
            {
                ErrorReportDialog err = new ErrorReportDialog(string.Format("Cannot export corpus {0}", crps.Name), ex);
                err.ShowDialog();
            }
            finally
            {
                m_Dlg.DialogResult = DialogResult.Cancel;
                m_WaitDone.Set();
            }
        }

        void OnCancelled(object sender, EventArgs e)
        {
            m_CancelFlag = true;
        }

        void OnWorkerProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            m_Dlg.ProgressCount = e.ProgressPercentage;
        }
    }
}
