#include "ws2812.h"

#include "mcc_generated_files/system/pins.h"
#include <xc.h>

/*
 * Timing targets for WS2812 using fixed-cycle delays.
 * Select profile by setting WS2812_TIMING_PROFILE in ws2812.h.
 */
#if (WS2812_TIMING_PROFILE == WS2812_TIMING_PROFILE_100MHZ)
#define WS2812_T0H_REPEAT      26U
#define WS2812_T0L_REPEAT      82U
#define WS2812_T1H_REPEAT      61U
#define WS2812_T1L_REPEAT      47U
#define WS2812_RESET_CYCLES    10000U
#elif (WS2812_TIMING_PROFILE == WS2812_TIMING_PROFILE_200MHZ)
#define WS2812_T0H_REPEAT      74U
#define WS2812_T0L_REPEAT      162U
#define WS2812_T1H_REPEAT      150U
#define WS2812_T1L_REPEAT      86U
#define WS2812_RESET_CYCLES    20000U
#else
#error "Unsupported WS2812_TIMING_PROFILE"
#endif

/* repeat #N executes the next instruction N+1 times */
#define WS2812_DELAY_REPEAT(count) \
    __asm__ volatile ("repeat #%0\n nop" : : "i"(count))

static uint8_t ws2812BrightnessPercent = 100U;

static uint8_t WS2812_ApplyBrightness(uint8_t value)
{
    return (uint8_t)(((uint16_t)value * (uint16_t)ws2812BrightnessPercent) / 100U);
}

static inline void WS2812_DelayReset(void)
{
    uint16_t cycles = WS2812_RESET_CYCLES;

    while(cycles > 0U)
    {
        __builtin_nop();
        cycles--;
    }
}

static inline void WS2812_SendBitOne(void)
{
    WS2812_SetHigh();
    WS2812_DELAY_REPEAT(WS2812_T1H_REPEAT);
    WS2812_SetLow();
    WS2812_DELAY_REPEAT(WS2812_T1L_REPEAT);
}

static inline void WS2812_SendBitZero(void)
{
    WS2812_SetHigh();
    WS2812_DELAY_REPEAT(WS2812_T0H_REPEAT);
    WS2812_SetLow();
    WS2812_DELAY_REPEAT(WS2812_T0L_REPEAT);
}

static void WS2812_SendByte(uint8_t value)
{
    uint8_t mask = 0x80U;

    while(mask != 0U)
    {
        if((value & mask) != 0U)
        {
            WS2812_SendBitOne();
        }
        else
        {
            WS2812_SendBitZero();
        }

        mask >>= 1;
    }
}

static void WS2812_LatchReset(void)
{
    WS2812_SetLow();
    WS2812_DelayReset();
}

void WS2812_Initialize(void)
{
    WS2812_SetDigitalOutput();
    WS2812_SetLow();
    WS2812_LatchReset();
}

void WS2812_SetBrightnessPercent(uint8_t brightnessPercent)
{
    if(brightnessPercent > 100U)
    {
        brightnessPercent = 100U;
    }

    ws2812BrightnessPercent = brightnessPercent;
}

void WS2812_SetColorRGB(uint8_t red, uint8_t green, uint8_t blue)
{
    uint8_t scaledRed = WS2812_ApplyBrightness(red);
    uint8_t scaledGreen = WS2812_ApplyBrightness(green);
    uint8_t scaledBlue = WS2812_ApplyBrightness(blue);

    __builtin_disable_interrupts();

    /* WS2812 expects color order as G-R-B */
    WS2812_SendByte(scaledGreen);
    WS2812_SendByte(scaledRed);
    WS2812_SendByte(scaledBlue);
    WS2812_LatchReset();

    __builtin_enable_interrupts();
}
