using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
using UnityEngine.UI;
using System;
using MathNet.Numerics.LinearAlgebra;
using MathNet.Numerics.LinearAlgebra.Double;

public class PathRenderer : MonoBehaviour
{
    [SerializeField]
    Square square;
    [SerializeField]
    GameObject PathColliderPrefab;
    [SerializeField]
    Camera playercamera;
    [SerializeField]
    LevelManager levelManager;
    [SerializeField]
    BackgroundMovement background;
    [SerializeField]
    LogScaleSlider velocityslider;

    LineRenderer _pathRenderer;
    float _originalPathColliderY;

    public BackgroundMovement Background 
    {
        get 
        {
            return background;
        }
        set
        {
            background = value;
        }
    }

    // Start is called before the first frame update
    void Start()
    {
        _originalPathColliderY = PathColliderPrefab.transform.localScale.y / 2;

        _pathRenderer = GetComponent<LineRenderer>();
        _ResetPaths();
    }

    // Update is called once per frame
    void Update()
    {
        if (Input.GetMouseButtonDown(1) && levelManager.player.isInertial())
        {
            RaycastHit hit;
            var ray = Camera.main.ScreenPointToRay(Input.mousePosition);

            if (Physics.Raycast(ray, out hit))
            {
                ray = playercamera.ViewportPointToRay(hit.textureCoord);
                if (Physics.Raycast(ray, out hit))
                {
                    if (Input.GetKey(KeyCode.LeftShift))
                    {
                        _DrawMorePath(hit.point);
                    }
                    else
                    {
                        _DrawOnePath(hit.point);
                    }
                }
            }
            /*if (Physics.Raycast(ray, out hit))
            {
                if (Input.GetKey(KeyCode.LeftShift))
                {
                    _DrawMorePath(hit.point);
                }
                else
                {
                    _DrawOnePath(hit.point);
                }
            }*/
        }
    }

    private void _DrawOnePath(Vector3 point)
    {
        _ResetPaths();
        square.pathList[0] = transform.localPosition;
        square.pathVelocity[0] = 0.0f;
        var tmp = transform.InverseTransformPoint(point);
        square.pathList[1] = new Vector3(tmp.x, tmp.y, 0.0f);
        square.pathVelocity[1] = velocityslider.GetLogScaleValue();
        _pathRenderer.SetPositions(square.pathList.ToArray());
        _InstantiatePathCollider(0);
    }

    private void _ResetPaths()
    {
        _pathRenderer.positionCount = 2;
        square.pathList.Clear();
        square.pathVelocity.Clear();
        square.pathList.Add(transform.localPosition);
        square.pathVelocity.Add(0.0f);
        square.pathList.Add(transform.localPosition);
        square.pathVelocity.Add(0.0f);
        _pathRenderer.SetPositions(square.pathList.ToArray());
        for (int i = 0; i < transform.childCount; i++)
        {
            Destroy(transform.GetChild(i).gameObject);
        }
    }

    private void _DrawMorePath(Vector3 point)
    {
        if (square.pathList.Count == 0)
        {
            _DrawOnePath(point);
        }
        else
        {
            var tmp = transform.InverseTransformPoint(point);
            square.pathList.Add(new Vector3(tmp.x, tmp.y, 0.0f));
            square.pathVelocity.Add(velocityslider.GetLogScaleValue());
            _pathRenderer.positionCount = square.pathList.Count();
            _pathRenderer.SetPositions(square.pathList.ToArray());
            _InstantiatePathCollider(square.pathList.Count() - 2);
        }
    }

    public void PathClear()
    {
        _ResetPaths();
    }

