#include "net.h"
#include "canvaspixel.h"
#include "led-matrix-c.h"

#include <stdlib.h>
#include <errno.h>
#include <stdio.h> //sprintf
#include <string.h>
#include <unistd.h>
#include <pthread.h>

unsigned int px_width = 192;
unsigned int px_height = 128;
unsigned int px_pixelcount = 0;
unsigned int px_clientcount = 0;
struct RGBLedMatrix *matrix;
struct LedCanvas *offscreen_canvas;
uint32_t colorarray[192][128] = {0}; //bad bad style hardcoded size...

// User sessions
typedef struct PxSession {

} PxSession;

// Helper functions

static inline int fast_str_startswith(const char* prefix, const char* str) {
	char cp, cs;
	while ((cp = *prefix++) == (cs = *str++)) {
		if (cp == 0)
			return 1;
	}
	return !cp;
}

// Decimal string to unsigned int. This variant does NOT consume +, - or whitespace.
// If **endptr is not NULL, it will point to the first non-decimal character, which
// may be \0 at the end of the string.
static inline uint32_t fast_strtoul10(const char *str, const char **endptr) {
	uint32_t result = 0;
	unsigned char c;
	for (; (c = *str - '0') <= 9; str++)
		result = result * 10 + c;
	if (endptr)
		*endptr = str;
	return result;
}

// Same as fast_strtoul10, but for hex strings.
static inline uint32_t fast_strtoul16(const char *str, const char **endptr) {
	uint32_t result = 0;
	unsigned char c;
	while ((c = *str - '0') <= 9 // 0-9
			|| ((c -= 7) >= 10 && c <= 15) // A-F
			|| ((c -= 32) >= 10 && c <= 15)) { // a-f
		result = result * 16 + c;
		str++;
	}
	if (endptr)
		*endptr = str;
	return result;
}

// server callbacks
void px_on_connect(NetClient *client) {
	px_clientcount++;
}

void px_on_close(NetClient *client, int error) {
	px_clientcount--;
}

static inline uint32_t blend(uint32_t fg, uint32_t bg)
{
    uint32_t result;
    uint32_t fg_red     = (fg & 0xFF000000) >> 24;
    uint32_t fg_green   = (fg & 0x00FF0000) >> 16;
    uint32_t fg_blue    = (fg & 0x0000FF00) >> 8;
    uint32_t fg_alpha   = (fg & 0x000000FF) >> 0;
    uint32_t bg_red     = (bg & 0xFF000000) >> 24;
    uint32_t bg_green   = (bg & 0x00FF0000) >> 16;
    uint32_t bg_blue    = (bg & 0x0000FF00) >> 8;
    uint32_t bg_alpha  = 256 - fg_alpha;
    result = (((fg_red * fg_alpha + bg_red * bg_alpha) >> 8)  << 24) + (((fg_green * fg_alpha + bg_green * bg_alpha) >> 8) << 16) + (((fg_blue * fg_alpha + bg_blue * bg_alpha) >> 8) << 8) + 0xff;
    return result;
}


