/** \file script_cmd.c
 * \brief <введите краткую информацию об этом файле здесь>
 *
 * <введите более подробную информацию об этом файле здесь>
 * \author ARV
 * \note <введите примечания здесь>
 * \date	5 апр. 2019 г.
 * \copyright 2019 © ARV. All rights reserved.
 *
 * Для компиляции требуется:\n
 * 	-# avr-gcc 6.3.0 или более новая версия
 *
 */

#include <stdlib.h>
#include "global.h"

EEMEM ws_led_t e_led = WS2812;

script_t script;
bool cfg_lock;
static uint8_t cur_row=0xFF, cur_col=0xFF;

/**
 * чтение одного ниббла шестнадцатиричного числа
 * @param s указатель на строку (символ ниббла)
 * @param dest байт-приемник результата
 * @return true, если символ соответствует 16-ричному числу
 */
static bool read_hex_nibble(char *s, uint8_t *dest){
	if((*s >= '0') && (*s <= '9')){
		*dest = *s - '0';
		return true;
	} else if((*s >= FIRST_EXT_VAR) && (*s <= LAST_HEX_SYMBOL)){
		*dest = *s - FIRST_EXT_VAR + 10;
		return true;
	}
	return false;
}

/**
 * читает номер переменной
 * @param s указатель на символ номера
 * @param dest указатель на приемник номера
 * @return true, если символ содержит корректный номер, иначе false
 */
static bool read_var_id(char *s, uint8_t *dest){
	// если считан корректный ниббл - готово
	if(read_hex_nibble(s, dest)) return true;
	// иначе надо проверить на "хвост" допустимых символов
	if((*s > LAST_HEX_SYMBOL) && (*s <= LAST_EXT_VAR)){
		*dest = *s - FIRST_EXT_VAR + 10;
		return true;
	}
	return false;
}

/**
 * чтение значения байта
 * @param s указатель на строку (символы байта)
 * @param dest байт-приемник результата
 * @return true, если символы представяют собой корректное 16-ричное число
 */
static bool read_byte(char *s, uint8_t *dest){
	uint8_t hi, lo;
	if(read_hex_nibble(s, &hi) && read_hex_nibble(s+1, &lo)){
		*dest = hi*16 + lo;
		return true;
	}
	return false;
}

/**
 * извлечение значения переменной из строки
 * @param s указатель на строку (переменную)
 * @param dest байт-приемник результата
 * @return true, если указана корректная перменная
 */
static bool value_var(char *s, uint8_t *dest){
	uint8_t id;

	if(read_var_id(s, &id)){
		*dest = script.var[id];
		return true;
	}
	return false;
}

/**
 * косвенное обращение к переменной
 * @param s указатель на строку
 * @param dest байт-приемник результата
 * @return true, если указана корректная перменная
 */
static bool xvalue_var(char *s, uint8_t *dest){
	uint8_t id;
	if(read_var_id(s, &id)){
		*dest = script.var[script.var[id] % VAR_CNT];
		return true;
	}
	return false;
}

static uint8_t ain_result(uint8_t ai){
	uint16_t result;
	result = script.ain_bounds[ai*2] + ((script.ain_bounds[ai*2+1] - script.ain_bounds[ai*2]) * ain[ai])/256;
	return result;
}

/**
 * чтение значения элемента
 * @param src указатель на символы элемента
 * @param dest байт-приемник
 * @return trueб если элемент описан корректно
 * \note элемент - это 2 символа, представляющие собой один из
 * допустимых элементов скрипта: число, переменная или функция.
 * если строка содержит корректный элемент скрипта, функция поместит
 * его значение в #dest
 */
