/*
 *
 */

#include "app.h"

// libs
#include "SPIFFS.h"
#include <WiFi.h>
#include <DNSServer.h>


portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;


// different shit
#include "hw/adc.h"
uint16_t brt_active;
uint32_t vbat;
uint16_t vbat_avg[16];

#include "led/led.h"
#include "led/ledprog.h"
uint16_t prog_timeout = 0;

#include "led/ir.h"

#include "sensor/adxl345.h"
volatile uint16_t adxl_ctr[4] = {0, 0, 0, 0xffff};	// some convoluted counter for adxl update rate

#include "disp/oled.h"
uint8_t oled_orientation = 0;

#include "ui/ui.h"
#include "menu/menu.h"
uint16_t name_scroll = 0;

uint16_t app_counter;								// tick counter
uint32_t app_uptime;								// uptime in quarters of a second

uint8_t  app_blackout = 0;							// if set, disables display shit


// bugfix
#include "hw/hal/timer.h"


// extras
static const char *titles[] = {
	"Ensign",
	"Hi! My Name Is",
	"Space Hacker",
	"Space Pirate",
	"Secret Space Agent",
	"SPACE FORCE Pilot",
	"Space Wall Builder",
	"I'm Amazing, I Am",
	"Alien Hunter",
	"Space Operative"
};

static const uint8_t *font_tbl[] = {
	u8g2_font_logisoso20_tr,
	u8g2_font_logisoso16_tf,
	u8g2_font_crox4h_tr,
	u8g2_font_helvB12_tr,
	u8g2_font_helvB10_tr,
	u8g2_font_ImpactBits_tr,
	u8g2_font_crox2tb_tr,
	u8g2_font_mercutio_basic_nbp_tr
};

const uint16_t anim_time[] = {
	24,			// pew pew pew
	48,			// we're hit
	600,		// erectus start
	80			// erectus done
};
uint8_t anim_sel;
uint16_t anim_ticks;
uint16_t anim_data[8];

static uint8_t title_sel = map(esp_random() & 0xffff, 0, 0xffff, 0, 10);


// -- application
void app_anim_start(uint8_t anim)
{
	anim_sel = anim;
	anim_ticks = anim_time[anim];
}

