using System;
using System.Collections.Generic;
using System.Linq;
using UniRx;
using UnityEngine;
using UnityEngine.UI;
using Random = UnityEngine.Random;

public enum CornResult
{
    Perfect,
    Good,
    Failure
}

public class CornManager : MonoBehaviour
{
    [SerializeField] private Transform cornSpawnTarget;
    [SerializeField] private Corn cornPrefab;
    [SerializeField] private GameObject spilledCornAnimationPrefab;
    [Space]
    [SerializeField] private int cornSpawnCount = 20;
    [SerializeField] private int maxFailedCornCount = 10;
    [SerializeField, ReadOnly] private float baseGrowSpeed = 1f;
    [SerializeField] private float coldGrowSpeed = .1f;
    [SerializeField] private float hotGrowSpeed = 2f;
    [SerializeField] private float baseCornPopTime = 5f;
    [SerializeField] private float cornBurntDuration = 5f;
    [SerializeField] private float cornBurntRandom = 1f;
    [SerializeField] private float cornSpillSpeed = 50f;
    [SerializeField] private float cornPopSpeed = 20f;
    [Space]
    [SerializeField, Range(0,1)] private float screenKoProbability;
    [SerializeField] private int maxScreenKoCount;

    public IReadOnlyReactiveProperty<CornResult> Result => result;
    private readonly ReactiveProperty<CornResult> result = new ReactiveProperty<CornResult>();

    public ReactiveProperty<GameState> GameState => gameState;
    private readonly ReactiveProperty<GameState> gameState = new ReactiveProperty<GameState>();
    
    private Corn[] cornArray;
    private bool isHot;
    private bool isCompleted;
    private readonly List<IObservable<CornCondition>> cornConditions = new List<IObservable<CornCondition>>();
    private readonly Subject<(CornCondition cond, Vector3 pos)> cornSpilledSubject = new Subject<(CornCondition, Vector3)>();
    private readonly ReactiveProperty<int> missCount = new ReactiveProperty<int>();
    private readonly ReactiveProperty<float> cornGrowSpeed = new FloatReactiveProperty(0f);
    private readonly CompositeDisposable compositeDisposable = new CompositeDisposable();

#if DEVELOPMENT_BUILD || UNITY_EDITOR
    [Space]
    [SerializeField] private Slider cornCountSlider;
    [SerializeField] private Slider faildCountSlider;
    [SerializeField] private Slider coldGrowSpeedSlider;
    [SerializeField] private Slider hotGrowSpeedSlider;
    [SerializeField] private Slider popTimeSlider;
    [SerializeField] private Slider burntDurationSlider;
    [SerializeField] private Slider burntRandomSlider;
    [SerializeField] private Slider spilledSpeedSlider;
    [SerializeField] private Slider popSpeedSlider;
#endif

    private void Awake()
    {
        cornArray = new Corn[cornSpawnCount];
        result.AddTo(this);
        cornGrowSpeed.AddTo(this);
        compositeDisposable.AddTo(this);
    }

    private void Start()
    {
        // コーン飛び出し制御
        // 5フレームごとに2回など演出の回数を制御
        cornSpilledSubject
            .Where(x => x.cond != CornCondition.Seed) // Seedは演出なし
            .Where(_ => Random.value < screenKoProbability) // 演出を出す確率
            .BatchFrame(5, FrameCountType.EndOfFrame) // 指定フレーム分まとめる
            .Subscribe(x =>
            {
                foreach (var item in x.RandomChoose(maxScreenKoCount))
                {
                    SpawnSpilledAnimation(item.cond, item.pos);
                }
            }).AddTo(this);

        // ミス数上限で失敗
        // missCount
        //     .Where(x => x == maxFailedCornCount)
        //     .Subscribe(_ =>
        //     {
        //         SetResult(CornResult.Failure);
        //     }).AddTo(this);

#if DEVELOPMENT_BUILD || UNITY_EDITOR
        cornCountSlider.wholeNumbers = true;
        cornCountSlider.value = cornSpawnCount;
        faildCountSlider.wholeNumbers = true;
        faildCountSlider.value = maxFailedCornCount;
        coldGrowSpeedSlider.value = coldGrowSpeed;
        hotGrowSpeedSlider.value = hotGrowSpeed;
        popTimeSlider.value = baseCornPopTime;
        burntDurationSlider.value = cornBurntDuration;
        burntRandomSlider.value = cornBurntRandom;
        spilledSpeedSlider.value = cornSpillSpeed;
        popSpeedSlider.value = cornPopSpeed;
        
        cornCountSlider.OnValueChangedAsObservable().Subscribe(x => cornSpawnCount = (int)x).AddTo(this);
        faildCountSlider.OnValueChangedAsObservable().Subscribe(x => maxFailedCornCount = (int)x).AddTo(this);
        coldGrowSpeedSlider.OnValueChangedAsObservable().Subscribe(x => coldGrowSpeed = x).AddTo(this);
        hotGrowSpeedSlider.OnValueChangedAsObservable().Subscribe(x => hotGrowSpeed = x).AddTo(this);
        popTimeSlider.OnValueChangedAsObservable().Subscribe(x => baseCornPopTime = x).AddTo(this);
        burntDurationSlider.OnValueChangedAsObservable().Subscribe(x => cornBurntDuration = x).AddTo(this);
        burntRandomSlider.OnValueChangedAsObservable().Subscribe(x => cornBurntRandom = x).AddTo(this);
        spilledSpeedSlider.OnValueChangedAsObservable().Subscribe(x => cornSpillSpeed = x).AddTo(this);
        popSpeedSlider.OnValueChangedAsObservable().Subscribe(x => cornPopSpeed = x).AddTo(this);
#endif
    }