static bool read_val(char *s, uint8_t *dest){
	// переменная
	if(*s == 'V')
		return value_var(s+1, dest);
	// косвенная переменная
	if(*s == 'X')
		return xvalue_var(s+1, dest);
	// функция
	else if((s[0] == 'R') && (s[1] == 'D')){
		// случайное число
		*dest = rnd(256);
		return true;
	} else if((s[0] == 'R') && (s[1] == 'P')){
		// случайный пиксел
		if(script.matrix){
			if(cur_col == 0xFF)
				*dest = rnd(cfg.mx);
			else
				*dest = rnd(cfg.my);
		} else {
			*dest = rnd(cfg.tp);
		}
		return true;
	} else if((s[0] == 'D') && (s[1] == 'P')){
/*
		// функция DP
		uint8_t dark = 0;
		// подсчитываем количество темных пикселов
		forall_px(if(((pixels[px].r * pixels[px].bright) >> 8) | ((pixels[px].g * pixels[px].bright) >> 8) | ((pixels[px].b * pixels[px].bright) >> 8)) dark++);
		if(dark){
			// выбираем случайный порядковый номер темного пиксела
			dark = rnd(dark);
			// отсчитываем по порядку нужнй номер
			for(uint8_t px=FIRST_PX; px < LAST_PX; px += DELTA_PX){
				if((((pixels[px].r * pixels[px].bright) >> 8) | ((pixels[px].g * pixels[px].bright) >> 8) | ((pixels[px].b * pixels[px].bright) >> 8)) && (px == dark)){
					// когда досчитаем - этот номр и вернем
					*dest = px;
					break;
				}
			}
		} else {
			// если темнх нет вообще - вернем несущетвующий номер
			*dest = LAST_PX+1;
		}
		return true;
*/
//* было
		uint8_t px, cnt;
		cnt = LAST_PX-FIRST_PX+1; // количество попыток найти темный пиксел ограничено общим кол-вом пикселов
		do{
			// выбираем новый пиксел
			px = rnd(LAST_PX-FIRST_PX+1);
			// пока хотя бы одно из произведений яркости на цвет не равно нулю
		} while((
				((pixels[px].r * pixels[px].bright) >> 8) |
				((pixels[px].g * pixels[px].bright) >> 8) |
				((pixels[px].b * pixels[px].bright) >> 8)) && cnt--);

		*dest = px;
//*/
		return true;
	} else if((s[0] == 'T') && (s[1] == 'P')){
		// количество пикселов
		if(script.matrix){
			if(cur_col == 0xFF)
				*dest = cfg.mx-1;
			else
				*dest = cfg.my-1;
		} else {
			*dest = cfg.tp-1;
		}
		return true;
	} else if((s[0] == 'T') && (s[1] == 'C')){
		// количество столбцов матрицы
		*dest = cfg.mx-1;
		return true;
	} else if((s[0] == 'T') && (s[1] == 'R')){
		// количество строк матрицы
		*dest = cfg.my-1;
		return true;
	} else if (s[0] == 'I') { // TODO выбрать букву для функции аналогового входа!
		switch(s[1]){
		case '0' ... AIN_CNT + '0':
			*dest = ain_result(s[1]-'0');
			return true;
		default:
			return false;
		}
	} else if((s[0]=='_') && (s[1]=='R')){
		*dest = script.pc.r;
		return true;
	} else if((s[0]=='_') && (s[1]=='G')){
		*dest = script.pc.g;
		return true;
	} else if((s[0]=='_') && (s[1]=='B')){
		*dest = script.pc.b;
		return true;
	} else
		// или просто число
		return read_byte(s, dest);
}

/**
 * чтение десятичного числа
 * @param s указатель на строку с числом
 * @return считанное значение или 0 при ошибке
 */
static uint16_t cfg_read_int(char *s){
	if(*s != '=') return 0;
	return atoi(s+1);
}

/**
 * считывание бинарного числа (0 или 1)
 * @param s указатель на число
 * @return значение
 */
static bool cfg_read_bool(char *s){
	if(*s != '=') return false;
	if((s[1] != '0') && (s[1] != '1')) return false;
	return s[1] == '1';
}

CMD(HELP){
	if(!console_mode()) return 0;
	print_line('=', 24);
	printf_P(PSTR("\nDIGISCRIPT: GENERATION 2\n Copyright 2019 (c) ARV\n"));
	print_line('=', 24);
#if	!defined(__AVR_ATmega328P__)
	// это на случай, если надо впихнуть в 16К флеша
	printf_P(PSTR("\nPlease, read the documentation!"));
	return 0;
#endif
	printf_P(PSTR("\n\nEnter any script or configuration command(s) and press Enter for immediately execute it\n"));
	printf_P(PSTR("or enter one of next debugging commands:\n"));
	printf_P(PSTR("\tAUTO=N - auto PNT command adding (N=0 - off, N=1 - on)\n"));
	printf_P(PSTR("\tCFG - print configuration parameters\n"));
	printf_P(PSTR("\tSTAT - print status of all variables\n"));
	printf_P(PSTR("\tCNT - print pixel count\n"), cfg.tp);
	printf_P(PSTR("\tCNT=N - limit pixel count to N (decimal, 16...%d)\n"), cfg.tp);
	printf_P(PSTR("\tINIT - set all debug parameters to default value:\n"));
	printf_P(PSTR("\t\tCNT=%d MX=16 MY=10 TP=%d\n"), cfg.tp, PIXEL_CNT);
	printf_P(PSTR("\tINIT=N - set all debug parameters to default value by N (decimal):\n"));
	printf_P(PSTR("\t\tMX=16 MY=N/4 TP=MX*MY CNT=TP\n"));
	return 0;
}

CMD(CNT){
	if(console_mode() || !cfg_lock){
		if(str[3] == '='){
			uint8_t tmp;
			tmp = cfg_read_int(str+3);
			if((tmp < 16) || ( tmp > PIXEL_CNT)){
				tmp = PIXEL_CNT;
			}
			cfg.tp = tmp;
			LAST_PX = tmp;
		} else {
			if(console_mode()) printf_P(PSTR("CNT=%d"), LAST_PX);
		}
	}
	return 0;
}

