using System;
using System.Collections.Generic;
using UnityEngine;

namespace FairyGUI
{
    /// <summary>
    /// 滑动手势。你可以通过onBegin+onMove+onEnd关心整个滑动过程，也可以只使用onAction关注最后的滑动结果。滑动结果包括方向和加速度，可以从position和velocity获得。
    /// 注意onAction仅当滑动超过一定距离(actionDistance)时才触发。
    /// </summary>
    public class SwipeGesture : EventDispatcher
    {
        /// <summary>
        /// 
        /// </summary>
        public GObject host { get; private set; }

        /// <summary>
        /// 当手指开始扫动时派发该事件。
        /// </summary>
        public EventListener onBegin { get; private set; }
        /// <summary>
        /// 手指离开屏幕时派发该事件。
        /// </summary>
        public EventListener onEnd { get; private set; }
        /// <summary>
        /// 手指在滑动时派发该事件。
        /// </summary>
        public EventListener onMove { get; private set; }
        /// <summary>
        /// 当手指从按下到离开经过的距离大于actionDistance时派发该事件。
        /// </summary>
        public EventListener onAction { get; private set; }

        /// <summary>
        /// 手指离开时的加速度
        /// </summary>
        public Vector2 velocity;

        /// <summary>
        /// 你可以在onBegin事件中设置这个值，那个后续将根据手指移动的距离修改这个值。如果不设置，那position初始为(0,0)，反映手指扫过的距离。
        /// </summary>
        public Vector2 position;

        /// <summary>
        /// 移动的变化值
        /// </summary>
        public Vector2 delta;

        /// <summary>
        /// The min distance to fire onAction event
        /// 派发onAction事件的最小距离。如果手指扫过的距离少于此值，onAction不会触发（但onEnd仍然会派发）
        /// </summary>
        public int actionDistance;

        /// <summary>
        /// 是否把变化量强制为整数。默认true。
        /// </summary>
        public bool snapping;

        Vector2 _startPoint;
        Vector2 _lastPoint;
        float _time;
        bool _started;
        bool _touchBegan;

        public static int ACTION_DISTANCE = 200;

        public SwipeGesture(GObject host)
        {
            this.host = host;
            actionDistance = ACTION_DISTANCE;
            snapping = true;
            Enable(true);

            onBegin = new EventListener(this, "onSwipeBegin");
            onEnd = new EventListener(this, "onSwipeEnd");
            onMove = new EventListener(this, "onSwipeMove");
            onAction = new EventListener(this, "onnSwipeAction");
        }

        public void Dispose()
        {
            Enable(false);
            host = null;
        }

        public void Enable(bool value)
        {
            if (value)
            {
                if (host == GRoot.inst)
                {
                    Stage.inst.onTouchBegin.Add(__touchBegin);
                    Stage.inst.onTouchMove.Add(__touchMove);
                    Stage.inst.onTouchEnd.Add(__touchEnd);
                }
                else
                {
                    host.onTouchBegin.Add(__touchBegin);
                    host.onTouchMove.Add(__touchMove);
                    host.onTouchEnd.Add(__touchEnd);
                }
            }
            else
            {
                _started = false;
                _touchBegan = false;
                if (host == GRoot.inst)
                {
                    Stage.inst.onTouchBegin.Remove(__touchBegin);
                    Stage.inst.onTouchMove.Remove(__touchMove);
                    Stage.inst.onTouchEnd.Remove(__touchEnd);
                }
                else
                {
                    host.onTouchBegin.Remove(__touchBegin);
                    host.onTouchMove.Remove(__touchMove);
                    host.onTouchEnd.Remove(__touchEnd);
                }
            }
        }

        void __touchBegin(EventContext context)
        {
            if (Stage.inst.touchCount > 1)
            {
                _touchBegan = false;
                if (_started)
                {
                    _started = false;
                    onEnd.Call(context.inputEvent);
                }
                return;
            }

            InputEvent evt = context.inputEvent;
            _startPoint = _lastPoint = host.GlobalToLocal(new Vector2(evt.x, evt.y));
            _lastPoint = _startPoint;

            _time = Time.unscaledTime;
            _started = false;
            velocity = Vector2.zero;
            position = Vector2.zero;
            _touchBegan = true;

            context.CaptureTouch();
        }

        void __touchMove(EventContext context)
        {
            if (!_touchBegan || Stage.inst.touchCount > 1)
                return;

            InputEvent evt = context.inputEvent;
            Vector2 pt = host.GlobalToLocal(new Vector2(evt.x, evt.y));
            delta = pt - _lastPoint;
            if (snapping)
            {
                delta.x = Mathf.Round(delta.x);
                delta.y = Mathf.Round(delta.y);
                if (delta.x == 0 && delta.y == 0)
                    return;
            }

            float deltaTime = Time.unscaledDeltaTime;
            float elapsed = (Time.unscaledTime - _time) * 60 - 1;
            if (elapsed > 1) //速度衰减
                velocity = velocity * Mathf.Pow(0.833f, elapsed);
            velocity = Vector3.Lerp(velocity, delta / deltaTime, deltaTime * 10);
            _time = Time.unscaledTime;
            position += delta;
            _lastPoint = pt;

            if (!_started)
            { //灵敏度检查，为了和点击区分
                int sensitivity;
                if (Stage.touchScreen)
                    sensitivity = UIConfig.touchDragSensitivity;
                else
                    sensitivity = 5;

                if (Mathf.Abs(delta.x) < sensitivity && Mathf.Abs(delta.y) < sensitivity)
                    return;

                _started = true;
                onBegin.Call(evt);
            }

            onMove.Call(evt);
        }

        void __touchEnd(EventContext context)
        {
            _touchBegan = false;

            if (!_started)
                return;

            _started = false;

            InputEvent evt = context.inputEvent;
            Vector2 pt = host.GlobalToLocal(new Vector2(evt.x, evt.y));
            delta = pt - _lastPoint;
            if (snapping)
            {
                delta.x = Mathf.Round(delta.x);
                delta.y = Mathf.Round(delta.y);
            }
            position += delta;

            //更新速度
            float elapsed = (Time.unscaledTime - _time) * 60 - 1;
            if (elapsed > 1)
                velocity = velocity * Mathf.Pow(0.833f, elapsed);
            if (snapping)
            {
                velocity.x = Mathf.Round(velocity.x);
                velocity.y = Mathf.Round(velocity.y);
            }
            onEnd.Call(evt);

            pt -= _startPoint;
            if (Mathf.Abs(pt.x) > actionDistance || Mathf.Abs(pt.y) > actionDistance)
                onAction.Call(evt);
        }
    }
}
