using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ChaKi.Entity.Corpora;
using ChaKi.Service.Database;
using ChaKi.Common;
using NHibernate;
using NHibernate.Criterion;

namespace ChaKi.Service.Lexicons
{
    public class LexemeEditService : ILexemeEditService
    {
        private Corpus m_Corpus;        // Lexemeの修正用にOpenするCorpus
        private OpContext m_Context;    // 修正操作のContext
        private Lexeme m_Target;        // 修正対象Lexeme

        private List<Corpus> m_Corpora;     //差分を取るためのCorpus(ReadOnly)
        private List<string> m_RefdicPaths; //差分を取るための参照辞書(ReadOnly)

        public LexemeEditService()
        {
            m_Context = null;
            m_Corpus = null;
            m_Target = null;
        }

        /// <summary>
        /// コーパスをupdateする処理について、開始前に排他チェックを行う.
        /// </summary>
        /// <param name="corpus"></param>
        /// <param name="lex"></param>
        /// <param name="callback"></param>
        public void Open(Corpus corpus, Lexeme lex, UnlockRequestCallback callback)
        {
            if (m_Context != null && m_Context.IsTransactionActive())
            {
                throw new InvalidOperationException("Active transaction exists. Close or Commit it first.");
            }

            m_Corpus = corpus;
            m_Corpora = new List<Corpus>() { m_Corpus };
            m_Target = lex;
            m_Context = OpContext.Create(m_Corpus.DBParam, callback, typeof(ILexemeEditService));
        }

        public void Close()
        {
            if (m_Context != null)
            {
                m_Context.Dispose();
                m_Context = null;
                m_Corpus = null;
            }
        }

        /// <summary>
        /// m_Corpus中のm_Lexemeの内容を修正する.
        /// m_Lexemeは必ず存在していること.
        /// Open/Close必要
        /// </summary>
        /// <param name="data"></param>
        public void Save(Dictionary<string, string> data)
        {
            if (m_Context == null || m_Target == null)
            {
                return;
            }
            bool changed = false;
            foreach (var pair in data)
            {
                if (pair.Key == Lexeme.PropertyName[LP.Reading])
                {
                    changed |= m_Target.ReplaceReading(pair.Value);
                }
                else if (pair.Key == Lexeme.PropertyName[LP.Pronunciation])
                {
                    changed |= m_Target.ReplacePronunciation(pair.Value);
                }
                else if (pair.Key == Lexeme.PropertyName[LP.LemmaForm])
                {
                    changed |= m_Target.ReplaceLemmaForm(pair.Value);
                }
                else if (pair.Key == Lexeme.PropertyName[LP.Lemma])
                {
                    changed |= m_Target.ReplaceLemma(pair.Value);
                }
                else if (pair.Key == Lexeme.PropertyName[LP.PartOfSpeech])
                {
                    if (m_Target.PartOfSpeech.Name != pair.Value)
                    {
                        changed = true;
                        var newval = FindPartOfSpeech(pair.Value, m_Context.Session);
                        if (newval == null)
                        {
                            newval = new PartOfSpeech(pair.Value);
                        }
                        m_Target.PartOfSpeech = newval;
                    }
                }
                else if (pair.Key == Lexeme.PropertyName[LP.CType])
                {
                    if (m_Target.CType.Name != pair.Value)
                    {
                        changed = true;
                        var newval = FindCType(pair.Value, m_Context.Session);
                        if (newval == null)
                        {
                            newval = new CType(pair.Value);
                        }
                        m_Target.CType = newval;
                    }
                }
                else if (pair.Key == Lexeme.PropertyName[LP.CForm])
                {
                    if (m_Target.CForm.Name != pair.Value)
                    {
                        changed = true;
                        var newval = FindCForm(pair.Value, m_Context.Session);
                        if (newval == null)
                        {
                            newval = new CForm(pair.Value);
                        }
                        m_Target.CForm = newval;
                    }
                }
                else if (pair.Key == "Custom")
                {
                    changed |= m_Target.ReplaceCustomProperty(pair.Value);
                }
            }
            if (changed)
            {
                m_Context.Session.SaveOrUpdate(m_Target.BaseLexeme);
                m_Context.Session.SaveOrUpdate(m_Target.PartOfSpeech);
                m_Context.Session.SaveOrUpdate(m_Target.CType);
                m_Context.Session.SaveOrUpdate(m_Target.CForm);
                m_Context.Session.SaveOrUpdate(m_Target);
                m_Context.Session.Flush();
                Commit();
            }
        }

        private void Commit()
        {
            if (!m_Context.IsTransactionActive())
            {
                throw new InvalidOperationException("No active transaction exists.");
            }

            // コミットする
            m_Context.Trans.Commit();
            m_Context.Trans.Dispose();
            m_Context.Trans = null;

            // 操作記録をScriptの形で得てstaticメンバにセットする.
            //@todo

        }

        // m_Corpus中の一致するPartOfSpeechを検索する。なければnullを返す。
        private PartOfSpeech FindPartOfSpeech(string name, ISession session)
        {
            if (name == null) name = string.Empty;
            return session.CreateQuery(string.Format("from PartOfSpeech p where p.Name='{0}'", name)).UniqueResult<PartOfSpeech>();
        }

