/*
 * led_matrix.c: main skull led matrix handling functions
 * 3414 true
 *
 * ----
 *
 * Resources:
 * - Uses TIM2 and TIM3 as PWM output; for frequency, see led_pwm.c
**/

#include <pirate.h>

#include "../interface/gpio.h"
#include "../interface/led_pwm.h"
#include "../device/lightsensor.h"

#include "led_matrix.h"


  // pwm devices
TIM_TypeDef *led_pwm[2] 			= {TIM2, TIM3};

  // pwm pins
  // order is 0=white, 1=purple. row=high, col=low
GPIO_TypeDef *led_row_port[2] 		= {GPIOA, GPIOC};
uint16_t led_row_pin_mask[2] 		= {0x000f, 0x03c0};
const uint8_t led_row_pin_low[2] 	= {0, 6};

GPIO_TypeDef *led_col_port[2] 		= {GPIOC, GPIOC};
uint16_t led_col_pin_mask[2] 		= {0xf000, 0x003c};
const uint8_t led_col_pin_low[2] 	= {12, 2};

  // led mode
uint8_t led_matrix_mode;

  // led brightness
uint8_t led_level[2][16];

  // led programs
#include "led_matrix_prog.h"
static void (*led_program)();
static void (*led_program_list[])() = {
	led_prog_debug,
	led_prog_loops,
	led_prog_loops_mic,
	led_prog_loops_rotate,
	led_prog_rand_on_rand_off,
	led_prog_mic_spl_meter
};
const char led_matrix_prog_name[LED_MATRIX_PROG_COUNT][16] = {
	{"Debug"},
	{"Loops"},
	{"Loops+Mic"},
	{"Loops+Rotate"},
	{"Random"},
	{"SPLMeter"},
};

  // currently lit LED
static uint8_t led_index;


/* functions */
void led_matrix_io_init()
{
	int i;
	GPIO_InitTypeDef gpio;

	// configure LOW as standard outputs
	gpio.GPIO_Mode = GPIO_Mode_OUT;
	gpio.GPIO_OType = GPIO_OType_PP;
	gpio.GPIO_Speed = GPIO_Speed_10MHz;

	gpio.GPIO_Pin = led_col_pin_mask[0];
	GPIO_Init(led_col_port[0], &gpio);

	gpio.GPIO_Pin = led_col_pin_mask[1];
	GPIO_Init(led_col_port[1], &gpio);

	// and set low
	GPIO_ResetBits(led_col_port[0], led_col_pin_mask[0]);
	GPIO_ResetBits(led_col_port[1], led_col_pin_mask[1]);

	// configure HIGH pins as AF
	gpio.GPIO_Mode = GPIO_Mode_AF;

	gpio.GPIO_Pin = led_row_pin_mask[0];
	GPIO_Init(led_row_port[0], &gpio);

	gpio.GPIO_Pin = led_row_pin_mask[1];
	GPIO_Init(led_row_port[1], &gpio);

	// set AF on HIGH pins
	for (i = 0; i < 4; i++) {
		GPIO_PinAFConfig(led_row_port[0], led_row_pin_low[0] + i, GPIO_AF_TIM2);
		GPIO_PinAFConfig(led_row_port[1], led_row_pin_low[1] + i, GPIO_AF_TIM3);
	}

	// MAKE SURE to enable PWM for these to work!
}

void led_matrix_io_disable()
{
	GPIO_InitTypeDef gpio;
	int i;

	gpio.GPIO_Mode = GPIO_Mode_OUT;
	gpio.GPIO_OType = GPIO_OType_OD;
	gpio.GPIO_PuPd = GPIO_PuPd_UP;

	for (i = 0; i < 4; i++) {
		gpio.GPIO_Pin = led_row_pin_low[0] + i;
		GPIO_Init(led_row_port[0], &gpio);
		gpio.GPIO_Pin = led_row_pin_low[1] + i;
		GPIO_Init(led_row_port[1], &gpio);
	}
}

