#ifndef SOLDERDOODLE_UI_H
#define SOLDERDOODLE_UI_H

#include <stdbool.h>
#include <stdint.h>
#include "const.h"
#include "LED.h"
#include "Touch_Sensor.h"
#include "Voltage_Test.h"

uint32_t now = 0;	// Current time, in millis (unsigned long)
uint8_t elapsed = 0;	// Elapsed millis this frame
uint8_t frames_since_draw = 0;

uint32_t idle_millis = 0;

//uint16_t tap_pos[MAX_TAPS];

// Wake + Sleep swoosh
const uint8_t WAKE_SWOOSH_BITS = 12;
const int16_t MAX_SWOOSH_VALUE = (3 << (WAKE_SWOOSH_BITS - 2));
int16_t wake_anim = 0;	// Green sweep, wake up / go to sleep

// Cursor position
const int8_t CURSOR_BITS_PER_LED = 12;	// >=8 please
int16_t cursor_pos = 0;
int8_t cursor_target = 0;	// Cursor will drift towards this LED

const int8_t CURSOR_SOLID_BITS = 12;
const int8_t CURSOR_SOLID_CHANGE_SPEED = 4;	// bits
int16_t cursor_solid = 0;

const int16_t VOLTAGE_ALERT_DURATION = 3 * 1000;

int16_t heat_touch_active_amt = 0;

// FPS
#if DEV_PRINT_FPS
uint16_t frame_count = 0;
uint32_t print_fps_when = 1000;
#endif

VoltageStatus ui_voltage_status = k_voltage_ok;
uint16_t voltage_alert_time = 0;

// State machine
bool (*do_state)(void);

// Forward func declarations for state machine.
// Returns true if any LEDs updated.
bool state_asleep();
bool state_wake_up();
bool state_active();
bool state_go_to_sleep();
bool state_low_power_warning();
bool state_critical_battery();

void ui_init()
{
	// DEV
#	if 1
		heat_enable();
		do_state = state_active;
		return;
#	endif

	// Production
	do_state = state_asleep;
}

// Returns true if LEDs were updated.
bool ui_tick()
{
	uint32_t current_millis = millis();
	elapsed = current_millis - now;
	now = current_millis;

#	if DEV_PRINT_FPS
		frame_count++;
		if (now >= print_fps_when) {
			Serial.print("FPS: ");
			Serial.println(frame_count);
			print_fps_when = now + 1000;
			frame_count = 0;
		}
#	endif

	led_clear();

	bool any_LED_updates = do_state();

	if (any_LED_updates) {
		frames_since_draw = 0;
		return true;
	}

	// After no updates: Draw a few frames anyway, so animated things clear out.
	if (frames_since_draw < 0x7f) {
		frames_since_draw++;
	}
	return (frames_since_draw < 5);
}

//
//  ANIMATION
//

bool animate_wake_up_start()
{
	wake_anim = 0;
}

bool animate_go_to_sleep_start()
{
	wake_anim = MAX_SWOOSH_VALUE;
}

bool animate_voltage_alert_start()
{
	voltage_alert_time = 0;
}

// Returns true while drawing, false after drawing completes.
bool draw_voltage_alert()
{
	voltage_alert_time += elapsed;
	if (voltage_alert_time >= VOLTAGE_ALERT_DURATION) {
		return false;
	}

	// Color is yellow (warning) or red (critical)
	uint8_t green = (ui_voltage_status == k_voltage_battery_low) ? 0xff : 0x0;

	led_clear();

	// Blink like crazy. Really try to grab attention.
	for (int8_t i = 0; i < 3; i++) {
		int8_t blink_offset = (voltage_alert_time & 0x100) ? 4 : 0;
		led_set(i + blink_offset, 0xff, green, 0x0);
	}

	return true;
}

