using System;
using System.Collections.Generic;
using System.Text;
using System.IO;

using CommonMP.HYSSOP.CoreImpl.HSTools;

namespace CommonMP.HYSSOP.CoreImpl.HSDBA.FileBase
{
    /// <summary>
    /// XMLデータファイルクラス
    /// </summary>
    /// <remarks><para>remarks:</para>
    /// <para>XMLファイルをデータベースとして利用するための
    /// 簡易ファイルアクセスおよびロック機構を提供する。</para>
    /// </remarks>
    /// <remarks><para>history:</para>
    /// <para>[CommonMP][ver 1.0.0][2009/10/07][新規作成]</para>
    /// </remarks>
    public class HySXMLDataFile
    {
        /// <summary>
        /// コミット処理用デリゲート定義
        /// </summary>
        /// <returns>true:正常、false:失敗</returns>
        private delegate bool CommitProc();

        /// <summary>
        /// データファイルパス（フルパス）
        /// </summary>
        private string m_csDataFilePath;

        /// <summary>
        /// XMLリーダ
        /// </summary>
        private HySXmlReader m_csXmlReader;

        /// <summary>
        /// XMLライタ
        /// </summary>
        private HySXmlWriter m_csXmlWriter;

        /// <summary>
        /// 更新用ルートノード(インデックスファイルの連続更新用)
        /// </summary>
        private HySXmlNode m_csRootNodeForUpdate;

        /// <summary>
        /// ロックファイル
        /// </summary>
        private HySDBALockFile m_csLockFile;

        /// <summary>
        /// 一時保存ファイル名の拡張子
        /// </summary>
        private const string TMP_FILE_EXTENSION = ".tmp";

        /// <summary>
        /// 旧データ一時退避ファイル名の拡張子
        /// </summary>
        private const string OLD_FILE_EXTENSION = ".old";

        /// <summary>
        /// コミット処理用デリゲート
        /// </summary>
        private CommitProc m_csCommitProc;

        /// <summary>
        /// 自クラス名（ログ出力用）
        /// </summary>
        private const string m_csMyClassName = "HySXMLDataFile";

        /// <summary><para>method outline:</para>
        /// <para>デフォルトコンストラクタ</para>
        /// </summary>
        /// <example><para>usage:</para>
        /// <para>使用禁止</para>
        /// </example>
        /// <param name="">無し</param>
        /// <returns>HySXMLDataFile 生成されたクラスのインスタンス</returns>
        /// <exception cref="">無し</exception>
        /// <remarks><para>remarks:</para>
        /// <para>使用禁止</para>
        /// </remarks>
        private HySXMLDataFile()
        {
        }


        /// <summary><para>method outline:</para>
        /// <para>コンストラクタ</para>
        /// </summary>
        /// <example><para>usage:</para>
        /// <para>HySXMLDataFile csFile = new HySXMLDataFile(csDataFilePath)</para>
        /// </example>
        /// <param name="csDataFilePath">データファイルパス（フルパス）</param>
        /// <returns>HySXMLDataFile 生成されたクラスのインスタンス</returns>
        /// <exception cref="">無し</exception>
        /// <remarks><para>remarks:</para>
        /// <para>無し</para>
        /// </remarks>
        public HySXMLDataFile(string csDataFilePath)
        {
            m_csDataFilePath = csDataFilePath;
        }

        /// <summary><para>method outline:</para>
        /// <para>ロック</para>
        /// </summary>
        /// <example><para>usage:</para>
        /// <para>bool bRtn = Lock()</para>
        /// </example>
        /// <param name="">無し</param>
        /// <returns>true:成功、false:失敗</returns>
        /// <exception cref="">無し</exception>
        /// <remarks><para>remarks:</para>
        /// <para>無し</para>
        /// </remarks>
        public bool Lock()
        {
            const string csMyMethodName = "Lock";
            if (m_csLockFile != null)
            {
                HySDBALog.WriteOnline(m_csMyClassName, csMyMethodName, "already locked",
                    "DataFilePath", m_csDataFilePath);
                return false;
            }
            // ロック実行
            HySDBALockFile csLockFile = new HySDBALockFile();
            if (!csLockFile.Lock(m_csDataFilePath, HySDBALockFile.LockMode.FOR_MODIFY))
            {
                HySDBALog.WriteOnline(m_csMyClassName, csMyMethodName, "HySDBALockFile.Lock() error",
                    "DataFilePath", m_csDataFilePath);
                return false;
            }
            m_csLockFile = csLockFile;
            return true;
        }