    /// <summary>
    /// 현재 설정된 경로를 반환.
    /// 0번은 내 위치
    /// x y 가 공간 z 가 시간
    /// </summary>
    public List<Vector3> PathPositionsXY
    {
        get
        {
            return square.pathList;
        }
    }
    /// <summary>
    /// 현재 설정된 경로를 반환.
    /// 0번은 현재위치 //패트롤 고려
    /// x z 가 공간 y 가 시간
    /// </summary>
    public List<Vector3> PathPositionsXZ
    {
        get
        {
            List<Vector3> list = new List<Vector3>();

            foreach(var a in square.pathList)
            {
                //xy -> xz
                list.Add(new Vector3(a.x, 0, a.y));
            }

            return list;
        }
    }
    /// <summary>
    /// 현재 설정된 경로의 속력을 반환.
    /// 0번은 0.0f
    /// </summary>
    public List<float> PathVelocities
    {
        get
        {
            return new List<float>(square.pathVelocity);
        }
    }
    private void _InstantiatePathCollider(int n)
    {
        var _pathCollider = Instantiate(PathColliderPrefab, transform);
        _pathCollider.name = "PathCollider-" + n;
        _pathCollider.tag = "path";
        _pathCollider.transform.localScale = new Vector3(0.1f, _originalPathColliderY, 0);
        _pathCollider.transform.localEulerAngles = new Vector3(0, 0,
            (float)Constants.RadianToDegree(Mathf.PI / 2 +
                Mathf.Atan
                (square.GetTangent(
                    square.GetNthPath(n)))));

        float _newY = square.GetNthPath(n).magnitude;
        _pathCollider.transform.localScale = new Vector3(0.1f, _newY * _originalPathColliderY, 0);

        _pathCollider.transform.localPosition = (square.pathList[n] + square.pathList[n + 1]) / 2;
    }

