/* Includes ------------------------------------------------------------------*/
#include "stm32f4xx_hal.h"

/* USER CODE BEGIN Includes */
#include "led_driver.h"
#include "globals.h"
#include <string.h>
#include <math.h>
#include <stdlib.h>
#include "flash.h"
#include "image_generator.h"

/* USER CODE END Includes */


//#define NB_LEDS_PER_STRIP 60
//#define NB_LINES 20
__attribute__((section("itcmram")))
inline uint32_t getRawOffset(uint32_t column,uint32_t line)
{
	return 3*(column+line*NB_COLUMNS);
}



// cf file LogoFiltered2.raw & cfromraw.cpp
// from LogoElectrolab.png
// 20 x 20 pixels
// Exploite la symétrie gauche droite => 10x20
const uint8_t logo_electrolab[200] = 
{
  0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x0D, 0x1A, 0x28, 0x30, 
	0x09, 0x09, 0x09, 0x09, 0x0A, 0x1F, 0x50, 0x83, 0xA7, 0xB5, 
	0x09, 0x09, 0x09, 0x0C, 0x3B, 0x91, 0xD6, 0xEF, 0xEB, 0xE1,
	0x09, 0x09, 0x0C, 0x4A, 0xB8, 0xEE, 0xD1, 0x93, 0x62, 0x4B, 
	0x09, 0x0A, 0x3F, 0xBB, 0xEF, 0xA9, 0x49, 0x17, 0x0A, 0x07,  
	0x09, 0x25, 0x9A, 0xF0, 0xA7, 0x33, 0x01, 0x01, 0x01, 0x01,  
	0x0F, 0x5B, 0xDF, 0xCE, 0x45, 0x0A, 0x00, 0xA4, 0xC5, 0xC5,
	0x1F, 0x91, 0xF3, 0x8B, 0x15, 0x08, 0x00, 0x96, 0xF7, 0xF7, 
	0x30, 0xB5, 0xE9, 0x59, 0x09, 0x08, 0x00, 0x04, 0x0D, 0x0D,  
	0x39, 0xC3, 0xDC, 0x42, 0x08, 0x01, 0x00, 0x86, 0xAE, 0xAE, 
	0x38, 0xC2, 0xDD, 0x44, 0x08, 0x00, 0x00, 0x9F, 0xE8, 0xE8,  
	0x2E, 0xB1, 0xEB, 0x5F, 0x0A, 0x05, 0x00, 0x04, 0x0D, 0x0D, 
	0x1C, 0x88, 0xF2, 0x96, 0x19, 0x08, 0x00, 0x6A, 0x99, 0x99,
	0x0D, 0x51, 0xD6, 0xD8, 0x54, 0x0C, 0x01, 0xB4, 0xEE, 0xEE,
	0x09, 0x1D, 0x8A, 0xEE, 0xB9, 0x44, 0x02, 0x05, 0x0E, 0x0E,
	0x09, 0x09, 0x32, 0xA9, 0xF0, 0xBE, 0x60, 0x24, 0x0F, 0x08,
	0x09, 0x09, 0x0A, 0x39, 0xA3, 0xE9, 0xE1, 0xAE, 0x7F, 0x66,
	0x09, 0x09, 0x09, 0x0A, 0x2B, 0x79, 0xC2, 0xE9, 0xF0, 0xEC,
	0x09, 0x09, 0x09, 0x09, 0x09, 0x16, 0x3D, 0x6A, 0x8D, 0x9B,
	0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x0A, 0x12, 0x1C, 0x22
};	


void putSpriteEL(uint8_t *raw, uint8_t offsetx, uint8_t *color)
{
	uint32_t raw_offset;
	int line, column;
	int j;
	
	for (line=0;line<20;line++)
	{
		j = line*10;
		raw_offset = getRawOffset(offsetx, line);
		
		for (column=0;column<20;column++)
		{
			// sequence j 0,1,2..9,9,8,7,...,1,0,0,1,2,...
				if (column>=10)
					j--;
				raw[raw_offset++] = (color[0]*logo_electrolab[j])>>8;
				raw[raw_offset++] = (color[1]*logo_electrolab[j])>>8;
				raw[raw_offset++] = (color[2]*logo_electrolab[j])>>8;
				if (column<10) 
					j++;
		}
	}
}