void px_on_read(NetClient *client, char *line) {
	if (fast_str_startswith("PX ", line)) {
		const char * ptr = line + 3;
		const char * endptr = ptr;
		errno = 0;

		uint32_t x = fast_strtoul10(ptr, &endptr);
		if (endptr == ptr) {
			net_err(client,
					"Invalid command (expected decimal as first parameter)");
			return;
		}
		if (*endptr == '\0') {
			net_err(client, "Invalid command (second parameter required)");
			return;
		}

		endptr++; // eat space (or whatever non-decimal is found here)

		uint32_t y = fast_strtoul10((ptr = endptr), &endptr);
		if (endptr == ptr) {
			net_err(client,
					"Invalid command (expected decimal as second parameter)");
			return;
		}

		// PX <x> <y> -> Get RGB color at position (x,y) or '0x000000' for out-of-range queries
		if (*endptr == '\0') {
			uint32_t c;
			//canvaspixel_get_px(x, y, &c);
            c = colorarray[x][y];
			char str[64];
			sprintf(str, "PX %u %u %06X", x, y, (c >> 8));
			net_send(client, str);
			return;
		}

		endptr++; // eat space (or whatever non-decimal is found here)

		// PX <x> <y> BB|RRGGBB|RRGGBBAA
		uint32_t c = fast_strtoul16((ptr = endptr), &endptr);
		if (endptr == ptr) {
			net_err(client,
					"Third parameter missing or invalid (should be hex color)");
			return;
		}

		if (endptr - ptr == 6) {
			// RGB -> RGBA (most common)
			c = (c << 8) + 0xff;
		} else if (endptr - ptr == 8) {
			// done
		} else if (endptr - ptr == 2) {
			// WW -> RGBA
			c = (c << 24) + (c << 16) + (c << 8) + 0xff;
		} else {
			net_err(client,
					"Color hex code must be 2, 6 or 8 characters long (WW, RGB or RGBA)");
			return;
		}

		px_pixelcount++;
		//canvaspixel_set_px(x, y, c);
        
        if (x < px_width && y < px_height){
            c = blend(c, colorarray[x][y]);
            colorarray[x][y] = c;
            led_canvas_set_pixel(offscreen_canvas, x, y, c >>24&255 , c>>16&255, c>>8&255);
        }
        
        //offscreen_canvas = led_matrix_swap_on_vsync(matrix, offscreen_canvas);

	} else if (fast_str_startswith("SIZE", line)) {

		char str[64];
		snprintf(str, 64, "SIZE %d %d", px_width, px_height);
		net_send(client, str);

	} else if (fast_str_startswith("STATS", line)) {

		char str[128];
		snprintf(str, 128, "STATS px:%u conn:%u", px_pixelcount,
				px_clientcount);
		net_send(client, str);

	} else if (fast_str_startswith("HELP", line)) {

		net_send(client,
				"\
PX x y: Get color at position (x,y)\n\
PX x y rrggbb(aa): Draw a pixel (with optional alpha channel)\n\
SIZE: Get canvaspixel size\n\
STATS: Return statistics");

	} else {

		net_err(client, "Unknown command");

	}
}

void px_on_key(int key, int scancode, int mods) {

	printf("Key pressed: key:%d scancode:%d mods:%d\n", key, scancode, mods);

	if (key == 301) { // F12
		canvaspixel_fullscreen(canvaspixel_get_display() + 1);
	} else if (key == 67) { // c
		canvaspixel_fill(0x00000088);
	} else if (key == 81 || key == 256) { // q or ESC
		canvaspixel_close();
	}
}

//void px_on_resize() {
//	canvaspixel_get_size(&px_width, &px_height);
//}

void px_on_window_close() {
	printf("Window closed\n");
	net_stop();
    //led_matrix_delete(matrix);
}

void *myThreadFun(/**struct RGBLedMatrix *matrix, struct LedCanvas *offscreen_canvas**/) {
    while(true){
        offscreen_canvas = led_matrix_swap_on_vsync(matrix, offscreen_canvas);
    }
}


int main(int argc, char **argv) {
	canvaspixel_setcb_key(&px_on_key);
    
    struct RGBLedMatrixOptions options;
    struct RGBLedRuntimeOptions rt_options;
    
    //int width, height;
    //int x, y, i;

    
    memset(&options, 0, sizeof(options));
    options.rows = 32;
    options.cols = 64;
    options.chain_length = 6;
    options.parallel = 2;
    options.hardware_mapping = "regular";
    options.pixel_mapper_config = "U-mapper";
    memset(&rt_options, 0, sizeof(rt_options));
    rt_options.gpio_slowdown = 4;
    options.chain_length = 6;

    /* This supports all the led commandline options. Try --led-help */
    matrix = led_matrix_create_from_options_and_rt_options(&options, &rt_options);
    if (matrix == NULL)
        return 1;

    /* Let's do an example with double-buffering. We create one extra
     * buffer onto which we draw, which is then swapped on each refresh.
     * This is typically a good aproach for animations and such.
     */
    offscreen_canvas = led_matrix_create_offscreen_canvas(matrix);
//	canvaspixel_setcb_resize(&px_on_resize);

	//canvaspixel_start(1024, &px_on_window_close);
    pthread_t thread_id;
    pthread_create(&thread_id, NULL, myThreadFun, NULL);

	net_start(1337, &px_on_connect, &px_on_read, &px_on_close);
	return 0;
}