// Returns true when drawing, false after completion
bool draw_wake_up_swoosh()
{
	const uint8_t STEP_BITS = (WAKE_SWOOSH_BITS - 5);

	// Fully awake
	if (wake_anim >= MAX_SWOOSH_VALUE) {
		return false;
	}

	// Fully asleep
	if (wake_anim < 0) {
		return false;
	}

	int16_t ease = wake_anim;

	uint8_t bright = (ease >> (WAKE_SWOOSH_BITS - 8)) + 0x10;
	bright = min(bright, 0x4f) << 1;

	const uint8_t BLUE_BITS_DOWN = 2;

	// Light up the solid LEDs
	uint8_t solid = (ease >> STEP_BITS);
	for (uint8_t i = 0; i < min(solid, LED_COUNT); i++) {
		led_set(i, 0, bright, bright >> BLUE_BITS_DOWN);	// green
	}

	// Final LED: Fade the color
	if (solid < LED_COUNT) {
		int16_t fader16 = ease - (solid << STEP_BITS);

		uint8_t fader = fader16 >> (STEP_BITS - 8);

		// Multiply by fader color
		fader = ((int16_t)(fader) * bright) >> 8;

		led_set(solid, fader, fader >> BLUE_BITS_DOWN, 0);
	}

	return true;
}

uint8_t wave_motion(uint32_t n)
{
	uint8_t wave = ((n + (n >> 1)) >> 3) & 0xff;	// speed

	// Invert the top half, create a triange wave: /\/\/
	if (wave & 0x80) {
		wave = ~wave;
	}

	/*
	wave >>= 1;
	//wave += (wave >> 1);	// wave *= 1.5. Range is now [0..bf] I think?
	wave += 0x08;	// A little brighter, don't plummet to zero.
	*/

	// More contrast (fake gamma correction)
	uint16_t wave16 = wave;
	wave16 *= wave;

	// 0x02: Never dim completely to black.
	wave = (wave16 >> 7) + 0x02;

	return wave;
}

bool draw_thermometer(bool is_heat_touch_active)
{
	if (!is_heat_touch_active) {
		heat_touch_active_amt = max(0, heat_touch_active_amt - (elapsed >> 1));

		// Descending: Pull quickly to zero after
		// the heat animation fades out.
		if (heat_touch_active_amt < 0x80) {
			heat_touch_active_amt = 0;
		}

	} else {
		heat_touch_active_amt = min(0xff, heat_touch_active_amt + (elapsed >> 1));
	}

	// Shape the heat_touch_active_amt, stay
	// dark for the first half
	uint8_t touch_amt = max(0, heat_touch_active_amt - 0x80);
	touch_amt *= 2;

	uint8_t led0 = constrain(cursor_pos >> CURSOR_BITS_PER_LED, 0, LED_COUNT - 1);
	uint8_t prog = constrain(cursor_pos >> (CURSOR_BITS_PER_LED - 5), 0, 0xdf) + 0x20;
	//uint8_t prog_inv = ~prog;

	// Gamma-ish correction
	uint16_t prog16 = prog;
	prog16 *= prog;
	prog = (prog16 >> 8);

	uint8_t red = prog + 0x04;

	for (int8_t i = 0; i <= led0; i++) {
		// Heat touch animation
		uint8_t wave = wave_motion(now * 2 - i * 220);

		// Saturate the triangle wave: Make this
		// more like a square wave.
		if (wave < 0x20) {
			wave = 0;
		} else if (wave >= 0x60) {
			wave = 0xff;
		} else {
			wave = ((wave - 0x20) << 2);
		}

		uint16_t wave16 = (uint16_t)(wave);

		// Brightness is based on heat_touch_active_amt
		wave = (wave16 * touch_amt) >> 8;

		led_set(i, max(red, wave), wave, wave);
	}
}

bool draw_cursor()
{
	uint8_t led0 = constrain(cursor_pos >> CURSOR_BITS_PER_LED, 0, LED_COUNT - 1);

	// Interpolate 2 LEDs
	uint8_t alpha1 = (cursor_pos & ((1L << CURSOR_BITS_PER_LED) - 1)) >> (CURSOR_BITS_PER_LED - 8);

	// To fix jittery drawing (like the LED creeps upward
	// when a touch starts): Skew the alpha towards the
	// nearest whole LED.
	// (alpha1 * 5/4), centered: _/^
	int16_t alpha1_16 = (((int16_t)(alpha1) * 5) >> 2) - 32;
	alpha1 = constrain(alpha1_16, 0, 0xff);

	uint8_t alpha0 = ~alpha1;

	// Solidness
	//uint16_t solid16 = ((cursor_solid >> (CURSOR_SOLID_BITS - 8)) & 0xff);

	uint8_t wave = wave_motion(now);

	uint8_t solid_amt = (cursor_solid >> (CURSOR_SOLID_BITS - 8)) & 0xff;
	uint8_t inv_solid_amt = ~solid_amt;

	uint16_t bright_mod_16 = (solid_amt * 0x100) + (inv_solid_amt * wave);
	uint8_t bright_mod = bright_mod_16 >> 8;

	//uint8_t bright_mod = (wave * (~solid_amt) + (0x100 * solid_amt)) >> 8;

	alpha0 = (alpha0 * bright_mod) >> 8;
	alpha1 = (alpha1 * bright_mod) >> 8;

	// Blend with the thermometer color
	led_mix(led0, 0xff, 0xff, 0xff, alpha0);

	if ((led0 + 1) < LED_COUNT) {
		led_set(led0 + 1, alpha1, alpha1, alpha1);
	}

	return true;
}