void app_random_xy(uint8_t &x, uint8_t &y)
{

}
void app_anim_run()
{
	if (!anim_ticks) {
		return;
	}

	switch (anim_sel) {
		case 0: {	// pew pew pew
			if (anim_ticks == 24) {
				for (int i = 0; i < 6; i += 2) {
					anim_data[i + 0] = map((esp_random() & 0xff), 0, 0xff, 28, (24 + 96) - 8);
					anim_data[i + 1] = map((esp_random() & 0xff), 0, 0xff, 13,            25);
				}

				anim_data[1] -= 4;

				anim_data[2] -= 4;
				anim_data[3] -= 2;

				anim_data[4] -= 8;
			}
	
			disp_ptr->setFont(u8g2_font_profont12_tr);
			disp_ptr->setCursor(anim_data[0], anim_data[1]);
			disp_ptr->print("pew");
			
			if (anim_ticks <= 18) {
				disp_ptr->setFont(u8g2_font_profont12_tr);
				disp_ptr->setCursor(anim_data[2], anim_data[3]);
				disp_ptr->print("pew");
			}

			if (anim_ticks <= 12) {
				disp_ptr->setFont(u8g2_font_8x13_tr);
				disp_ptr->setCursor(anim_data[4], anim_data[5]);
				disp_ptr->print("pew");
			}

			break;
		}

		case 1: {	// we've been hit
			uint8_t w;

			disp_ptr->setFont(u8g2_font_profont10_tr);
			w = disp_ptr->getStrWidth("o noes!");
			disp_ptr->setCursor(24 + 48 - (w >> 1), 6);
			disp_ptr->print("o noes!");

			if (anim_ticks <= 40) {
				disp_ptr->setFont(u8g2_font_logisoso16_tf);
				disp_ptr->setCursor(24, 30);
				disp_ptr->print("WE'RE");
			}

			if (anim_ticks <= 34) {
				disp_ptr->setCursor(80, 30);
				disp_ptr->print("HIT!");
			}

			if (anim_ticks <= 27) {
				disp_ptr->setCursor(115, 30);
				disp_ptr->print("!");
			}

			if (anim_ticks <= 20) {
				disp_ptr->setCursor(123, 30);
				disp_ptr->print("!");
			}

			break;
		}

		case 2: {	// draw erectus			
			if (anim_ticks == 600) {

			}

			if (anim_ticks >= 592) {
				disp_ptr->setDrawColor(anim_ticks & 1);
				disp_ptr->drawBox(12, 0, 127, 31);
				disp_ptr->setDrawColor(1);
			}

			if (anim_ticks >= 540 && anim_ticks < 590) {
				disp_ptr->setFont(u8g2_font_logisoso16_tf);
				disp_ptr->setCursor(13, 16);
				disp_ptr->print("YOU");

				if (anim_ticks <= 586) {
					disp_ptr->setCursor(53, 16);
					disp_ptr->print("TOUCHED");
				}

				if (anim_ticks <= 580) {
					disp_ptr->setCursor(15, 32);
					disp_ptr->print("MY");
				}

				if (anim_ticks <= 574) {
					disp_ptr->setCursor(45, 32);
					disp_ptr->print("ERECTUS!");
				}
			}

			if (anim_ticks == 540) {
				// precalc on what frames we'll start erectuses
				// bitmask:
				//   high   15-10 x, 9-6 y, 5-4 size, 3-0 length
				//   low    15-8 when, 3-0 other
				for (uint8_t i = 0; i < 8; i++) {
					anim_data[i] = esp_random() & 0xffff;
				}
			}

			if (anim_ticks < 540) {
				int16_t t;
				int16_t c;
				uint8_t i, s, x, y;
				uint8_t len1, len2;
				uint16_t *a1, *a2;

				t = 540 - anim_ticks;
				
				for (i = 0; i < 8; i += 2) {
					a1 = &anim_data[i];
					a2 = &anim_data[i + 1];

					// when to start?
					c = ((*a2) >> 8);

					if (t >= c) {
						// where to start?
						x = ((*a1        ) >> 10);
						y = ((*a1 & 0x1e0) >>  5) << 1;

						// how big?
						s = ((*a1 &  0x30) >>  4);
						disp_ptr->setFont(font_tbl[s + 2]);

						y = map(y, 0, 31, disp_ptr->getMaxCharHeight(), 31);
						disp_ptr->setCursor(x, y);

						len1 = (*a1) & 0x7;
						len2 = (*a2) & 0x3;

						// c = random value, 0-255
						// t = incrementing value starting at 0
						// we draw when t >= c
						// progress is t - c
						// end tick is c + len1 + len2
						// we only want to print non-stale erectus
						
						// start it up
						c = t - c;
						
						if (c > len1 + len2 + 30) {
							continue;
						}

						disp_ptr->print("8");

						while (c && len1) {
							disp_ptr->print("=");
							c--;
							len1--;
						}

						disp_ptr->print("D");

						if (c) {
							while (c && len2) {
								disp_ptr->print("~");
								c--;
								len2--;
							}
						}
					}
				}
			}

			break;
		}

		case 3: {	// that was an x erectus.
			const char *exclaims[] = {
				"Phew!",		"Wow,",			"Incredible,",	"Amazing!",
				"*moans*",		"Hey!",			"covfefe",		"Thanks,",
				"Believe me,",	"Brother,",		"Just WOW,",	"I dunno,",
				"Joy!",			"bleh,",		"Hmm...",		"Um...,"
			};
			const char *erectuses[] = {
				"bulging",		"huge",			"big league",	"incredible",
				"bk worthy",	"unexpected",	"a bit wimpy",	"just right",
				"too long",		"too short",	"worth it",		"not rape",
				"friendly",		"amazing",		"unjustified",	"so good",		//16
				"needed",		"grand",		"crazy",		"erectus",
				"something",	"a good time",	"fantastic",	"good",
				"heroic",		"unfulfilling",	"unnecessary",	"horrific",
				"nerdy",		"remorseful",	"so retro",		"memorable",	//32
				"elevated",		"repeatable",	"offensive",	"a masterpiece",
				"lucky",		"doomed",		"smooth",		"soft",
				"bk's wish",	"bk's desire",	"bk's job",		"bk",
				"bk",			"bk",			"bk",			"bk",			//48
				"cawkulous",	"cawktastic",	"cawkgasmic",	"cawked up",
				"suppressed",	"surprising",	"sensational",	"needing lube",
				"<3",			"not wanted",	"a failure",	"a good wall",
				"the one",		"my favorite",	"the best",		"the game"
			};
			
			uint8_t w;
			char t[32];

			if (anim_ticks >= 80) {
				anim_data[0] = esp_random() & 0xf;
			}

			if (anim_ticks >= 50) {
				anim_data[1] = esp_random() & 0x3f;
			}

			strcpy(t, (char *)exclaims[anim_data[0]]);
			strcat(t, " that was...");

			disp_ptr->setFont(u8g2_font_profont10_tr);
			w = disp_ptr->getStrWidth(t);
			disp_ptr->setCursor(24 + 48 - (w >> 1), 6);
			disp_ptr->print(t);

			if (anim_ticks <= 70) {
				disp_ptr->setFont(u8g2_font_helvB12_tr);
				w = disp_ptr->getStrWidth((char *)erectuses[anim_data[1]]);
				disp_ptr->setCursor(21 + 48 - (w >> 1), 27);
				disp_ptr->print(erectuses[anim_data[1]]);
			}
		}
	}

	anim_ticks--;
}