        // m_Corpus中の一致するCFormを検索する。なければnullを返す。
        private CForm FindCForm(string name, ISession session)
        {
            if (name == null) name = string.Empty;
            return session.CreateQuery(string.Format("from CForm p where p.Name='{0}'", name)).UniqueResult<CForm>();
        }

        // m_Corpus中の一致するCTypeを検索する。なければnullを返す。
        private CType FindCType(string name, ISession session)
        {
            if (name == null) name = string.Empty;
            return session.CreateQuery(string.Format("from CType p where p.Name='{0}'", name)).UniqueResult<CType>();
        }

        /// <summary>
        /// corporaで与えられるコーパスの内部辞書の語のうち、
        /// どの参照辞書(refdicpaths)にも含まれないものをリストする.
        /// Open/Close不要
        /// </summary>
        /// <param name="corpora"></param>
        /// <param name="refdicpaths"></param>
        /// <returns></returns>
        public List<LexemeCorpusBoolTuple> ListLexemesNotInRefDic(List<Corpus> corpora, List<string> refdicpaths, IProgress progress)
        {
            m_Corpora = corpora;
            m_RefdicPaths = refdicpaths;

            List<LexemeCorpusBoolTuple> newlexemes = new List<LexemeCorpusBoolTuple>();
            List<ISession> refsessions = new List<ISession>();
            try
            {
                foreach (var refdicpath in refdicpaths)
                {
                    var dict = Dictionary.CreateFromFile(refdicpath);
                    var session = DBService.Create(dict.DBParam).GetConnection().BuildSessionFactory().OpenSession();
                    refsessions.Add(session);
                }

                // Count lexemes in all corpu
                long total = 0;
                if (progress != null)
                {
                    foreach (var crps in corpora)
                    {
                        DBService dbs = DBService.Create(crps.DBParam);
                        NHibernate.Cfg.Configuration cfg = dbs.GetConnection();
                        ISessionFactory factory = cfg.BuildSessionFactory();
                        using (ISession session = factory.OpenSession())
                        {
                            // ローカルLexiconのすべてのLexemeに対して、
                            var result = (long)session.CreateQuery("select count(*) from Lexeme").UniqueResult<long>();
                            total += result;
                        }
                    }
                    progress.ProgressMax = 100;
                    progress.ProgressCount = 0;
                }
                long count = 0;
                int last_percentage = -1;

                foreach (var crps in corpora)
                {
                    DBService dbs = DBService.Create(crps.DBParam);
                    NHibernate.Cfg.Configuration cfg = dbs.GetConnection();
                    ISessionFactory factory = cfg.BuildSessionFactory();
                    using (ISession session = factory.OpenSession())
                    {
                        // ローカルLexiconのすべてのLexemeに対して、
                        var result = session.CreateQuery("from Lexeme").List<Lexeme>();
                        foreach (var lex in result)
                        {
                            var found = false; // そのLexemeがReferenceに存在しているか.
                            foreach (var refsession in refsessions)
                            {
                                if (IsLexemeExists(lex, refsession))
                                {
                                    found = true;
                                    break;
                                }
                            }
                            if (!found)
                            {
                                newlexemes.Add(new LexemeCorpusBoolTuple(lex, crps, false));
                            }
                            count++;
                            if (progress != null)
                            {
                                var percentage = (int)(count * 100 / total);
                                progress.ProgressCount = percentage;
                                last_percentage = percentage;
                            }
                        }
                    }
                }
            }
            finally
            {
                foreach (var refdic in refsessions)
                {
                    refdic.Close();
                }
                progress.EndWork();
            }
            return newlexemes;
        }

        /// <summary>
        /// 参照用辞書を含め、全ての使用可能なPOS, CType, CFormタグのリストを得る.
        /// stringキーは辞書名（カレントコーパスについては"Default"という名称とする）.
        /// Open/Close不要.
        /// </summary>
        /// <param name="pos"></param>
        /// <param name="ctypes"></param>
        /// <param name="cforms"></param>
        public void GetLexiconTags(out Dictionary<string, IList<PartOfSpeech>> pos, out Dictionary<string, IList<CType>> ctypes, out Dictionary<string, IList<CForm>> cforms)
        {
            pos = new Dictionary<string, IList<PartOfSpeech>>();
            ctypes = new Dictionary<string, IList<CType>>();
            cforms = new Dictionary<string, IList<CForm>>();

            // コーパスのタグ
            foreach (var crps in m_Corpora)
            {
                DBService dbs = DBService.Create(crps.DBParam);
                NHibernate.Cfg.Configuration cfg = dbs.GetConnection();
                ISessionFactory factory = cfg.BuildSessionFactory();
                using (ISession session = factory.OpenSession())
                {
                    pos.Add(crps.Name, GetPOSList(session));
                    ctypes.Add(crps.Name, GetCTypeList(session));
                    cforms.Add(crps.Name, GetCFormList(session));
                }
            }

            // 参照用辞書のタグ
            if (m_RefdicPaths != null)
            {
                foreach (var refdicpath in m_RefdicPaths)
                {
                    var dict = Dictionary.CreateFromFile(refdicpath);
                    using (var session = DBService.Create(dict.DBParam).GetConnection().BuildSessionFactory().OpenSession())
                    {
                        pos.Add(dict.Name, GetPOSList(session));
                        ctypes.Add(dict.Name, GetCTypeList(session));
                        cforms.Add(dict.Name, GetCFormList(session));
                    }
                }
            }
        }

