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

namespace CommonMP.HYMCO.OptionImpl.CommonControlLibrary
{
    /// <summary>
    /// wisefファイル読み込みクラス
    /// </summary>
    public class ReadWisefFile
    {
        private static readonly string ERROR_MSG_ELSE = Properties.CommonControlLibraryResources.MSG_FAIL_TO_READ_WISEF;

        /// <summary> 対象とする観測所種別 </summary>
        private string m_sStationType = "";
        /// <summary> 対象とする観測所種別名称 </summary>
        private string m_sStationName = "";
        /// <summary> 対象とするデータセット名 </summary>
        private string m_sDataTypesName = "";

        /// <summary> 対象とするデータセット名リスト </summary>
        private Dictionary<string, string> m_sDataTypes = new Dictionary<string, string>();

        /// <summary> 読み込んだWisefデータ (Key=日時文字列, value=値[観測所地点, セル]) </summary>
        private SortedList<string, double[,]> m_WisefData = null;

        /// <summary> エラーメッセージ </summary>
        private string m_sErrorMsg = "";


        /// <summary>
        /// 読み込んだWisefデータ (Key=日時文字列, value=値[観測所地点, セル])
        /// </summary>
        public SortedList<string, double[,]> WisefData
        {
            get
            {
                return m_WisefData;
            }
        }

        /// <summary>
        /// エラーメッセージ
        /// </summary>
        public string ErrorMessage
        {
            get
            {
                return m_sErrorMsg;
            }
        }

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="sStationType">対象とする観測所種別</param>
        /// <param name="sDataType">対象とするデータセット名</param>
        public ReadWisefFile(string sStationType, params string[] sDataType)
        {
            //読み込む観測所種別を格納
            m_sStationType = sStationType.ToUpper();
            m_sStationName = GetStationName(m_sStationType);

            //読み込むデータセット名を格納
            foreach (string sType in sDataType)
            {
                if (!m_sDataTypes.ContainsKey(sType.ToUpper()))
                {
                    string sName = GetDataTypeName(sType.ToUpper());
                    m_sDataTypes.Add(sType.ToUpper(), sName);
                    m_sDataTypesName += (m_sDataTypesName.Length > 0 ? "," : "") + sType.ToUpper();
                }
            }

        }

        /// <summary>
        /// 観測所種別名を取得する
        /// </summary>
        /// <param name="sStationType">観測所種別</param>
        /// <returns>観測所種別名称</returns>
        private string GetStationName(string sStationType)
        {
            if (sStationType == "H") return Properties.CommonControlLibraryResources.STAGE_DISCHARGE_STATION;   //水位流量観測所
            if (sStationType == "R") return Properties.CommonControlLibraryResources.RAINFALL_GAUGING_STATION;  //雨量観測所
            if (sStationType == "WQ") return Properties.CommonControlLibraryResources.QUALITY_AND_SEDIMENT_STATION; //水質・底質観測所
            if (sStationType == "GH") return Properties.CommonControlLibraryResources.GROUNDWATER_LEVEL_STATION;  //地下水位観測所
            if (sStationType == "GWQ") return Properties.CommonControlLibraryResources.GROUNDWATER_QUALITY_STATION;   //地下水水質観測所
            if (sStationType == "DAM") return Properties.CommonControlLibraryResources.DAM;   //ダム
            if (sStationType == "PUMP") return Properties.CommonControlLibraryResources.AMOUNT_OF_INTAKE_WATER;    //取水量
            if (sStationType == "NET") return Properties.CommonControlLibraryResources.WEATHER_STATION; //気象観測所
            if (sStationType == "OCE") return Properties.CommonControlLibraryResources.OCEANOGRAPHIC_PHENOMENA;   //海象

            return "";
        }

