#include "WS2812.h"

/* Quick and dirty, we use one big DMA buffer for the whole strip length.
 * TODO: use smaller DMA buffer and fill in bit patterns on the fly */
uint8_t dma_buffer[WS2812_DMABUF_LEN(WS2812_LEDS_MAX)];

/* scale uint8 value from range 2-255 to range 0-scale */
static inline uint8_t WS2812_Scale(uint8_t value, uint8_t scale)
{
	uint32_t tmp;

	tmp = value * scale;
	tmp /= 256;

	return (uint8_t)tmp;
}

// wake up waiting tasks when DMA transfer is complete 
static void master_tr_done_callback(void *pdata, SpiIrq event)
{
	BaseType_t task_woken, result;
	WS2812_t *cfg;

	task_woken = pdFALSE;
	cfg = (WS2812_t *) pdata;

	switch (event) 
	{
		case SpiRxIrq:
			break;
			
		case SpiTxIrq:
			result = xEventGroupSetBitsFromISR(cfg->events, BIT_DONE, &task_woken);
			if(result == pdPASS)
			{
				portYIELD_FROM_ISR(task_woken);
			}
			break;
			
		default:
			DBG_8195A("WS2812: Unknown SPI irq event!\n");
	}
}

static void WS2812_HSV2RGB(WS2812_HSV_t *hsv, WS2812_RGB_t *rgb)
{
	//uint8_t r, g, b;
	uint8_t hue, sat, val;
	uint8_t base, sector, offset;
	uint8_t rise, fall;

	// scale hue to range 0- 3*64. Makes subsequent calculations easier
	hue = WS2812_Scale(hsv->HUE, 192);
	sat = hsv->Sat;
	val = hsv->Value;

	sector = hue / 64;
	offset = hue % 64;

	// get common white base level and remaining colour amplitude
	base = 255 - sat;

	rise = (offset * sat * 4) / 256;
	fall = 255 - base - rise;

	rise = (rise * val) / 256;
	fall = (fall * val) / 256;
	base = (base * val) / 256;

	rgb->R = base;
	rgb->G = base;
	rgb->B = base;

	switch (sector)
	{
	case 0:
		rgb->R += fall;
		rgb->G += rise;
		break;
	case 1:
		rgb->G += fall;
		rgb->B += rise;
		break;
	case 2:
		rgb->R += rise;
		rgb->B += fall;
		break;
	}
}

// convert a RGB byte into SPI data stream with 2 bits per byte
static uint8_t *WS2812_RGB2PWM(uint8_t *dst, const uint8_t colour)
{
	uint32_t cnt, data = colour;

	for (cnt = 0; cnt < 4; ++cnt)
	{
		switch (data & 0xC0)
		{
		case 0x00:
			*dst = WS2812_BITS_00;
			break;
		case 0x40:
			*dst = WS2812_BITS_01;
			break;
		case 0x80:
			*dst = WS2812_BITS_10;
			break;
		case 0xC0:
			*dst = WS2812_BITS_11;
			break;
		}
		dst++;
		data <<= 2;
	}
	return dst;
}

static int32_t WS2812_TX(WS2812_t *cfg, uint16_t delay)
{
    int32_t result;
    EventBits_t rcvd_events;
    TickType_t timeout;
    BaseType_t status;

    // wait for any SPI transfer to finish
    while(cfg->spi_master.state & SPI_STATE_TX_BUSY)
	{
        vTaskDelay(0);
    }

    result = 0;
    
    // obey requested delay
    if(delay > 0)
	{
        vTaskDelay(delay);
    }

    // lock the DMA buffer mutex while it is transferred
    status = xSemaphoreTake(cfg->mutex, configTICK_RATE_HZ);
    if(status != pdTRUE)
	{
        printf("WS2812: [%s] Timeout waiting for config mutex.\n", __func__);
        result = -1;
        goto err_out;
    }
    
    if(cfg->dma_buff == NULL || cfg->buff_len == 0)
	{
        printf("WS2812: [%s] DMA buffer invalid\n", __func__);
        result = -1;
        goto err_out;
    }

    xEventGroupClearBits(cfg->events, BIT_DONE);
    spi_master_write_stream_dma(&cfg->spi_master, (char *)(cfg->dma_buff[0]), cfg->buff_len);

    timeout = 1000 / portTICK_PERIOD_MS;
    rcvd_events = xEventGroupWaitBits(
                        cfg->events, 
                        BIT_DONE,        // wait for DMA TX done
                        pdTRUE,          // clear event bit
                        pdFALSE,         // do not wait for all bits to be set
                        timeout );

    if(!(rcvd_events & BIT_DONE))
	{
        printf("WS2812: [%s] DMA timeout\n", __func__);
        result = -1;
        goto err_out;
    }

err_out:
    xSemaphoreGive(cfg->mutex);
 
    return result;
}