CMD(STAT){
	if(!console_mode()) return 0;

	printf_P(PSTR("STATE:\n\t"));
	for(uint8_t i=0; i<VAR_CNT; i++){
		printf_P(PSTR("V%c "), i < 10 ? '0'+i : 'A'+i-10);
	}
	printf_P(PSTR("\n\t"));
	for(uint8_t i=0; i<VAR_CNT; i++){
		printf_P(PSTR("%02X "), script.var[i]);
	}
	if(script.rev) printf_P(PSTR("\n\tREVERSE ON"));
	printf_P(PSTR("\n\tPC=%02X%02X%02X PF=%02X PB=%02X"), script.pc.r, script.pc.g, script.pc.b,  script.fd, script.br);
	if(script.matrix){
		printf_P(PSTR("\n\tMATRIX ON\t"));
		if(cur_row != 0xFF) printf_P(PSTR("Selected ROW=%02X\n"), cur_row);
		if(cur_col != 0xFF) printf_P(PSTR("Selected COL=%02X\n"), cur_col);
	} else {
		printf_P(PSTR("\n\tMATRIX OFF\n"));
	}
	return 0;
}

CMD_ALIAS(LST, STAT);

CMD(AUTO){
	if(!console_mode()) return 0;

	autopaint = cfg_read_bool(str+4);
	return 0;
}

CMD(CFG){
	if(!console_mode()) return 0;

	printf_P(PSTR("CONFIG:\n\tTF=%d(ignored)\tRP=%d(ignored)\tIG=%d(ignored)\tPS=%d(ignored)\n\tIB=%d"),
			cfg.total_files, cfg.random, cfg.skip_missing, cfg.pause/100, cfg.run_limit);
	printf_P(PSTR("\tMX=%d\tMY=%d\tTP=%d\n"), cfg.mx, cfg.my, cfg.tp);
	printf_P(PSTR("\tAUTO PNT %S\n"), autopaint ? PSTR("ON") : PSTR("OFF"));
	return 0;
}


CMD(SCR){
	if(!console_mode()) return 0;

	printf_P(PSTR("SCRIPT:\n\tDELTA=%d\tMIN_PX=%d\tMAX_PX=%d\n"),DELTA_PX, FIRST_PX, LAST_PX);
	return 0;
}

CMD(ALL){
	FCMD(CFG)(str);
	FCMD(SCR)(str);
	FCMD(STAT)(str);
	return 0;
}

static int f_cmd_CLR(char *);

CMD(INIT){
	if(!console_mode()) return 0;

	if(str[4]=='='){
		uint8_t tmp;
		tmp = atoi(str+5);
		if((tmp <= PIXEL_CNT) && (tmp >= 16)) cfg.tp = tmp;
	}
	cfg.mx = 16;
	cfg.my = cfg.tp / cfg.mx;
	cfg.tp = cfg.mx * cfg.my;
	LAST_PX = cfg.tp;
	FCMD(CLR)(str);
	return 0;
}

// начало блока команд конфигурации
// блок этих команд должен быть перед блоком команд скрипта, т.к.
// в памяти размещение обратное, т.е. первыми будут команды, определенные
// в исходнике последними. а команды конфигурации не обязаны быть
// быстродействующими
// функции обработки команд конфигурации всегда должны возвращать 1.
CMD(MY){
	if(console_mode() || !cfg_lock){
		// количество строк
		cfg.my = cfg_read_int(str+2);
	}
	return 0;
}

CMD(MX){
	if(console_mode() || !cfg_lock){
		// количество столбцов
		cfg.mx = cfg_read_int(str+2);
	}
	return 0;
}

CMD(TF){
	if(console_mode() || !cfg_lock){
		// TOTAL FILES - общее количество файлов скрипта
		cfg.total_files = cfg_read_int(str+2);
	}
	return 0;
}

CMD(RP){
	if(cfg_lock) return 0;

	// RANDOM PLAY - воспроизведение скриптов в случайном порядке
	cfg.random = cfg_read_bool(str+2);
	return 0;
}

/********************************************************
 *         начало поддержки условной команды			*
 ********************************************************/

/*
 * условный блок задается командами
 * I<условие> [блок] [ALT [альтернативный блок]] EI
 *
 * блок команд исполняется только втом случае, если условие выполняется.
 * (не работает!)альтернативный блок выполняется только в том случае, если условие не выполняется.
 *
 * условие задается выражением
 * <операнд 1>[<знак отношения><операнд 2>]
 * операнды 1 и 2 - любые допустимые обозначения чисел, переменных или функций
 * знак отношения - один из нижеследующих:
 * > - больше
 * < - меньше
 * = - равно
 * ! - не равно
 * * - битовое И
 */

