#include"relay.h"

#include"img.h"
#include<tmmintrin.h>
#include<X11/XKBlib.h>
#include<X11/extensions/record.h>
#include<stdio.h>
#include<pthread.h>
#include<stdatomic.h>
#include<math.h>

static int scale_perform(CHiPubNode *n) {
	float *scales = CHi_Crawl(&n->sinks[0])->data.vec4;
	CHiImage *img = CHi_Crawl(&n->sinks[1])->data.sample;
	
	if(n->sources[0].data.sample) {
		CHi_Image_Free(n->sources[0].data.sample);
	}
	CHiImage *ret = n->sources[0].data.sample = CHi_Image_New(img->bpc, img->channels, img->stride, img->width, img->height, NULL);
	
	__m128i iscales = _mm_set_epi16(
		scales[3] * 65535, scales[0] * 65535, scales[1] * 65535, scales[2] * 65535,
		scales[3] * 65535, scales[0] * 65535, scales[1] * 65535, scales[2] * 65535
	);
	
	for(size_t y = 0; y < img->height; y++) {
		for(size_t x = 0; x < img->width; x += 16) {
			__m128i pixels8 = _mm_loadu_si128((__m128i*) ((uintptr_t) img->data16 + y * img->stride + x));
			__m128i mulled = _mm_mulhi_epu16(pixels8, iscales);
			_mm_storeu_si128((__m128i*) ((uintptr_t) ret->data16 + y * img->stride + x), mulled);
		}
	}
	
	return 1;
}

CUTIVIS CHiPubNode *CHi_ComponentScale() {
	CHiPubNode *n = calloc(1, sizeof(*n));
	n->type = CUTIHI_T('CCmp','nScl');
	n->Start = n->Stop = NULL;
	n->Perform = scale_perform;
	n->sinkCount = 2;
	n->sinks = calloc(sizeof(*n->sinks), n->sinkCount);
	n->sourceCount = 1;
	n->sources = calloc(sizeof(*n->sources), n->sourceCount);
	return n;
}

static Display *dpy;
static XkbDescPtr xKeyboardDesc;

typedef struct {
	CHiPubNode pub;
	XRecordContext rc;
	pthread_t thrd;
	
	char key[64];
	atomic_bool on;
} CHiKeyhookNode;

static void keyhook_handler(XPointer ud, XRecordInterceptData *recdata) {
	if(recdata->category != XRecordFromServer) {
		return;
	}
	
	int type = ((unsigned char*) recdata->data)[0] & 0x7F;
	int key = ((unsigned char*) recdata->data)[1];
	int repeat = recdata->data[2] & 1;
	
	CHiKeyhookNode *n = (CHiKeyhookNode*) ud;
	
	char keyname[XkbKeyNameLength + 1] = {};
	memcpy(keyname, xKeyboardDesc->names->keys[key].name, XkbKeyNameLength);
	
	if(!repeat && !strcmp(keyname, n->key)) {
		if(type == KeyPress) {
			n->on = 1;
		} else if(type == KeyRelease) {
			n->on = 0;
		}
	}
	
	XRecordFreeData(recdata);
}

static void *keyhook_thread(void *ud) {
	CHiKeyhookNode *n = ud;
	
	XRecordRange *rr = XRecordAllocRange();
	rr->device_events.first = KeyPress;
	rr->device_events.last = ButtonRelease;
	n->rc = XRecordCreateContext(dpy, 0, &(XRecordClientSpec) {XRecordAllClients}, 1, &rr, 1);
	XRecordEnableContext(dpy, n->rc, keyhook_handler, (XPointer) n);
	
	return NULL;
}

static int keyhook_perform(CHiPubNode *n) {
	CHiKeyhookNode *me = (CHiKeyhookNode*) n;
	
	strncpy(me->key, CHi_Crawl(&n->sinks[0])->data.text, 63);
	me->key[63] = '\0';
	
	n->sources[0].type = CUTIHI_VAL_VEC4;
	
	if(n->ng->compilationStatus == CUTIHI_COMP_READY || n->sinks[1].data.vec4[0] == 0) {
		n->sources[0].data.vec4[0] = ((CHiKeyhookNode*) n)->on;
	} else if(((CHiKeyhookNode*) n)->on) {
		n->sources[0].data.vec4[0] = fminf(1, n->sources[0].data.vec4[0] + CHi_Time_GetDelta(n->ng) * n->sinks[1].data.vec4[0]);
	} else {
		n->sources[0].data.vec4[0] = fmaxf(0, n->sources[0].data.vec4[0] - CHi_Time_GetDelta(n->ng) * n->sinks[1].data.vec4[0]);
	}
	
	return 1;
}

static void keyhook_destroy(CHiPubNode *pubn) {
	CHiKeyhookNode *n = (void*) pubn;
	
	XRecordDisableContext(dpy, n->rc);
	XRecordFreeContext(dpy, n->rc);
	
	free(n);
}

CUTIVIS CHiPubNode *CHi_Keyhook() {
	CHiKeyhookNode *n = calloc(1, sizeof(*n));
	n->pub.type = CUTIHI_T('CKey','hook');
	n->pub.Start = n->pub.Stop = NULL;
	n->pub.Perform = keyhook_perform;
	n->pub.Destroy = keyhook_destroy;
	n->pub.sinkCount = 2;
	n->pub.sinks = calloc(sizeof(*n->pub.sinks), n->pub.sinkCount);
	n->pub.sourceCount = 1;
	n->pub.sources = calloc(sizeof(*n->pub.sources), n->pub.sourceCount);
	
	n->on = 0;
	n->key[0] = '\n';
	
	if(!dpy) {
		dpy = XOpenDisplay(NULL);
		
		xKeyboardDesc = XkbGetMap(dpy, 0, XkbUseCoreKbd);
		XkbGetNames(dpy, XkbKeyNamesMask, xKeyboardDesc);
	}
	
	pthread_create(&n->thrd, NULL, keyhook_thread, n);
	
	return &n->pub;
}