using UnityEngine;

namespace ServiceSim
{
    /// <summary>
    /// 道の決め方（依頼 v9 の 2 章）。
    ///   MapPlanner … ロボットが地図（サイトの {名前}_map.json）の上で自分で道を引く。NavMesh は使わない
    ///   SiteRoutes … サイトの routes の点を順にたどる（v5b〜v8b と同じ。比べるために残す）
    /// </summary>
    public enum PathMode
    {
        MapPlanner,
        SiteRoutes
    }

    /// <summary>
    /// 配膳ロボットの数字。ロボットに関する数字はすべてこの .asset に置く。
    /// </summary>
    [CreateAssetMenu(menuName = "ServiceSim/Robot Spec", fileName = "RobotSpec")]
    public class RobotSpec : ScriptableObject
    {
        [Header("大きさ")]
        [Tooltip("直径（m）")]
        public float diameter = 0.5f;

        [Tooltip("高さ（m）")]
        public float height = 1.2f;

        [Header("走り")]
        [Tooltip("最高速度（m/秒）")]
        public float maxSpeed = 0.6f;

        [Tooltip("加速（m/秒^2）")]
        public float acceleration = 1.0f;

        [Tooltip("減速（m/秒^2）")]
        public float deceleration = 1.0f;

        [Tooltip("向きを変える速さ（度/秒）。仮の値には無かったので追加した")]
        public float turnSpeedDegPerSec = 180f;

        [Header("前のセンサー")]
        [Tooltip("進む向きの前、どこまで見るか（m）")]
        public float sensorRange = 0.6f;

        [Tooltip("センサーの幅（m）。ロボットの直径に合わせる")]
        public float sensorWidth = 0.5f;

        [Tooltip("センサーの高さ（m）")]
        public float sensorHeight = 1.2f;

        [Header("止まる・動き出す")]
        [Tooltip("前がこの秒数だけ空いたら動き出す（秒）")]
        public float resumeAfterClearSec = 1.0f;

        [Tooltip("止まってこの秒数たったら経路を引き直す（秒）")]
        public float replanAfterStoppedSec = 10f;

        [Tooltip("この秒数動けなかったら「動けなくなった」と記録する（秒）")]
        public float stuckAfterStoppedSec = 60f;

        [Tooltip("使っていない（v5 3章で、受け渡し口へ移すのをやめた）。" +
                 "動けなくなったらスタッフを呼び、ServiceSpec.rescueSec（サイトの rescue_s）動かしてもらう")]
        public float stuckRecoveryWaitSec = 15f;

        [Tooltip("1往復がこの秒数を超えたら、受け渡し口へ置き直して往復を打ち切る（秒）。無限に止まらないための保険")]
        public float maxTripSec = 600f;

        [Header("通路の譲り合い")]
        [Tooltip("行き先の細い通路にもう1台のロボットがいたら、太い通路側の入口で待つ。場面2の撮影では切る")]
        public bool aisleYielding = true;

        [Header("台どうしの譲り合い（Unity だけの仮の値。サイトには無い）")]
        [Tooltip("入口で順番を待つ上限（秒）。これを過ぎたら譲らずに進む。" +
                 "人がロボットを待つ上限（PeopleSpec.robotYieldMaxSec 20 秒）と同じ考え方")]
        public float aisleWaitMaxSec = 20f;

        [Tooltip("入口で待つとき、入口の点から通路の外へ下がる距離（m）。" +
                 "入口の点そのものに立つと、中の台が出られなくなる")]
        public float aisleWaitSetbackM = 0.9f;

        [Tooltip("入口で待つとき、中の台が出てくる道をふさがないよう横へずれる距離（m）")]
        public float aisleWaitSideM = 0.6f;

        [Tooltip("もう1台にふさがれてから、譲り始めるまでの秒数（秒）。" +
                 "すれ違うだけで譲り合わないよう、少し待ってから決める")]
        public float robotYieldAfterSec = 2f;

        [Tooltip("譲って下がったあと、相手を待つ上限（秒）。これを過ぎたら戻って進む")]
        public float robotYieldMaxSec = 20f;

        [Tooltip("相手が通り過ぎたとみなす、外周どうしの空き（m）")]
        public float robotYieldClearM = 0.6f;

        [Tooltip("下がる先が見つからないときに、その場から後ろへ下がる距離（m）")]
        public float robotYieldBackM = 0.9f;

