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

namespace Dreamteck.Splines.IO
{
    public class SplineParser 
    {
        protected string fileName = "";
        public string name
        {
            get { return fileName; }
        }

        private System.Globalization.CultureInfo culture = new System.Globalization.CultureInfo("en-US");
        private System.Globalization.NumberStyles style = System.Globalization.NumberStyles.Any;

        internal class Transformation
        {
            protected static Matrix4x4 matrix = new Matrix4x4();

            internal static void ResetMatrix()
            {
                matrix.SetTRS(Vector3.zero, Quaternion.identity, Vector3.one);
            }

            internal virtual void Push()
            {

            }

            internal static void Apply(SplinePoint[] points)
            {
                for (int i = 0; i < points.Length; i++)
                {
                    SplinePoint p = points[i];
                    p.position = matrix.MultiplyPoint(p.position);
                    p.tangent = matrix.MultiplyPoint(p.tangent);
                    p.tangent2 = matrix.MultiplyPoint(p.tangent2);
                    points[i] = p;
                }
            }
        }

        internal class Translate : Transformation
        {
            private Vector2 offset = Vector2.zero;
            public Translate(Vector2 o)
            {
                offset = o;
            }

            internal override void Push()
            {
                Matrix4x4 translate = new Matrix4x4();
                translate.SetTRS(new Vector2(offset.x, -offset.y), Quaternion.identity, Vector3.one);
                matrix = matrix * translate;
            }
        }

        internal class Rotate : Transformation
        {
            private float angle = 0f;
            public Rotate(float a)
            {
                angle = a;
            }

            internal override void Push()
            {
                Matrix4x4 rotate = new Matrix4x4();
                rotate.SetTRS(Vector3.zero, Quaternion.AngleAxis(angle, Vector3.back), Vector3.one);
                matrix = matrix * rotate;
            }
        }

        internal class Scale : Transformation
        {
            private Vector2 multiplier = Vector2.one;
            public Scale(Vector2 s)
            {
                multiplier = s;
            }

            internal override void Push()
            {
                Matrix4x4 scale = new Matrix4x4();
                scale.SetTRS(Vector3.zero, Quaternion.identity, multiplier);
                matrix = matrix * scale;
            }
        }

        internal class SkewX : Transformation
        {
            private float amount = 0f;
            public SkewX(float a)
            {
                amount = a;
            }

            internal override void Push()
            {
                Matrix4x4 skew = new Matrix4x4();
                skew[0, 0] = 1.0f;
                skew[1, 1] = 1.0f;
                skew[2, 2] = 1.0f;
                skew[3, 3] = 1.0f;
                skew[0, 1] = Mathf.Tan(-amount * Mathf.Deg2Rad);
                matrix = matrix * skew;
            }
        }

        internal class SkewY : Transformation
        {
            private float amount = 0f;
            public SkewY(float a)
            {
                amount = a;
            }

            internal override void Push()
            {
                Matrix4x4 skew = new Matrix4x4();
                skew[0, 0] = 1.0f;
                skew[1, 1] = 1.0f;
                skew[2, 2] = 1.0f;
                skew[3, 3] = 1.0f;
                skew[1, 0] = Mathf.Tan(-amount * Mathf.Deg2Rad);
                matrix = matrix *skew;
            }
        }

        internal class MatrixTransform : Transformation
        {
            private Matrix4x4 transformMatrix = new Matrix4x4();

            public MatrixTransform(float a, float b, float c, float d, float e, float f)
            { 
                transformMatrix.SetRow(0, new Vector4(a, c, 0f, e));
                transformMatrix.SetRow(1, new Vector4(b, d, 0f, -f));
                transformMatrix.SetRow(2, new Vector4(0f, 0f, 1f, 0f));
                transformMatrix.SetRow(3, new Vector4(0f, 0f, 0f, 1f));
            }

            internal override void Push()
            {
                matrix = matrix * transformMatrix;
            }
        }


        internal class SplineDefinition
        {
            internal string name = "";
            internal Spline.Type type = Spline.Type.Linear;
            internal List<SplinePoint> points = new List<SplinePoint>();
            internal bool closed = false;

            internal int pointCount
            {
                get { return points.Count; }
            }

            internal Vector3 position = Vector3.zero;
            internal Vector3 tangent = Vector3.zero;
            internal Vector3 tangent2 = Vector3.zero;
            internal Vector3 normal = Vector3.back;
            internal float size = 1f;
            internal Color color = Color.white;