void cursor_drift_towards_target(bool is_press_active)
{
	int16_t target16 = (int16_t)(cursor_target) << CURSOR_BITS_PER_LED;

	uint8_t speed;

	if (is_press_active) {
		speed = (elapsed >> 1);	// drift slower
	} else {
		speed = (elapsed * 6);	// drift faster
	}

	if (target16 < cursor_pos) {
		cursor_pos = max(target16, cursor_pos - speed);

	} else if (cursor_pos < target16) {
		cursor_pos = min(target16, cursor_pos + speed);
	}
}

//
//  STATE MACHINE
//

bool state_asleep()
{
	TouchResult * result = touch_tick(now);

	// Any taps?
	if (result->status & k_touch_taps) {
		if (tap_count == TAPS_TO_WAKE) {
			// Clear touches & taps
			touch_reset_taps();

			// If we're in a low or critical battery state:
			// Do a quick voltage check to see if we're charged.
			if (ui_voltage_status != k_voltage_ok) {
				ui_voltage_status = voltage_test(false);
			}

			// If we're in critical battery mode:
			// Show battery warning, and go back to sleep.
			if (ui_voltage_status == k_voltage_battery_critical) {
				animate_voltage_alert_start();
				do_state = state_critical_battery;
				return false;
			}

			// Wake up!
			animate_wake_up_start();
			do_state = state_wake_up;
		}
	}

	return false;
}

bool state_wake_up()
{
	wake_anim += ((int32_t)(elapsed) << 2);	// speed control

	bool did_draw = draw_wake_up_swoosh();

	// When anim is complete: Go to ready state
	if (!did_draw) {
		idle_millis = 0;
		cursor_pos = 0;
		touch_reset_taps();
		heat_enable();

		// Are we in a low-battery state? Immediately show
		// the low-battery alert.
		if (ui_voltage_status == k_voltage_battery_low) {
			animate_voltage_alert_start();
			do_state = state_low_power_warning;
			return true;
		}

		do_state = state_active;
	}

	return true;
}