        /// <summary>
        /// データセット名を取得する
        /// </summary>
        /// <param name="sDataType">データセット名</param>
        /// <returns>データセット名称</returns>
        private string GetDataTypeName(string sDataType)
        {
            if (sDataType == "HU") return Properties.CommonControlLibraryResources.WATER_LEVEL;   //水位
            if (sDataType == "HH") return Properties.CommonControlLibraryResources.WATER_LEVEL;   //水位
            if (sDataType == "HHA") return Properties.CommonControlLibraryResources.WATER_LEVEL;  //水位
            if (sDataType == "RU") return Properties.CommonControlLibraryResources.RAINFALL;   //雨量
            if (sDataType == "RH") return Properties.CommonControlLibraryResources.RAINFALL;   //雨量
            if (sDataType == "RHA") return Properties.CommonControlLibraryResources.RAINFALL;  //雨量
            if (sDataType == "QU") return Properties.CommonControlLibraryResources.DISCHARGE;   //流量
            if (sDataType == "QH") return Properties.CommonControlLibraryResources.DISCHARGE;   //流量
            if (sDataType == "QHA") return Properties.CommonControlLibraryResources.DISCHARGE;  //流量
            if (sDataType == "O1U") return Properties.CommonControlLibraryResources.TIDE_LEVEL;  //潮位
            if (sDataType == "DAMU") return Properties.CommonControlLibraryResources.DAM_DATA;   //ダム堰諸量
            if (sDataType == "PUMPU") return Properties.CommonControlLibraryResources.AMOUNT_OF_INTAKE_WATER;  //取水量

            return "";
        }

        /// <summary>
        /// 列数を取得する
        /// </summary>
        /// <param name="sDataType">データセット名</param>
        /// <returns>列数</returns>
        private int GetDataTypeColumnCnt(string sDataType)
        {
            if (sDataType == "HU") return 3;    //年月日+時分+値
            if (sDataType == "HH") return 25;   //年月日+1時〜24時
            if (sDataType == "HHA") return 49;  //年月日+1時〜24時(*2)
            if (sDataType == "RU") return 4;    //年月日+時分+値+累加
            if (sDataType == "RH") return 25;   //年月日+1時〜24時
            if (sDataType == "RHA") return 49;  //年月日+1時〜24時(*2)
            if (sDataType == "QU") return 3;    //年月日+時分+値
            if (sDataType == "QH") return 25;   //年月日+1時〜24時
            if (sDataType == "QHA") return 49;  //年月日+1時〜24時(*2)
            if (sDataType == "O1U") return 3;   //年月日+時分+値
            if (sDataType == "DAMU") return 8;  //年月日+時分+値(*6)
            if (sDataType == "PUMPU") return 3; //年月日+時分+値

            return 0;
        }

