using System;
using System.Threading.Tasks;
using Sensors;
using UnityEngine;

namespace SicknessReduction.Haptic
{
    public enum VibrationControllerMode
    {
        Continuous,
        AlternatingFixed,
        AlternatingCadenceBased
    }

    [RequireComponent(typeof(DynamicReductionSource))]
    public class VibrationController : EspController
    {
        private const string TOPIC_CYCLE = "Vibration/Control/Cycle";
        private const string TOPIC_CADENCE = "Vibration/Control/Cadence";
        private const int THRES_CADENCE_CHANGE = 4;

        public int cycle = 128; //vibro motor specs: 11000 ± 3,000rpm = 133.33 - 183.33 Hz 
        public VibrationControllerMode mode;
        public int fixedCycleRpm;

        private bool initialCyclePublished;
        private int previousCadence = -1;
        private DynamicReductionSource reductionSource;

        private int currentCycle = 0;

        protected override void Start()
        {
            base.Start();
            reductionSource = GetComponent<DynamicReductionSource>();
        }

        protected override async void Update()
        {
            base.Update();

            // > 0 while cornering
            var reductionValue = reductionSource.CurrentValue; 

            if (!DoUpdate || PreviousUpdateActive) return;
            if (reductionValue > 0)
            {
                switch (mode)
                {
                    case VibrationControllerMode.Continuous:
                        await VibrateContinuous((int) (reductionValue * cycle));
                        break;
                    case VibrationControllerMode.AlternatingFixed:
                        await VibrateAlternatingFixed((int) (reductionValue * cycle));
                        break;
                    case VibrationControllerMode.AlternatingCadenceBased:
                        await VibrateAlternatingCadenceBased();
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }
            }
            else if(currentCycle != 0)
            {
                await StopVibrating();
            }

            PreviousUpdateActive = false;
        }

        private async Task StopVibrating()
        {
            Debug.Log("Stop Vibrating");
            await Broker.Publish(TOPIC_CYCLE, "0");
            currentCycle = 0;
        }

        private async Task VibrateAlternatingCadenceBased()
        {
            var cadence = BikeSensorData.Instance.PowermeterData?.InstantaneousCadence;
            if (!cadence.HasValue) return;
            var c = cadence.Value;

            PreviousUpdateActive = true; //flag to avoid concurrent updates
            //as soon as we have a cadence, we want the motors to vibrate
            if (!initialCyclePublished)
            {
                await Broker.Publish(TOPIC_CYCLE, $"{cycle}");
                initialCyclePublished = true;
            }

            //if the cadence changes to 0, we have to switch off vibration
            if (c == 0)
            {
                await Broker.Publish(TOPIC_CYCLE, "0");
                previousCadence = c;
            }
            //as soon as we have cadence again, we want to switch on vibration again, and then immediately set cadence again
            else if (previousCadence == 0)
            {
                await Broker.Publish(TOPIC_CYCLE, $"{cycle}");
                await PublishCadence(c);
            }
            //if we have never set cadence, or the change of cadence is high enough, we tell the ESP to change cadence
            else if (previousCadence < 0 || c - previousCadence > THRES_CADENCE_CHANGE)
            {
                await PublishCadence(c);
            }
        }

        private async Task VibrateAlternatingFixed(int cycleValue)
        {
            if (Math.Abs(cycleValue - currentCycle) > 0)
            {
                await Broker.Publish(TOPIC_CYCLE, $"{cycleValue}");
                await PublishCadence(fixedCycleRpm);
            }
        }

        private async Task VibrateContinuous(int cycleValue)
        {
            if (Math.Abs(cycleValue - currentCycle) > 0)
            {
                Debug.Log($"Sending Cycle {cycleValue}");
                await Broker.Publish(TOPIC_CYCLE, $"{cycleValue}");
            }
        }

        private async Task PublishCadence(int cadence)
        {
            Debug.Log($"Sending Cadence {cadence}");
            await Broker.Publish(TOPIC_CADENCE, $"{cadence}");
            previousCadence = cadence;
        }
    }
}