using System.Collections.Generic;
using System.Globalization;
using System.Text;
using UnityEngine;

namespace ServiceSim
{
    /// <summary>
    /// 確かめ A（依頼 v9 の 7 章 A）：人のいない店で、受け渡し口から全卓へ道を引く。
    ///
    /// 見るもの:
    ///   ・狭い道を使った卓が、サイトの routes の use と同じか（③は 0 卓・⑦は 24 卓）
    ///   ・引いた道の長さと、サイトの routes の length の比（最小・平均・最大）
    ///   ・道探しの時間（平均・中央・最大）
    ///   ・道が引けない卓が無いこと。待機場所へも引けること
    ///
    /// 店も人も建てずに、JSON と地図だけで回せるようにしてある。
    /// Unity のメニュー（ServiceSim/Map Planner）と、Unity の外（mono）の両方から
    /// **同じこの関数**を呼ぶので、出る数字は必ず同じになる。
    /// </summary>
    public static class MapPlanCheck
    {
        /// <summary>1 卓ぶんの結果</summary>
        public struct Row
        {
            public string tableId;
            public string siteUse;      // サイトの routes の use（normal / narrow）
            public string planUse;      // 引いた道（normal / narrow）
            public float siteLengthM;
            public float planLengthM;
            public float ratio;         // 引いた道 ÷ サイトの length
            public int corners;
            public float planMs;
            public string status;
        }

        /// <summary>1 店ぶんの結果</summary>
        public class Result
        {
            public bool ok;
            public string note = "";
            public string report = "";
            public readonly List<Row> rows = new List<Row>();
            public int narrowMine, narrowSite, failed, mismatch, snapped;
            public float ratioMin, ratioMean, ratioMax;
            public float planMsMean, planMsMedian, planMsMax;
            public int standbyOk, standbyCount;

            /// <summary>CSV（table,...）。そのまま書き出せる</summary>
            public string Csv()
            {
                StringBuilder sb = new StringBuilder();
                sb.Append("table,site_use,plan_use,site_length_m,plan_length_m,ratio,corners,plan_ms,status\n");
                for (int i = 0; i < rows.Count; i++)
                {
                    Row r = rows[i];
                    sb.Append(r.tableId).Append(',').Append(r.siteUse).Append(',').Append(r.planUse).Append(',')
                      .Append(F(r.siteLengthM)).Append(',').Append(F(r.planLengthM)).Append(',')
                      .Append(F(r.ratio)).Append(',').Append(r.corners).Append(',')
                      .Append(F(r.planMs)).Append(',').Append(r.status).Append('\n');
                }
                return sb.ToString();
            }
        }