        [Header("受け渡し口の前の通り方（Unity だけの決まり。サイトには無い）")]
        [Tooltip("戻る台が、受け渡し口の前の点を通らず、その少し下を通って右の待機場所へ抜けるか。" +
                 "線が通れる床に乗らないときは、使わずに今までどおり受け渡し口の前へ戻る")]
        public bool useReturnLine = true;

        [Tooltip("戻りの線を、受け渡し口の前の点より何 m 下（−z）に通すか")]
        public float passReturnOffsetM = 1.0f;

        [Tooltip("戻りの線を、出ていく台の線（受け渡し口の前の点を通る線）から" +
                 "中心どうしで何 m 以上離すか")]
        public float passReturnSeparationM = 1.0f;

        [Tooltip("戻りの線を、ドリンクバーの並ぶ点から何 m 以上離すか")]
        public float passReturnKeepAwayM = 0.6f;

        [Tooltip("戻りの線が通れる床に乗っているかを調べる刻み（m）")]
        public float passReturnCheckStepM = 0.05f;

        [Header("同じ通路に2台を集めない（Unity だけの決まり。サイトには無い）")]
        [Tooltip("料理を受け持つとき、ほかの台が向かっている・中にいる細い通路（店の形の aisles）の" +
                 "卓を後に回すか。後に回せる料理が無ければ、通路の入口ではなく待機場所で待つ。" +
                 "外すと、これまでどおり近い卓から順に受け持つ")]
        public bool oneRobotPerAisle = true;

        [Header("譲る順番と、道から横へどく（Unity だけの仮の値。サイトには無い）")]
        [Tooltip("譲る順番の決まりを使うか。外すと、前のとおり互いに相手が動くのを待つ")]
        public bool yieldOrderEnabled = true;

        [Tooltip("譲る側が、相手の道（サイトの routes の線）から横へどく最小の距離（m）")]
        public float yieldSideClearM = 0.6f;

        [Tooltip("どく点を探す広さ（m）。相手の道から横へこの距離まで探す")]
        public float yieldSideSearchM = 2.5f;

        [Tooltip("どく点を探す刻み（m）")]
        public float yieldSideStepM = 0.2f;

        [Tooltip("どいて相手を待つ上限（秒）。過ぎたら元の道へ戻って進む")]
        public float yieldSideWaitMaxSec = 30f;

        [Tooltip("スタッフが譲るかを決めるとき、ロボットの「これから進む道」" +
                 "（サイトの routes の線の、ロボットの今の位置から先）を何 m 分見るか。" +
                 "この中に、横 yieldSideClearM＋体の半径 以内で入っているときだけ譲る。" +
                 "ただのすれ違い（近くにいるだけ）では譲らない（v6b）")]
        public float yieldPathAheadM = 2.0f;

        [Header("角の止まりを数える範囲（Unity だけの仮の値。記録だけ）")]
        [Tooltip("受け渡し口と客席をつなぐ角。x の下限（m）")]
        public float cornerMinX = 10.5f;

        [Tooltip("同じく x の上限（m）")]
        public float cornerMaxX = 13.5f;

        [Tooltip("同じく z の下限（m）")]
        public float cornerMinZ = 3.5f;

        [Tooltip("同じく z の上限（m）")]
        public float cornerMaxZ = 5.8f;

        [Header("卓での止まり")]
        [Tooltip("卓に着いたら止まる秒数（秒）。客が料理を取る時間。「止まった」には数えない")]
        public float tableDwellSec = 25f;

        [Header("経路をたどる（仮の値には無かったので追加した）")]
        [Tooltip("行き先にこれだけ近づいたら着いたとみなす（m）")]
        public float arriveRadius = 0.25f;

        [Tooltip("経路の角にこれだけ近づいたら次の角へ進む（m）")]
        public float cornerReachDistance = 0.12f;

        [Tooltip("これより速度が小さければ「止まっている」とみなす（m/秒）")]
        public float stoppedSpeedEpsilon = 0.02f;

        [Header("止まったときに測る左右の空き")]
        [Tooltip("左右を見る高さ（m）。椅子（0.45 m）に当たる高さにする")]
        public float clearanceProbeHeight = 0.3f;

        [Tooltip("左右を見る距離（m）。これより遠いものは見ない")]
        public float clearanceProbeRange = 3.0f;

        [Tooltip("道が引けないとき、ふさいでいるものを探す半径（m）")]
        public float pathBlockSearchRadius = 2.0f;

