using UnityEngine;

namespace ServiceSim
{
    /// <summary>
    /// 店の回し方の数字。試算（haizen_sim.py の COMMON と STORES["family"]）と同じ値にそろえる。
    /// 店の運営に関する数字はすべてこの .asset に置く。
    ///
    /// 三角分布は（いちばん短い, いちばん多い, いちばん長い）の分（min）で持つ。
    /// </summary>
    [CreateAssetMenu(menuName = "ServiceSim/Service Spec", fileName = "ServiceSpec")]
    public class ServiceSpec : ScriptableObject
    {
        [Header("来客（STORES[\"family\"][\"arrivals\"]）")]
        [Tooltip("混み具合ごとの来客のペース（組/時）。[空いている, 普通, 混雑]")]
        public int[] arrivalsPerHourByCrowd = { 25, 35, 45 };

        [Tooltip("ピークの長さ（分）。1回の実行の長さ（STORES[\"family\"][\"peak_min\"]）")]
        public float peakMinutes = 120f;

        [Header("サイトの試算から読む値（v5 3章。SimParams が回すときだけ入れる）")]
        [Tooltip("その条件の来客のペース（組/時）。unity_conditions の arrivals_per_h。" +
                 "0 なら、これまでどおり arrivalsPerHourByCrowd（混み具合）を使う")]
        public int arrivalsPerHour;

        [Tooltip("動けなくなったロボットを、スタッフが動かす時間（秒）（common.rescue_s）")]
        public float rescueSec = 15f;

        [Tooltip("来客が終わったあと、全員が帰るのを待つ上限（分）（common.max_extra_min）")]
        public float maxExtraMin = 600f;

        [Header("組（Unity だけの値。試算に人数はない）")]
        [Tooltip("組の人数を選ぶ重み。[1人, 2人, 3人, 4人]")]
        public float[] groupSizeWeights = { 0.20f, 0.35f, 0.20f, 0.25f };

        [Header("案内・会計・呼び出し（COMMON）")]
        [Tooltip("案内にかかる時間（秒）。歩く時間は別（guide_s）")]
        public float guideSec = 45f;

        [Tooltip("会計の時間（秒）（pay_s）")]
        public float paySec = 90f;

        [Tooltip("呼び出し対応の時間（秒）。歩く時間は別（call_s）")]
        public float callSec = 30f;

        [Tooltip("レジの前の人数を数える半径（m）。記録だけに使う")]
        public float registerCrowdRadiusM = 2.0f;

        [Tooltip("会計はレジに立つ組の1人だけで済ませる（ほかの人は席で待つ）。" +
                 "外すと前のとおり、誰もレジに行かずスタッフだけで会計する（前後を比べるときに使う）")]
        public bool payAtRegisterByOne = true;

        [Tooltip("会計のとき、スタッフが立つ点からこれ以上離れていたら数える（m）。" +
                 "店員側に入れていないしるしなので、0 回になるはず")]
        public float registerPayOffSpotM = 0.1f;

        [Tooltip("1組あたりの呼び出しの起きやすさ（call_rate）。試算は2回ぶん 0.6/2 で判定")]
        public float callRate = 0.6f;

        [Header("料理（first_dishes・extra・fragile）")]
        [Tooltip("最初の注文：1皿めができるまで（分, 最短/最頻/最長）")]
        public Vector3 firstDish1CookMin = new Vector3(5f, 8f, 12f);

        [Tooltip("最初の注文：2皿めができるまで（分, 最短/最頻/最長）")]
        public Vector3 firstDish2CookMin = new Vector3(8f, 12f, 18f);

        [Tooltip("食事中のデザート・ドリンクの数の平均（fragile）")]
        public float fragilePerGroup = 0.5f;

        [Tooltip("デザート・ドリンクができるまで（分, 最短/最頻/最長）（fragile_cook）")]
        public Vector3 fragileCookMin = new Vector3(2f, 4f, 7f);

        [Tooltip("デザート・ドリンクをロボットも運ぶか。false なら人だけ（fragile_by）")]
        public bool fragileByRobot = true;

        [Tooltip("これを超えて届いた皿を「待たせた皿」とする（分）（cold_min）")]
        public float coldMin = 3f;

        [Header("運ぶ（COMMON）")]
        [Tooltip("ロボットが1走りで載せる料理の数（trays）。1 か 3")]
        public int trays = 3;

        [Tooltip("客がロボットから皿を取る時間（秒）（robot_handoff_s）")]
        public float robotHandoffSec = 25f;

        [Tooltip("スタッフが皿を置く時間（秒）（staff_place_s）")]
        public float staffPlaceSec = 10f;