int32_t WS2812_Update(WS2812_t *cfg, WS2812_HSV_t hsv_values[], uint32_t strip_len, uint16_t delay)
{
    uint32_t i;
    uint8_t *bufp;
    uint16_t len;
    BaseType_t status;
    int32_t result;
	WS2812_RGB_t rgb;

    // make sure DMA buffer is not in use
    while(cfg->spi_master.state & SPI_STATE_TX_BUSY)
	{
        vTaskDelay(0);
    }

    // lock the DMA buffer mutex while we fill it
    status = xSemaphoreTake(cfg->mutex, configTICK_RATE_HZ);
    if(status != pdTRUE)
	{
        printf("WS2812: [%s] Timeout waiting for config mutex.\n", __func__);
        result = -1;
        goto err_out;
    }

    bufp = &(cfg->dma_buff[0]);
    
    // make sure that we do not exceed the buffer
    len = min(strip_len, cfg->strip_len);

    // copy pixel data into DMA buffer
    for(i = 0; i < len; ++i) // ++i ??
	{
        WS2812_HSV2RGB(&hsv_values[i], &rgb);
        bufp = WS2812_RGB2PWM(bufp, rgb.G);
        bufp = WS2812_RGB2PWM(bufp, rgb.R);
        bufp = WS2812_RGB2PWM(bufp, rgb.B);
    }
    
    /* turn unused pixels at end of strip off */ 
    if(cfg->strip_len > len)
	{
        memset(bufp, WS2812_BITS_00, cfg->strip_len - len);
        bufp += cfg->strip_len - len;
    }

    /* add reset pulse */
    memset(bufp, 0x0, WS2812_RESET_LEN); 

    /* release buffer mutex */
    xSemaphoreGive(cfg->mutex);
 
    /* send it off to the strip */   
    result = WS2812_TX(cfg, delay);

err_out:    
    return result;
}

WS2812_t *WS2812_Init(uint16_t strip_len)
{
    int32_t result;
    WS2812_t *cfg;

    result = 0;

    cfg = malloc(sizeof(*cfg));
    if(cfg == NULL)
	{
        printf("WS2812: [%s] malloc for cfg failed\n", __func__);
        result = -1;
        goto err_out;
    }

    memset(cfg, 0x0, sizeof(*cfg));

    cfg->mutex = xSemaphoreCreateMutex();
    if(cfg->mutex == NULL)
	{
        printf("WS2812: [%s] Mutex creation failed\n", __func__);
        result = -1;
        goto err_out;
    }
    
    cfg->events = xEventGroupCreate();
    if(cfg->events == NULL)
	{
        printf("WS2812: [%s] Creating event group failed\n", __func__);
        result = -1;
        goto err_out;
    }

    spi_init(&(cfg->spi_master), WS2812_SPI_MOSI, WS2812_SPI_MISO, WS2812_SPI_SCLK, WS2812_SPI_CS);
    spi_format(&(cfg->spi_master), 8, 3, 0);
    spi_frequency(&(cfg->spi_master), WS2812_SPI_FREQ);
    spi_irq_hook(&(cfg->spi_master), master_tr_done_callback, (uint32_t)cfg);

    result = WS2812_SetLen(cfg, strip_len);
    if(result != 0)
	{
        printf("WS2812: [%s] ws2812_set_len() failed\n", __func__);
    }

err_out:
    if(result != 0 && cfg != NULL)
	{
        if(cfg->mutex != NULL)
		{
            vQueueDelete(cfg->mutex);
        }

        if(cfg->events != NULL)
		{
            vEventGroupDelete(cfg->events);
        }

        if(cfg->dma_buff != NULL)
		{
            free(cfg->dma_buff);
        }

        free(cfg);
        cfg = NULL;
    }
    
    return cfg;
}

int32_t WS2812_SetLen(WS2812_t *cfg, uint16_t strip_len)
{
    int32_t result;
    BaseType_t status;
    uint32_t reset_off;

    result = 0;

    if(cfg == NULL)
	{
        printf("WS2812: [%s] no config given\n", __func__);
        result = -1;
        goto err_out;
    }

    /* lock the config mutex */
    status = xSemaphoreTake(cfg->mutex, configTICK_RATE_HZ);
    if(status != pdTRUE)
	{
        printf("WS2812: [%s] Timeout waiting for config mutex.\n", __func__);
        result = -1;
        goto err_out;
    }

    if(strip_len <= WS2812_LEDS_MAX)
	{
        /* TODO: use dynamically allocated buffer */
        cfg->dma_buff = dma_buffer;

        /* initialise LEDs to off and add reset pulse at end of strip */
        reset_off = WS2812_DMABUF_LEN(strip_len) - WS2812_RESET_LEN;
        memset(&(cfg->dma_buff[0]), WS2812_BITS_00, reset_off);
        memset(&(cfg->dma_buff[reset_off]), 0x0, WS2812_RESET_LEN);
        cfg->strip_len = strip_len;
        cfg->buff_len = WS2812_DMABUF_LEN(strip_len);
    } 
	else 
	{
        printf("WS2812: [%s] Strip too long for DMA buffer\n", __func__);
        result = -1;
    }

    xSemaphoreGive(cfg->mutex);

err_out:
    return result;
}