
// code to compute RPM
#include "stdint.h"

#include "libIgnTiming/rpm.h"
#if defined RPMTIMER

extern "C"
{
    typedef enum
    {
        PULSE_LOW = 1,
        PULSE_HIGH = 2,
        PULSE_BOTH = 3
    } pulseState_t;
    // with a dwell angle of 45 degrees , 4 cylinders and a maximum RPM of 5000
    // freq = 5000/60 * 2 = 166Hz.
    // the TIM2 counter counts in 10uS increments,
    // Need to accumulate low level for a 400th of a second before accepting it as a pulse
    const uint16_t ACCUM_MAX = (RPM_COUNT_RATE / 400);

    // shared variables used in calculation - a pipeline of samples
    static volatile unsigned long RPM_Time[RPM_SAMPLES]; // sampled on both edges
    static volatile unsigned long RPM_Count;             // incremented every reading

    void TIMER_IRQ_HANDLER(void)
    {
        static char level = 0;
        char valid = 0;
        uint16_t high_count = 0;
        uint16_t low_count = 0;
        // rising edge CB pulse
        if (__HAL_TIM_GET_FLAG(&TIMER_HANDLE, TIM_FLAG_CC1))
        {
            __HAL_TIM_CLEAR_FLAG(&TIMER_HANDLE, TIM_FLAG_CC1);
            low_count = __HAL_TIM_GET_COMPARE(&TIMER_HANDLE, TIM_CHANNEL_1);
            valid = PULSE_LOW; // record we have a low_count val

            // trigger any other event at rising edge
            AUXILIARY_HIGH;
        }
        // falling edge trigger CB pulse
        if (__HAL_TIM_GET_FLAG(&TIMER_HANDLE, TIM_FLAG_CC2))
        {
            __HAL_TIM_CLEAR_FLAG(&TIMER_HANDLE, TIM_FLAG_CC2);

            high_count = __HAL_TIM_GET_COMPARE(&TIMER_HANDLE, TIM_CHANNEL_2);
            valid |= PULSE_HIGH;
            // trigger any other event at falling edge
            AUXILIARY_LOW;
        }

        switch (valid)
        {
        case pulseState_t::PULSE_LOW:
            // count width of a low period
            RPM_Time[RPM_Count] = low_count;
            RPM_Count = (RPM_Count + 1) % RPM_SAMPLES;
            level = 0; // remember level
            break;
        case pulseState_t::PULSE_HIGH:
            // count width of a high period
            RPM_Time[RPM_Count] = high_count | RPM_FLAG;
            RPM_Count = (RPM_Count + 1) % RPM_SAMPLES;
            level = 1; // remember level
            break;
            // there has been both a high level and a low level
        case pulseState_t::PULSE_BOTH:
            if (level == 1) // next level = 0 ,then 1 again
            {
                RPM_Time[RPM_Count] = low_count;
                RPM_Count = (RPM_Count + 1) % RPM_SAMPLES;
                RPM_Time[RPM_Count] = high_count | RPM_FLAG;
                RPM_Count = (RPM_Count + 1) % RPM_SAMPLES;
            }
            else
            {
                RPM_Time[RPM_Count] = high_count | RPM_FLAG;
                RPM_Count = (RPM_Count + 1) % RPM_SAMPLES;
                RPM_Time[RPM_Count] = low_count;
                RPM_Count = (RPM_Count + 1) % RPM_SAMPLES;
            }
            break;
        default:
            break;
        }
    }

    int CalculateRPM(void)
    {
        // compute the timer values
        // snapshot timers

        // Next state of pulse high/low
        static unsigned char RPM_State = 1;
        // Current state of pulse high/low
        static unsigned char RPM_State_Curr = 1;

        // variables used in calculation of RPM value
        static uint16_t last_dwell_end = 0;
        static uint16_t RPM_Period[RPM_AVERAGE];
        static unsigned int RPM_Period_Ptr = 0;

        static uint16_t RPM_Count_Latch = 0;

        // accumulators
        static int16_t RPM_Pulsecount = 0;

        __disable_irq(); // copy the counter value
        // current RPM pulse next slot index
        uint16_t RPM_Count_Val = RPM_Count;
        __enable_irq();
        // do calculations

        // if there is only one entry, cannot get difference
        if (RPM_Count_Latch != RPM_Count_Val)
        {
            while (1)
            {
                unsigned int base_time;
                unsigned int new_time;
                // if we are at N-1, stop.
                unsigned int next_count = (RPM_Count_Latch + 1) % RPM_SAMPLES;
                if (next_count == RPM_Count_Val)
                {
                    break; // completed loop
                }
                char pulse_level = (RPM_Time[RPM_Count_Latch] & RPM_FLAG) ? 1 : 0;
                base_time = RPM_Time[RPM_Count_Latch] & ~RPM_FLAG;
                new_time = RPM_Time[next_count] & ~RPM_FLAG;
                RPM_Count_Latch = next_count;

                uint16_t RPM_Pulsewidth = new_time - base_time;

                if (pulse_level == 0 && (RPM_Pulsewidth > ACCUM_MAX))
                    RPM_State = 1;
                if (pulse_level == 1)
                    RPM_State = 0;

                // low pulse has reached at least minimum width, count it.
                if ((RPM_State == 1) && (RPM_State_Curr == 0))
                {
                    // Rev counter processing from original RevCounter Project
                    uint16_t RPM_Diff = new_time - last_dwell_end;

                    RPM_Period[RPM_Period_Ptr] = RPM_Diff;
                    RPM_Period_Ptr = (RPM_Period_Ptr + 1) % RPM_AVERAGE;
                    if (RPM_Pulsecount < RPM_AVERAGE)
                        RPM_Pulsecount++; // count one pulse
                    last_dwell_end = new_time;
                }
                RPM_State_Curr = RPM_State;
            }
        }

        if (RPM_Pulsecount == RPM_AVERAGE)
        {
            // now have time for N pulses in clocks
            // 1Hz is 30 RPM
            int i;
            unsigned int RPM_FilteredWidth = 0;
            for (i = 0; i < RPM_AVERAGE; i++)
                RPM_FilteredWidth += RPM_Period[i];

#if !defined MY_DEBUG
            // reset here unless we want to debug
            RPM_Pulsecount = 0;
#endif
            return (Scale * 30.0 * RPM_AVERAGE * RPM_COUNT_RATE) / (RPM_FilteredWidth);
        }
        else
        {
            return -1; // flag no reading
        }
    }
}
#else

int CalculateRPM(void) { return 0; };

#endif // RPMTIMER