using System.Collections.Generic;
using System.Linq;
using DG.Tweening;
using UnityEngine;
using UnityEngine.UI;

[CreateAssetMenu(fileName = "TweenSet", menuName = "TweenSet")]
public class TweenSet : ScriptableObject
{
    public class Args
    {
        public List<Graphic> Graphics;
        public SpriteRenderer SpriteRenderer;
        public CanvasGroup CanvasGroup;
        public Material Material;
        public Vector3? TargetPos;
        public bool IsLocalMovement;
        public bool IsRelativeMovement;
        public Vector3 DirectMoveScale = Vector3.one;
        public bool UnitSizeOnStart;
        public float InnerKeysOffsetForMove;
        public float? CustomDuration;

        public Graphic Graphic
        {
            set
            {
                if (Graphics == null)
                    Graphics = new List<Graphic>();

                if (Graphics.Any())
                    Graphics[0] = value;
                else
                    Graphics.Add(value);
            }
        }
    }

    public float Duration = 1;
    public UpdateType Type = UpdateType.Normal;

    public AxisCurveSet DirectMove = AxisCurveSet.EaseInOut(0, 0, 1, 0);
    public AxisCurveSet CustomMove = AxisCurveSet.EaseInOut(0, 0, 1, 1);
    public bool BlendCustomMove;
    public AxisCurveSet Scale = AxisCurveSet.EaseInOut(0, 0, 1, 0);
    public AxisCurveSet Rotation = AxisCurveSet.EaseInOut(0, 0, 1, 0);
    public AnimationCurve Fade = AnimationCurve.EaseInOut(0, 0, 1, 1);

    public bool SetStartAlpha;
    public float StartAlpha = 0f;
    public bool TargetFadeEnabled;
    public float TargetFadeAlpha = 1f;
    public int Loops = 1;
    public LoopType LoopType;

    public CanvasGroup CustomTargetCanvasGroup;

    public Sequence Apply(Transform t, Args args = null)
    {
        if (args == null)
            args = new Args();

        if (CustomTargetCanvasGroup && !args.CanvasGroup)
            args.CanvasGroup = CustomTargetCanvasGroup;

        var duration = args.CustomDuration ?? Duration;

        var seq = DOTween.Sequence();
        seq.SetTarget(t);

        if (args.UnitSizeOnStart)
            t.localScale = Vector3.one;
        
        seq.Append(t.DOBlendableLocalRotateBy(new Vector3(1, 0, 0), duration)
            .SetEase(Rotation.X));
        seq.Join(t.DOBlendableLocalRotateBy(new Vector3(0, 1, 0), duration)
            .SetEase(Rotation.Y));
        seq.Join(t.DOBlendableLocalRotateBy(new Vector3(0, 0, 1), duration)
            .SetEase(Rotation.Z));
        seq.Join(t.DOBlendableScaleBy(Vector3.right, duration)
            .SetEase(Scale.X));
        seq.Join(t.DOBlendableScaleBy(Vector3.up, duration)
            .SetEase(Scale.Y));
        seq.Join(t.DOBlendableScaleBy(Vector3.forward, duration)
            .SetEase(Scale.Z));
        seq.Join(t.DOBlendableLocalMoveBy(Vector3.right * args.DirectMoveScale.x, duration)
            .SetEase(DirectMove.X));
        seq.Join(t.DOBlendableLocalMoveBy(Vector3.up * args.DirectMoveScale.y, duration)
            .SetEase(DirectMove.Y));
        seq.Join(t.DOBlendableLocalMoveBy(Vector3.forward * args.DirectMoveScale.z, duration)
            .SetEase(DirectMove.Z));

        if (args.TargetPos != null)
        {
            var customMoveX = CustomMove.X;
            var customMoveY = CustomMove.Y;
            var customMoveZ = CustomMove.Z;
            if (args.InnerKeysOffsetForMove > 0)
            {
                customMoveX = CustomMove.X.OffsetInnerKeys(args.InnerKeysOffsetForMove);
                customMoveY = CustomMove.Y.OffsetInnerKeys(args.InnerKeysOffsetForMove);
                customMoveZ = CustomMove.Z.OffsetInnerKeys(args.InnerKeysOffsetForMove);
            }

            var delta = t.position - args.TargetPos.Value;

            var twMoveX = args.IsLocalMovement
                ? t.DOLocalMoveX(args.TargetPos.Value.x, duration)
                : BlendCustomMove
                    ? t.DOBlendableMoveBy(delta.x * Vector3.left, duration)
                    : t.DOMoveX(args.TargetPos.Value.x, duration);
            var twMoveY = args.IsLocalMovement
                ? t.DOLocalMoveY(args.TargetPos.Value.y, duration)
                : BlendCustomMove
                    ? t.DOBlendableMoveBy(delta.y * Vector3.down, duration)
                    : t.DOMoveY(args.TargetPos.Value.y, duration);
            var twMoveZ = args.IsLocalMovement
                ? t.DOLocalMoveZ(args.TargetPos.Value.z, duration)
                : BlendCustomMove
                    ? t.DOBlendableMoveBy(delta.z * Vector3.forward, duration)
                    : t.DOMoveZ(args.TargetPos.Value.z, duration);

            twMoveX.SetEase(customMoveX);
            twMoveY.SetEase(customMoveY);
            twMoveZ.SetEase(customMoveZ);

            if (args.IsRelativeMovement)
            {
                twMoveX.SetRelative();
                twMoveY.SetRelative();
            }
            seq.Join(twMoveX);
            seq.Join(twMoveY);
        }

        if (TargetFadeEnabled)
        {
            if (args.Graphics != null)
            {
                if (SetStartAlpha)
                {
                    foreach (var g in args.Graphics)
                        g.color = g.color.SetAlpha(StartAlpha);
                }

                if (TargetFadeEnabled)
                {
                    foreach (var g in args.Graphics)
                        seq.Join(g.DOFade(TargetFadeAlpha, duration).SetEase(Fade));
                }
            }

            if (args.SpriteRenderer)
            {
                if (SetStartAlpha)
                    args.SpriteRenderer.color = args.SpriteRenderer.color.SetAlpha(StartAlpha);

                if (TargetFadeEnabled)
                    seq.Join(args.SpriteRenderer.DOFade(TargetFadeAlpha, duration).SetEase(Fade));
            }

            if (args.Material)
            {
                if (SetStartAlpha)
                    args.Material.color = args.Material.color.SetAlpha(StartAlpha);

                if (TargetFadeEnabled)
                    seq.Join(args.Material.DOFade(TargetFadeAlpha, duration).SetEase(Fade));
            }
        }

        seq.SetUpdate(Type);
        if (Loops != 1)
            seq.SetLoops(Loops, LoopType);
        return seq;
    }
}