        [Tooltip("料理ができてから、ロボットだけを待つ秒数（robot_first_wait_s）。" +
                 "このあいだスタッフは料理を持たず、案内・片付け・会計を続ける。" +
                 "過ぎたら、ロボットとスタッフのうち先に空いた方が運ぶ。" +
                 "0 にすると、はじめから先に空いた方が運ぶ（v2.7 より前と同じ）")]
        public float robotFirstWaitSec = 120f;

        [Header("食事（eat_min）")]
        [Tooltip("食事の長さ（分, 最短/最頻/最長）")]
        public Vector3 eatMin = new Vector3(20f, 30f, 45f);

        [Tooltip("1人あたりドリンクバーへ行く平均の回数（Unity だけの値）")]
        public float drinkTripsPerPerson = 1.5f;

        [Tooltip("ドリンクバーで過ごす時間（秒）（Unity だけの値）")]
        public float drinkBarSec = 20f;

        [Tooltip("1組あたりトイレへ行く平均の回数（Unity だけの値）")]
        public float toiletTripsPerGroup = 0.3f;

        [Tooltip("トイレの中で過ごす時間（秒）（Unity だけの値）")]
        public float toiletSec = 180f;

        [Header("片付け（COMMON・bus_trips）")]
        [Tooltip("卓の皿をまとめる時間（秒）（clear_s）")]
        public float clearSec = 40f;

        [Tooltip("卓を拭いてセットする時間（秒）（wipe_s）")]
        public float wipeSec = 40f;

        [Tooltip("下げ膳の往復回数（皿の量）（bus_trips）")]
        public int busTrips = 2;

        [Header("待合")]
        [Tooltip("待合に立たせる組の上限。あふれた組は店の外で待つ扱い（人として置かない）")]
        public int waitingVisibleMax = 8;

        [Header("あきらめて、やり直す（B1）")]
        [Tooltip("会計・片付けで止まって卓が空かない状態がこの秒数続いたら、卓を空ける")]
        public float tableStuckFreeSec = 600f;

        // ---- 読み出し ----

        public int ArrivalsPerHour(CrowdLevel crowd)
        {
            // サイトの条件で回すときは、混み具合を使わずその値をそのまま使う（v5 3章）
            if (arrivalsPerHour > 0) return arrivalsPerHour;
            if (arrivalsPerHourByCrowd == null || arrivalsPerHourByCrowd.Length == 0) return 35;
            int i = Mathf.Clamp((int)crowd, 0, arrivalsPerHourByCrowd.Length - 1);
            return arrivalsPerHourByCrowd[i];
        }

        /// <summary>三角分布（分）から秒を引く。試算の tri() と同じ</summary>
        public static float TriangularSec(System.Random rng, Vector3 lowModeHigh)
        {
            float lo = lowModeHigh.x, mode = lowModeHigh.y, hi = lowModeHigh.z;
            if (hi <= lo) return lo * 60f;
            mode = Mathf.Clamp(mode, lo, hi);
            double u = rng.NextDouble();
            double c = (mode - lo) / (double)(hi - lo);
            double v = u < c
                ? lo + System.Math.Sqrt(u * (hi - lo) * (mode - lo))
                : hi - System.Math.Sqrt((1.0 - u) * (hi - lo) * (hi - mode));
            return (float)v * 60f;
        }

        /// <summary>指数分布（秒）。試算の expovariate と同じ</summary>
        public static float ExponentialSec(System.Random rng, float ratePerSec)
        {
            if (ratePerSec <= 0f) return float.MaxValue;
            double u = rng.NextDouble();
            if (u <= 1e-12) u = 1e-12;
            return (float)(-System.Math.Log(u) / ratePerSec);
        }

        /// <summary>組の人数を重みで選ぶ</summary>
        public int PickGroupSize(System.Random rng)
        {
            float total = 0f;
            int n = groupSizeWeights != null ? groupSizeWeights.Length : 0;
            for (int i = 0; i < n; i++) total += Mathf.Max(0f, groupSizeWeights[i]);
            if (total <= 0f) return 2;

            float r = (float)rng.NextDouble() * total;
            for (int i = 0; i < n; i++)
            {
                r -= Mathf.Max(0f, groupSizeWeights[i]);
                if (r <= 0f) return i + 1;
            }
            return n;
        }

        /// <summary>平均 mean 回を、整数回に落とす（試算の fragile と同じやり方）</summary>
        public static int PickCount(System.Random rng, float mean)
        {
            int n = (int)mean;
            if (rng.NextDouble() < mean - n) n++;
            return n;
        }
    }
}