// vert vert vert...
uint8_t generateLogoElectrolab(uint8_t *raw, uint8_t intensity)
{
	uint8_t offsetx;
	uint8_t color[3];

	
	offsetx = 0;
	color[0] = 0;
	color[1] = intensity;
	color[2] = 0;
	
	putSpriteEL(raw, offsetx,  color);
	
	offsetx = 20;
	color[0] = 0;
	color[1] = intensity;
	color[2] = 0;
	
	putSpriteEL(raw, offsetx, color);

	offsetx = 40;
	color[0] = 0;
	color[1] = intensity;
	color[2] = 0;
	
	putSpriteEL(raw, offsetx, color);
	
	return 0;
}


__attribute__((section("dtcmram")))
float sintable[91] = {
0.000000,0.017452,0.034899,0.052336,0.069756,0.087156,0.104528,0.121869,
0.139173,0.156434,0.173648,0.190809,0.207912,0.224951,0.241922,0.258819,
0.275637,0.292372,0.309017,0.325568,0.342020,0.358368,0.374607,0.390731,
0.406737,0.422618,0.438371,0.453990,0.469472,0.484810,0.500000,0.515038,
0.529919,0.544639,0.559193,0.573576,0.587785,0.601815,0.615662,0.629320,
0.642788,0.656059,0.669131,0.681998,0.694658,0.707107,0.719340,0.731354,
0.743145,0.754710,0.766044,0.777146,0.788011,0.798635,0.809017,0.819152,
0.829038,0.838671,0.848048,0.857167,0.866025,0.874620,0.882948,0.891007,
0.898794,0.906308,0.913545,0.920505,0.927184,0.933580,0.939693,0.945519,
0.951057,0.956305,0.961262,0.965926,0.970296,0.974370,0.978148,0.981627,
0.984808,0.987688,0.990268,0.992546,0.994522,0.996195,0.997564,0.998630,
0.999391,0.999848,1.000000
};

	
// si  pi/2 <= x <= pi
// sin(x) = sin(pi-x) 
// si  pi <= x <= 3/4 pi
// sin(x) = -sin(x-pi)
// si  3/4 pi <= x <= 2pi
// sin(x) = -sin(-(x-2pi))

__attribute__((section("itcmram")))

float myfsin(float x)
{
	int i;
	
	// for plasma : that's the difference between 30hz and 70Hz...(with microlib)
	// not using microlib makes it go to 100Hz.
	i = /*fmod(x*180.0f/M_PI,360);*/  ((int) (x*180.0f/M_PI)) % 360;
	
	if (i<0)
	{
		i+=360;
	}

	if (i<=90)
		return sintable[i];
	if (i<=180)
		return sintable[180-i];
	if (i<=270)
		return -sintable[i-180];
	return -sintable[-(i-360)];
}

	
//cos(x) = sin(pi/2-x)
__attribute__((section("itcmram")))
	
float myfcos(float x)
{
	return myfsin(M_PI/2-x);
}
	

__attribute__((section("itcmram")))