CMD(EI){
	// конец условного блока
	if(script.skip) script.skip--;
	return EXEC_NEXT;
}

CMD(I){
	if(script.skip) {
		script.skip++;
		return EXEC_NEXT;
	}
	// начало условного блока Ixx=yy
	uint8_t op1, op2;
	if(!read_val(str+1, &op1)) return EXEC_NEXT;
	if(!separator(str[3])){
		if(!read_val(str+4, &op2)) return EXEC_NEXT;
		switch(str[3]){
		case '>': op1 = op1 > op2; break;
		case '<': op1 = op1 < op2; break;
		case '!': op1 = op1 != op2; break;
		case '=': op1 = op1 == op2; break;
		case '*': op1 = op1 & op2; break; // битовое И
		default:
			return EXEC_NEXT;
		}
	}
	if(!op1) script.skip++;
	return EXEC_NEXT;
}

/********************************************************
 *          конец поддержки условной команды			*
 ********************************************************/

CMD(IG){
	if(cfg_lock) return 0;
	// IGNORE - игнорировать отсутствующие скрипты при последователном воспроизведении
	cfg.skip_missing = cfg_read_bool(str+2);
	return 0;
}

CMD(IB){
	if(!cfg_lock){
		// INFINITY BREAK - принудительное прерывание зациклившихся скриптов
		cfg.run_limit = cfg_read_int(str+2) * 100;
		limit = cfg.run_limit;
	}
	return 0;
}

CMD(WS){
	if(!cfg_lock){
		uint8_t tmp;
		// тип ленты
		tmp = cfg_read_int(str+2);
		switch(tmp){
		case 11: cfg.led = WS2811; break;
		case 12:
		default:
			cfg.led = WS2812; break;
		}
		eeprom_update_byte(&e_led, cfg.led);
	}
	return 0;
}

CMD(PS){
	if(cfg_lock) return 0;
	// PAUSE - принудительная пауза между воспроизведением разных файлов
	cfg.pause = cfg_read_int(str+2) * 100;
	return 0;
}
// конец блока команд конфигурации

static void shift_L_color(void){
	for(uint8_t i=FIRST_PX; i<(LAST_PX-DELTA_PX); i += DELTA_PX)
		pixels[i].rgb = pixels[i+DELTA_PX].rgb;
}

// начало блока команд скрипта
// первыми должны следовать наименее ходовые команды, т.к. они будут обработаны в последнюю очередь
/*
 * функция-обработчик команды может возвратить следующие значения:
 * EXEC_NEXT и более - нормальная обработка, надо сдвинуть указатель на следующую команду и продолжить работу
 *  или
 * EXEC_END - достигнут конец скрипта, надо завершить работу
 * EXEC_SKIP - продолжить работу, не сдвигая указатель
 * EXEC_SEEK - спозиционировать файл и начать с этой позиции
 */

CMD(AB){
	// диапазон аналоговых сигналов
	if(str[2] == '='){
		uint8_t min0, min1, max0, max1;
		if(read_val(str+3, &min0) && read_val(str+5, &max0) && read_val(str+7, &min1) && read_val(str+9, &max1)){
			script.ain_bounds[0] = min0;
			script.ain_bounds[1] = max0;
			script.ain_bounds[2] = min1;
			script.ain_bounds[3] = max1;
		}
	}
	return EXEC_NEXT;
}

CMD_DEF(V);

CMD(_){
	// псевдопеременные цветовых составляющих
	if((str[1] == 'R') ||
	   (str[1] == 'G') ||
	   (str[1] == 'B')
	){
		uint8_t tmp_v0 = script.var[0];
		char tmp = str[1];
		str[0] = 'V';
		str[1] = '0';
		switch(tmp){
		case 'R': script.var[0] = script.pc.r; break;
		case 'G': script.var[0] = script.pc.g; break;
		case 'B': script.var[0] = script.pc.b; break;
		}
		FCMD(V)(str);
		switch(tmp){
		case 'R': script.pc.r = script.var[0]; break;
		case 'G': script.pc.g = script.var[0]; break;
		case 'B': script.pc.b = script.var[0]; break;
		}
		script.var[0] = tmp_v0;
		str[0] = '_';
		str[1] = tmp;
	}
	return EXEC_NEXT;
}

CMD(MMD){
	if(script.matrix){
		// запрет матричного режима
		script.matrix = false;
		FIRST_PX = 0;
		LAST_PX = cfg.tp;
		DELTA_PX = 1;
	}
	return EXEC_NEXT;
}

CMD(PP){
	// PEEK PIXEL 'PP=XX'
	uint8_t px;
	if(read_val(str+3, &px)){
		if(px < (LAST_PX - FIRST_PX + 1)){
			if(script.rev)
				px = LAST_PX - DELTA_PX * px - 1;
			else
				px = FIRST_PX + DELTA_PX * px;
			script.pc = pixels[px].rgb;
			script.br = pixels[px].bright;
			script.fd = pixels[px].fade;
		}
	}
	return EXEC_NEXT;
}