int8_t app_ledprog_next()
{
	uint8_t p = 0;

	for (int i = 0; i < (sizeof(progu) / sizeof(progu[0])); i++) {
		if (progu[i].runena) {
			do {
				p = map((esp_random() & 0xffff), 0, 0xffff, 0, (sizeof(progu) / sizeof(progu[0])));
			} while (!progu[p].runena);

			return p;
		}
	}

	return -1;
}
uint16_t app_ledprog_timeout()
{
	return map((esp_random() & 0xffff), 0, 0xffff, 30, 120);
}
void app_ledprog_set_random()
{
	int8_t next;

	next = app_ledprog_next();
	if (next >= 0) {
		prog = &progu[next];
		ledprog_init(prog);
		prog_timeout = app_ledprog_timeout();
	}
}

void app_btn_ship()
{
	if (!anim_ticks) {
		app_anim_start(0);
		ir_tx_test();
	}
}

void app_btn_erectus_p()
{
	app_anim_start(2);
}
void app_btn_erectus_r()
{
	anim_data[7] = anim_ticks;
	app_anim_start(3);
}

void app_btn_init()
{
	btn_set_cb(0, 0, 0, 0);
	btn_set_cb(1, 0, 0, 0);
	btn_set_cb(2, app_btn_ship,      0, 0);
	btn_set_cb(3, app_btn_erectus_p, 0, app_btn_erectus_r);
}