// Blinking white cursor
bool state_active()
{
	TouchResult * result = touch_tick(now);

	// Any taps?
	if (result->status & k_touch_taps) {
		if (tap_count == TAPS_TO_SLEEP) {
			// Clear touches & taps
			touch_reset_taps();
			animate_go_to_sleep_start();
			heat_disable();
			do_state = state_go_to_sleep;
			return false;
		}
	}

	bool is_touch_active = (result->status & k_touch_active);
	bool is_swipe_pressure = (touch_pressure >= PRESS_THRESHOLD_DRAG);

	// Gestures: Sometimes we know if it's tap vs drag.
	bool can_tap = true;
	bool can_drag = true;
	if (result->status & k_touch_is_tap) {
		can_drag = false;
	} else if (result->status & k_touch_is_drag) {
		can_tap = false;
	}

	// Idle timeout?
	if (is_touch_active) {
		idle_millis = 0;

	} else {
		idle_millis += elapsed;

#		if (DEV_PRINT_IDLE_MILLIS)
			Serial.print(idle_millis);
			Serial.print(" - ");
			Serial.println(IDLE_TIMEOUT_MILLIS);
#		endif

		if (idle_millis >= IDLE_TIMEOUT_MILLIS) {
			// Idle timeout: Disable heat, and go to sleep.
			touch_reset_taps();
			animate_go_to_sleep_start();
			heat_disable();
			do_state = state_go_to_sleep;
			return false;
		}
	}

	// Cursor blinking/"breathing" brightness:
	if (is_touch_active) {
		// Touch is active: Get solid (no blinking).
		cursor_solid = min(cursor_solid + (elapsed << CURSOR_SOLID_CHANGE_SPEED), (1 << CURSOR_SOLID_BITS) - 1);
	} else {
		// No touch: Resume idle blinking.
		cursor_solid = max(0, cursor_solid - (elapsed << (CURSOR_SOLID_CHANGE_SPEED - 1)));
	}

	// No-touch: Cursor drifts towards nearest dot
	if (!is_touch_active) {
		// Target == closest LED
		uint8_t drift_dir = (cursor_pos & (1 << (CURSOR_BITS_PER_LED - 1))) ? 1 : 0;

		cursor_target = constrain(
			(cursor_pos >> CURSOR_BITS_PER_LED) + drift_dir,
			0,
			LED_COUNT - 1
		);

		cursor_drift_towards_target(false);

		// Testing interpolation
		//cursor_pos = (cursor_pos + elapsed) % (LED_COUNT << CURSOR_BITS_PER_LED);

	} else if (!is_swipe_pressure) {	// light touch, no hard pressure
		cursor_drift_towards_target(true);

	} else {	// hard pressure for swipe
		// Active press & slide
		if (abs(result->dy) < DRAG_JITTER_THRESHOLD) {
			cursor_drift_towards_target(true);
		}

		// Drag gesture OK (it's not a tap)?
		if (can_drag) {
			cursor_pos = constrain(
				cursor_pos + ((result->dy * elapsed) * CURSOR_DRAG_SPEED_MULT),
				0,
				((LED_COUNT - 1) << CURSOR_BITS_PER_LED) + 1
			);
		}
	}

	// Only allow heating if we're not dragging.
	// (can_tap must be true)
	int16_t heat_touch_pressure = (can_tap ? touch_pressure : 0);

	// Nearest dot (cursor_target): Set the appropriate heat level
	HeatTickStatus heat_status = heat_tick(elapsed, cursor_target, heat_touch_pressure);

#	if HEAT_ALL_THE_TIME
		// HEAT_ALL_THE_TIME: Don't show heat animation.
		const bool is_heat_touch_active = false;

#	elif HEAT_ON_FIRM_TOUCH
		bool is_heat_touch_active = (heat_status & k_heat_touch_ok);
#	endif

	draw_thermometer(is_heat_touch_active);

	bool did_draw = draw_cursor();

	// When heat changes: Read the voltage
	if (heat_status & k_heat_did_change) {
		bool is_heat_on = (heat_status & k_heat_on);

		VoltageStatus vstat = voltage_test(is_heat_on);

		// Not enough power?
		if (is_heat_on) {
			if (vstat == k_voltage_battery_critical) {
				ui_voltage_status = k_voltage_battery_critical;
				animate_voltage_alert_start();
				do_state = state_critical_battery;

			} else if ((vstat == k_voltage_battery_low) && (ui_voltage_status == k_voltage_ok)) {
				// Transition from OK to low-battery warning
				ui_voltage_status = k_voltage_battery_low;
				animate_voltage_alert_start();
				do_state = state_low_power_warning;
			}

		} else {	// Heat is off
			// Device is being used, while plugged into USB:
			// Charged enough to recover from low battery warning?
			if (vstat == k_voltage_ok) {

#				if (DEV_PRINT_VOLTAGE)
					if (ui_voltage_status != k_voltage_ok) {
						Serial.println("     Setting voltage to OK!");
					}
#				endif

				ui_voltage_status = k_voltage_ok;
			}

		}	// !voltage status check conditions

	}	// !heat did change (on/off)

	return did_draw;
}

bool state_go_to_sleep() {
	wake_anim -= ((int32_t)(elapsed) << 2);	// speed

	bool did_draw = draw_wake_up_swoosh();

	// When anim is complete: Go to ready state
	if (!did_draw) {
		do_state = state_asleep;
	}

	return did_draw;
}

bool state_low_power_warning()
{
	bool did_draw = draw_voltage_alert();

	if (did_draw) {
		// OK to tick the heat while
		// showing the low power warning.
		heat_tick(elapsed, cursor_target, touch_pressure);

	} else {
		// Animation is complete. Return to state_active.
		do_state = state_active;
	}
}

bool state_critical_battery()
{
	// TURN OFF THE HEAT.
	heat_disable();

	bool did_draw = draw_voltage_alert();

	if (!did_draw) {
		// Animation is complete. Force asleep now.
		led_clear();
		do_state = state_asleep;
	}
}

#endif