CMD(MC){
	uint8_t mpx;
	// установить столбец матрицы
	do{
		if(!cfg.tp || str[2] != '=') break;
		if(!read_val(str+3, &mpx)) break;
		if(mpx > cfg.mx) break;
		FIRST_PX = mpx;
		cur_col=atoi(str+3);
		cur_row=0xFF;
		DELTA_PX = cfg.mx;
		LAST_PX = DELTA_PX*(cfg.my-1) + mpx + 1;
		script.matrix = true;
	} while(0);
	return EXEC_NEXT;
}

CMD(MR){
	uint8_t mpx;
	// установить строку матрицы
	do{
		if(!cfg.tp || str[2] != '=') break;
		if(!read_val(str+3, &mpx)) break;
		if(mpx > cfg.my) break;
		LAST_PX = mpx;
		cur_row=atoi(str+3);
		cur_col=0xFF;
		DELTA_PX = 1;
		FIRST_PX = LAST_PX * cfg.mx;
		LAST_PX = FIRST_PX + cfg.mx;
		script.matrix = true;
	} while(0);
	return EXEC_NEXT;
}

CMD(MRL){
	// прокрутка матрицы влево
	pixel_t pix;
	for(uint8_t y = 0; y < cfg.my; y++){
		pix = pixels[y*cfg.mx];
		memmove(&pixels[y*cfg.mx], &pixels[y*cfg.mx+1], sizeof(pixel_t)*(cfg.mx-1));
		pixels[(y+1)*cfg.mx-1] = pix;
	}
	return EXEC_NEXT;
}

CMD(MSL){
	// сдвиг матрицы влево
	pixel_t pix;
	pix.rgb = script.pc;
	pix.fade = script.fd;
	pix.bright = script.br;

	for(uint8_t y = 0; y < cfg.my; y++){
		memmove(&pixels[y*cfg.mx], &pixels[y*cfg.mx+1], sizeof(pixel_t)*(cfg.mx-1));
		pixels[(y+1)*cfg.mx-1] = pix;
	}
	return EXEC_NEXT;
}

CMD(MRR){
	// прокрутка матрицы вправо
	pixel_t pix;
	for(uint8_t y = 0; y < cfg.my; y++){
		pix = pixels[(y+1)*cfg.mx-1];
		memmove(&pixels[y*cfg.mx+1], &pixels[y*cfg.mx], sizeof(pixel_t)*(cfg.mx-1));
		pixels[y*cfg.mx] = pix;
	}
	return EXEC_NEXT;
}

CMD(MSR){
	// сдвиг матрицы вправо
	pixel_t pix;

	pix.rgb = script.pc;
	pix.fade = script.fd;
	pix.bright = script.br;

	for(uint8_t y = 0; y < cfg.my; y++){
		memmove(&pixels[y*cfg.mx+1], &pixels[y*cfg.mx], sizeof(pixel_t)*(cfg.mx-1));
		pixels[y*cfg.mx] = pix;
	}
	return EXEC_NEXT;
}

CMD(MRD){
	// прокрутка матрицы вниз
	pixel_t tmp[cfg.mx];

	memcpy(tmp, &pixels[cfg.tp-cfg.mx], sizeof(pixel_t)*cfg.mx);
	memmove(&pixels[cfg.mx], pixels, sizeof(pixel_t)*(cfg.tp-cfg.mx));
	memcpy(pixels, tmp, sizeof(pixel_t)*cfg.mx);
	return EXEC_NEXT;
}

CMD(MSD){
	// сдвиг матрицы вниз
	pixel_t tmp;
	tmp.rgb = script.pc;
	tmp.fade = script.fd;
	tmp.bright = script.br;

	memmove(&pixels[cfg.mx], pixels, sizeof(pixel_t)*(cfg.tp-cfg.mx));
	for(uint8_t px=0; px<cfg.mx; px++) pixels[px] = tmp;
	return EXEC_NEXT;
}

CMD(MRU){
	// прокрутка матрицы вверх
	pixel_t tmp[cfg.mx];

	memcpy(tmp, pixels, sizeof(pixel_t)*cfg.mx);
	memmove(pixels, &pixels[cfg.mx], sizeof(pixel_t)*(cfg.tp-cfg.mx));
	memcpy(&pixels[cfg.tp-cfg.mx], tmp, sizeof(pixel_t)*cfg.mx);
	return EXEC_NEXT;
}