        [Tooltip("NavMeshAgent の譲り合いの優先度。小さいほど譲らない。人より小さくして人がよけるようにする")]
        public int avoidancePriority = 0;

        [Tooltip("経路を引き直しても見つからないとき、次に試すまでの間（秒）")]
        public float repathRetrySec = 1.0f;

        [Header("途中までの経路から出る（B7）")]
        [Tooltip("止まっているあいだの「道が引けていなかった秒数（no_path_s）」がこれを超えたら、" +
                 "repathRetrySec を待たずに強く引き直す（秒）")]
        public float noPathForceReplanSec = 10f;

        [Tooltip("強く引き直してもまだ途中までしか引けないとき、いちど通路の入口を目指して出る")]
        public bool escapeToAisleEntranceOnPartialPath = true;

        public float Radius { get { return diameter * 0.5f; } }

        // ---- 譲る順番（1か所にまとめる）----

        /// <summary>
        /// 譲る順番。**小さいほど先に通る**（譲られる側）。
        ///   ロボット（0）＞ スタッフ（1）＞ 客（2）
        ///
        /// 立ち往生を助けに行くスタッフは、この順番の外（PersonRobotYield.ignoreRobot）。
        /// その台には近づかないと仕事ができないので、今までどおりよけない。
        /// </summary>
        public const int RankRobot = 0;
        public const int RankStaff = 1;
        public const int RankGuest = 2;

        /// <summary>その人の順番</summary>
        public static int RankOf(PersonKind kind)
        {
            return kind == PersonKind.Staff ? RankStaff : RankGuest;
        }

        /// <summary>その人はロボットに譲るか（順番が後ろなら譲る）</summary>
        public static bool YieldsToRobot(PersonKind kind)
        {
            return RankOf(kind) > RankRobot;
        }

        /// <summary>受け渡し口と客席をつなぐ角の中か（記録のため）</summary>
        public bool InCorner(Vector3 p)
        {
            return p.x >= cornerMinX && p.x <= cornerMaxX &&
                   p.z >= cornerMinZ && p.z <= cornerMaxZ;
        }
        [Header("道順（v5b・サイトから受け取る）")]
        [Tooltip("サイトの routes（曲がり角の点の並び）をたどるか。" +
                 "外すと、今までどおり行き先まで Unity の道探しで直接行く（比べるとき用）")]
        public bool followSiteRoutes = true;

        [Tooltip("道順の点に着いたとみなす距離（m）。最後の点（止まる位置）だけは arriveRadius を使う")]
        public float waypointReachDistance = 0.25f;

        [Tooltip("道順の最後の点（止まる位置）を、焼いた面へ寄せるときに探す半径（m）。" +
                 "サイトが通れる所に置いているので、寄せない（0.1 m 以下）。" +
                 "この半径で面に乗らなければ広げて引き直し、報告に回数を出す")]
        public float routePointSnapM = 0.1f;

        // ================= 依頼 v9：地図の上で自分で道を引く =================

        [Header("道の決め方（依頼 v9 の 2 章）")]
        [Tooltip("MapPlanner … 地図の上でロボットが自分で道を引く（新しい・既定）。" +
                 "SiteRoutes … サイトの routes の点をたどる（v8b までと同じ。比べるとき用）。" +
                 "戻りの線（useReturnLine）・横へどく点（yieldOrderEnabled）・角の箱（cornerMin/Max）は " +
                 "SiteRoutes のときだけ働く")]
        public PathMode pathMode = PathMode.MapPlanner;

        [Tooltip("通れる幅の道だけのマス（free_class 1）を通るときの重さの倍。" +
                 "狭い所は必要な所だけ通らせるため（目安 3 倍）")]
        public float narrowCellCost = 3f;

        [Tooltip("通路の真ん中寄りを走らせる強さ。1 歩の重さに " +
                 "centerWeight × max(0, preferClearM − clear) / preferClearM を足す（エイイチの決定）")]
        public float centerWeight = 2f;

        [Tooltip("障害物からこれだけ離れていれば、真ん中寄りの重さを足さない（m）。" +
                 "1.2 m の通路ならちょうど真ん中（エイイチの決定）")]
        public float preferClearM = 0.6f;

        [Tooltip("道を引き直すのは、1 台につきこの秒数に 1 回まで（秒）")]
        public float replanMinIntervalSec = 0.5f;