void app_render_oled(void *p)
{
	int8_t rot;
	uint8_t rotflags = 0;
	
	// figure out display orientation
	rot = adxl_get_rotation(&accel);

	if ((rot > (96+4)) || (rot < (32-4))) {
		rotflags &= ~(1 << 0);
	} else if ((rot > (32+4)) && (rot < (96-4))) {
		rotflags |=  (1 << 0);
	}

	if ((rot > (64+4)) && (rot < 124)) {
		rotflags &= ~(1 << 1);
	} else if ((rot > 4) && (rot < (64-4))) {
		rotflags |=  (1 << 1);
	}

	disp_ptr->clearBuffer();
	disp_ptr->setDrawColor(1);

	// shift to the right, act like we are only 96x32
	int x_offset;

	/*
	if (accel.z >= 127 || accel.z <= -127) {
		// we're flat do nothing.
		
	} else
	*/
	
	if (rotflags & 0x02 || (accel.z < -121)) {
		u8g2_uint_t w;
		const char *t;

		// when right side up, show nametag or animations
		
		// first make sure we render in the correct direction
		disp_ptr->setDisplayRotation(U8G2_R0);
		x_offset = 24;

		// and use whatever buttons we want here	
		app_btn_init();

		if (!app_blackout) {
			if (anim_ticks) {
				app_anim_run();
			} else {			
				if (conf_name == "BK") {
					t = "Erectus Fondler";
				} else {
					if (conf_titl.length() > 0) {
						t = conf_titl.c_str();
					} else {
						t = titles[title_sel];
					}
				}

				disp_ptr->setFont(u8g2_font_profont10_tr);
				
				w = disp_ptr->getStrWidth(t);
				disp_ptr->setCursor(x_offset + 48 - (w >> 1), 6);
				disp_ptr->print((const char *)t);

				for (int i = 0; i < 8; i++) {
					disp_ptr->setFont(font_tbl[i]);
					w = disp_ptr->getStrWidth(conf_name.c_str());
					if (w < 96) {
						disp_ptr->setCursor(x_offset + 48 - (w >> 1), 30 - conf_yoff.toInt());
						disp_ptr->print(conf_name.c_str());
						break;
					}
				}

				// we need to scroll your long fucking name.
				// but no time to implement it so fuck you. do it yourself
			}

			// draw box over left hand side
			// disp_ptr->setDrawColor(0);
			// disp_ptr->drawBox(0, 0, x_offset - 1, 31);
		}
	} else {
		// when upside down, show menu
		
		// first make sure we render in the correct direction
		disp_ptr->setDisplayRotation(U8G2_R2);
		x_offset = 8;

		// the menu is drawn now and buttons are active for it
		menu_btn_init();
		menu_tick();
	}

	// old debug
	/*
	disp_ptr->setCursor(0, 20);
	disp_ptr->printf("x: %d", accel.x);
	disp_ptr->setCursor(36, 20);
	disp_ptr->printf("y: %d", accel.y);
	disp_ptr->setCursor(72, 20);
	disp_ptr->printf("z: %d", accel.z);

	disp_ptr->setCursor(0, 10);
	disp_ptr->printf("bat: %d.%02dV", vbat / 1000, (vbat % 1000) / 10);

	disp_ptr->setCursor(60, 10);
	disp_ptr->printf("t: %d %d %d %d", btn_read_counts(0), btn_read_counts(1), btn_read_counts(2), btn_read_counts(3));
	disp_ptr->setCursor(0, 30);

	portENTER_CRITICAL(&mux);
	//disp_ptr->printf("c: %d/s", adxl_ctr[2]);
	disp_ptr->printf("b: %d", adc_val_brightness());
	portEXIT_CRITICAL(&mux);

	disp_ptr->setCursor(48, 30);
	disp_ptr->printf("uptime: %d:%02d.%01d", app_uptime / 60, app_uptime % 60, app_counter / 400);
	 */

	vTaskDelete(NULL);
}

// interrupts
static void app_isr()
{
	int i;

	// portENTER_CRITICAL_ISR(&mux);
	
	tmr_isr_pending[0]++;

	// portEXIT_CRITICAL_ISR(&mux);

	// timekeeper
	if (++app_counter >= 4000) {
		app_counter = 0;
		app_uptime++;
	}

	  // timing-critical parts
	// led
	led_tick();

	// touch and ui
	if ((app_counter % 4) == 0) {	// 1000 times/second, for 250 full scans/second
		btn_tick();
	}

	// adc
	// light sense
	if ((app_counter % 4000) == 60) {
		if (WiFi.getMode() != WIFI_OFF) {
			brt_active = 0xfff0;
		} else {
			brt_active = 0;
		}
	}
	if ((app_counter % 4000) >= 60) {
		if (!brt_active) {
			// can only start when not using ir
			brt_active = 0xffff;
		} else if (brt_active == 0xfffe) {
			if (!adc_get_brightness(ADC_GET_RESTART)) {
				// extremely fucking bright
				brt_active = 0xfffc;
			} else {
				brt_active = app_counter;
			}
		} else if (brt_active < 0xfff0) {	
			if (app_counter % 20 == 0) {	
				// should we continue?
				if (adc_get_brightness_c() & 0x80) {
					if (adc_get_brightness_c() > (0x80 + 32)) {
						// shit is too dark?
						adc_get_brightness(ADC_GET_STOP_DARK);
						brt_active = 0xfffc;
					} else {
						// start another conversion
						if (!adc_get_brightness(ADC_GET_PROCESS)) {
							// conversion is done or errored, we can process ir tx again
							brt_active = 0xfffc;
						}
					}
				}
			}
		}
	}

	// battery sense
	if ((app_counter % 500) >= (500 - 16 - 1)) {
		i = (app_counter % 500) - (500 - 16 - 1);
		if (i == 16) {
			uint32_t a = 0;
			for (i = 0; i < 16; i++) {
				a += vbat_avg[i];
			}
			vbat = (a >> 4);
		} else {
			vbat_avg[i] = adc_get_vbat();
		}
	}
}

