using System;
using System.Collections.Generic;
using UniRx;
using UniRx.Triggers;
using UnityEngine;
using Random = UnityEngine.Random;

public enum CustomerMovingType
{
    WalkSide,
    WalkSideEat,
    WalkCenter,
    TouchNotice,
    TouchQuestion,
    StayBackOrder,
    StayBack,
    WalkBack,
    WalkBackHalf,
    WalkFront,
    WalkFrontEat,
}

public enum CustomerState
{
    Walk,
    WalkShop,
    CenterStop,
    TouchNotice,
    TouchQuestion,
    Wait,
    Order,
    Leave,
    SadLeave,
    EatingLeave,
}

public class CustomerController : MonoBehaviour
{
    public int OrderCount;
    public CustomerType CustomerType;
    public CustomerAnimator CustomerPrefab;
    public bool IsPause;
    
    // 歩道の幅(min,max)
    [SerializeField] private float walkSideTopPosition;
    [SerializeField] private float walkSideMidPos;
    [SerializeField] private float walkSideBottomPos;
    [SerializeField] private float walkSideLeavePos;
    // 立ち止まる範囲
    [SerializeField] private float stopPosision = 0f;
    [SerializeField] private float stopPositionRange = 2f;
    // オーダー待ちは複数箇所用意
    private List<Transform> orderPositions = new List<Transform>();
    [SerializeField] private List<Transform> waitPositions = new List<Transform>();
    private int orderPositionIndex;
    // 購入ポジ
    [SerializeField] private float orderPosision = .5f;
    [SerializeField] private float orderPositionRange = 1f;
    [SerializeField] private float waitOrderPosision = .2f;
    // 歩行速度
    [SerializeField] private float walkSideSpeed = 1.3f; // 1.3f
    [SerializeField] private float walkFrontBackSpeed = 1.0f; // 1.0f
    private static readonly float leftEndPosision = -10f;
    private Vector3 beginPos;
    private Vector3 wayPoint;
    private bool isMoveOverride;
    private List<(CustomerMovingType type, float duration)> moves = new List<(CustomerMovingType, float)>();
    private float speed = 0f;
    private float completedDuration;
    private float durationDelta;

    public float DurationDelta => durationDelta;

    // 左右どちらから出るか
    private float walkSideDirection;
    public IReadOnlyReactiveProperty<CustomerMovingType> CurrentMovingType => currentMovingType;
    private readonly ReactiveProperty<CustomerMovingType> currentMovingType = new ReactiveProperty<CustomerMovingType>();
    public IReadOnlyReactiveProperty<CustomerMovingType> MoveEndObservable => moveEndSubject;
    private readonly ReactiveProperty<CustomerMovingType> moveEndSubject = new ReactiveProperty<CustomerMovingType>();
    public IReadOnlyReactiveProperty<CustomerState> State => state;
    private readonly ReactiveProperty<CustomerState> state = new ReactiveProperty<CustomerState>();
    
    // 向き
    public IReadOnlyReactiveProperty<bool> IsDefaultSide => isDefaultSide;
    private readonly ReactiveProperty<bool> isDefaultSide = new ReactiveProperty<bool>();
    public IReadOnlyReactiveProperty<ProductStockData> WantFlavor => wantFlavor;
    private readonly ReactiveProperty<ProductStockData> wantFlavor = new ReactiveProperty<ProductStockData>();
    public IReadOnlyReactiveProperty<bool> IsComplain => isComplain;
    private readonly ReactiveProperty<bool> isComplain = new ReactiveProperty<bool>();

    public IObservable<Unit> Purchased => purchased;
    private readonly Subject<Unit> purchased = new Subject<Unit>();
    public IObservable<Unit> TappedObservable => tappedSubject;
    private Subject<Unit> tappedSubject = new Subject<Unit>();

