using System.Linq;
using TMPro;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Splines;

namespace space.chikalin.textdecal
{
/// <summary>
///
/// Should execute after <c>space.chikalin.textdecal.TextMeshProDecal</c> script
/// </summary>
[ExecuteAlways]
[DisallowMultipleComponent]
[DefaultExecutionOrder(-90)]
[AddComponentMenu("Mesh/TextMeshPro - Spline")]
#if UNITY_2021_2_OR_NEWER
[Icon("Packages/com.unity.splines/Editor/Resources/Icons/SplineComponent.png")]
#endif
[RequireComponent(typeof(SplineContainer), typeof(TMP_Text))]
public class TextMeshProSpline : MonoBehaviour
{
    private TMP_Text _text;
    private SplineContainer _spline;

    private Spline DefaultSpline(float3 start, float3 end)
    {
        var spline = new Spline
        {
            { start, TangentMode.Linear },
            { end, TangentMode.Linear }
        };
        return spline;
    }

    private void Awake()
    {
        _text = GetComponent<TMP_Text>();
        _spline = GetComponent<SplineContainer>();
    }

    private void OnEnable()
    {
        _text.OnPreRenderText += OnPreRenderText;
    }

    private void OnDisable()
    {
        _text.OnPreRenderText -= OnPreRenderText;
        _text.ClearMesh();
        _text.mesh.RecalculateBounds();
        _text.ForceMeshUpdate();
    }

    private void OnPreRenderText(TMP_TextInfo textInfo)
    {
        FitSpline(textInfo);
    }

    private void FitSpline(TMP_TextInfo textInfo)
    {
        FixSpline();

        var splineLength = _spline.CalculateLength();
        if (splineLength <= 0) return;

        for (var i = 0; i < textInfo.characterCount; i++)
        {
            if (!textInfo.characterInfo[i].isVisible)
                continue;

            var vertexIndex = textInfo.characterInfo[i].vertexIndex;
            var materialIndex = textInfo.characterInfo[i].materialReferenceIndex;
            var vertices = textInfo.meshInfo[materialIndex].vertices;
            var bl = vertices[vertexIndex + 0];
            var tr = vertices[vertexIndex + 2];
            var charCenter = bl + (tr - bl) / 2;
            Evaluate(splineLength, charCenter.x, out var position, out var xAxis, out var yAxis);
            xAxis = transform.InverseTransformDirection(math.normalize(xAxis));
            yAxis = transform.InverseTransformDirection(math.normalize(yAxis));
            position = transform.InverseTransformPoint(position);
            var newCenter = position + yAxis * charCenter.y;
            for (var v = 0; v < 4; v++)
            {
                var d = vertices[vertexIndex + v] - charCenter;
                vertices[vertexIndex + v] = newCenter + xAxis * d.x + yAxis * d.y;
            }
        }
    }

    private void FixSpline()
    {
        var textRectTransform = _text.rectTransform;
        if (_spline.Spline.Count >= 2) return;
        var rect = textRectTransform.rect;
        var position = textRectTransform.position;
        var start = new float3(rect.xMin, rect.yMin, position.z);
        var end = new float3(rect.xMax, rect.yMax, position.z);
        _spline.Spline = DefaultSpline(start, end);
    }

    private void Evaluate(float length, float xPos, out float3 position, out float3 xAxis, out float3 yAxis)
    {
        var s = (xPos / length) * transform.localScale.x + .5f; // centered
        _spline.Evaluate(math.clamp(s, 0.01f, .99f), out position, out xAxis, out yAxis);
    }

    private Quaternion ReflectRotation(Quaternion source, Vector3 normal)
    {
        return Quaternion.LookRotation(Vector3.Reflect(source * Vector3.forward, normal),
            Vector3.Reflect(source * Vector3.up, normal));
    }

#if UNITY_EDITOR
    [ExecuteAlways]
    [ContextMenu("Mirror")]
    public void Mirror()
    {
        var rectTransform = _text.rectTransform;
        rectTransform.GetPositionAndRotation(out var position, out var rotation);
        var max = position + _text.transform.right * rectTransform.rect.width * rectTransform.localScale.x;
        max.x = -max.x;
        var angles = rotation.eulerAngles;
        rotation = Quaternion.Euler(angles.x, 180 - (angles.y - 180), -angles.z);
        rectTransform.SetPositionAndRotation(max, rotation);

        var spline = _spline.Splines[0];
        var array = spline.Knots.ToArray();
        var lastX = array[^1].Position.x;
        var r = array.Length - 1;
        for (int i = 0; i < array.Length; i++)
        {
            var knot = array[i];
            knot.Position.x = -knot.Position.x + lastX;
            spline.SetKnot(r - i, knot);
        }
    }
#endif
    
#if UNITY_EDITOR
    private void OnValidate()
    {
        ForceUpdate();
    }
#endif

    [ExecuteAlways]
    public void ForceUpdate()
    {
        if (_text != null)
        {
            _text.ForceMeshUpdate();
        }
    }
}
}