// non-timing critical
void app_loop()
{
	int16_t ctr;
	uint8_t adxl_updated = 0;

	if (tmr_isr_pending[0]) {
		tmr_isr_pending[0]--;

		ctr = (tmr_isr_pending[0] > app_counter) ? 4000 : 0;
		ctr += (int16_t)(app_counter - tmr_isr_pending[0]);

		// adc brightness
		// can't do this step in ISR, otherwise we crash with abort()
		switch (brt_active) {
			case 0xfffc: {
				if (!ir_tx_is_active()) {
					ir_tx_start();
				}
				brt_active = 0xfff0;
				break;
			}
			case 0xffff: {
				if (ir_tx_is_active()) {
					if (ir_tx_is_busy()) {
						break;
					} else {
						ir_tx_stop();
					}
				}
				brt_active--;
				break;
			}
		}

		// buttons
		if (ctr % 20 == 10) {	// 200 times/second
			btn_cb_process();
		}

		// led program random select
		if (ctr == 0) {
			if (prog_timeout) {
				prog_timeout--;
			} else {
				app_ledprog_set_random();
			}
		}
		
		// led program update
		// TODO: possibly increase this rate
		if (ctr % 20 == 14) {	// 200 times/second
			if (prog->prog_idx < (sizeof(ledprog_list) / sizeof(ledprog_list[0]))) {
				if (app_blackout) {
					for (int i = 0; i < 10; i++) {
						led_scaled[i] = 0;
					}
				} else {
					ledprog_list[prog->prog_idx].progfn(prog);
					led_scale();
				}
			}
		}

		// display
		if ((ctr % 200) == 0) {			// 20fps
			// should we flip the screen?
			// TODO

			disp_ptr->sendBuffer();
		}
		if ((ctr % 200) == 1) {
			// when we get time, update the display contents
			xTaskCreatePinnedToCore(
				app_render_oled,	// Task function.
				"render_oled",		// String with name of task.
				8192,				// Stack size in bytes.
				NULL,				// Parameter passed as input of the task
				31,					// Priority of the task.
				NULL,				// Task handle.
				0);					// CPU core
		}

		// accelerometer
		if ((ctr % 20) == 0) {	// peak 200 times/second
			// because this is on the same bus as OLED,
			// we won't read ADXL while OLED is updating.
			// this means we're maybe getting less readings/sec,
			// and there is some dead time. but hey, it works for us...
			
			//disp_ptr->setBusClock(400000);
			if (!adxl_updated) {
				adxl_update();
				adxl_updated = 1;

				adxl_ctr[0]++;
				adxl_ctr[1]++;
			}
			//disp_ptr->setBusClock(800000);
		}

		// accelerometer stats
		if (ctr == 0) {
			// set last second value
			adxl_ctr[2] = adxl_ctr[1];

			// set lowest seen value
			if (adxl_ctr[1] < adxl_ctr[3]) {
				adxl_ctr[3] = adxl_ctr[1];
			}

			// clear per-second counter
			adxl_ctr[1] = 0;
		}

		// serial
	}

	// infrared
	if (ir_rx_test()) {
		if (!anim_ticks) {
			anim_sel = 1;
			anim_ticks = anim_time[1];
		}
	}

	// dns
	if (WiFi.getMode() != WIFI_OFF) {
		dns.processNextRequest();
	}
}

// initialization helpers
void app_timer_init(uint8_t timer, void (*isr)(), uint32_t rate)
{
	tmr_isr[timer] = timerBegin(timer, 10, true);

	timerAttachInterrupt(tmr_isr[timer], isr, true);

	timerAlarmWrite(tmr_isr[timer], 8000000 / rate, true);
	timerAlarmEnable(tmr_isr[timer]);
}

// initialization
void app_init()
{
	// filesystem
	SPIFFS.begin(true);

	// adc
	adc_init();

	// leds
	led_init();
	
	// led programs
	ledprog_load(0);
	app_ledprog_set_random();
	
	// infrared
	ir_tx_start();
	ir_rx_start();

	// accelerometer
	adxl_init();

	// display
	oled_init();

	// touch
	btn_init();
	btn_set_cb(2, &ir_tx_test, 0, 0);	// temporary, to test IR send/recv

	// webserver
	httpd_init();

	// main timing interrupt timer
	app_timer_init(0, &app_isr, 4000);
	timer_fucking_retarded_workaround();
}