        /// <summary>
        /// 回す。storeJson は店の形の JSON、mapJsonText は地図の JSON。
        /// spec が null なら GridPlanner.Settings.Default で引く。
        /// </summary>
        public static Result Run(string label, string storeJson, string mapJsonText, RobotSpec spec)
        {
            Result res = new Result();
            StringBuilder sb = new StringBuilder();
            sb.AppendLine("## " + label);

            string err;
            JsonValue store = JsonValue.Parse(storeJson, out err);
            if (store == null || !store.IsObject)
            {
                res.note = "店の形の JSON を読めません: " + err;
                sb.AppendLine("  " + res.note);
                res.report = sb.ToString().TrimEnd();
                return res;
            }

            RobotMap map = RobotMap.Read(mapJsonText, out err);
            if (map == null)
            {
                res.note = "地図を読めません: " + err;
                sb.AppendLine("  " + res.note);
                res.report = sb.ToString().TrimEnd();
                return res;
            }
            sb.AppendLine(map.Report());

            GridPlanner.Settings st = spec != null ? spec.PlannerSettings() : GridPlanner.Settings.Default;
            sb.AppendLine("  道の引き方: narrowCellCost " + F(st.narrowCellCost) +
                          " / centerWeight " + F(st.centerWeight) +
                          " / preferClearM " + F(st.preferClearM) +
                          " / maxExpand " + st.maxExpand +
                          " / snapM " + F(st.snapM) +
                          " / simplifyMaxM " + F(st.simplifyMaxM));

            Vector3 pass = Vector3.zero;
            bool hasPass = false;
            List<Vector3> standby = new List<Vector3>();
            JsonValue places = store["places"];
            for (int i = 0; i < places.Count; i++)
            {
                string kind = places[i]["kind"].S("");
                Vector3 p = new Vector3(places[i]["point"][0].F(0f), 0f, places[i]["point"][1].F(0f));
                if (kind == "FoodPass") { pass = p; hasPass = true; }
                else if (kind == "RobotStandby") standby.Add(p);
            }
            if (!hasPass)
            {
                res.note = "places に FoodPass（受け渡し口の前）がありません";
                sb.AppendLine("  " + res.note);
                res.report = sb.ToString().TrimEnd();
                return res;
            }
            sb.AppendLine("  受け渡し口の前 " + P(pass) + " → マスの種類 " + map.ClassAt(pass));

            GridPlanner planner = map.Planner;
            JsonValue fromPass = store["routes"]["from_pass"];
            JsonValue tables = store["tables"];

            List<float> ratios = new List<float>();
            List<float> times = new List<float>();
            List<string> mismatchList = new List<string>();

            for (int i = 0; i < tables.Count; i++)
            {
                Row row = new Row();
                row.tableId = tables[i]["id"].S("T" + (i + 1).ToString("00"));
                Vector3 stop = new Vector3(tables[i]["stop"][0].F(0f), 0f, tables[i]["stop"][1].F(0f));

                JsonValue route = fromPass[row.tableId];
                row.siteUse = route["use"].S("");
                row.siteLengthM = route["length"].F(-1f);
                if (row.siteUse == "narrow") res.narrowSite++;

                planner.ClearMarks();
                GridPlanner.Status s = planner.Plan(pass, stop, st);
                row.planMs = planner.LastPlanMs;
                times.Add(row.planMs);
                if (planner.LastStartWasBlocked || planner.LastGoalWasBlocked) res.snapped++;

                if (s != GridPlanner.Status.Ok)
                {
                    res.failed++;
                    row.status = planner.StatusText;
                    res.rows.Add(row);
                    sb.AppendLine("  × " + row.tableId + " 道が引けません: " + row.status);
                    continue;
                }

                row.planUse = planner.UsedNarrow ? "narrow" : "normal";
                if (planner.UsedNarrow) res.narrowMine++;
                row.planLengthM = planner.LastLengthM;
                row.corners = planner.Points.Count;
                row.status = "ok";
                row.ratio = row.siteLengthM > 0.0005f ? row.planLengthM / row.siteLengthM : -1f;
                if (row.ratio > 0f) ratios.Add(row.ratio);
                if (row.planUse != row.siteUse)
                {
                    res.mismatch++;
                    mismatchList.Add(row.tableId + "（サイト " + row.siteUse + " / 引いた道 " + row.planUse + "）");
                }
                res.rows.Add(row);
            }

            sb.AppendLine("  狭い道を使った卓: 引いた道 " + res.narrowMine +
                          " 卓 / サイトの routes の use " + res.narrowSite + " 卓  → " +
                          (res.mismatch == 0 ? "一致" : "ちがう（" + res.mismatch + " 卓）"));
            for (int i = 0; i < mismatchList.Count && i < 12; i++)
                sb.AppendLine("    - " + mismatchList[i]);
            if (mismatchList.Count > 12)
                sb.AppendLine("    - ほか " + (mismatchList.Count - 12) + " 卓");
            sb.AppendLine("  道が引けない卓: " + res.failed + " / " + tables.Count);

            if (ratios.Count > 0)
            {
                ratios.Sort();
                float sum = 0f;
                for (int i = 0; i < ratios.Count; i++) sum += ratios[i];
                res.ratioMin = ratios[0];
                res.ratioMean = sum / ratios.Count;
                res.ratioMax = ratios[ratios.Count - 1];
                sb.AppendLine("  長さの比（引いた道 ÷ サイトの length）: 最小 " + F(res.ratioMin) +
                              " / 平均 " + F(res.ratioMean) + " / 最大 " + F(res.ratioMax) +
                              "（真ん中寄りにした分だけ延びる）");
            }
            if (times.Count > 0)
            {
                times.Sort();
                float sum = 0f;
                for (int i = 0; i < times.Count; i++) sum += times[i];
                res.planMsMean = sum / times.Count;
                res.planMsMedian = times[times.Count / 2];
                res.planMsMax = times[times.Count - 1];
                sb.AppendLine("  道探しの時間: 平均 " + F(res.planMsMean) + " ms / 中央 " +
                              F(res.planMsMedian) + " ms / 最大 " + F(res.planMsMax) + " ms");
            }
            sb.AppendLine("  出発点・行き先が地図で通れないマス（0）だったので寄せた卓: " +
                          res.snapped + " / " + tables.Count);

            res.standbyCount = standby.Count;
            for (int i = 0; i < standby.Count; i++)
            {
                planner.ClearMarks();
                GridPlanner.Status s = planner.Plan(pass, standby[i], st);
                bool good = s == GridPlanner.Status.Ok;
                if (good) res.standbyOk++;
                sb.AppendLine("  待機場所" + (i + 1) + " " + P(standby[i]) +
                              " マスの種類 " + map.ClassAt(standby[i]) + " → " +
                              (good ? "引けた（" + F(planner.LastLengthM) + " m・" +
                                      F(planner.LastPlanMs) + " ms・" +
                                      (planner.UsedNarrow ? "狭い道を使った" : "ふだんの道") + "）"
                                    : "引けない：" + planner.StatusText));
            }

            res.ok = res.failed == 0 && res.mismatch == 0 &&
                     res.standbyOk == res.standbyCount && map.CountsMatch;
            sb.AppendLine("  → " + (res.ok ? "確かめ A は通った" : "確かめ A は通っていない"));
            res.report = sb.ToString().TrimEnd();
            return res;
        }

        static string F(float v) { return v.ToString("0.###", CultureInfo.InvariantCulture); }
        static string P(Vector3 v) { return "(" + F(v.x) + ", " + F(v.z) + ")"; }
    }
}