        private IList<PartOfSpeech> GetPOSList(ISession session)
        {
            return session.CreateQuery("from PartOfSpeech x order by x.Name asc").List<PartOfSpeech>();
        }
        private IList<CType> GetCTypeList(ISession session)
        {
            return session.CreateQuery("from CType x order by x.Name asc").List<CType>();
        }
        private IList<CForm> GetCFormList(ISession session)
        {
            return session.CreateQuery("from CForm x order by x.Name asc").List<CForm>();
        }


        /// <summary>
        /// listで与えられた語をuserdicpathで指定される辞書DBに追加する.
        /// 但し、既に一致する語が含まれている場合を除く.
        /// Open/Close不要
        /// </summary>
        /// <param name="list"></param>
        /// <param name="userdicpath"></param>
        public void AddToUserDictionary(List<LexemeCorpusBoolTuple> list, string userdicpath)
        {
            var dict = Dictionary.CreateFromFile(userdicpath);
            var conn = DBService.Create(dict.DBParam).GetConnection();
            using (var session = conn.BuildSessionFactory().OpenSession())
            {
                var trans = session.BeginTransaction();
                foreach (var lexbool in list)
                {
                    if (lexbool.Item3)
                    {
                        continue;
                    }
                    var lex = lexbool.Item1;
                    if (!IsLexemeExists(lex, session))
                    {
                        AddLexeme(lex, session);
                    }
                }
                trans.Commit();
                trans.Dispose();
            }
        }

        /// <summary>
        /// LexemeをDBに登録する.
        /// Base, POS, CForm, CTypeも必要に応じて登録する.
        /// </summary>
        private void AddLexeme(Lexeme lex, ISession session)
        {
            // POS
            var pos = FindPartOfSpeech(lex.PartOfSpeech.Name, session);
            if (pos == null)
            {
                pos = new PartOfSpeech(lex.PartOfSpeech);
                session.Save(pos);
            }
            lex.PartOfSpeech = pos;
            // CType
            var ctype = FindCType(lex.CType.Name, session);
            if (ctype == null)
            {
                ctype = new CType(lex.CType);
                session.Save(ctype);
            }
            lex.CType = ctype;
            // CForm
            var cform = FindCForm(lex.CForm.Name, session);
            if (cform == null)
            {
                cform = new CForm(lex.CForm);
                session.Save(cform);
            }
            lex.CForm = cform;
            if (lex.CForm != lex.BaseLexeme.CForm)
            {
                cform = FindCForm(lex.BaseLexeme.CForm.Name, session);
                if (cform == null)
                {
                    cform = new CForm(lex.CForm);
                    session.Save(cform);
                }
            }
            lex.CForm = cform;

            // Base
            if (!IsLexemeExists(lex.BaseLexeme, session))
            {
                session.Save(lex.BaseLexeme);
            }

            // LexemeをSave
            session.Save(lex);
        }

        private bool IsLexemeExists(Lexeme lex, ISession session)
        {
            var result = session
                .CreateCriteria<Lexeme>()
                .Add(Expression.Eq("Surface", lex.Surface))
                .List<Lexeme>();
            foreach (var lex2 in result)
            {
                var lex3 = new Lexeme(lex2);
                // 比較元（内部辞書）のReading,Pronunciationが空なら比較対象から除外（内部辞書における仮の基本形を除外するため）
                if (lex.Reading.Length == 0 && lex.Pronunciation.Length == 0)
                {
                    lex3.Reading = string.Empty;
                    lex3.Pronunciation = string.Empty;
                }
                if (lex.CForm.Name == "基本形" && lex3.CForm.Name.StartsWith("終止形"))
                {
                    lex3.CForm = lex.CForm;
                }
                if (lex.ToString() == lex3.ToString())
                {
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// listで与えられたLexemeに基づき、コーパス内辞書を更新する.
        /// </summary>
        /// <param name="list"></param>
        public void UpdateCorpusInternalDictionaries(List<LexemeCorpusBoolTuple> list)
        {
            foreach (var item in list)
            {
                try
                {
                    Open(item.Item2, item.Item1, null);
                    // Reload Target Lexeme, because it may have modified fields.
                    m_Target = m_Context.Session.CreateQuery(string.Format("from Lexeme where ID={0}", item.Item1.ID)).UniqueResult<Lexeme>();
                    var updateData = item.Item1.GetPropertyAsDictionary();
                    Save(updateData);
                }
                finally
                {
                    Close();
                }
            }
        }
    }
}