        /// <summary><para>method outline:</para>
        /// <para>ロック解放</para>
        /// </summary>
        /// <example><para>usage:</para>
        /// <para>Unlock()</para>
        /// </example>
        /// <param name="">無し</param>
        /// <returns>無し</returns>
        /// <exception cref="">無し</exception>
        /// <remarks><para>remarks:</para>
        /// <para>無し</para>
        /// </remarks>
        public void Unlock()
        {
            const string csMyMethodName = "Unlock";
            // ロックを解放する
            if (m_csLockFile != null)
            {
                m_csLockFile.UnLock();
                m_csLockFile = null;
            }
            m_csXmlReader = null;
            m_csXmlWriter = null;

            // 作業ファイル残チェック(警告ログのみ)
            string csOldFilePath = m_csDataFilePath + OLD_FILE_EXTENSION;
            string csTmpFilePath = m_csDataFilePath + TMP_FILE_EXTENSION;
            foreach (string csFilePath in new string[] { csOldFilePath, csTmpFilePath })
            {
                if (File.Exists(csFilePath))
                {
                    HySDBALog.WriteOnline(m_csMyClassName, csMyMethodName, "warning: working file remained",
                        "FilePath", csFilePath);
                }
            }
        }

        /// <summary><para>method outline:</para>
        /// <para>保存（一時保存）</para>
        /// </summary>
        /// <example><para>usage:</para>
        /// <para>bool bRtn = Save()</para>
        /// </example>
        /// <param name="">無し</param>
        /// <returns>true:成功、false:失敗</returns>
        /// <exception cref="">無し</exception>
        /// <remarks><para>remarks:</para>
        /// <para>処理を確定するには<see cref="Commit"/>を呼び出す必要がある</para>
        /// <para><see cref="CreateRoot"/>か<see cref="GetRootNodeForUpdate"/>を事前に呼び出して
        /// 保存するべきルートノードを作成している必要がある。</para>
        /// </remarks>
        public bool Save()
        {
            const string csMyMethodName = "Save";
            if (m_csLockFile == null)
            {
                HySDBALog.WriteOnline(m_csMyClassName, csMyMethodName, "not locked",
                    "DataFilePath", m_csDataFilePath);
                return false;
            }
            if (m_csXmlWriter == null)
            {
                HySDBALog.WriteOnline(m_csMyClassName, csMyMethodName, "no data to save",
                    "DataFilePath", m_csDataFilePath);
                return false;
            }
            if (!m_csXmlWriter.Save())
            {
                HySDBALog.WriteOnline(m_csMyClassName, csMyMethodName, "XML save error",
                    "DataFilePath", m_csDataFilePath);
                return false;
            }

            // コミット処理をセット
            m_csCommitProc = new CommitProc(CommitSave);
            return true;
        }

        /// <summary><para>method outline:</para>
        /// <para>削除</para>
        /// </summary>
        /// <example><para>usage:</para>
        /// <para>bool bRtn = Delete()</para>
        /// </example>
        /// <param name="">無し</param>
        /// <returns>true:成功、false:失敗</returns>
        /// <exception cref="">無し</exception>
        /// <remarks><para>remarks:</para>
        /// <para>処理を確定するには<see cref="Commit"/>を呼び出す必要がある</para>
        /// </remarks>
        public bool Delete()
        {
            const string csMyMethodName = "Delete";
            if (m_csLockFile == null)
            {
                HySDBALog.WriteOnline(m_csMyClassName, csMyMethodName, "not locked",
                    "DataFilePath", m_csDataFilePath);
                return false;
            }
            if (!File.Exists(m_csDataFilePath))
            {
                HySDBALog.WriteOnline(m_csMyClassName, csMyMethodName, "data file not exists",
                    "DataFilePath", m_csDataFilePath);
                return false;
            }
            m_csCommitProc = new CommitProc(CommitDelete);
            return true;
        }