    public void RespawnCorn()
    {
        cornGrowSpeed.Value = 0f;
        isHot = false;
        missCount.Value = 0;
        isCompleted = false;
        compositeDisposable.Clear();
        cornConditions.Clear();
        for (int i = 0; i < cornArray.Length; i++)
        {
            if (cornArray[i] != null)
            {
                Destroy(cornArray[i].gameObject);
            }
        }
        cornArray = new Corn[cornSpawnCount];
        
        // コーン生成
        var shuffledArray = Enumerable.Range(0, cornSpawnCount).OrderBy(_ => Random.value).ToArray();
        for (int i = 0; i < cornSpawnCount; i++)
        {
            var corn = Instantiate(cornPrefab, cornSpawnTarget);
            cornArray[i] = corn;
            corn.SetCornProperty( CornPopParameters.CornPopTimeArray[shuffledArray[i]], CornPopParameters.CornBurntTimeArray[shuffledArray[i]], cornSpillSpeed, cornPopSpeed);
            // 進行速度の変更時、コーン速度変更
            cornGrowSpeed.TakeWhile(_ => !isCompleted).Subscribe(x =>
            {
                // コーンが弾けた後はRedの場合のみ焦げ進行する
                if (corn.Condition.Value == CornCondition.Simple && !isHot)
                {
                    corn.ChangeGrowSpeed(0f);
                    return;
                }
                corn.ChangeGrowSpeed(x);
                corn.ChangeHotStatus(isHot);
            }).AddTo(compositeDisposable);

            gameState
                .Where(x => x == global::GameState.Playing)
                .Subscribe(x =>
            {
                corn.StartCornDance();
            }).AddTo(compositeDisposable);
            
            
            corn.Condition
                .Where(_ => !isCompleted) // 結果確定後通知抑制
                .Pairwise()
                .Subscribe(condition =>
                {
                    switch (condition.Current)
                    {
                        case CornCondition.Seed:
                            break;
                        case CornCondition.Simple:
                            // コーンが弾けたとき、GrowSpeedを再設定する
                            // ThermalCondition.Hot以外では焦げ進行なし
                            if (!isHot)
                            {
                                corn.ChangeGrowSpeed(0f);
                            }
                            break;
                        case CornCondition.Burnt:
                            // ミス数加算
                            missCount.Value++;
                            break;
                        default:
                            throw new ArgumentOutOfRangeException(nameof(condition), condition, null);
                    }
                }).AddTo(compositeDisposable);

            corn.Spilled
                .Subscribe(_ => cornSpilledSubject.OnNext((corn.Condition.Value, corn.transform.position))).AddTo(compositeDisposable);

            cornConditions.Add(corn.Condition);
        }

        var cornConditionsObservable = Observable.CombineLatest(cornConditions).Publish().RefCount();

        // すべてのコーンがSeedではなくなった場合
        cornConditionsObservable
            // .Do(x => { Debug.Log($"done:{x.Count(y => y != CornCondition.Seed):00} fail:{x.Count(y => y == CornCondition.Burnt || y == CornCondition.Spilled):00}");})
            .FirstOrDefault(x => !x.Contains(CornCondition.Seed))
            .Subscribe(x =>
            {
                if (x == null) return;
                if (x.Count(c => c == CornCondition.Simple) == x.Count) // すべてのコーンがPopped
                {
                    SetResult(CornResult.Perfect);
                }
                else if (x.Count(c => c == CornCondition.Burnt) >= maxFailedCornCount) // コーンが一定数焦げた
                {
                    SetResult(CornResult.Failure);
                }
                else
                {
                    SetResult(CornResult.Good);
                }
            }).AddTo(compositeDisposable);
    }

    public void ChangeGrowSpeed(ThermalCondition condition)
    {
        switch (condition)
        {
            case ThermalCondition.Cold:
                isHot = false;
                cornGrowSpeed.Value = coldGrowSpeed;
                break;
            case ThermalCondition.Yellow:
                isHot = false;
                cornGrowSpeed.Value = baseGrowSpeed;
                break;
            case ThermalCondition.Hot:
                isHot = true;
                cornGrowSpeed.Value = hotGrowSpeed;
                break;
            default:
                throw new ArgumentOutOfRangeException(nameof(condition), condition, null);
        }
    }

    public void SetCornsActive(bool active)
    {
        cornSpawnTarget.gameObject.SetActive(active);
    }

    private void SpawnSpilledAnimation(CornCondition condition, Vector3 position)
    {
        // 画面KO再生
        var animation = Instantiate(spilledCornAnimationPrefab, position, Quaternion.identity);
        // KOコーンスキン変更
        animation.GetComponent<PopcornStrikeEffect>().ChangeSkin(condition);
        VibrationManager.Instance.PlayVibrationDoubleStrong();
        this.CallWaitForSeconds(1.5f, () => Destroy(animation));
    }

    private void SetResult(CornResult resultValue)
    {
        cornGrowSpeed.Value = 0f;
        isCompleted = true;
        result.SetValueAndForceNotify(resultValue);
    }
}
