using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;

namespace ServiceSim.EditorTools
{
    /// <summary>
    /// 1条件だけ回すメニュー（v2 の確かめ用）。
    /// 条件と数字は ExperimentPlan の .asset。回し方だけここで決める。
    /// </summary>
    public static class ServiceSimStage2
    {
        [MenuItem("ServiceSim/Run One/1条件を回す（1コマ固定 0.05 秒）", false, 40)]
        public static void RunMeasure() { Run(0f, "v3", false, 0f); }

        [MenuItem("ServiceSim/Run One/1条件を回す（確かめ用 0.02 秒）", false, 41)]
        public static void RunReference() { Run(-1f, "v3ref", false, 0f); }

        // C1：停止点だけを変えて2回回す。ほかはすべて ExperimentPlan の trial… の値。
        // Inspector を触らずにメニューから選べるようにして、出力フォルダ名に停止点を入れる
        // （…_off000 と …_off015）ので、2回目で1回目が上書きされない。
        // 選んだ値はその回の Job にだけ使う。StoreLayout の .asset は書き換えない
        // （ExperimentRunner が回し終わりにもとの値へ戻す）。
        [MenuItem("ServiceSim/Run One/停止点 0 で回す（通路の中央）", false, 50)]
        public static void RunOffset000() { Run(0f, "v3", true, 0f); }

        [MenuItem("ServiceSim/Run One/停止点 0.15 で回す（卓寄り・直す前と同じ）", false, 51)]
        public static void RunOffset015() { Run(0f, "v3", true, 0.15f); }

        // 見る用：その回だけ字幕と描画を出し、1倍速で進める。
        // 設定ファイル（ExperimentPlan・StoreLayout）は書き換えない。
        // 計測の比べには使わない回なので、出力フォルダの名前に _view を付ける。
        [MenuItem("ServiceSim/Run One/見る用：字幕あり・描画あり（1倍速）", false, 55)]
        public static void RunView() { Run(0f, "view", false, 0f, true); }

        [MenuItem("ServiceSim/Run One/出力フォルダを開く", false, 60)]
        public static void OpenOutput()
        {
            ExperimentPlan plan = AssetDatabase.LoadAssetAtPath<ExperimentPlan>(ServiceSimSetup.ExperimentPlanPath);
            if (plan == null) { Debug.LogError("[ServiceSim] ExperimentPlan が見つかりません"); return; }
            if (!Directory.Exists(plan.trialOutputRoot))
            {
                Debug.LogWarning("[ServiceSim] まだありません: " + plan.trialOutputRoot);
                return;
            }
            EditorUtility.RevealInFinder(plan.trialOutputRoot);
        }

        /// <summary>
        /// captureOverride: 0 なら plan の計測用、-1 なら plan の確かめ用。
        /// setOffset が true なら、卓の停止点を offset（m）にしてこの回だけ回す。
        /// .asset は書き換えない（ExperimentRunner が回し終わりにもとの値へ戻す）。
        /// false なら .asset にいま入っている値のまま回す。
        /// どちらでも、出力フォルダ名には実際に使う停止点を入れる。
        /// </summary>
        static void Run(float captureOverride, string label, bool setOffset, float offset)
        {
            Run(captureOverride, label, setOffset, offset, false);
        }

        /// <summary>view が true なら、その回だけ字幕と描画を出して1倍速で回す（見る用）</summary>
        static void Run(float captureOverride, string label, bool setOffset, float offset, bool view)
        {
            ExperimentRunner runner;
            ExperimentPlan plan;
            if (!ServiceSimRunSupport.Prepare(out runner, out plan)) return;

            StoreLayout layout = CurrentLayout();
            if (layout == null)
            {
                Debug.LogError("[ServiceSim] StoreLayout が見つかりません。ServiceSim/Setup を実行してください");
                return;
            }

            float useOffset = setOffset ? offset : layout.deliveryOffsetFromAisleCenter;
            float capture = captureOverride < 0f ? plan.referenceCaptureDeltaTime : 0f;

            ExperimentRunner.Job job = new ExperimentRunner.Job();
            job.aisleWidthIndex = plan.trialAisleWidthIndex;
            job.crowd = plan.trialCrowd;
            job.robots = plan.trialRobots;
            job.seed = plan.trialSeed;
            job.applyDeliveryOffset = setOffset;
            job.deliveryOffset = offset;
            job.folder = Path.Combine(plan.trialOutputRoot,
                "one_" + plan.runName + "_" + label + "_w" + plan.trialAisleWidthIndex +
                "_" + ExperimentPlan.CrowdName(plan.trialCrowd) +
                "_r" + plan.trialRobots + "_s" + plan.trialSeed +
                "_" + OffsetTag(useOffset) + (view ? "_view" : "") +
                "_" + DateTime.Now.ToString("yyyyMMdd_HHmm"));

            // フォルダ名に回し始めた時刻を入れるので、同じ条件でも上書きにならない
            if (Directory.Exists(job.folder))
                Debug.LogWarning("[ServiceSim] このフォルダはすでにあります。上書きになります: " + job.folder);

            runner.SetSingleJob(job);
            runner.timeMode = view ? ExperimentRunner.TimeMode.RealTime
                                   : ExperimentRunner.TimeMode.FixedStep;
            runner.captureDeltaTimeOverride = view ? 0f : capture;
            runner.runLabel = label;
            runner.viewMode = view;
            runner.runOnPlay = true;
            EditorUtility.SetDirty(runner);

            float stepSec = capture > 0f ? capture : plan.measureCaptureDeltaTime;
            float simMin = plan.warmupMinutes + plan.measureMinutes;
            Debug.Log("[ServiceSim] " + (view ? "見る用に1条件を回します" : "1条件を回します") +
                      "（Play に入ります）。" +
                      (view ? "\n  見る用：字幕あり・描画あり・1倍速。設定ファイルは書き換えていません。" +
                              "\n  計測の比べには使いません（フォルダ名に _view が付きます）。" +
                              "\n  途中で Play を止めてよいです（そのときは何も書き出しません）"
                            : "") +
                      "\n  1コマ " + (view ? "実時間（1倍速）" : stepSec.ToString("0.###") + " 秒") +
                      "\n  店の中 " + simMin + " 分（慣らし " + plan.warmupMinutes + " 分を含む）" +
                      "\n  細い通路: 番号 " + plan.trialAisleWidthIndex +
                      " / 混み具合: " + ExperimentPlan.CrowdName(plan.trialCrowd) +
                      " / 台数: " + plan.trialRobots + " / 種: " + plan.trialSeed +
                      "\n  停止点: " + useOffset.ToString("0.###") + " m" +
                      (setOffset
                        ? "（メニューが決めた値。この回だけ使い、.asset は "
                          + layout.deliveryOffsetFromAisleCenter.ToString("0.###") + " m のまま）"
                        : "（.asset の値）") +
                      "\n  置き場所: " + job.folder +
                      "\n  おわると自動で Play を抜けます");

            EditorApplication.EnterPlaymode();
        }

