// NeoPixel Driver using Direct Memory Access
//
// This implementation is based on the one by Alan Hawse of Elkhorn Creek,
// documented at https://iotexpert.com/2019/01/08/psoc-6-dma-ws2812-leds/.
// We are grateful to Mr. Hawse for sharing this.

#include "KTag.h"

#define NEOPIXEL_ZOFFSET (1)
#define NEOPIXEL_ONE3  (0b110<<24)
#define NEOPIXEL_ZERO3 (0b100<<24)
#define NEOPIXEL_SPI_BIT_PER_BIT (3)
#define NEOPIXEL_COLOR_PER_PIXEL (3)
#define NEOPIXEL_BYTES_PER_PIXEL (NEOPIXEL_SPI_BIT_PER_BIT * NEOPIXEL_COLOR_PER_PIXEL)
#define FRAME_BUFFER_SIZE (NEOPIXEL_ZOFFSET + (CONFIG_KTAG_MAX_NEOPIXELS_PER_CHANNEL * NEOPIXEL_BYTES_PER_PIXEL))

#if (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 1)
    static uint8_t NeoPixel_Barrel_Channel_Frame_Buffer[FRAME_BUFFER_SIZE];
#elif (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 4)  
    static uint8_t NeoPixel_Barrel_Channel_Frame_Buffer[FRAME_BUFFER_SIZE];
    static uint8_t NeoPixel_Receiver_Channel_Frame_Buffer[FRAME_BUFFER_SIZE];
    static uint8_t NeoPixel_Display_Channel_Frame_Buffer[FRAME_BUFFER_SIZE];
    static uint8_t NeoPixel_Effects_Channel_Frame_Buffer[FRAME_BUFFER_SIZE];
#else
    #error "Unsupported number of NeoPixel channels defined.  Supported configurations are 1 and 4."
#endif

static uint8_t* NeoPixel_Frame_Buffers[CONFIG_KTAG_N_NEOPIXEL_CHANNELS] = 
{
#if (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 1)
    NeoPixel_Barrel_Channel_Frame_Buffer
#elif (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 4)  
    NeoPixel_Barrel_Channel_Frame_Buffer,
    NeoPixel_Receiver_Channel_Frame_Buffer,
    NeoPixel_Display_Channel_Frame_Buffer,
    NeoPixel_Effects_Channel_Frame_Buffer
#else
    #error "Unsupported number of NeoPixel channels defined.  Supported configurations are 1 and 4."
#endif
};

ColorOrder_T ColorOrderByChannel[CONFIG_KTAG_N_NEOPIXEL_CHANNELS];