uint8_t generatePlasma(uint8_t *raw,float u_time, uint8_t plasma_type, uint8_t intensity)
{
	uint32_t raw_offset;
	uint32_t line, column;
	float u_k[2];
  float v_coords[2];
  float v;
	float c[2];
	uint8_t x;
	uint8_t s1,s2,s4,c1;
	
	u_k[0] = u_k[1] = 4;
	
	for (line=0;line<NB_LINES;line++)
	{
		v_coords[1] = 1.0f*line/NB_LINES;
		
		for (column=0;column<NB_COLUMNS;column++)
		{
				v_coords[0] = 1.0f*column/NB_COLUMNS;
			
				raw_offset = getRawOffset(column, line);

				c[0] = u_k[0]*(v_coords[0] - 0.5f);
				c[1] = u_k[1]*(v_coords[1] - 0.5f);

				v = myfsin( c[0]+u_time);
				v += myfsin((c[1]+u_time)*0.5f);
				v += myfsin((c[0]+c[1]+u_time)*0.5f);
				c[0] += u_k[0]*0.5f * myfsin(u_time*0.33333f);
				c[1] += u_k[1]*0.5f * myfcos(u_time*0.5f);
				v += myfsin(sqrt(c[0]*c[0]+c[1]*c[1]+1.0f)+u_time);
				v = v*0.5f;

				switch(plasma_type)
				{
					// type RGB
					case 1:
					case 4:
					case 19:
					case 20:
										s1 = 127.5f*myfsin(M_PI*v)+127.5f;
										s2 = 127.5f*myfsin(M_PI*v+2.0f/3.0f*M_PI)+127.5f;
										s4 = 127.5f*myfsin(M_PI*v+4.0f/3.0f*M_PI)+127.5f;		
					break;
					// type 2 couleurs pures	
					case 2:
					case 16:
					case 18:
					// type 2 couleurs white
					case 3:
					case 12:
					case 17:
					case 14:
										s1 = 127.5f*myfsin(M_PI*v)+127.5f;
										c1 = 127.5f*myfcos(M_PI*v)+127.5f;
					break;

					case 13:
					case 15:
										c[0] = 0.5f+0.5f*myfsin(M_PI*v+4.0f/3.0f*M_PI);
					break;


	// 5

					default :
					break;
				}
				
				switch(plasma_type)
				{
// RGB Type					
					case 1:
					// RGB Type
					raw[raw_offset++] = s1;
					raw[raw_offset++] = s2;
					raw[raw_offset++] = s4;		
					break;
					
					case 19:
					// RGB Type 2
					raw[raw_offset++] = s2;
					raw[raw_offset++] = s1;
					raw[raw_offset++] = s4;		
					break;

					case 20:
					// RGB Type 3
					raw[raw_offset++] = s4;		
					raw[raw_offset++] = s2;
					raw[raw_offset++] = s1;
					break;
// Yellow 
					case 4:
					// White Yellow Type
					raw[raw_offset++] = 255; //(0.5f+0.5f*myfsin(M_PI*v))*255;
					raw[raw_offset++] = 255; //(0.5f+0.5f*myfsin(M_PI*v+2.0f/3.0f*M_PI))*255;
					raw[raw_offset++] = s4;		
					break;
					
// 2 Couleurs	pures				
					case 2:
					// RED GREEN TYPE
					raw[raw_offset++] = s1;
					raw[raw_offset++] = c1;
					raw[raw_offset++] = 0;						
					break;
					
					case 16:
					// VERT / BLEU
					raw[raw_offset++] = 0;
					raw[raw_offset++] = c1;
					raw[raw_offset++] = s1;
					break;

					case 18:
					// ROUGE / BLEU
					raw[raw_offset++] = s1;
					raw[raw_offset++] = 0;
					raw[raw_offset++] = c1;
					break;						
// Mix White
					case 3:
					// YELLOW/MAGENTA/PINK/WHITE TYPE
					raw[raw_offset++] = 255;
					raw[raw_offset++] = c1;
					raw[raw_offset++] = s1;
					break;
					
					case 12:
					// CYAN/YELLOW/WHITE TYPE
					raw[raw_offset++] = s1;
					raw[raw_offset++] = 255;
					raw[raw_offset++] = c1;
					break;						

					case 17:
					// MAGENTA/CYAN/WHITE
					raw[raw_offset++] = s1;
					raw[raw_offset++] = c1;
					raw[raw_offset++] = 255;						
					break;
//
					case 14:
					// MAGENTA TYPE
					raw[raw_offset++] = 255;
					raw[raw_offset++] = s1;
					raw[raw_offset++] = 255;
					break;

					case 5:
					// BLACK AND WHITE
					x = 127.5f*myfsin(M_PI*4.0f*v)+127.5f;
					raw[raw_offset++] = x;
					raw[raw_offset++] = x;
					raw[raw_offset++] = x;	
					break;					
					
					case 13:
					// CYAN TYPE
					raw[raw_offset++] = (c[0]*c[0])*intensity;
					raw[raw_offset++] = 255;
					raw[raw_offset++] = 255;
					break;

					case 15:
					// MAGENTA TYPE DEEP
					raw[raw_offset++] = 255;
					raw[raw_offset++] = (c[0]*c[0])*intensity;
					raw[raw_offset++] = 255;
					break;

					default :
					raw[raw_offset++] = 0x80;
					raw[raw_offset++] = 0x80;
					raw[raw_offset++] = 0x80;
					break;
			}
		}
	}
	
	return 1;
	
}