            internal SplineDefinition(string n, Spline.Type t)
            {
                name = n;
                type = t;
            }

            internal SplineDefinition(string n, Spline spline)
            {
                name = n;
                type = spline.type;
                closed = spline.isClosed;
                points = new List<SplinePoint>(spline.points);
            }

            internal SplinePoint GetLastPoint()
            {
                if (points.Count == 0) return new SplinePoint();
                return points[points.Count - 1];
            }

            internal void SetLastPoint(SplinePoint point)
            {
                if (points.Count == 0) return;
                points[points.Count - 1] = point;
            }

            internal void CreateClosingPoint()
            {
                SplinePoint p = new SplinePoint(points[0]);
                points.Add(p);
            }

            internal void CreateSmooth()
            {
                points.Add(new SplinePoint(position, tangent, normal, size, color));
            }

            internal void CreateBroken()
            {
                SplinePoint point = new SplinePoint(new SplinePoint(position, tangent, normal, size, color));
                point.type = SplinePoint.Type.Broken;
                point.SetTangent2Position(point.position);
                point.normal = normal;
                point.color = color;
                point.size = size;
                points.Add(point);
            }

            internal void CreateLinear()
            {
                tangent = position;
                CreateSmooth();
            }

            internal SplineComputer CreateSplineComputer(Vector3 position, Quaternion rotation)
            {
                GameObject go = new GameObject(name);
                go.transform.position = position;
                go.transform.rotation = rotation;
                SplineComputer computer = go.AddComponent<SplineComputer>();
#if UNITY_EDITOR
                if(Application.isPlaying) computer.ResampleTransform();
#endif
                computer.type = type;
                if(closed)
                {
                    if (points[0].type == SplinePoint.Type.Broken) points[0].SetTangentPosition(GetLastPoint().tangent2);
                }
                computer.SetPoints(points.ToArray(), SplineComputer.Space.Local);
                if (closed) computer.Close();
                return computer;
            }

            internal Spline CreateSpline()
            {
                Spline spline = new Spline(type);
                spline.points = points.ToArray();
                if (closed) spline.Close();
                return spline;
            }

            internal void Transform(List<Transformation> trs)
            {
                SplinePoint[] p = points.ToArray();
                Transformation.ResetMatrix();
                foreach(Transformation t in trs) t.Push();
                Transformation.Apply(p);
                for (int i = 0; i < p.Length; i++) points[i] = p[i];
                SplinePoint[] debugPoints = new SplinePoint[1];
                debugPoints[0] = new SplinePoint();
                Transformation.Apply(debugPoints);
            }
        }

        internal SplineDefinition buffer = null;

        internal Vector2[] ParseVector2(string coord)
        {
            List<float> list = ParseFloatArray(coord.Substring(1));
            int count = list.Count / 2;
            if (count == 0)
            {
                Debug.Log("Error in " + coord);
                return new Vector2[] { Vector2.zero };
            }
            Vector2[] vectors = new Vector2[count];
            for (int i = 0; i < count; i++)
            {
                vectors[i] = new Vector2(list[0 + i * 2], -list[1 + i * 2]);
            }
            return vectors;
        }

        internal float[] ParseFloat(string coord)
        {
            List<float> list = ParseFloatArray(coord.Substring(1));
            if (list.Count < 1)
            {
                Debug.Log("Error in " + coord);
                return new float[] { 0f };
            }
            return list.ToArray();
        }

        internal List<float> ParseFloatArray(string content)
        {
            string accumulated = "";
            List<float> list = new List<float>();
            foreach (char c in content)
            {
                if (c == ',' || c == '-' || char.IsWhiteSpace(c) || (accumulated.Contains(".") && c == '.'))
                {
                    if (!IsWHiteSpace(accumulated))
                    {
                        float parsed = 0f;
                        float.TryParse(accumulated, style, culture, out parsed);
                        list.Add(parsed);
                        accumulated = "";
                        if (c == '-') accumulated = "-";
                        if (c == '.') accumulated = "0.";
                        continue;
                    }
                }
                if (!char.IsWhiteSpace(c)) accumulated += c;
            }
            if (!IsWHiteSpace(accumulated))
            {
                float p = 0f;
                float.TryParse(accumulated, style, culture, out p);
                list.Add(p);
            }
            return list;
        }

        public bool IsWHiteSpace(string s)
        {
            foreach (char c in s)
            {
                if (!char.IsWhiteSpace(c))
                {
                    return false;
                }
            }
            return true;
        }
    }
}