        /// <summary><para>method outline:</para>
        /// <para>コミット</para>
        /// </summary>
        /// <example><para>usage:</para>
        /// <para>bool bRtn = Commit()</para>
        /// </example>
        /// <param name="">無し</param>
        /// <returns>true:成功、false:失敗</returns>
        /// <exception cref="">無し</exception>
        /// <remarks><para>remarks:</para>
        /// <para>無し</para>
        /// </remarks>
        public bool Commit()
        {
            const string csMyMethodName = "Commit";

            if (m_csLockFile == null)
            {
                HySDBALog.WriteOnline(m_csMyClassName, csMyMethodName, "not locked",
                    "DataFilePath", m_csDataFilePath);
                return false;
            }
            if (m_csCommitProc != null)
            {
                if (!m_csCommitProc())
                {
                    HySDBALog.WriteOnline(m_csMyClassName, csMyMethodName, "commit error");
                    return false;
                }
            }
            return true;
        }

        /// <summary><para>method outline:</para>
        /// <para>保存コミット処理</para>
        /// </summary>
        /// <example><para>usage:</para>
        /// <para>bool bRtn = CommitSave()</para>
        /// </example>
        /// <param name="">無し</param>
        /// <returns>true:成功、false:失敗</returns>
        /// <exception cref="">無し</exception>
        /// <remarks><para>remarks:</para>
        /// <para>無し</para>
        /// </remarks>
        private bool CommitSave()
        {
            const string csMyMethodName = "CommitSave";
            string csOldFilePath = m_csDataFilePath + OLD_FILE_EXTENSION;
            string csTmpFilePath = m_csDataFilePath + TMP_FILE_EXTENSION;

            // 保存されていない場合、エラーとする
            if (!File.Exists(csTmpFilePath))
            {
                HySDBALog.WriteOnline(m_csMyClassName, csMyMethodName, "data not saved",
                    "DataFilePath", m_csDataFilePath);
                return false;
            }

            // PH1: 変更前ファイルをOLDファイルに移動
            if (File.Exists(m_csDataFilePath))
            {
                try
                {
                    if (File.Exists(csOldFilePath))
                    {
                        HySDBAFileIOWrapper.FileReplace(m_csDataFilePath, csOldFilePath, null);
                    }
                    else
                    {
                        HySDBAFileIOWrapper.FileMove(m_csDataFilePath, csOldFilePath);
                    }
                }
                catch (Exception ex)
                {
                    HySDBALog.WriteOnline(m_csMyClassName, csMyMethodName, "commit phase1 error",
                        "DataFilePath", m_csDataFilePath,
                        "exception", ex);
                    return false;
                }
            }
            // PH2: TMPファイルを正式ファイル名に移動
            try
            {
                HySDBAFileIOWrapper.FileMove(csTmpFilePath, m_csDataFilePath);
            }
            catch (Exception ex)
            {
                HySDBALog.WriteOnline(m_csMyClassName, csMyMethodName, "commit phase2 error",
                    "DataFilePath", m_csDataFilePath,
                    "exception", ex);
                return false;
            }

            // 後処理: OLDファイルを削除
            if (File.Exists(csOldFilePath))
            {
                try
                {
                    HySDBAFileIOWrapper.FileDelete(csOldFilePath);
                }
                catch (Exception ex)
                {
                    HySDBALog.WriteOnline(m_csMyClassName, csMyMethodName, "warning: cleanup error",
                        "DataFilePath", m_csDataFilePath,
                        "exception", ex);
                }
            }
            return true;
        }

