using FundamentalLib;
using System;
using System.Collections.Generic;
using System.Numerics;
using System.Text;
using System.Diagnostics;

namespace CommonUsage.Chassis
{
	public class DifferentialChassis : AbstractChassis
	{
		public void SetLeftRightWheels(Wheel wheelL, Wheel wheelR)
		{
			_leftWheel = wheelL;
			_rightWheel = wheelR;
			_halfWheelTrack = Math.Abs(_leftWheel.Position.Y);
		}

        public override void Visualize()
        {
            
        }

        public override CarSpeed GetCarSpeed(bool isActual = false)
        {
            if (!isActual)
            {
                return new CarSpeed()
                {
                    Vx = (_speedL + _speedR) / 2f,
                    Vw = (_speedR - _speedL) / Math.Abs(_leftWheel.Position.Y - _rightWheel.Position.Y) /
                        (float)Math.PI * 180f * 1000f,
                    Vy = 0
                };
            }
            else
            {
                return new CarSpeed()
                {
                    Vx = GetLinearSpeed(),
                    Vw = (_rightWheel.ReadSpeed() - _leftWheel.ReadSpeed()) /
                        Math.Abs(_leftWheel.Position.Y - _rightWheel.Position.Y) /
                        (float)Math.PI * 180f * 1000f,
                    Vy = 0
                };
            }
            
        }

        public (Wheel,Wheel) GetWheels()
        {
            return (_leftWheel, _rightWheel);
        }

		public float GetLinearSpeed()
		{
			return (_leftWheel.ReadSpeed() + _rightWheel.ReadSpeed()) / 2f;
		}

		public override void Initialize()
		{
			GeometricControlPoints.Add(new GeometricControlPoint(new Vector2(0, 0)));
			Valid = true;
		}

		public override void AfterDirectionChanged()
		{

		}

		public override void PredefinedDriveStop()
		{
			if (!Valid) return;
			_sendSpeedL = _sendSpeedR = 0;
            _speedL = _speedR = 0;
			_leftWheel.WriteSpeed(_sendSpeedL);
			_rightWheel.WriteSpeed(_sendSpeedR);
			GoingActive = false;
			RotatingActive = false;
		}

        protected override void DefineGeometricWheelComputation(float speed)
        {
            var now = DateTime.Now;
            if (!GoingActive) LastMoveTime = now;

            SendSpeed(speed, GeometricControlPoints[0].Theta, now - LastMoveTime);

            GoingActive = true;
            RotatingActive = false;
        }

		public override bool ComputeRotateWheels(float rotSpeed)
		{
			if (!RotatingActive) LastMoveTime = DateTime.Now;

			SendSpeed(0, rotSpeed);

			GoingActive = false;
			RotatingActive = true;
			return true;
		}

		public override float CalculateTurningSpeedDecayFac(float turn)
		{
			return 1 - Math.Min(turn, MaxTurnThreshold) / MaxTurnThreshold * MinTurnSpeedFac;
		}

		public void SendSpeed(float linearSpeed, float angularSpeed, TimeSpan? deltaTime = null)
		{
			var edgeLinearSpeed = (float)(angularSpeed / 180f * Math.PI * _halfWheelTrack / 1000);
			var vl = linearSpeed - edgeLinearSpeed;
			var vr = linearSpeed + edgeLinearSpeed;
            _speedL = vl;
			_speedR = vr;
			AccumulateSpeed(vl, vr, deltaTime);
            LastMoveTime = DateTime.Now;
        }

		private void AccumulateSpeed(float vl, float vr, TimeSpan? deltaTime = null)
		{
            // var dTime = (float)(deltaTime ?? DateTime.Now - LastMoveTime).TotalSeconds;
            //
            // var speedSignL = Math.Sign(vl - _sendSpeedL);
            // var accL = Math.Abs(vl) > Math.Abs(_sendSpeedL) ? AccPerSecond : DeAccPerSecond;
            // _sendSpeedL += speedSignL * Math.Min(Math.Abs(vl - _sendSpeedL), accL * dTime);
            // _leftWheel.WriteSpeed(_sendSpeedL);
            //
            // var speedSignR = Math.Sign(vr - _sendSpeedR);
            // var accR = Math.Abs(vr) > Math.Abs(_sendSpeedR) ? AccPerSecond : DeAccPerSecond;
            // _sendSpeedR += speedSignR * Math.Min(Math.Abs(vr - _sendSpeedR), accR * dTime);
            // _rightWheel.WriteSpeed(_sendSpeedR);

            // if (Debug)
            // 	Console.WriteLine($"DiffChassis, target:{v:0.00},send:{_sendSpeed:0.0}");
            // var dTime = (float)(deltaTime ?? DateTime.Now - LastMoveTime).TotalSeconds;
            var dTime = (float)(deltaTime ?? DateTime.Now - LastMoveTime).TotalSeconds;
            float diffL = vl - _sendSpeedL;
            float diffR = vr - _sendSpeedR;

            float accL = Math.Abs(vl) > Math.Abs(_sendSpeedL) ? AccPerSecond : DeAccPerSecond;
            float accR = Math.Abs(vr) > Math.Abs(_sendSpeedR) ? AccPerSecond : DeAccPerSecond;

            float maxDeltaL = accL * dTime;
            float maxDeltaR = accR * dTime;

            float factorL = Math.Abs(diffL) > maxDeltaL ? maxDeltaL / Math.Abs(diffL) : 1.0f;
            float factorR = Math.Abs(diffR) > maxDeltaR ? maxDeltaR / Math.Abs(diffR) : 1.0f;

            float factor = Math.Min(factorL, factorR);

            _sendSpeedL += diffL * factor;
            _sendSpeedR += diffR * factor;

            _leftWheel.WriteSpeed(_sendSpeedL);
            _rightWheel.WriteSpeed(_sendSpeedR);
            // Console.WriteLine($"DiffChassis, target:{vl:0.00},send:{_sendSpeedL:0.00} dTime：{dTime} diffL:{diffL} factor:{factor}" );
        }

        private Wheel _leftWheel;
		private Wheel _rightWheel;
		private float _halfWheelTrack; // millimeter

		private float _sendSpeedL;
		private float _sendSpeedR;
		private int _direction = 1; // 1 forward, -1 backward
        private float _speedL;
        private float _speedR;
    }
}