        /// <summary>
        /// Wisefファイル読み込み処理
        /// </summary>
        /// <param name="wisefFile">ファイル名パス</param>
        /// <param name="spotNum">観測所地点数</param>
        /// <returns>true:正常/false:異常（エラー内容は ErrorMessage プロパティで取得できます。）</returns>
        public bool ReadWisefMain(string wisefFile, long spotNum)
        {
            m_sErrorMsg = "";
            m_WisefData = new SortedList<string, double[,]>();

            List<string[]> lstDatas = new List<string[]>();
            SortedList<string, int[]> lstDataType = new SortedList<string, int[]>();

            int iDataCount = 0;
            int iStationCount = 0;
            string sKey = "";
            string sType = "";
            string sStation = "";
            string sDataType = "";
            string sLine = null;
            System.IO.StreamReader reader = null;
            try
            {
                reader = new System.IO.StreamReader(wisefFile, System.Text.Encoding.Default);

                //1行目：FILE
                if (!ReadLine(reader, ref sKey, ref sType, ref sLine))
                {   //有効行なし
                    m_sErrorMsg = ERROR_MSG_ELSE;
                    return false;
                }
                if (sKey != "FILE")
                {   //先頭行がFILEでない
                    //"先頭に「FILE」がありません。"
                    m_sErrorMsg = string.Format("{0}（{1}）", ERROR_MSG_ELSE, Properties.CommonControlLibraryResources.MSG_NOT_FILE_AT_BEGINNING);
                    return false;
                }

                bool bNotEOF = true;
                while (bNotEOF)
                {
                    //1行読み込み
                    bNotEOF = ReadLine(reader, ref sKey, ref sType, ref sLine);
                    if (!bNotEOF)
                        break;  //終了

                    if (sKey == "STATION")
                    {
                        sStation = sType;
                        iStationCount++;

                        //観測所種別チェック
                        if (!sStation.Equals(m_sStationType))
                        {   //観測所種別一致しない場合エラー
                            //"このWisefファイルの観測所種別は、「{0}」ではありません。"
                            m_sErrorMsg = string.Format(Properties.CommonControlLibraryResources.MSG_DIFFERENCE_OF_OBSERVATION_STATION, m_sStationName);
                            return false;
                        }
                    }
                    else if (sKey == "DATA")
                    {
                        sDataType = sType;
                        iDataCount = 0;

                        if (iStationCount == 0)
                        {   //観測所が指定されていなければエラー
                            //"「DATA」の前に「STATION」がありません。"
                            m_sErrorMsg = string.Format("{0} ({1})", ERROR_MSG_ELSE, Properties.CommonControlLibraryResources.MSG_NOT_STATION_BEFORE_DATA);
                            return false;
                        }

                        //データセット名をチェック
                        bool bTargetData = m_sDataTypes.ContainsKey(sDataType);
                        if (!bTargetData)
                        {   //データセット名一致しない場合エラー
                            //"このWisefファイルの項目は、「{0}」ではありません。"
                            m_sErrorMsg = string.Format(Properties.CommonControlLibraryResources.MSG_DIFFERENCE_OF_WISEF_ITEM, m_sDataTypesName);
                            return false;
                        }

                        while (bNotEOF)
                        {   //EODまで読み込み
                            bNotEOF = ReadLine(reader, ref sKey, ref sType, ref sLine);
                            if (!bNotEOF)
                            {   //EODがない
                                //"「DATA」と対になる「EOD」がありません。"
                                m_sErrorMsg = string.Format("{0} ({1})", ERROR_MSG_ELSE, Properties.CommonControlLibraryResources.MSG_NO_EXISTENT_PAIRED_EOD);
                                return false;
                            }
                            if (sKey == "EOD")
                                break;  //データ部終了

                            iDataCount++;

                            if (bTargetData)
                            {   //読み込んだデータを格納しておく
                                string[] sData = new string[3];
                                sData[0] = sDataType;                   //データセット名
                                sData[1] = iStationCount.ToString();    //観測所No
                                sData[2] = sLine;                       //１行データ
                                lstDatas.Add(sData);
                            }
                        }

                        //データセット名を格納
                        if (!lstDataType.ContainsKey(sDataType))
                        {
                            int[] iValue = new int[2] { iStationCount, iDataCount };
                            lstDataType.Add(sDataType, iValue);
                        }
                        else
                        {   //同じデータセット名がすでに格納されている
                            int[] iValue = lstDataType[sDataType];
                            if (iValue[0] == iStationCount)
                            {   //同じ観測所内にすでに同じデータセット名が存在するエラー
                                //"同じ観測所種別内に同じデータセット名が存在します。"
                                m_sErrorMsg = string.Format("{0} ({1})", ERROR_MSG_ELSE, Properties.CommonControlLibraryResources.MSG_SAME_DATA_SET_NAME);
                                return false;
                            }
                            if (iValue[1] != iDataCount)
                            {   //データ行が一致していないエラー
                                //"各データ部分の行数が一致していません。"
                                m_sErrorMsg = string.Format("{0} ({1})", ERROR_MSG_ELSE, Properties.CommonControlLibraryResources.MSG_DIFFERENCE_OF_LINAGE);
                                return false;
                            }
                        }

                    }

                }

                if (lstDatas.Count == 0)
                {
                    m_sErrorMsg = ERROR_MSG_ELSE;
                    return false;
                }

                //データの観測所数とプロパティでの設定した観測所数と一致するかのチェック
                if (iStationCount != spotNum)
                {
                    //"設定した地点数がファイルの内容と異なります。"
                    m_sErrorMsg = Properties.CommonControlLibraryResources.MSG_DIFFERENCE_OF_SETTING_POINTS_NUM;
                    return false;
                }

                //データセット名ごとに処理（現在は１種類しか対応しない）
                for (int i = 0; i < Math.Min(lstDataType.Count, 1); i++)
                {
                    sDataType = lstDataType.Keys[i];
                    try
                    {
                        for (int iRow = 0; iRow < lstDatas.Count; iRow++)
                        {
                            string[] sData = lstDatas[iRow];    //0:データセット名, 1:観測所No, 2:１行データ
                            if (sDataType != sData[0])
                                continue;   //対象データセット名に一致しないものは読み飛ばし

                            int iStationNo = int.Parse(sData[1]);       //観測所No
                            string[] cols = sData[2].Split(',');        //１行データ

                            //列数チェック
                            int iColCount = GetDataTypeColumnCnt(sDataType);
                            if (iColCount != 0 && cols.Length != iColCount)
                            {
                                //"{0}（データセット名[ {1} ]の列数が一致しません。）"
                                m_sErrorMsg = string.Format(Properties.CommonControlLibraryResources.MSG_DIFFERENCE_OF_DATA_SET_NAMES_COLUMN_NUM, ERROR_MSG_ELSE, sDataType);
                                return false;
                            }

                            if (sDataType == "HU" || sDataType == "RU" || sDataType == "QU" || sDataType == "O1U" || sDataType == "PUMPU")
                            {   //観測データ
                                if (!UDataAnalysis(sDataType, iStationNo, iStationCount, cols))
                                    return false;
                            }
                            else if (sDataType == "HH" || sDataType == "RH" || sDataType == "QH")
                            {   //正時データ
                                if (!HDataAnalysis(sDataType, iStationNo, iStationCount, cols))
                                    return false;
                            }
                            else if (sDataType == "HHA" || sDataType == "RHA" || sDataType == "QHA")
                            {   //属性付正時データ
                                if (!HADataAnalysis(sDataType, iStationNo, iStationCount, cols))
                                    return false;
                            }
                            else if (sDataType == "DAMU")
                            {   //ダム諸量データ
                                if (!DAMUDataAnalysis(sDataType, iStationNo, iStationCount, cols))
                                    return false;
                            }
                            else
                            {
                                //"{0}（データセット名[ {1} ]は未対応です。）"
                                m_sErrorMsg = string.Format(Properties.CommonControlLibraryResources.MSG_NOT_SUPPORTED_DATA_SET_NAME, ERROR_MSG_ELSE, sDataType);
                                return false;
                            }
                        }
                    }
                    catch
                    {
                        //"{0}（データセット名[{1}]の解析ができませんでした。）"
                        m_sErrorMsg = string.Format(Properties.CommonControlLibraryResources.MSG_FAIL_TO_ANALYSIS_OF_DATA_SET_NAME, ERROR_MSG_ELSE, sDataType);
                        return false;
                    }
                }

                return true;
            }
            catch
            {
                m_sErrorMsg = ERROR_MSG_ELSE;
                return false;
            }
            finally
            {
                if (reader != null)
                    reader.Close();
            }
        }