uint8_t generateUniformColor(uint8_t *raw,uint8_t *color)
{
	uint32_t p=0;
	
	while (p<NB_PIXELS)
	{
		*raw++=color[0];
		*raw++=color[1];
		*raw++=color[2];
		p++;
	}
	return 0;
}

uint8_t generateBlack(uint8_t *raw)
{
	memset(raw,0,RAW_SIZE);	
	return 0;
}


uint8_t generateWhite(uint8_t *raw, uint8_t intensity)
{
	memset(raw,intensity,RAW_SIZE);	
	return 0;
}

uint8_t generateYellow(uint8_t *raw, uint8_t intensity)
{
	uint8_t color[3];
	
	color[0] = color[1] = intensity;
	color[2] = intensity>>2;
	generateUniformColor(raw,color);	

	return 0;
}


// GAME OF LIFE CODE START

// well, ok, ram footprint could be / 8
// but that's ok, plenty of ram
uint8_t imageff[2][NB_LINES][NB_COLUMNS];

void randfill(void)
{
	int line, column;
	for (line=0;line<NB_LINES;line++)
	{
		for (column=0;column<NB_COLUMNS;column++)
		{
	    imageff[0][line][column] = (rand()>=RAND_MAX/2?1:0);
		}
	}
}

uint8_t get8brothers(uint8_t flip,int line,int column)
{
	int cl,cr,lu,ld;
// manage rollover on borders	
	if (line==0)
		lu = NB_LINES-1;
	else
		lu = line-1;
	
	if (line == NB_LINES-1)
		ld = 0;
	else
		ld = line +1;

	if (column==0)
		cl = NB_COLUMNS-1;
	else
		cl = column-1;
	
	if (column==NB_COLUMNS-1)
		cr = 0;
	else
		cr = column+1;
// addup brothers	
	return 
		imageff[flip][lu][cl] + 
    imageff[flip][lu][column] +	  
    imageff[flip][lu][cr] +	
    imageff[flip][line][cl] +	
    imageff[flip][line][cr] +	
    imageff[flip][ld][cl] +	
    imageff[flip][ld][column] +	
    imageff[flip][ld][cr];	
}

// goal : detect situations of cycles
// not a good way to compare, best method is to compute a hash and compare the hashs on more that 2 or 3 cycles.
// if match, then start a few seconds countdown before restart
uint8_t compareGOLImages(void)
{
	uint8_t *p0,*p1;
	uint32_t p;
	
	p0 = &imageff[0][0][0];
	p1 = &imageff[1][0][0];
	p = 0;
	
	while (p<NB_PIXELS)
	{
    if ((*p0)!=(*p1))
				return 0;
		p0++;
		p1++;
		p++;
	}
	return 1;
}

uint8_t generateGameOfLife(uint8_t *raw, float time, uint8_t intensity)
{
	static uint8_t initialized = 0;
	static uint8_t flip, flop;
	int line, column;
	static float lastTime = 0;
	uint8_t brothers;
	uint32_t raw_offset;
	static float prepareRestart;
	static float autoRestart;
	
	if (((!initialized)||(time-lastTime>2))||((prepareRestart!=0)&&(time-prepareRestart>10))||(time-autoRestart>60))
	{
		flip = 0;
		flop = 1;
		srand(time*20);
		randfill();
		initialized = 1;
		prepareRestart = 0;
		autoRestart = time;
		lastTime = time;
	}
	else
	{
		if (time-lastTime<0.3f)
		{
			return 1;
		}
		flip = 1-flip;
		flop = 1-flop;
	}

	// calcule de flip vers flop
		
	for (line=0;line<NB_LINES;line++)
	{
		for (column=0;column<NB_COLUMNS;column++)
		{
			brothers = get8brothers(flip,line,column);
			if (imageff[flip][line][column]==0)
			{
				if (brothers==3)
					imageff[flop][line][column] = 1;
				else
					imageff[flop][line][column] = 0;
			}
			else if (imageff[flip][line][column]==1)
			{
				if ((brothers==2)||(brothers==3))
					imageff[flop][line][column] = 1;
				else
					imageff[flop][line][column] = 0;
			}
			else
			{
				crash(0);
			}
			raw_offset = getRawOffset(column, line);
			brothers = intensity*imageff[flop][line][column];
			raw[raw_offset++] = brothers;
			raw[raw_offset++] = brothers;
			raw[raw_offset++] = brothers;
	  }
	}
/*	
	if (compareGOLImages())
	{
		prepareRestart = time;
	}
*/	
	lastTime = time;
	
	return 1;
}
// GAME OF LIFE CODE END