CMD(MSU){
	// сдвиг матрицы вверх
	pixel_t tmp;
	tmp.rgb = script.pc;
	tmp.fade = script.fd;
	tmp.bright = script.br;

	memmove(pixels, &pixels[cfg.mx], sizeof(pixel_t)*(cfg.tp-cfg.mx));
	for(uint8_t px=cfg.tp-cfg.mx; px<cfg.tp; px++) pixels[px] = tmp;
	return EXEC_NEXT;
}

CMD(END){
	// проверка - на "пропускаемую" команду
	if(script.skip) return EXEC_NEXT;
	// завершение скрипта
	return EXEC_END;
}

CMD(RST){
	// рестарт скрипта
	if(console_mode()) return EXEC_NEXT;
	script.seek = 0;
	return EXEC_SKIP;
}

void clr_script(void){
	script.br = 255;
	// запрет матричного режима
	script.matrix = false;
	LAST_PX = cfg.tp;
	DELTA_PX = 1;
	// масштаб аналоговых входов по умолчанию
	script.ain_bounds[1] = 0xFF;
	script.ain_bounds[3] = 0xFF;
}

CMD(CLR){
	memset(pixels, 0, cfg.tp * sizeof(pixel_t));
	// очистка встроенных параметров скрипта
	memset(script.stack, 0, CLR_MEMSET_LEN);
	clr_script();
	return EXEC_NEXT;
}

CMD(PB){
	// установка яркости рисования
	if(str[2] == '=') read_val(str+3, &script.br);
	return EXEC_NEXT;
}

CMD(GB){
	// установка яркости всех пикселов
	if(str[2] == '='){
		uint8_t br;
		if(read_val(str+3, &br)){
			forall_px(pixels[px].bright = br);
		}
	}
	return EXEC_NEXT;
}

static hsv_t rnd_hsv(void){
	hsv_t h;
	h.h = rnd(256);
	h.s = 0xFF;
	h.v = 0xFF;
	return h;
}

CMD(REV){
	// реверсирование нумерации пикселов
	script.rev = !script.rev;
	return EXEC_NEXT;
}

CMD(NEG){
	// инверсия яркости всех пикселов
	forall_px(pixels[px].bright ^= 0xFF);
	return EXEC_NEXT;
}

static uint16_t pop_stack(void){
	if(script.stack_head == 0)
		return 0;	// если стек пуст - все равно вернем 0
	else
		return script.stack[--script.stack_head];
}

static void push_stack(uint16_t pos){
	if(script.stack_head >= STACK_DEPTH) return;
	script.stack[script.stack_head++] = pos;
}

CMD(RLC){
	// сдвиг цвета влево с переносом
	rgb_t c = pixels[FIRST_PX].rgb;
	shift_L_color();
	pixels[LAST_PX-DELTA_PX].rgb = c;
	return EXEC_NEXT;
}

CMD(SLC){
	// сдвиг цвета влево
	shift_L_color();
	memset(&pixels[LAST_PX-DELTA_PX], 0, sizeof(rgb_t));
	return EXEC_NEXT;
}

static void shift_R_color(void){
	for(uint8_t i=LAST_PX-DELTA_PX; i >= (FIRST_PX + DELTA_PX); i -= DELTA_PX)
		pixels[i].rgb = pixels[i-DELTA_PX].rgb;
}

CMD(RRC){
	// сдвиг цвета вправо с переносом
	rgb_t c = pixels[LAST_PX-DELTA_PX].rgb;
	shift_R_color();
	pixels[FIRST_PX].rgb = c;
	return EXEC_NEXT;
}

CMD(SRC){
	// сдвиг цвета вправо
	shift_R_color();
	memset(&pixels[FIRST_PX], 0, sizeof(rgb_t));
	return EXEC_NEXT;
}

static void shift_L_bright(void){
	for(uint8_t i=FIRST_PX; i<(LAST_PX-DELTA_PX); i += DELTA_PX)
		pixels[i].bright = pixels[i+DELTA_PX].bright;
}

CMD(RLB){
	// сдвиг яркости влево с переносом
	uint8_t b = pixels[FIRST_PX].bright;
	shift_L_bright();
	pixels[LAST_PX-DELTA_PX].bright = b;
	return EXEC_NEXT;
}

CMD(SLB){
	// сдвиг яркости влево
	shift_L_bright();
	pixels[LAST_PX-DELTA_PX].bright = 0;
	return EXEC_NEXT;
}

static void shift_R_bright(void){
	for(uint8_t i=LAST_PX-DELTA_PX; i >= (FIRST_PX+DELTA_PX); i -= DELTA_PX)
		pixels[i].bright = pixels[i-DELTA_PX].bright;
}

CMD(RRB){
	// сдвиг яркости вправо с переносом
	uint8_t b = pixels[FIRST_PX-DELTA_PX].bright;
	shift_R_bright();
	pixels[FIRST_PX].bright = b;
	return EXEC_NEXT;
}

CMD(SRB){
	// сдвиг яркости вправо
	shift_R_bright();
	pixels[FIRST_PX].bright = 0;
	return EXEC_NEXT;
}