        /// <summary>
        /// 有効な一行データ読み込み
        /// </summary>
        /// <param name="reader">StreamReader</param>
        /// <param name="sKey">読み込んだ行のKEY(1列目)</param>
        /// <param name="sType">読み込んだ行のTYPE(2列目)</param>
        /// <param name="sLine">読み込んだ行</param>
        /// <returns></returns>
        private bool ReadLine(System.IO.StreamReader reader, ref string sKey, ref string sType, ref string sLine)
        {
            sKey = "";
            sType = "";
            sLine = null;
            bool bTarget = false;
            while (!bTarget && (sLine = reader.ReadLine()) != null)
            {
                if (sLine.Length == 0)
                {   //空行は読み飛ばし
                    continue;
                }
                else if (sLine.Length >= 3 && sLine.ToUpper().StartsWith("COM"))
                {   //コメント行は読み飛ばし
                    continue;
                }
                else if (sLine.Length >= 3 && sLine.Trim().ToUpper().Equals("EOF"))
                {   //終了
                    break;
                }

                //有効行
                string[] cols = sLine.Split(',');
                if (cols.Length > 0)
                    sKey = cols[0].Trim().ToUpper();
                if (cols.Length > 1)
                    sType = cols[1].Trim().ToUpper();
                bTarget = true;
            }
            return bTarget;
        }

