/*
 * @file motor_speed.c
 * @brief monitor and update the speed of the wheels
 * @author Richie
 */
#ifndef MOTOR_SPEED_H
#define MOTOR_SPEED_H

#include "motor_init.h"
#include "magnetometer_init.h"
#include "magnetometer_direction.h"

/*!
 * @brief Interrupt handler for the wheel sensor
 */
void
h_wheel_sensor_isr_handler(void)
{
    if (gpio_get_irq_event_mask(SPEED_PIN_LEFT) & GPIO_IRQ_EDGE_FALL)
    {
        gpio_acknowledge_irq(SPEED_PIN_LEFT, GPIO_IRQ_EDGE_FALL);

        BaseType_t xHigherPriorityTaskWoken = pdFALSE;
        xSemaphoreGiveFromISR(g_left_sem,
                              &xHigherPriorityTaskWoken);
        portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
    }

    if (gpio_get_irq_event_mask(SPEED_PIN_RIGHT) & GPIO_IRQ_EDGE_FALL)
    {
        gpio_acknowledge_irq(SPEED_PIN_RIGHT, GPIO_IRQ_EDGE_FALL);

        BaseType_t xHigherPriorityTaskWoken = pdFALSE;
        xSemaphoreGiveFromISR(g_right_sem,
                              &xHigherPriorityTaskWoken);
        portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
    }
}


/*!
 * @brief Task to monitor and control the speed of the wheel
 * @param ppp_motor motor_speed_t struct pointer
 * @see motor_speed_t
 */
void
monitor_wheel_speed_task(void *ppp_motor)
{
    volatile motor_t *p_motor = NULL;
    p_motor                   = (motor_t *)ppp_motor;

    uint64_t curr_time    = 0u;
    uint64_t prev_time    = 0u;
    uint64_t elapsed_time = 0u;

    for (;;)
    {
        if ((xSemaphoreTake(*p_motor->p_sem, pdMS_TO_TICKS(100))
            == pdTRUE) && (*p_motor->use_pid == true))
        {
            curr_time    = time_us_64();
            elapsed_time = curr_time - prev_time;
            prev_time    = curr_time;

            p_motor->speed.current_cms
                = (float) (SLOT_DISTANCE_CM_MODIFIED / elapsed_time);

            p_motor->speed.distance_cm += SLOT_DISTANCE_CM;
        }
        else
        {
            p_motor->speed.current_cms = 0.f;
        }
    }
}

/*!
 * @brief Set the speed of the wheels
 * @param pwm_level The pwm_level of the wheels, from 0 to 99
 * @param p_motor The motor to set the speed
 */
void
set_wheel_speed(uint32_t pwm_level, motor_t *p_motor)
{
    if (pwm_level > MAX_PWM_LEVEL)
    {
        pwm_level = MAX_PWM_LEVEL;
    }

    p_motor->pwm.level = pwm_level;

    pwm_set_chan_level(
        p_motor->pwm.slice_num, p_motor->pwm.channel, p_motor->pwm.level);
}

/*!
 * @brief Set the speed of the wheels
 * @param pwm_level The pwm_level of the wheels, from 0 to 99
 * @param pp_car_strut The car struct pointer
 */
void
set_wheel_speed_synced(uint32_t pwm_level, car_struct_t *pp_car_strut)
{
    set_wheel_speed(pwm_level, pp_car_strut->p_left_motor);
    set_wheel_speed(pwm_level, pp_car_strut->p_right_motor);
}

/*!
 * @brief Set the distance to travel before stopping, must be called after
 * setting the speed and direction.
 * @param distance_cm distance to travel in cm
 */
void
distance_to_stop(car_struct_t * pp_car_struct, float distance_cm)
{
    float initial = pp_car_struct->p_left_motor->speed.distance_cm;

    for (;;)
    {
        if (pp_car_struct->p_left_motor->speed.distance_cm - initial >= distance_cm)
        {
            set_wheel_speed_synced(0u, pp_car_struct);
            break;
        }
        vTaskDelay(pdMS_TO_TICKS(10));
    }
    vTaskDelay(pdMS_TO_TICKS(1000));
    pp_car_struct->p_right_motor->speed.distance_cm =
        pp_car_struct->p_left_motor->speed.distance_cm;
}

#endif /* MOTOR_SPEED_H */