CMD(RPT){
	// начало цикла
	push_stack(script.next - script.readed + skip_invalid(str+3) - buf);
	return EXEC_NEXT;
}

CMD(RGC){
	// один случайный цвет для всех пикселов
	hsv_t h = rnd_hsv();
	forall_px(set_hsv_color(px, h));
	return EXEC_NEXT;
}

CMD(RPC){
	// случайный цвет рисования
	hsv_t h = rnd_hsv();
	hsv_to_rgb(&h, &script.pc);
	return EXEC_NEXT;
}

// эта команда должна быть ПЕРЕД командой I - обязательно!
CMD(INF){
	// бесконечный цикл
	uint16_t go;

	go = script.stack_head ? script.stack[script.stack_head-1] : 0;
	script.seek = go;
	return EXEC_SEEK;
}

//*
CMD(L1){
	// новая версия конца цикла - расширенный синтаксис
	uint8_t op1, op2;
	bool to_go = false;

	// значение первого операнда
	if(!read_val(str+1, &op1)) return EXEC_NEXT;
	if(separator(str[3])){
		// только один операнд и есть
		if(str[1] == 'V'){
			// и если этот операнд - переменная
			// выясняем номер переменной
			if(!read_var_id(str+2, &op2)) return EXEC_NEXT;
			// если переменная не равна нулю
			if(op1){
				// уменьшаем и повторяем цикл
				script.var[op2]--;
				to_go = true;
			}
		} else {
			// единственный операнд не переменная
			to_go = op1 != 0;
		}
	} else {
		// операндов два
		if(!read_val(str+4, &op2)) return EXEC_NEXT;
		switch(str[3]){
		case '>': to_go = op1 > op2; break;
		case '<': to_go = op1 < op2; break;
		case '!': to_go = op1 != op2; break;
		case '=': to_go = op1 == op2; break;
		default:
			return EXEC_NEXT;
		}
	}

	if(to_go){
		// если повтор цикла, то выполняем его
		return FCMD(INF)(str);
	} else {
		// иначе чистим стек циклов и продолжаем работу
		pop_stack();
	}
	return EXEC_NEXT;
}
//*/

CMD(LV){
	// конец цикла
	uint8_t val, id;
	bool to_go = false;

	if(read_var_id(str+2, &id)){
		if(!separator(str[3])){
			if(!read_val(str+4, &val)) return EXEC_NEXT;
			switch(str[3]){
			case '>': to_go = script.var[id] > val; break;
			case '<': to_go = script.var[id] < val; break;
			case '!': to_go = script.var[id] != val; break;
			case '=': to_go = script.var[id] == val; break;
			// TODO протестировать
			case '}' : to_go = script.var[id] >= val; break;
			case '{' : to_go = script.var[id] <= val; break;
			default:
				return EXEC_NEXT;
			}
		} else {
			if(script.var[id]){
				script.var[id]--;
				to_go = true;
			}
		}
		if(to_go){
			return FCMD(INF)(str);
		} else {
			pop_stack();
		}
	}
	return EXEC_NEXT;
}

static bool read_color(char *s, rgb_t *color){
	return read_val(s, &color->r) && read_val(s+2, &color->g) && read_val(s+4, &color->b);
}

CMD(PF){
	// задать затухание рисования
	if(str[2] == '=') read_val(str+3, &script.fd);
	return EXEC_NEXT;
}

CMD(GF){
	// задать затухание для всех пикселов
	if(str[2] == '='){
		uint8_t fd;
		if(read_val(str+3, &fd)){
			forall_px(pixels[px].fade = fd);
		}
	}
	return EXEC_NEXT;
}

CMD(BM){
	// карта яркости
	uint8_t id, px;
	if(str[2] == '='){
		id = 3;
		while(read_val(str+id, &px)){
			if(px < (LAST_PX - FIRST_PX + 1)){
				if(script.rev)
					px = LAST_PX - DELTA_PX * px - 1;
				else
					px = FIRST_PX + DELTA_PX * px;
				pixels[px].bright = script.br;
			}
			id += 2;
		}
	}
	return EXEC_NEXT;
}

CMD(FM){
	// карта затухания
	uint8_t id, px;
	if(str[2] == '='){
		id = 3;
		while(read_val(str+id, &px)){
			if(px < (LAST_PX - FIRST_PX + 1)){
				if(script.rev)
					px = LAST_PX - DELTA_PX * px - 1;
				else
					px = FIRST_PX + DELTA_PX * px;
				pixels[px].fade = script.fd;
			}
			id += 2;
		}
	}
	return EXEC_NEXT;
}

CMD(GC){
	// задать цвет всех пикселов
	if(str[2] == '='){
		rgb_t color;
		if(read_color(str+3, &color))
			forall_px(pixels[px].rgb = color);
	}
	return EXEC_NEXT;
}

