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

public class CanvasManager3D : MonoBehaviour
{
    Dictionary<string, Point> points;
    Dictionary<string, Line> lines;
    List<Vector> vectors;
    List<Angle> angles;

    List<Constraint> constraints;
    public CanvasManager3D()
    {
        points = new Dictionary<string, Point>();
        lines = new Dictionary<string, Line>();
        vectors = new List<Vector>();
        angles = new List<Angle>();

        constraints = new List<Constraint>();
    }
    public void calculateDig()
    {
        Point first = null;
        if(points.Count != 0)
        {
            first = points.First().Value;
        }
        else
        {
            return ;
        }

        first.setPoint(1000, 0, 0);
        first.setState(State.confirm);

        HashSet<Point> usedPoint = new HashSet<Point>();
        Queue<Point> restPoint = new Queue<Point>();
        restPoint.Enqueue(first);

        bool flag = true;
        while(restPoint.Count != 0 && flag)
        {
            Point tmp = restPoint.Peek();

            foreach(Line l in tmp.lines)
            {
                Point p = l.getAnotherPoint(tmp);
                if(!restPoint.Contains(p) && !usedPoint.Contains(p))
                    restPoint.Enqueue(p);
            }

            if (tmp.getState() == State.confirm)
            {
                restPoint.Dequeue();
                usedPoint.Add(tmp);
            }
            else
            {
                int check = tmp.checkConstraint();
                if (check == 0)
                {
                    tmp.setState(State.confirm);
                    restPoint.Dequeue();
                    usedPoint.Add(tmp);
                }
                else if (check == 1)
                {
                    //calculate constaint
                    List<Constraint> calculates = new List<Constraint>();
                    foreach (Constraint c in tmp.constraints)
                    {
                        if (c.getState() == State.halfconfirm)
                        {
                            calculates.Add(c);
                        }
                    }

                    if (calculates.Count == 1)
                    {
                        calculates[0].calculateConstraint(tmp);
                    }
                    else
                    {
                        int count = 0;
                        bool isFinish = false;
                        while (count < 10000)
                        {
                            foreach (Constraint c in calculates)
                            {
                                c.tryCalculateConstraint(tmp);
                            }
                            if (checkConstraints(calculates))
                            {
                                isFinish = true;
                                foreach (Constraint c in calculates)
                                {
                                    c.setState(State.confirm);
                                }
                                break;
                            }
                            count++;
                        }
                        if (!isFinish)
                        {
                            foreach (Constraint c in calculates)
                            {
                                c.setState(State.halfconfirm);
                            }
                        }

                    }

                    restPoint.Dequeue();
                    restPoint.Enqueue(tmp);
                }
                else if (check == 2)
                {
                    //random
                    restPoint.Dequeue();
                    restPoint.Enqueue(tmp);
                }
                else
                {
                    restPoint.Dequeue();
                    restPoint.Enqueue(tmp);
                }
            }

            if(restPoint.Count == 0 && usedPoint.Count != points.Count)
            {
                foreach(var p in points)
                {
                    if (!usedPoint.Contains(p.Value))
                    {
                        restPoint.Enqueue(p.Value);
                        break;
                    }
                }
            }
        }

        foreach(Point p in usedPoint)
        {
            p.x -= 1000;
        }

        PrintPoints();
    }

    public void createPoint(string name = "")
    {
        if (name.Equals(""))
        {
            char n = 'A';
            while (!points.ContainsKey(n.ToString()))
            {
                n++;
            }
            name = n.ToString();
        }

        if (!points.ContainsKey(name))
        {
            Point p = new Point(0, 0, 0, name);
            points.Add(name, p);
        }
    }

    public void setPoint(string name, float x, float y, float z)
    {
        if (points.ContainsKey(name))
        {
            Point p = points[name];
            p.setPoint(x, y, z);
            p.update();
        }
    }

    public Point GetPoint(string name)
    {
        if (points.ContainsKey(name))
        {
            Point p = points[name];
            return p;
        }
        else
            return null;
    }

    public Vector3[] GetPoints()
    {
        Vector3[] tmp = new Vector3[points.Count];
        int i = 0;
        foreach (var p in points)
        {
            tmp[i] = new Vector3(p.Value.x, p.Value.y, p.Value.z);
            i++;
        }
        return tmp;
    }

    public void createLine(string n1, string n2, float length = 0)
    {
        Point p1 = null, p2 = null;

        points.TryGetValue(n1, out p1);
        points.TryGetValue(n2, out p2);

        if (p1 != null && p2 != null)
        {
            Line line = new Line(p1, p2);
            if(length != 0)
            {
                createConstraint(line, length, ConstraintType.LineLength);
            }
            string lineName = p1.name + p2.name;
            lines[lineName] = line;

            p1.addLine(line);
            p2.addLine(line);
        }
    }

    public void setLine(string n, float length)
    {
        Line l1 = null;

        lines.TryGetValue(n, out l1);

        if(l1 != null)
        {
            createConstraint(l1, length, ConstraintType.LineLength);
        }
    }

    public Line GetLine(string n)
    {
        Line l1 = null;

        lines.TryGetValue(n, out l1);

        return l1;
    }

    public List<Line> GetLines()
    {
        List<Line> tmp = new List<Line>();
        foreach (var l in lines)
        {
            tmp.Add(l.Value);
        }
        return tmp;
    }

    public void createConstraint(object obj1, object obj2, ConstraintType type)
    {
        Constraint constraint = new Constraint(type, obj1, obj2);
        constraints.Add(constraint);
    }

    void PrintPoints()
    {
        foreach(var p in points)
        {
            Point tmp = p.Value;
            Debug.Log(tmp.name + ":(" + tmp.x.ToString() + ", " + tmp.y.ToString() + ", " + tmp.z.ToString() + ")");
        }

        foreach(var l in lines)
        {
            Line tmp = l.Value;
            Debug.Log(l.Key + ":" + tmp.getLength());
        }
    }

    bool checkConstraints(List<Constraint> constraint)
    {
        bool flag = true;
        foreach(Constraint c in constraint)
        {
            if(Mathf.Abs(c.getConstraintValue()) > 0.000001f)
            {
                flag = false;
                break;
            }
        }
        return flag;
    }
}
