#include "WS2812.h"
#include "cy_scb_spi.h"
#include "cy_dma.h"

uint8_t buffer[WS2812_RESET_BYTES + WS2812_MAX_LEDS * 9];

static cy_stc_scb_spi_context_t spiCtx;
static cy_stc_dma_descriptor_t dmaDescriptor;


void WS2812_Init() {
	for (size_t i = 0; i < WS2812_RESET_BYTES; i++) {
        buffer[i] = 0;
    }
	for (size_t i = 0; i < WS2812_MAX_LEDS; i++) {
		buffer[WS2812_RESET_BYTES + i * 9 + 0] = buffer[WS2812_RESET_BYTES + i * 9 + 3] = buffer[WS2812_RESET_BYTES + i * 9 + 6] = 0b10010010;
		buffer[WS2812_RESET_BYTES + i * 9 + 1] = buffer[WS2812_RESET_BYTES + i * 9 + 4] = buffer[WS2812_RESET_BYTES + i * 9 + 7] = 0b01001001;
		buffer[WS2812_RESET_BYTES + i * 9 + 2] = buffer[WS2812_RESET_BYTES + i * 9 + 5] = buffer[WS2812_RESET_BYTES + i * 9 + 8] = 0b00100100;
	}

	cy_rslt_t status;

    NVIC_ClearPendingIRQ(scb_1_interrupt_IRQn);
    NVIC_EnableIRQ(scb_1_interrupt_IRQn);

	cy_stc_scb_spi_config_t spiConfig;
    spiConfig.spiMode = CY_SCB_SPI_MASTER;
    spiConfig.subMode = CY_SCB_SPI_MOTOROLA;
    spiConfig.sclkMode = CY_SCB_SPI_CPHA0_CPOL0;
    spiConfig.oversample = 8;
    spiConfig.rxDataWidth = 8UL;
    spiConfig.txDataWidth = 8UL;
    spiConfig.enableMsbFirst = true;
    spiConfig.enableInputFilter = false;
    spiConfig.enableFreeRunSclk = false;
    spiConfig.enableMisoLateSample = true;
    spiConfig.enableTransferSeperation = false;
    spiConfig.ssPolarity = 0;
    spiConfig.enableWakeFromSleep = false;
    spiConfig.rxFifoTriggerLevel = 63UL;
    spiConfig.rxFifoIntEnableMask = 0UL;
    spiConfig.txFifoTriggerLevel = 63UL;
    spiConfig.txFifoIntEnableMask = 0UL;
    spiConfig.masterSlaveIntEnableMask = 0UL;

    status = Cy_SCB_SPI_Init(SCB1, &spiConfig, &spiCtx);
    CY_ASSERT(status == CY_RSLT_SUCCESS);
    Cy_SCB_SPI_Enable(SCB1);

	cy_stc_dma_descriptor_config_t descriptorConfig;
    descriptorConfig.channelState = CY_DMA_CHANNEL_ENABLED;
    descriptorConfig.interruptType = CY_DMA_DESCR;
    descriptorConfig.triggerOutType = CY_DMA_DESCR;
    descriptorConfig.triggerInType = CY_DMA_1ELEMENT;
    descriptorConfig.srcXincrement = 1;
    descriptorConfig.dstXincrement = 0;
    descriptorConfig.xCount = sizeof(buffer);
    descriptorConfig.srcYincrement = 0;
    descriptorConfig.dstYincrement = 0;
    descriptorConfig.yCount = 1;
    descriptorConfig.descriptorType = CY_DMA_1D_TRANSFER;
    descriptorConfig.dataSize = CY_DMA_BYTE;
    descriptorConfig.dstAddress = (void *)&(SCB1->TX_FIFO_WR);
	descriptorConfig.retrigger = CY_DMA_RETRIG_IM;
    descriptorConfig.srcTransferSize = CY_DMA_TRANSFER_SIZE_DATA;
    descriptorConfig.dstTransferSize = CY_DMA_TRANSFER_SIZE_WORD;
    descriptorConfig.srcAddress = buffer;
    descriptorConfig.nextDescriptor = &dmaDescriptor;

    status = Cy_DMA_Descriptor_Init(&dmaDescriptor, &descriptorConfig);
    CY_ASSERT(status == 0);

    NVIC_SetPriority(cpuss_interrupts_dw0_18_IRQn, 3);
    NVIC_ClearPendingIRQ(cpuss_interrupts_dw0_18_IRQn);
    NVIC_EnableIRQ(cpuss_interrupts_dw0_18_IRQn);

	Cy_DMA_Channel_SetDescriptor(DW0, 18, &dmaDescriptor);
    Cy_DMA_Channel_SetPriority(DW0, 18, 2);
    Cy_DMA_Channel_SetInterruptMask(DW0, 18, CY_DMA_INTR_MASK);
}

static void WS2812_SetByte(uint8_t* destination, uint8_t value) {
	//                777666555444333222111000
	uint32_t mask = 0b010000000000000000000000;
	for (int i = 0; i < 8; i++) {
		uint8_t mask0 = mask >> 16;
		uint8_t mask1 = mask >> 8;
		uint8_t mask2 = mask >> 0;

		if (value & (1 << (7 - i))) {
			destination[0] |= mask0;
			destination[1] |= mask1;
			destination[2] |= mask2;
		} else {
			destination[0] &= ~mask0;
			destination[1] &= ~mask1;
			destination[2] &= ~mask2;
		}
		mask >>= 3;
	}
}

void WS2812_SetLed(size_t index, uint8_t r, uint8_t g, uint8_t b) {
	WS2812_SetByte(buffer + WS2812_RESET_BYTES + index * 9 + 0, g);
	WS2812_SetByte(buffer + WS2812_RESET_BYTES + index * 9 + 3, r);
	WS2812_SetByte(buffer + WS2812_RESET_BYTES + index * 9 + 6, b);
}

void scb_1_interrupt_IRQHandler(void) {
    Cy_SCB_SPI_Interrupt(SCB1, &spiCtx);
}

void cpuss_interrupts_dw0_18_IRQHandler(void) {
    cy_en_dma_intr_cause_t status = Cy_DMA_Channel_GetStatus(DW0, 18);
    if (status != CY_DMA_INTR_CAUSE_COMPLETION) {
        __BKPT(0);
    }
    Cy_DMA_Channel_Disable(DW0, 18);
    Cy_DMA_Channel_ClearInterrupt(DW0, 18);
}

void WS2812_Transmit() {
	Cy_DMA_Channel_SetDescriptor(DW0, 18, &dmaDescriptor);
    Cy_DMA_Enable(DW0);
    Cy_DMA_Channel_Enable(DW0, 18);
}