        /// <summary>
        /// 値セット
        /// </summary>
        /// <param name="sTime">時間</param>
        /// <param name="dVal">値</param>
        /// <param name="iStationNo">観測所地点</param>
        /// <param name="iStationCount">観測所地点数</param>
        private bool SetValue(string sTime, double dVal, int iStationNo, int iStationCount)
        {
            //時間行を検索
            if (!m_WisefData.ContainsKey(sTime))
            {   //新しく時間行を追加
                double[,] dData = new double[iStationCount, 1];
                dData[iStationNo - 1, 0] = dVal;
                m_WisefData.Add(sTime, dData);

                if (iStationNo > 1)
                {   //２地点目なのに時間行が存在しない（各地点で日時がずれている）
                    //"{0}（データ行の日時が合っていません。）"
                    m_sErrorMsg = string.Format("{0}（データ行の日時が合っていません。）", ERROR_MSG_ELSE);
                    return false;
                }
            }
            else
            {   //すでに存在するので取り出して値セット
                m_WisefData[sTime][iStationNo - 1, 0] = dVal;
            }
            return true;
        }
        /// <summary>
        /// 値セット
        /// </summary>
        /// <param name="sTime">時間</param>
        /// <param name="dVal">値</param>
        /// <param name="iStationNo">観測所地点</param>
        /// <param name="iStationCount">観測所地点数</param>
        private bool SetValue(string sTime, double[] dVal, int iStationNo, int iStationCount)
        {
            //時間行を検索
            if (!m_WisefData.ContainsKey(sTime))
            {   //新しく時間行を追加
                double[,] dData = new double[iStationCount, dVal.Length];
                for (int iCell = 0; iCell < dVal.Length; iCell++)
                {
                    dData[iStationNo - 1, iCell] = dVal[iCell];
                }
                m_WisefData.Add(sTime, dData);

                if (iStationNo > 1)
                {   //２地点目なのに時間行が存在しない（各地点で日時がずれている）
                    m_sErrorMsg = string.Format(Properties.CommonControlLibraryResources.MSG_NOT_CORRECTLY_TIME_OF_DATA_ROWS, ERROR_MSG_ELSE);
                    return false;
                }
            }
            else
            {   //すでに存在するので取り出して値セット
                for (int iCell = 0; iCell < dVal.Length; iCell++)
                {
                    m_WisefData[sTime][iStationNo - 1, iCell] = dVal[iCell];
                }
            }
            return true;
        }



        //************************************************************
        // 各データセット名ごとの行解析メソッド
        //************************************************************

        //add_s 20100514
        /// <summary>
        /// 時間文字列をチェック（24:00→翌日の00:00に変換）
        /// </summary>
        /// <param name="sTime"></param>
        /// <returns></returns>
        private string CheckDateTime(string sTime)
        {
            string t = sTime;
            DateTime dte = new DateTime();
            if (!DateTime.TryParse(t, out dte))
            {   //変換できない
                //年月日を取り出す
                string sYmd = sTime.Substring(0, sTime.IndexOf(" "));
                string sHms = sTime.Substring(sTime.IndexOf(" ") + 1);

                if (sHms.StartsWith("24"))
                {   //24時の場合、翌日の00時に変換
                    dte = DateTime.Parse(sYmd + " 00" + sHms.Substring(2));
                    t = dte.AddDays(1).ToString("yyyy/MM/dd HH:mm:ss");
                    return t;
                }
            }

            return DateTime.Parse(t).ToString("yyyy/MM/dd HH:mm:ss");
        }
        //add_e 20100514