    private void Start()
    {
        currentMovingType.AddTo(this);
        moveEndSubject.AddTo(this);
        state.AddTo(this);
        isDefaultSide.AddTo(this);
        purchased.AddTo(this);
        
        durationDelta = 0f;
#if UNITY_EDITOR
        // Observable.Interval(TimeSpan.FromSeconds(.1f)).Subscribe(_ =>
        // {
        //     if (Vector3.Distance(transform.localPosition, wayPoint) < .09f)
        //     {
        //         Debug.Log($"{transform.localPosition}, {wayPoint}, {currentMovingType.Value}, {moveEndSubject.Value} {GetHashCode()}");
        //     }
        // }).AddTo(this);
#endif
        this.UpdateAsObservable().Subscribe(_ =>
        {
            if (IsPause)
            {
                return;
            }
            var localPosition = transform.localPosition;
            localPosition = Vector2.MoveTowards(localPosition, wayPoint, speed * Time.deltaTime);
            // キャラの奥行き描画順のため、y方向に対応したz軸を設定
            localPosition = localPosition + Vector3.forward * (localPosition.y + Mathf.Abs(walkSideBottomPos));
            transform.localPosition = localPosition;
            if (Vector2.Distance(localPosition, wayPoint) < .01f)
            {
                if (durationDelta < completedDuration)
                {
                    durationDelta += Time.deltaTime;
                    return;
                }
                moveEndSubject.Value = currentMovingType.Value;
                if (isMoveOverride)
                {
                    isMoveOverride = false;
                    return;
                }
                if (moves.Count > 0)
                {
                    var move = moves[0];
                    moves.RemoveAt(0);
                    SetMove(move.type, move.duration);
                }
            }
        }).AddTo(this);
    }

    public void Setup(List<Transform> newOrderPositions)
    {
        walkSideDirection = Mathf.Sign(Random.value - .5f);
        var pos = Vector3.zero;
        pos.x = leftEndPosision * walkSideDirection;
        pos.y = Random.Range(walkSideMidPos, walkSideBottomPos);
        Setup(newOrderPositions, pos);
    }
    
    public void SetupCenter(List<Transform> newOrderPositions)
    {
        walkSideDirection = Mathf.Sign(Random.value - .5f);
        var pos = Vector3.zero;
        pos.x = Random.Range(-stopPositionRange, stopPositionRange) + stopPosision;
        pos.y = walkSideMidPos;
        Setup(newOrderPositions, pos);
    }

    private void Setup(List<Transform> newOrderPositions, Vector2 position)
    {
        orderPositions = newOrderPositions;
        beginPos = position;
        transform.localPosition = beginPos;
        currentMovingType.Value = CustomerMovingType.StayBack;
        moveEndSubject.Value = CustomerMovingType.StayBack;
        isMoveOverride = false;
        SetWayPoint(beginPos);
    }

    public void ChangeCustomerState(CustomerState state)
    {
        this.state.Value = state;
        // Debug.Log(state);
        switch (state)
        {
            case CustomerState.Walk:
                ForceSetMove(CustomerMovingType.WalkCenter);
                // 数秒立ち止まる
                AddMove(CustomerMovingType.StayBack, 2f);
                AddMove(CustomerMovingType.WalkSide);
                break;
            case CustomerState.WalkShop:
                ForceSetMove(CustomerMovingType.WalkCenter);
                AddMove(CustomerMovingType.WalkSide);
                break;
            case CustomerState.CenterStop:
                // 中央付近で立ち止まる
                ForceSetMove(CustomerMovingType.WalkCenter);
                AddMove(CustomerMovingType.StayBack);
                break;
            case CustomerState.TouchNotice:
                ForceSetMove(CustomerMovingType.TouchNotice, 1f);
                break;
            case CustomerState.TouchQuestion:
                ForceSetMove(CustomerMovingType.TouchQuestion, 1.2f);
                AddMove(CustomerMovingType.StayBack);
                AddMove(CustomerMovingType.WalkSide);
                break;
            case CustomerState.Wait:
                // 店に向かう&順番待ち
                // 近くの場合その場に待機
                // まだ遠い場合待機場所に移動
                ForceSetMove(CustomerMovingType.WalkBackHalf);
                AddMove(CustomerMovingType.StayBack);
                break;
            case CustomerState.Order:
                // 受付まで行く
                AddMove(CustomerMovingType.WalkBack);
                AddMove(CustomerMovingType.StayBackOrder);
                break;
            case CustomerState.Leave:
                ForceSetMove(CustomerMovingType.WalkSide);
                break;
            case CustomerState.SadLeave:
                AddMove(CustomerMovingType.WalkFront);
                AddMove(CustomerMovingType.WalkSide);
                break;
            case CustomerState.EatingLeave:
                AddMove(CustomerMovingType.WalkFrontEat);
                AddMove(CustomerMovingType.WalkSideEat);
                break;
            default:
                throw new ArgumentOutOfRangeException(nameof(state), state, null);
        }
    }