// Since the descriptors are (or should be) set to "trigger on descriptor completion" (`.interruptType = CY_DMA_DESCR`),
// this ISR is called after each channel has been written.
static void NeoPixel_DMA_Complete(void)
{
    static BaseType_t xHigherPriorityTaskWoken = pdFALSE;

    Cy_DMA_Channel_ClearInterrupt(DMA_NeoPixel_HW, DMA_NeoPixel_DW_CHANNEL);

    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

#define NEOPIXEL_N_DESCRIPTORS CONFIG_KTAG_N_NEOPIXEL_CHANNELS
static cy_stc_dma_descriptor_t NeoPixel_Descriptors[NEOPIXEL_N_DESCRIPTORS];
static void NeoPixel_Configure_DMA(void)
{
    // I [AH] copied this structure from the PSoC Creator Component configuration 
    // in the generated source.
    const cy_stc_dma_descriptor_config_t NeoPixel_DMA_Descriptor_Config =
    {
    .retrigger       = CY_DMA_RETRIG_IM,
    .interruptType   = CY_DMA_DESCR,
    .triggerOutType  = CY_DMA_1ELEMENT,
    .channelState    = CY_DMA_CHANNEL_ENABLED,
    .triggerInType   = CY_DMA_1ELEMENT,
    .dataSize        = CY_DMA_BYTE,
    .srcTransferSize = CY_DMA_TRANSFER_SIZE_DATA,
    .dstTransferSize = CY_DMA_TRANSFER_SIZE_WORD,
    .descriptorType  = CY_DMA_1D_TRANSFER,
    .srcAddress      = NULL,
    .dstAddress      = NULL,
    .srcXincrement   = 1L,
    .dstXincrement   = 0L,
    .xCount          = 256UL,
    .srcYincrement   = 0L,
    .dstYincrement   = 0L,
    .yCount          = 1UL,
    .nextDescriptor  = NULL
    };

    for (uint_fast8_t i=0; i < NEOPIXEL_N_DESCRIPTORS; i++)
    {
        Cy_DMA_Descriptor_Init(&NeoPixel_Descriptors[i], &NeoPixel_DMA_Descriptor_Config);
        Cy_DMA_Descriptor_SetSrcAddress(&NeoPixel_Descriptors[i], (uint8_t *)&NeoPixel_Frame_Buffers[i][0]);
        Cy_DMA_Descriptor_SetDstAddress(&NeoPixel_Descriptors[i], (void *)&SPI_NeoPixel_HW->TX_FIFO_WR);
        Cy_DMA_Descriptor_SetXloopDataCount(&NeoPixel_Descriptors[i], FRAME_BUFFER_SIZE);
    }

    // Initialize and enable the interrupt from DMA_NeoPixel_HW.
    Cy_SysInt_Init(&DMA_NeoPixel_Int_cfg, &NeoPixel_DMA_Complete);
    NVIC_EnableIRQ(DMA_NeoPixel_Int_cfg.intrSrc);
    Cy_DMA_Channel_SetInterruptMask(DMA_NeoPixel_HW, DMA_NeoPixel_DW_CHANNEL, DMA_NeoPixel_INTR_MASK);
    
    Cy_DMA_Enable(DMA_NeoPixel_HW);    
}

// Function: NeoPixel_Trigger_DMA
// This function sets up the channel... then enables it to dump the frameBuffer to pixels.
void NeoPixel_Trigger_DMA(uint_fast8_t channel)
{    
    cy_stc_dma_channel_config_t channel_config;	
    channel_config.descriptor  = &NeoPixel_Descriptors[channel];
    channel_config.preemptable = DMA_NeoPixel_PREEMPTABLE;
    channel_config.priority    = DMA_NeoPixel_PRIORITY;
    channel_config.enable      = false;
    Cy_DMA_Channel_Init(DMA_NeoPixel_HW, DMA_NeoPixel_DW_CHANNEL, &channel_config);
    Cy_DMA_Channel_Enable(DMA_NeoPixel_HW, DMA_NeoPixel_DW_CHANNEL);
}

//! Takes an 8-bit value representing a color level and turns it into a WS2812 bit code...
/*!
 *  ...where 1=110 and 0=011
 *  One input byte turns into three output bytes of a uint32_t.
 */
uint32_t NeoPixel_ConvertTo3Code(uint8_t input)
{
    uint32_t rval=0;
    for (uint_fast8_t i=0; i < 8; i++)
    {
        if (input % 2)
        {
            rval |= NEOPIXEL_ONE3;
        }
        else
        {
            rval |= NEOPIXEL_ZERO3;
        }
        rval = rval >> 3;
        
        input = input >> 1;
    }
    return rval;
}

//! Takes a position and a three byte RGB value and updates the corresponding NeoPixel_Frame_Buffer with the correct nine bytes.
SystemKResult_T HW_NeoPixels_Set_RGB(NeoPixelsChannel_T channel, uint8_t position, uint8_t red, uint8_t green, uint8_t blue)
{
    typedef union {
    uint8_t bytes[4];
    uint32_t word;
    } NeoPixel_ColorByNumber;
    
    NeoPixel_ColorByNumber color;
    ColorOrder_T order = ColorOrderByChannel[channel];

    if (order == COLOR_ORDER_RGB)
    {
        color.word = NeoPixel_ConvertTo3Code(red);
        NeoPixel_Frame_Buffers[channel][position*NEOPIXEL_BYTES_PER_PIXEL+0+NEOPIXEL_ZOFFSET] = color.bytes[2];
        NeoPixel_Frame_Buffers[channel][position*NEOPIXEL_BYTES_PER_PIXEL+1+NEOPIXEL_ZOFFSET] = color.bytes[1];
        NeoPixel_Frame_Buffers[channel][position*NEOPIXEL_BYTES_PER_PIXEL+2+NEOPIXEL_ZOFFSET] = color.bytes[0];
        
        color.word = NeoPixel_ConvertTo3Code(green);
        NeoPixel_Frame_Buffers[channel][position*NEOPIXEL_BYTES_PER_PIXEL+3+NEOPIXEL_ZOFFSET] = color.bytes[2];
        NeoPixel_Frame_Buffers[channel][position*NEOPIXEL_BYTES_PER_PIXEL+4+NEOPIXEL_ZOFFSET] = color.bytes[1];
        NeoPixel_Frame_Buffers[channel][position*NEOPIXEL_BYTES_PER_PIXEL+5+NEOPIXEL_ZOFFSET] = color.bytes[0];

        color.word = NeoPixel_ConvertTo3Code(blue);
        NeoPixel_Frame_Buffers[channel][position*NEOPIXEL_BYTES_PER_PIXEL+6+NEOPIXEL_ZOFFSET] = color.bytes[2];
        NeoPixel_Frame_Buffers[channel][position*NEOPIXEL_BYTES_PER_PIXEL+7+NEOPIXEL_ZOFFSET] = color.bytes[1];
        NeoPixel_Frame_Buffers[channel][position*NEOPIXEL_BYTES_PER_PIXEL+8+NEOPIXEL_ZOFFSET] = color.bytes[0];
    }
    else if (order == COLOR_ORDER_GRB)
    {
        color.word = NeoPixel_ConvertTo3Code(green);
        NeoPixel_Frame_Buffers[channel][position*NEOPIXEL_BYTES_PER_PIXEL+0+NEOPIXEL_ZOFFSET] = color.bytes[2];
        NeoPixel_Frame_Buffers[channel][position*NEOPIXEL_BYTES_PER_PIXEL+1+NEOPIXEL_ZOFFSET] = color.bytes[1];
        NeoPixel_Frame_Buffers[channel][position*NEOPIXEL_BYTES_PER_PIXEL+2+NEOPIXEL_ZOFFSET] = color.bytes[0];
        
        color.word = NeoPixel_ConvertTo3Code(red);
        NeoPixel_Frame_Buffers[channel][position*NEOPIXEL_BYTES_PER_PIXEL+3+NEOPIXEL_ZOFFSET] = color.bytes[2];
        NeoPixel_Frame_Buffers[channel][position*NEOPIXEL_BYTES_PER_PIXEL+4+NEOPIXEL_ZOFFSET] = color.bytes[1];
        NeoPixel_Frame_Buffers[channel][position*NEOPIXEL_BYTES_PER_PIXEL+5+NEOPIXEL_ZOFFSET] = color.bytes[0];

        color.word = NeoPixel_ConvertTo3Code(blue);
        NeoPixel_Frame_Buffers[channel][position*NEOPIXEL_BYTES_PER_PIXEL+6+NEOPIXEL_ZOFFSET] = color.bytes[2];
        NeoPixel_Frame_Buffers[channel][position*NEOPIXEL_BYTES_PER_PIXEL+7+NEOPIXEL_ZOFFSET] = color.bytes[1];
        NeoPixel_Frame_Buffers[channel][position*NEOPIXEL_BYTES_PER_PIXEL+8+NEOPIXEL_ZOFFSET] = color.bytes[0];
    }
    else
    {
        // Color order is not handled--log this and use RGB.
        {
            static bool error_logged = false;
            if (error_logged == false)
            {
               COMM_Console_Print_String("Color order ");
               COMM_Console_Print_UInt8(order);
               COMM_Console_Print_String(" not yet supported!");
               error_logged = true;
            }
        }
        
        color.word = NeoPixel_ConvertTo3Code(red);
        NeoPixel_Frame_Buffers[channel][position*NEOPIXEL_BYTES_PER_PIXEL+0+NEOPIXEL_ZOFFSET] = color.bytes[2];
        NeoPixel_Frame_Buffers[channel][position*NEOPIXEL_BYTES_PER_PIXEL+1+NEOPIXEL_ZOFFSET] = color.bytes[1];
        NeoPixel_Frame_Buffers[channel][position*NEOPIXEL_BYTES_PER_PIXEL+2+NEOPIXEL_ZOFFSET] = color.bytes[0];
        
        color.word = NeoPixel_ConvertTo3Code(green);
        NeoPixel_Frame_Buffers[channel][position*NEOPIXEL_BYTES_PER_PIXEL+3+NEOPIXEL_ZOFFSET] = color.bytes[2];
        NeoPixel_Frame_Buffers[channel][position*NEOPIXEL_BYTES_PER_PIXEL+4+NEOPIXEL_ZOFFSET] = color.bytes[1];
        NeoPixel_Frame_Buffers[channel][position*NEOPIXEL_BYTES_PER_PIXEL+5+NEOPIXEL_ZOFFSET] = color.bytes[0];

        color.word = NeoPixel_ConvertTo3Code(blue);
        NeoPixel_Frame_Buffers[channel][position*NEOPIXEL_BYTES_PER_PIXEL+6+NEOPIXEL_ZOFFSET] = color.bytes[2];
        NeoPixel_Frame_Buffers[channel][position*NEOPIXEL_BYTES_PER_PIXEL+7+NEOPIXEL_ZOFFSET] = color.bytes[1];
        NeoPixel_Frame_Buffers[channel][position*NEOPIXEL_BYTES_PER_PIXEL+8+NEOPIXEL_ZOFFSET] = color.bytes[0];
    }
    
    return SYSTEMK_RESULT_SUCCESS;
}


//! Initializes the hardware.
SystemKResult_T HW_NeoPixels_Init(void)
{
    Cy_SCB_SPI_Init(SPI_NeoPixel_HW, &SPI_NeoPixel_config, &SPI_NeoPixel_context);
    Cy_SCB_SPI_Enable(SPI_NeoPixel_HW);
    NeoPixel_Configure_DMA();
    
#if (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 1)
    ColorOrderByChannel[NEOPIXEL_CHANNEL_BARREL] = NVM_BARREL_COLOR_ORDER;
#elif (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 4)  
    ColorOrderByChannel[NEOPIXEL_CHANNEL_BARREL] = NVM_BARREL_COLOR_ORDER;
    ColorOrderByChannel[NEOPIXEL_CHANNEL_RECEIVER] = NVM_RECEIVER_COLOR_ORDER;
    ColorOrderByChannel[NEOPIXEL_CHANNEL_DISPLAY] = NVM_DISPLAY_COLOR_ORDER;
    ColorOrderByChannel[NEOPIXEL_CHANNEL_EFFECTS] = NVM_EFFECTS_COLOR_ORDER;
#else
    #error "Unsupported number of NeoPixel channels defined.  Supported configurations are 1 and 4."
#endif

    return SYSTEMK_RESULT_SUCCESS;
}


#if (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 1)
static inline __attribute__((always_inline)) void NeoPixels_Set_Color_On_All_Channels(uint8_t position, color_t color) 
{
    HW_NeoPixels_Set_RGB(NEOPIXEL_CHANNEL_BARREL, position, Gamma8[Red(color)], Gamma8[Green(color)], Gamma8[Blue(color)]);
}

static inline __attribute__((always_inline)) void NeoPixel_Disable_All_Channels() 
{
    // Nothing to do.
}

static inline __attribute__((always_inline)) void NeoPixel_Enable_Channel(uint_fast8_t  __attribute__ ((unused)) channel)
{
    // Nothing to do.
}

static inline __attribute__((always_inline)) void NeoPixel_Disable_Channel(uint_fast8_t  __attribute__ ((unused)) channel)
{
    // Nothing to do.
}
#elif (CONFIG_KTAG_N_NEOPIXEL_CHANNELS == 4)  

static inline __attribute__((always_inline)) void NeoPixel_Enable_Barrel_Channel() 
{
    Cy_GPIO_Write(Pin_NeoPixel_Select_0_PORT, Pin_NeoPixel_Select_0_NUM, 1);
}

static inline __attribute__((always_inline)) void NeoPixel_Disable_Barrel_Channel() 
{
    Cy_GPIO_Write(Pin_NeoPixel_Select_0_PORT, Pin_NeoPixel_Select_0_NUM, 0);
}

static inline __attribute__((always_inline)) void NeoPixel_Enable_Receiver_Channel()
{
    Cy_GPIO_Write(Pin_NeoPixel_Select_1_PORT, Pin_NeoPixel_Select_1_NUM, 1);
}

static inline __attribute__((always_inline)) void NeoPixel_Disable_Receiver_Channel() 
{
    Cy_GPIO_Write(Pin_NeoPixel_Select_1_PORT, Pin_NeoPixel_Select_1_NUM, 0);
}

static inline __attribute__((always_inline)) void NeoPixel_Enable_Display_Channel() 
{
    Cy_GPIO_Write(Pin_NeoPixel_Select_2_PORT, Pin_NeoPixel_Select_2_NUM, 1);
}

static inline __attribute__((always_inline)) void NeoPixel_Disable_Display_Channel() 
{
    Cy_GPIO_Write(Pin_NeoPixel_Select_2_PORT, Pin_NeoPixel_Select_2_NUM, 0);
}

static inline __attribute__((always_inline)) void NeoPixel_Enable_Effects_Channel()
{
    Cy_GPIO_Write(Pin_NeoPixel_Select_3_PORT, Pin_NeoPixel_Select_3_NUM, 1);
}

static inline __attribute__((always_inline)) void NeoPixel_Disable_Effects_Channel() 
{
    Cy_GPIO_Write(Pin_NeoPixel_Select_3_PORT, Pin_NeoPixel_Select_3_NUM, 0);
}

static inline __attribute__((always_inline)) void NeoPixel_Enable_All_Channels() 
{
    NeoPixel_Enable_Barrel_Channel();
    NeoPixel_Enable_Receiver_Channel();
    NeoPixel_Enable_Display_Channel();
    NeoPixel_Enable_Effects_Channel();
}

static inline __attribute__((always_inline)) void NeoPixel_Disable_All_Channels() 
{
    NeoPixel_Disable_Barrel_Channel();
    NeoPixel_Disable_Receiver_Channel();
    NeoPixel_Disable_Display_Channel();
    NeoPixel_Disable_Effects_Channel();
}

static inline __attribute__((always_inline)) void NeoPixel_Enable_Channel(uint_fast8_t channel)
{
    switch (channel)
    {
        case NEOPIXEL_CHANNEL_BARREL:
            NeoPixel_Enable_Barrel_Channel();
            break;

        case NEOPIXEL_CHANNEL_RECEIVER:
            NeoPixel_Enable_Receiver_Channel();
            break;

         case NEOPIXEL_CHANNEL_DISPLAY:
            NeoPixel_Enable_Display_Channel();
            break;

         case NEOPIXEL_CHANNEL_EFFECTS:
            NeoPixel_Enable_Effects_Channel();
            break;
            
         default:
            // Do nothing.
            break;
    }
}

static inline __attribute__((always_inline)) void NeoPixel_Disable_Channel(uint_fast8_t channel)
{
    switch (channel)
    {
        case NEOPIXEL_CHANNEL_BARREL:
            NeoPixel_Disable_Barrel_Channel();
            break;

        case NEOPIXEL_CHANNEL_RECEIVER:
            NeoPixel_Disable_Receiver_Channel();
            break;

         case NEOPIXEL_CHANNEL_DISPLAY:
            NeoPixel_Disable_Display_Channel();
            break;

         case NEOPIXEL_CHANNEL_EFFECTS:
            NeoPixel_Disable_Effects_Channel();
            break;
            
         default:
            // Do nothing.
            break;
    }
}
#else
    #error "Unsupported number of NeoPixel channels defined.  Supported configurations are 1 and 4."
#endif

SystemKResult_T HW_NeoPixels_Publish(void)
{
    // Update the NeoPixels using DMA.
    for (uint_fast8_t Current_NeoPixel_Channel = 0; Current_NeoPixel_Channel < CONFIG_KTAG_N_NEOPIXEL_CHANNELS; Current_NeoPixel_Channel++)
    {
        xSemaphoreTake(NeoPixels_Semaphore, portMAX_DELAY);
        NeoPixel_Enable_Channel(Current_NeoPixel_Channel);
        NeoPixel_Trigger_DMA(Current_NeoPixel_Channel);
        // Allow time for the DMA transfer to go out on the wire.
        vTaskDelay(portTICK_PERIOD_MS);
        NeoPixel_Disable_Channel(Current_NeoPixel_Channel);
        xSemaphoreGive(NeoPixels_Semaphore);
    }
    
    return SYSTEMK_RESULT_SUCCESS;
}

//! \todo Refactor this somehow...it doesn't belong here.
color_t HW_NeoPixels_Get_My_Color(void)
{
   return PROTOCOLS_GetColor(GetWeaponFromID(NVM_WEAPON_ID).Protocol, NVM_TEAM_ID, NVM_PLAYER_ID); 
}