__attribute__((section("itcmram")))
void setIntensity(uint8_t *raw_out, uint8_t *raw_in, uint8_t intensity)
{
	int i = RAW_SIZE*3;
	
	intensity = (intensity*intensity)>>8;
	
	while(i)
	{
		*raw_out = ((*raw_in)*(intensity+1))>>8;
		raw_out++;
		raw_in++;
		i--;
	}
}


// POWER LIMITER CODE START
#define MAX_LEDTUBE_LINE_POWER (NB_COLUMNS*3*POWER_LIMIT)

__attribute__((section("itcmram")))
int powerLimiter(uint8_t *raw_out, uint8_t *raw_in, uint8_t intensity)
{
	int line, column, i;
	uint8_t *p_in;
	uint16_t linePower;
	uint16_t maxLinePower;
	uint16_t ipowerReduction;
	
	p_in = raw_in;
	maxLinePower = 0;
	intensity = (intensity*intensity)>>8;
	
	for (line=0;line<NB_LINES;line++)
	{
		linePower = 0;
		for (column=0;column<NB_COLUMNS;column++)
		{
			linePower+= p_in[0] + p_in[1] + p_in[2];
			p_in+=3;
		}
		linePower = (linePower*(intensity+1))>> 8;
		if (linePower>maxLinePower)
			maxLinePower = linePower;
	}
	
	if (maxLinePower>MAX_LEDTUBE_LINE_POWER) // power will be limited
	{
		ipowerReduction = (uint16_t) (256.0f*MAX_LEDTUBE_LINE_POWER/maxLinePower);
		
		i=RAW_SIZE;
		do
		{
			*raw_out = ((*raw_in)*(ipowerReduction)) >> 8;
			raw_in++;
			raw_out++;
		} while(--i);
		return 1;
	}
	
	return 0;
}
// POWER LIMITER CODE END



void ledstripXmax(uint8_t *raw);

uint8_t imageGenerator(uint32_t currentMode, uint8_t *raw, float time, uint8_t intensity)
{
	uint8_t dynamic;
	int raw_image_number;
	
	switch(currentMode)
	{
		case 0: dynamic = 0; break;
		case 1:
		case 2:
		case 3:
		case 4:
		case 5: dynamic = generatePlasma(raw,time,(uint8_t) currentMode, intensity); break;
		case 6:	dynamic = generateLogoElectrolab(raw, intensity); break;
		case 7:	dynamic = generateBlack(raw); break;
		case 8: dynamic = generateWhite(raw, intensity); break;
		case 9: dynamic = generateYellow(raw, intensity); break;
		case 10: dynamic = generateLogoElectrolab(raw, intensity); break;	 
		case 11: dynamic = generateGameOfLife(raw, time, intensity); break;		
		case 12: 
		case 13:
		case 14:
		case 15:
		case 16:
		case 17:
		case 18:			
		case 19:			
		case 20:			
		 dynamic = generatePlasma(raw,time,(uint8_t) currentMode, intensity); break;
//		case 21: dynamic = generateElabLogos(raw);break; // Pas beau
		case 21:
			ledstripXmax(raw); 
			dynamic = 1; break;
		default: break;
	}
	
	if (currentMode>=IMAGE_MODES_START) // 22..22+31=53
	{
		raw_image_number = currentMode-IMAGE_MODES_START;
		readImageFromFlash(raw,raw_image_number);
		dynamic = 0;
	}

	return dynamic;
}