void inline led_matrix_next()
{
	uint32_t update_col;
	uint32_t update_row;
	uint16_t update_scaler;
	uint32_t update_level[2][4] = {
		//{0xff, 0xff, 0xff, 0xff},
		//{0xff, 0xff, 0xff, 0xff}
		{0x100, 0x100, 0x100, 0x100},
		{0x100, 0x100, 0x100, 0x100}
	};

	// set next index
	led_index++;
	led_index &= 0x0f;

	// set active column
	update_col = led_index % 4;
	update_row = led_index >> 2;

	// set new levels
	update_scaler = (settings.led_autoadjust & 0x80) ? lightsensor_get_scalerval(LIGHTSENS_SCALED_SKULL_WHT) : 256;
	update_level[0][update_row] = 0x100 - ((led_level[0][led_index] * update_scaler) >> 8);
	update_scaler = (settings.led_autoadjust & 0x80) ? lightsensor_get_scalerval(LIGHTSENS_SCALED_SKULL_PUR) : 256;
	update_level[1][update_row] = 0x100 - ((led_level[1][led_index] * update_scaler) >> 8);

	// updating the matrix is glitchy as best and I know I am doing something wrong here.
	// but this is the best I could come up with while sleep-deprived forceful debugging.
	// disable PWM
	TIM_Cmd(led_pwm[0], DISABLE);
	TIM_Cmd(led_pwm[1], DISABLE);

	// set new PWM value for the active row
	led_pwm_set_oc(led_pwm[0], update_level[0]);
	led_pwm_set_oc(led_pwm[1], update_level[1]);

	// reset pwm counter (fixes matrix glitches)
	TIM_SetCounter(led_pwm[0], 0xff);
	TIM_SetCounter(led_pwm[1], 0xff);

	// generate update event to apply pwm values
	TIM_GenerateEvent(led_pwm[0], TIM_EventSource_Update);
	TIM_GenerateEvent(led_pwm[1], TIM_EventSource_Update);

	// clear all columns
	// we do this after setting PWM to fix glitchy other LEDs lighting
	GPIO_ResetBits(led_col_port[0], led_col_pin_mask[0]);
	GPIO_ResetBits(led_col_port[1], led_col_pin_mask[1]);

	// and now set the active column
	GPIO_SetBits(led_col_port[0], (1 << (led_col_pin_low[0] + update_col)));
	GPIO_SetBits(led_col_port[1], (1 << (led_col_pin_low[1] + update_col)));

	// re-enable PWM
	TIM_Cmd(led_pwm[0], ENABLE);
	TIM_Cmd(led_pwm[1], ENABLE);
}

void led_matrix_set_mode(uint8_t mode)
{
	led_matrix_mode = mode;
}

void led_matrix_set_program(uint8_t program_idx, uint8_t init,
		uint16_t wait, uint16_t level, uint32_t offset, uint32_t settings)
{
	// we need to be in program mode
	led_matrix_set_mode(LED_MATRIX_MODE_PROGRAM);
	// update the program pointer
	led_program = led_program_list[program_idx];

	// set initial program variables
	led_prog_set_wait = wait;
	led_prog_set_level = level;
	led_prog_set_offset = offset;
	led_prog_set_option = settings;

	// is this a new set? if so, initialize program parameters
	if (init) {
		int i;

		led_prog_wait = 0;
		for (i = 0; i < 4; i++) {
			led_prog_state[i] = 0;
			led_prog_work[i] = 0;
		}
	}
}

uint8_t led_matrix_get_mode()
{
	return led_matrix_mode;
}

void led_matrix_mode_update()
{
	int i;

	switch (led_matrix_mode) {
		case LED_MATRIX_MODE_PROGRAM: {
			// if we have a valid program loaded, run it
			if (led_program != NULL) {
				led_program();
			}
			break;
		}
		case LED_MATRIX_MODE_OFF: {
			led_program = NULL;
			for (i = 0; i < 16; i++) {
				led_level[LED_MATRIX_WHITE][i] = 0;
				led_level[LED_MATRIX_PURPLE][i] = 0;
			}
			break;
		}
	}
}