    public IEnumerator _StartMovingPath(int finalPathNum)
    {
        int i = 0;
        var u = levelManager.player.v;
        int tinterval = Constants.alphatinterval;
        VectorBuilder<double> V = Vector<double>.Build;

        while (i <= finalPathNum)
        {
            Vector3 startpos = new Vector3(levelManager.player.transform.position.x, 0, levelManager.player.transform.position.z);
            Vector3 dest = new Vector3(square.GetNthPath(i).x, 0f, square.GetNthPath(i).y);
            Vector3 acceleration = dest.normalized;

            var v = square.GetPathVelocity(i);

            var atmp = (float)(v * Constants.c);

            double[] vtmp = { (atmp * (acceleration.x / acceleration.magnitude)), 0.0, (atmp * (acceleration.z / acceleration.magnitude)) };
            double[] xtmp = { dest.magnitude / (v * Constants.c), dest.x, dest.z };
            var deltavnaive = V.DenseOfArray(vtmp);
            var deltaxnaive = V.DenseOfArray(xtmp);

            double[] tmp = {Constants.c * Constants.Gamma(deltavnaive.L2Norm()),
                      deltavnaive[0] * Constants.Gamma(deltavnaive.L2Norm()),
                      deltavnaive[2] * Constants.Gamma(deltavnaive.L2Norm())};

            var deltav = V.DenseOfArray(tmp);

            Vector<double> deltax = deltaxnaive;

            if (u.magnitude > 0.0f)
            {
                deltav = Constants.BoostMatrix(-u) * deltav;
                deltax = Constants.BoostMatrix(-u) * deltaxnaive;
            }

            var tt = deltav[0] / Constants.c;

            Vector3 finaldeltav = new Vector3((float)(deltav[1] / tt), 0.0f, (float)(deltav[2] / tt));
            Vector3 finaldeltax = new Vector3((float)deltax[1], 0.0f, (float)deltax[2]);

            levelManager.player.v = finaldeltav;

            while(true)
            {
                var currentpos = new Vector3(levelManager.player.transform.position.x, 0, levelManager.player.transform.position.z);
                var traveledpos = currentpos - startpos;
                if (traveledpos.magnitude >= finaldeltax.magnitude)
                {
                    break;
                }
                yield return new WaitForFixedUpdate();
            }
            /*
            levelManager.player.alpha = new Vector3(0, 0, 0);

            var v = square.GetPathVelocity(i);

            var atmp = (float)(v * Constants.c);

            double[] vtmp = { ((acceleration.x / acceleration.magnitude)), 0.0, ((acceleration.z / acceleration.magnitude)) };
            var deltavnaive = V.DenseOfArray(vtmp);

            double[] tmp = {Constants.c * Constants.Gamma(deltavnaive.L2Norm()),
                      deltavnaive[0] * Constants.Gamma(deltavnaive.L2Norm()),
                      deltavnaive[2] * Constants.Gamma(deltavnaive.L2Norm())};

            var deltav = V.DenseOfArray(tmp);

            if (u.magnitude > 0.0f)
            {
                deltav = Constants.BoostMatrix(u) * deltav;

                var deltavsize = Mathf.Sqrt((float)(deltav[1] * deltav[1] + deltav[2] * deltav[2]));

                deltav = atmp / deltavsize * deltav;

                deltav[0] = Constants.Gamma(atmp) * Constants.c;
                deltav[1] *= Constants.Gamma(atmp);
                deltav[2] *= Constants.Gamma(atmp);

                deltav = Constants.BoostMatrix(-u) * deltav;
            }
            else
            {
                var deltavsize = Mathf.Sqrt((float)(deltav[1] * deltav[1] + deltav[2] * deltav[2]));

                deltav = atmp / deltavsize * deltav;

                deltav[0] = Constants.Gamma(atmp) * Constants.c;
                deltav[1] *= Constants.Gamma(atmp);
                deltav[2] *= Constants.Gamma(atmp);
            }

            var tt = deltav[0] / Constants.c;

            Vector3 finaldeltav = new Vector3((float)(deltav[1] / tt), 0.0f, (float)(deltav[2] / tt));

            levelManager.player.v = finaldeltav;
            var prevdistance = (dest
                    + (new Vector3(Background.transform.position.x, 0, Background.transform.position.z))
                    - (new Vector3(levelManager.player.transform.position.x, 0, levelManager.player.transform.position.z)));

            yield return new WaitForFixedUpdate();

            while (true)
            {
                var distanceleft = (dest
                    + (new Vector3(Background.transform.position.x, 0, Background.transform.position.z))
                    - (new Vector3(levelManager.player.transform.position.x, 0, levelManager.player.transform.position.z)));
                if (distanceleft.magnitude - prevdistance.magnitude >= 0.0f)
                    break;
                prevdistance = distanceleft;
                yield return new WaitForFixedUpdate();
            }*/


            /*
            var offsetstartpos = new Vector3(Background.transform.position.x, 0, Background.transform.position.z);
            var startpos = new Vector3(levelManager.player.transform.position.x, 0, levelManager.player.transform.position.z);

            acceleration = acceleration.normalized;

            var deltat = Time.fixedDeltaTime * tinterval;

            var v = square.GetPathVelocity(i);

            acceleration = (float)((Constants.c / deltat) * MathNet.Numerics.Trig.Asinh(v * Constants.Gamma(v * Constants.c))) * acceleration;
            // acceleration required to accelerate to v in deltat seconds: see https://en.wikiversity.org/wiki/Theory_of_relativity/Rindler_coordinates

            var startv = levelManager.player.v;

            levelManager.player.alpha = acceleration;

            for (var j = 0; j < tinterval; ++j)
            {
                var tmp = (dest
                    + (new Vector3(Background.transform.position.x, 0, Background.transform.position.z))
                    - (new Vector3(levelManager.player.transform.position.x, 0, levelManager.player.transform.position.z)));
                yield return new WaitForFixedUpdate();
            }
            levelManager.player.alpha = new Vector3(0, 0, 0);
            var offsetnewpos = new Vector3(Background.transform.position.x, 0, Background.transform.position.z);
            var newpos = new Vector3(levelManager.player.transform.position.x, 0, levelManager.player.transform.position.z);

            var posdiff = newpos - startpos;
            var offset = offsetnewpos - offsetstartpos;

            posdiff = posdiff - offset;
             
            while(true)
            {
                var tmp = (dest
                    + (new Vector3(Background.transform.position.x, 0, Background.transform.position.z)) 
                    - (new Vector3(levelManager.player.transform.position.x, 0, levelManager.player.transform.position.z)));
                if (tmp.magnitude - posdiff.magnitude < 0.0f)
                    break;
                yield return new WaitForFixedUpdate();
            }

            acceleration = -acceleration;

            levelManager.player.alpha = acceleration;

            for (var j = 0; j < tinterval; ++j)
            {
                yield return new WaitForFixedUpdate();
            }
            levelManager.player.alpha = new Vector3(0, 0, 0);
            */

            levelManager.player.v = u;
            levelManager.player.transform.position = new Vector3(startpos.x + finaldeltax.x
                                                               , levelManager.player.transform.position.y
                                                               , startpos.z + finaldeltax.z);



            i++;
        }

        Background.Toggle = true;

    }

    public void DeletePathsAfterNthPath(int n)
    {
        square.pathList.RemoveRange(n, _pathRenderer.positionCount - n);
        square.pathVelocity.RemoveRange(n, _pathRenderer.positionCount - n);
        for (int i = 0; i < transform.childCount; i++)
        {
            if (i + 1 >= n) Destroy(transform.GetChild(i).gameObject);
        }
        _pathRenderer.positionCount = square.pathList.Count();
    }
}