CMD(PC){
	// задать цвет рисования
	if(str[2] == '='){
		rgb_t color;
		if(read_color(str+3, &color))
			script.pc = color;
	}
	return EXEC_NEXT;
}

// TODO mix color
static void mix_color(uint8_t px){
	pixels[px].rgb = script.pc;
	pixels[px].bright = script.br;
	pixels[px].fade = script.fd;
}

CMD(PM){
	// карта рисунка
	uint8_t id, px;
	if(str[2] == '='){
		id = 3;
		while(read_val(str+id, &px)){
			if(px < (LAST_PX - FIRST_PX + 1)){
				if(script.rev)
					px = LAST_PX - DELTA_PX * px - 1;
				else
					px = FIRST_PX + DELTA_PX * px;
				mix_color(px);
			}
			id += 2;
		}
	}
	return EXEC_NEXT;
}

CMD(GI){
	rgb_t rgb;
	// задать всем пикселам цвет по индексу
	if(str[2] == '='){
		hsv_t h;
		if(read_val(str+3, &h.h)){
			h.s = 0xFF; h.v = 0xFF;
			hsv_to_rgb(&h, &rgb);
			forall_px(pixels[px].rgb = rgb);
		}
	}
	return EXEC_NEXT;
}

CMD(PI){
	// задать индекс цвета рисования
	if(str[2] == '='){
		hsv_t h;
		if(read_val(str+3, &h.h)){
			h.s = 0xFF; h.v = 0xFF;
			hsv_to_rgb(&h, &script.pc);
		}
	}
	return EXEC_NEXT;
}

CMD(PNT){
	// отрисовка с мнимальной задержкой
	return 1;
}

CMD(WT){
	// выполнить отрисовку с задержкой
	if(str[2] == '='){
		uint8_t wt;
		if(read_val(str+3, &wt))
			return wt;
	}
	return EXEC_NEXT;
}

CMD(V){
	// операция с переменной
	uint8_t id;
	int tmp;
	uint8_t dummy;
	bool valid;

	if(!read_var_id(str+1, &id)) return EXEC_NEXT;

	valid = read_val(str+3, &dummy);
	tmp = script.var[id];

	switch(str[2]){
	case '=' : read_val(str+3, &script.var[id]); return EXEC_NEXT;
	case '+' :
		tmp += dummy;
		break;
	case '-' :
		tmp -= dummy;
		if(tmp < 0) tmp = 0xFF;
		break;
	case '*' :
		tmp *= dummy;
		break;
	case '/' :
		tmp /= dummy;
		break;
	case '%' :
		tmp %= dummy;
		break;
	// TODO новые операции вычисления
	case '!':
		tmp = tmp != dummy;
		break;
	case '>':
		tmp = tmp > dummy;
		break;
	case '<':
		tmp = tmp < dummy;
		break;
	case '}':
		tmp = tmp >= dummy;
		break;
	case '{':
		tmp = tmp <= dummy;
		break;
	default:
		return EXEC_NEXT;
	}
	if(valid) {
		if(tmp > 255) tmp = 255;
		script.var[id] = tmp;
	}
	return EXEC_NEXT;
}

CMD(X){
	// операция с косвенной переменной
	uint8_t id;
	char tmp = str[1];
	int result;

	if(!read_var_id(str+1, &id)) return EXEC_NEXT;
	// если указан допустимый номер адресной переменной,
	// то подменяем в строке ссылку на адресуемую переменную
	id = script.var[id] % VAR_CNT;
	str[0] = 'V';
	str[1] = (id < 10) ? ('0'+ id) : ('A' + id - 10);
	// и вызываем обработку подставленной переменной
	result = FCMD(V)(str);
	// а потом восстанавливаем строку, как было
	str[0] = 'X';
	str[1] = tmp;
	// и выдаем результат обработки
	return result;
}


INIT(8){
	// other
	DDR(PORT_SPI) |= _BV(PIN_MOSI) | _BV(PIN_SCK) | _BV(PIN_SS);
	DDR(WS_LOCK_PORT) |= _BV(WS_LOCK_PIN);
	PORT(SD_PRESENT_PORT) |= _BV(SD_PRESENT_PIN); // подтяжка на опрос карты
#if defined(CON_ENABLED)
	PORT(PORT_CON) |= _BV(PIN_CON);
#endif
	SPCR = _BV(SPE) | _BV(MSTR) | _BV(CPHA);//fosc/2 16MHz/2
	//SPSR = _BV(SPI2X); //TODO
	SPSR; SPDR;
	enter_sd_mode();
}

/*
INIT=160 MX=16 MY=10
CLR GB=FF PB=FF PC=FFFFFF MR=04 PM=04050607 MR=05 PM=0407 MR=06 PM=0407 MR=07 PM=04050607 PNT

 */