        /// <summary><para>method outline:</para>
        /// <para>削除コミット処理</para>
        /// </summary>
        /// <example><para>usage:</para>
        /// <para>bool bRtn = CommitDelete()</para>
        /// </example>
        /// <param name="">無し</param>
        /// <returns>true:成功、false:失敗</returns>
        /// <exception cref="">無し</exception>
        /// <remarks><para>remarks:</para>
        /// <para>無し</para>
        /// </remarks>
        private bool CommitDelete()
        {
            const string csMyMethodName = "CommitDelete";

            // 一時ファイルが残っていれば削除する
            string csTmpFilePath = m_csDataFilePath + TMP_FILE_EXTENSION;
            if (File.Exists(csTmpFilePath))
            {
                try
                {
                    HySDBAFileIOWrapper.FileDelete(csTmpFilePath);
                }
                catch (Exception ex)
                {
                    HySDBALog.WriteOnline(m_csMyClassName, csMyMethodName, "warining: tmp file delete error",
                        "filepath", csTmpFilePath,
                        "exception", ex);
                    // 処理は続行する
                }
            }

            // データファイル本体を削除する
            if (File.Exists(m_csDataFilePath))
            {
                try
                {
                    HySDBAFileIOWrapper.FileDelete(m_csDataFilePath);
                }
                catch (Exception ex)
                {
                    HySDBALog.WriteOnline(m_csMyClassName, csMyMethodName, "data file delete error",
                        "filepath", m_csDataFilePath,
                        "exception", ex);
                    return false;
                }
            }
            return true;
        }

        /// <summary><para>method outline:</para>
        /// <para>ルートノードを取得する（読み取り専用）</para>
        /// </summary>
        /// <example><para>usage:</para>
        /// <para>HySXmlNode csNode = GetRootNode()</para>
        /// </example>
        /// <param name="">無し</param>
        /// <returns>ルートノード。ファイルが存在しない場合、null</returns>
        /// <exception cref="">無し</exception>
        /// <remarks><para>remarks:</para>
        /// <para>無し</para>
        /// </remarks>
        public HySXmlNode GetRootNode()
        {
            if (m_csXmlReader == null)
            {
                if (File.Exists(m_csDataFilePath))
                {
                    m_csXmlReader = new HySXmlReader(m_csDataFilePath);
                }
            }
            
            if (m_csXmlReader != null)
            {
                return m_csXmlReader.GetRootNode();
            }
            else
            {
                return null;
            }
        }

        /// <summary><para>method outline:</para>
        /// <para>ルートノードを作成する（新規XMLファイル作成用）</para>
        /// </summary>
        /// <example><para>usage:</para>
        /// <para>HySXmlNode csNode = CreateRoot(csNodeName)</para>
        /// </example>
        /// <param name="csNodeName">ノード名</param>
        /// <returns>作成されたルートノード</returns>
        /// <exception cref="">無し</exception>
        /// <remarks><para>remarks:</para>
        /// <para>既存のXMLドキュメントを部分更新する場合は<see cref="GetRootNodeForUpdate"/>を使用すること。</para>
        /// </remarks>
        public HySXmlNode CreateRoot(string csNodeName)
        {
            const string csMyMethodName = "CreateRoot";

            if (m_csRootNodeForUpdate == null)
            {
                if (m_csXmlWriter == null)
                {
                    string csTmpFilePath = m_csDataFilePath + TMP_FILE_EXTENSION;
                    m_csXmlWriter = new HySXmlWriter(csTmpFilePath);
                }
                m_csRootNodeForUpdate = m_csXmlWriter.CreateRoot(csNodeName);
            }
            else
            {
                // ルート名称チェック
                if (csNodeName != m_csRootNodeForUpdate.GetName())
                {
                    HySDBALog.WriteOnline(m_csMyClassName, csMyMethodName, "root name unmatch",
                        "original", m_csRootNodeForUpdate.GetName(),
                        "specified", csNodeName);
                    return null;    // 既に作成済みルートと名前が異なる
                }
            }
            return m_csRootNodeForUpdate;
        }

        /// <summary><para>method outline:</para>
        /// <para>エレメントを作成する</para>
        /// </summary>
        /// <example><para>usage:</para>
        /// <para>HySXmlNode csNode = CreateElement(csNodeName)</para>
        /// </example>
        /// <param name="csNodeName">ノード名</param>
        /// <returns>作成されたエレメント</returns>
        /// <exception cref="">無し</exception>
        /// <remarks><para>remarks:</para>
        /// <para>無し</para>
        /// </remarks>
        public HySXmlNode CreateElement(string csNodeName)
        {
            return m_csXmlWriter.CreateElement(csNodeName);
        }