        /// <summary>
        /// 観測データ行解析
        /// </summary>
        /// <param name="sDataType">データセット名</param>
        /// <param name="iStationNo">観測所地点</param>
        /// <param name="iStationCount">観測所地点数</param>
        /// <param name="cols">列データ</param>
        /// <returns>true/false</returns>
        private bool UDataAnalysis(string sDataType, int iStationNo, int iStationCount, string[] cols)
        {
            //時間を取得し、秒の部分を補充する
            //string sTime = cols[0] + " " + cols[1] + ":00"; //mod 20100514 24:00→翌日の00:00に変換
            string sTime = CheckDateTime(cols[0] + " " + cols[1] + ":00");

            //値を取得
            double dVal = Convert.ToDouble(cols[2]);

            //時間と値を設定
            if (!SetValue(sTime, dVal, iStationNo, iStationCount))
                return false;

            return true;
        }

        /// <summary>
        /// ダム諸量データ行解析
        /// </summary>
        /// <param name="sDataType">データセット名</param>
        /// <param name="iStationNo">観測所地点</param>
        /// <param name="iStationCount">観測所地点数</param>
        /// <param name="cols">列データ</param>
        /// <returns>true/false</returns>
        private bool DAMUDataAnalysis(string sDataType, int iStationNo, int iStationCount, string[] cols)
        {
            //時間を取得し、秒の部分を補充する
            //string sTime = cols[0] + " " + cols[1] + ":00"; //mod 20100514 24:00→翌日の00:00に変換
            string sTime = CheckDateTime(cols[0] + " " + cols[1] + ":00");

            //値を取得
            double[] dVal = new double[6];
            for (int iCell = 0; iCell < dVal.Length; iCell++)
            {
                dVal[iCell] = Convert.ToDouble(cols[2 + iCell]);
            }

            //時間と値を設定
            if (!SetValue(sTime, dVal, iStationNo, iStationCount))
                return false;

            return true;
        }

        /// <summary>
        /// 正時データ行解析
        /// </summary>
        /// <param name="sDataType">データセット名</param>
        /// <param name="iStationNo">観測所地点</param>
        /// <param name="iStationCount">観測所地点数</param>
        /// <param name="cols">列データ</param>
        /// <returns>true/false</returns>
        private bool HDataAnalysis(string sDataType, int iStationNo, int iStationCount, string[] cols)
        {
            //時間を取得し、時間の部分を補充する
            string sTime = cols[0] + " 00:00:00";

            //スタート時間を設定
            DateTime dteTime = DateTime.Parse(sTime);

            for (int iHour = 1; iHour < 25; iHour++)
            {   //1時〜24時
                dteTime = dteTime.AddHours(1);
                sTime = dteTime.ToString("yyyy/MM/dd HH:mm:ss");

                //値を取得
                double dVal = Convert.ToDouble(cols[iHour]);

                //時間と値を設定
                if (!SetValue(sTime, dVal, iStationNo, iStationCount))
                    return false;
            }

            return true;
        }

        /// <summary>
        /// 属性付正時データ行解析
        /// </summary>
        /// <param name="sDataType">データセット名</param>
        /// <param name="iStationNo">観測所地点</param>
        /// <param name="iStationCount">観測所地点数</param>
        /// <param name="cols">列データ</param>
        /// <returns>true/false</returns>
        private bool HADataAnalysis(string sDataType, int iStationNo, int iStationCount, string[] cols)
        {
            //時間を取得し、時間の部分を補充する
            string sTime = cols[0] + " 00:00:00";

            //スタート時間を設定
            DateTime dteTime = DateTime.Parse(sTime);

            for (int iHour = 1; iHour < 49; iHour += 2)
            {   //1時〜24時
                dteTime = dteTime.AddHours(1);
                sTime = dteTime.ToString("yyyy/MM/dd HH:mm:ss");

                //値を取得
                double dVal = Convert.ToDouble(cols[iHour]);

                //時間と値を設定
                if (!SetValue(sTime, dVal, iStationNo, iStationCount))
                    return false;
            }

            return true;
        }



    }

}