        /// <summary>フォルダ名に入れる停止点（0 → off000、0.15 → off015）</summary>
        static string OffsetTag(float offsetM)
        {
            return "off" + Mathf.RoundToInt(offsetM * 100f).ToString("000");
        }

        static StoreLayout CurrentLayout()
        {
            StoreBuilder builder = UnityEngine.Object.FindObjectOfType<StoreBuilder>();
            if (builder != null && builder.layout != null) return builder.layout;
            return AssetDatabase.LoadAssetAtPath<StoreLayout>(ServiceSimSetup.FamilyLayoutPath);
        }
    }

    /// <summary>メニューから回すときの、共通の下ごしらえ</summary>
    public static class ServiceSimRunSupport
    {
        public static bool Prepare(out ExperimentRunner runner, out ExperimentPlan plan)
        {
            runner = null;
            plan = null;

            if (EditorApplication.isPlayingOrWillChangePlaymode)
            {
                Debug.LogWarning("[ServiceSim] すでに Play 中です。止めてからもう一度押してください");
                return false;
            }

            Scene active = SceneManager.GetActiveScene();
            if (active.path != ServiceSimSetup.ScenePath)
            {
                if (!File.Exists(ServiceSimSetup.ScenePath))
                {
                    Debug.LogError("[ServiceSim] シーンがありません。先に ServiceSim/Setup を実行してください");
                    return false;
                }
                if (!EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()) return false;
                EditorSceneManager.OpenScene(ServiceSimSetup.ScenePath, OpenSceneMode.Single);
            }

            runner = UnityEngine.Object.FindObjectOfType<ExperimentRunner>();
            if (runner == null)
            {
                Debug.LogError("[ServiceSim] シーンに ExperimentRunner がありません。ServiceSim/Setup を実行してください");
                return false;
            }

            plan = AssetDatabase.LoadAssetAtPath<ExperimentPlan>(ServiceSimSetup.ExperimentPlanPath);
            if (plan == null) { Debug.LogError("[ServiceSim] ExperimentPlan が見つかりません"); return false; }

            if (runner.plan == null) runner.plan = plan;
            if (runner.service == null)
                runner.service = AssetDatabase.LoadAssetAtPath<ServiceSpec>(ServiceSimSetup.ServiceSpecPath);
            if (runner.robotPrefab == null)
                runner.robotPrefab = AssetDatabase.LoadAssetAtPath<GameObject>(
                    ServiceSimSetup.Root + "/Robot/ServingRobot.prefab");
            if (runner.robotSpec == null)
                runner.robotSpec = AssetDatabase.LoadAssetAtPath<RobotSpec>(ServiceSimSetup.RobotSpecPath);
            if (runner.simParams == null)
                runner.simParams = AssetDatabase.LoadAssetAtPath<SimParams>(ServiceSimSetup.SimParamsPath);

            // 実時間の上限と、版の覚え書きは **段階Bだけ** が入れる。
            // ここで 0・空に戻しておき、ほかのメニューの回し方を前と同じに保つ
            runner.maxWallSecondsPerRun = 0f;
            runner.unityCs = "";
            runner.pendingNote = "";
            return true;
        }

        /// <summary>Play を抜けたら runOnPlay を下ろす（手で Play しても回り出さないように）</summary>
        [InitializeOnLoadMethod]
        static void Hook()
        {
            EditorApplication.playModeStateChanged -= OnChanged;
            EditorApplication.playModeStateChanged += OnChanged;
        }

        static void OnChanged(PlayModeStateChange state)
        {
            if (state != PlayModeStateChange.EnteredEditMode) return;
            ExperimentRunner runner = UnityEngine.Object.FindObjectOfType<ExperimentRunner>();
            if (runner == null) return;
            if (!runner.runOnPlay && !runner.viewMode) return;
            runner.runOnPlay = false;
            runner.viewMode = false;      // 見る用は、その回だけ
            EditorUtility.SetDirty(runner);
        }
    }
}