        [Tooltip("1 回の道探しで広げるマスの数の上限。超えたら引けないことにする。" +
                 "いつまでも探して回が止まらないようにするための保険")]
        public int planMaxExpand = 200000;

        [Tooltip("ロボットがいま立っている所が地図では通れないマス（0）のとき、" +
                 "近くの通れるマスを探す半径（m）。" +
                 "NavMesh は体の半径 0.25 m で焼いてあるのに free_class は道の幅で削ってあるので、" +
                 "通路の入口で待つ点・横へどいた点は 0 になる。地図は塗り直さず、出発点だけ寄せる")]
        public float mapSnapM = 1.0f;

        [Tooltip("曲がり角だけに縮めるとき、1 本の近道の長さの上限（m）。" +
                 "長すぎる近道を試すと道探しが重くなる")]
        public float planSimplifyMaxM = 6f;

        [Header("人や物で前がふさがったとき（依頼 v9 の 3 章）")]
        [Tooltip("止まってこの秒数たっても前が空かなければ、回り込む道を探す（秒。エイイチの決定）。" +
                 "引けなければ待ち、この秒数ごとに探し直す")]
        public float detourAfterSec = 3f;

        [Tooltip("回り込む道を探すとき、通れないマスとして置く人・ロボットを探す半径（m）")]
        public float detourNearM = 3f;

        [Tooltip("回り込む道と人のあいだに足す余裕（m）。" +
                 "人の半径＋ロボットの半径＋この値 を通れないマスにする")]
        public float detourPersonMarginM = 0.1f;

        [Tooltip("回り込む道が、もとの残りの道のこの倍＋detourExtraAddM より長ければ取らずに待つ")]
        public float detourMaxFactor = 2f;

        [Tooltip("同じく、足し算のぶん（m）")]
        public float detourExtraAddM = 5f;

        [Header("ロボットどうし（依頼 v9 の 4 章・エイイチの決定）")]
        [Tooltip("相手の「先の道」を何 m 分見るか（m）")]
        public float robotPathAheadM = 4f;

        [Tooltip("優先が高い台の先の道から、2 台の半径の和＋この値 以内のマスを重くする（m）")]
        public float robotPathCostMarginM = 0.2f;

        [Tooltip("重くする強さ（1 歩の重さに足す）。相手の道とかぶらない道があればそちらを選ばせる")]
        public float robotPathCostAdd = 6f;

        [Tooltip("よける場所は、相手の先の道から 2 台の半径の和＋この値 以上離れていること（m）")]
        public float robotYieldSeparationM = 0.1f;

        [Tooltip("よける場所を探す広さ（m）。自分のまわりこの距離まで探す")]
        public float robotYieldSpotSearchM = 2.5f;

        [Tooltip("優先が高い台がこの距離まで来て、先の道がかぶるときに引き直す（m）")]
        public float robotReplanNearM = 3f;

        [Tooltip("2 台が向かい合ってこの秒数止まったら、優先が低い方がよける場所へ動く（秒）")]
        public float headOnStuckSec = 10f;

        [Tooltip("「向かい合っている」と見る、2 台の向きの内積の上限。" +
                 "−1 が真正面。−0.5 は 120 度より正面寄り（依頼に数字が無かったのでここで決めた）")]
        public float headOnDotMax = -0.5f;

        [Header("見せる（依頼 v9 の 6 章。測る回でも出してよい）")]
        [Tooltip("いま引いている道を床の上に細い線で出すか")]
        public bool showPathLine = true;

        [Tooltip("線の太さ（m）")]
        public float pathLineWidthM = 0.04f;

        [Tooltip("線を床から浮かせる高さ（m）。床の塗りより上に出す")]
        public float pathLineHeightM = 0.04f;

        /// <summary>MapPlanner で走るか（依頼 v9）</summary>
        public bool UseMapPlanner { get { return pathMode == PathMode.MapPlanner; } }

        /// <summary>道の引き方の数字を GridPlanner に渡す形にする</summary>
        public GridPlanner.Settings PlannerSettings()
        {
            GridPlanner.Settings s = new GridPlanner.Settings();
            s.narrowCellCost = narrowCellCost;
            s.centerWeight = centerWeight;
            s.preferClearM = preferClearM;
            s.maxExpand = planMaxExpand;
            s.snapM = mapSnapM;
            s.simplifyMaxM = planSimplifyMaxM;
            return s;
        }
    }
}