        /// <summary><para>method outline:</para>
        /// <para>ルートノードを取得する（既存XMLファイルの部分変更用）</para>
        /// </summary>
        /// <example><para>usage:</para>
        /// <para>HySXmlNode csNode = GetRootNodeForUpdate()</para>
        /// </example>
        /// <param name="">無し</param>
        /// <returns>修正対象となるXMLドキュメントのルートノード。ファイルが存在しない場合、null</returns>
        /// <exception cref="">無し</exception>
        /// <remarks><para>remarks:</para>
        /// <para>無し</para>
        /// </remarks>
        public HySXmlNode GetRootNodeForUpdate()
        {
            if (m_csRootNodeForUpdate == null)
            {
                if (!File.Exists(m_csDataFilePath))
                {
                    return null;
                }
                if (m_csXmlReader == null)
                {
                    m_csXmlReader = new HySXmlReader(m_csDataFilePath);
                }
                if (m_csXmlWriter == null)
                {
                    string csTmpFilePath = m_csDataFilePath + TMP_FILE_EXTENSION;
                    m_csXmlWriter = new HySXmlWriter(csTmpFilePath);
                }
                HySXmlNode csRootNode = m_csXmlReader.GetRootNode();
                if (csRootNode != null)
                {
                    m_csRootNodeForUpdate = m_csXmlWriter.ImportRootNode(csRootNode, true);
                }
            }
            return m_csRootNodeForUpdate;
        }

        /// <summary><para>method outline:</para>
        /// <para> 単一の子ノードを取得する</para>
        /// </summary>
        /// <example><para>usage:</para>
        /// <para>HySXmlNode csNode = GetSingleChildNode(csParentNode, csChildNodeName)</para>
        /// </example>
        /// <param name="csParentNode">親ノード</param>
        /// <param name="csChildNodeName">子ノード名</param>
        /// <returns>単一の子ノード。存在しない場合はnull</returns>
        /// <exception cref="">無し</exception>
        /// <remarks><para>remarks:</para>
        /// <para>子ノードが複数ある場合、先頭の子ノードを返す。</para>
        /// </remarks>
        public static HySXmlNode GetSingleChildNode(HySXmlNode csParentNode, string csChildNodeName)
        {
            HySXmlNodeList csNodeList = csParentNode.GetChildNodeListByTagName(csChildNodeName);
            if (csNodeList != null && csNodeList.GetCount() > 0)
            {
                return csNodeList.GetNode(0);
            }
            else
            {
                return null;
            }
        }

        /// <summary><para>method outline:</para>
        /// <para>複数の子ノードリストを取得する</para>
        /// </summary>
        /// <example><para>usage:</para>
        /// <para>HySXmlNodeList csNodeList = GetChildNodeList(csParentNode, csChildNodeName)</para>
        /// </example>
        /// <param name="csParentNode">親ノード</param>
        /// <param name="csChildNodeName">子ノード名</param>
        /// <returns>子ノードリスト。存在しない場合はnull</returns>
        /// <exception cref="">無し</exception>
        /// <remarks><para>remarks:</para>
        /// <para>無し</para>
        /// </remarks>
        public static HySXmlNodeList GetChildNodeList(HySXmlNode csParentNode, string csChildNodeName)
        {
            HySXmlNodeList csNodeList = csParentNode.GetChildNodeListByTagName(csChildNodeName);
            if (csNodeList != null && csNodeList.GetCount() > 0)
            {
                return csNodeList;
            }
            else
            {
                return null;
            }
        }

        /// <summary><para>method outline:</para>
        /// <para>データファイルパス取得</para>
        /// </summary>
        /// <example><para>usage:</para>
        /// <para>string csFilePath = GetDataFilePath()</para>
        /// </example>
        /// <param name="">無し</param>
        /// <returns>データファイルパス</returns>
        /// <exception cref="">無し</exception>
        /// <remarks><para>remarks:</para>
        /// <para>無し</para>
        /// </remarks>
        public string GetDataFilePath()
        {
            return m_csDataFilePath;
        }
    }
}