    private void AddMove(CustomerMovingType type, float duration = 0f)
    {
        moves.Add((type, duration));
    }

    private void ForceSetMove(CustomerMovingType type, float duration = 0f)
    {
        moves.Clear();
        isMoveOverride = true;
        SetMove(type, duration);
    }

    private void SetMove(CustomerMovingType type, float duration = 0f)
    {
        if (currentMovingType.Value == type)
        {
            return;
        }
        completedDuration = duration;
        durationDelta = 0f;
        switch (type)
        {
            case CustomerMovingType.WalkSide:
                SetWayPoint(new Vector3(-leftEndPosision * walkSideDirection, transform.localPosition.y));
                speed = walkSideSpeed;
                break;
            case CustomerMovingType.WalkSideEat:
                SetWayPoint(new Vector3(-leftEndPosision * walkSideDirection, transform.localPosition.y));
                speed = walkSideSpeed;
                break;
            case CustomerMovingType.WalkCenter:
                if (Mathf.Abs(transform.localPosition.x - stopPosision) <= stopPositionRange)
                {
                    SetWayPoint(new Vector3(transform.localPosition.x, beginPos.y));
                }
                else
                {
                    SetWayPoint(new Vector3(stopPosision - Random.value * stopPositionRange * walkSideDirection, beginPos.y));
                }
                speed = walkSideSpeed;
                break;
            case CustomerMovingType.TouchNotice:
            case CustomerMovingType.TouchQuestion:
                SetWayPoint(transform.localPosition);
                break;
            case CustomerMovingType.StayBackOrder:
            case CustomerMovingType.StayBack:
                speed = 0f;
                break;
            case CustomerMovingType.WalkBack:
                // 購入場所に行く
                SetWayPoint(orderPositions[orderPositionIndex].localPosition);
                speed = walkFrontBackSpeed;
                break;
            case CustomerMovingType.WalkBackHalf:
                // 待機列に入る
                if (Mathf.Abs(transform.localPosition.x - stopPosision) <= stopPositionRange)
                {
                    SetWayPoint(new Vector3(transform.localPosition.x, waitOrderPosision));
                }
                else
                {
                    SetWayPoint(new Vector3(stopPosision - Random.value * stopPositionRange * walkSideDirection, waitOrderPosision));
                }
                speed = walkFrontBackSpeed;
                break;
            case CustomerMovingType.WalkFront:
                SetWayPoint(new Vector3(wayPoint.x, Random.Range(walkSideLeavePos, walkSideTopPosition)));
                speed = walkFrontBackSpeed;
                break;
            case CustomerMovingType.WalkFrontEat:
                SetWayPoint(new Vector3(wayPoint.x, Random.Range(walkSideLeavePos, walkSideTopPosition)));
                speed = walkFrontBackSpeed;
                break;
            default:
                throw new ArgumentOutOfRangeException(nameof(type), type, null);
        }
        currentMovingType.Value = type;
    }

    private void SetWayPoint(Vector3 point)
    {
        // Debug.Log($"SetWayPoint {transform.localPosition}, {wayPoint}, {point}");
        wayPoint = point;
        isDefaultSide.Value = (wayPoint - transform.localPosition).x >= 0;
    }

    public void SetOrderPosition(int index)
    {
        orderPositionIndex = index;
    }
    
    public void Tapped()
    {
        tappedSubject.OnNext(Unit.Default);
    }
    
    public void SetPurchased()
    {
        purchased.OnNext(Unit.Default);
    }
    public void SetWantFlavor(ProductStockData stockData)
    {
        wantFlavor.Value = stockData;
    }

    public void SetComplain()
    {
        isComplain.SetValueAndForceNotify(true);
    }
}