#include "nfn.h"

#include <unistd.h>
#include <stdlib.h>

namespace nfn {

//	static

	size_t min(size_t a, size_t b) {
		return a > b ? b : a;
	}

	size_t max(size_t a, size_t b) {
		return a > b ? a : b;
	}

	void *memcpy(char *dest, const char *src, size_t count) {
		void *ret = dest;
		while (count--) {
			*dest = *src;
			dest = dest + 1;
			src = src + 1;
		}
		return ret;
	}

	void memset(void *dest, const char val, size_t count) {
		while (count--) {
			*(char *) dest = val;
			dest = (char *) dest + 1;
		}
	}

	bool strcmp(char *str1, char *str2) {
		if (*str1 == '\0' || *str2 == '\0') return false;
		while (*str1 != '\0' && *str2 != '\0' && *str1 == *str2) {
			str1++;
			str2++;
		}

		return *str1 == *str2;
	}

	nfn_buffer *buffer_init(size_t max) {
		nfn_buffer *buf = new nfn_buffer;

		buf->max = max;
		buf->len = 0;
		buf->data = malloc(buf->max + 1);

		return buf;
	};

	void buffer_free(nfn_buffer *buf) {
		free(buf->data);
		delete buf;
	}

	Local<String> BuftoString(Isolate *isolate, nfn_buffer *buf) {
		if (buf->len == 0) return String::NewFromUtf8(isolate, "");

		char *c = (char *) buf->data;
		c[buf->len] = 0;

		return String::NewFromUtf8(isolate, c);
	}

	Local<Uint8Array> BuftoHex(Isolate *isolate, nfn_buffer *value, bool init) {
		Local<ArrayBuffer> data = init ?
								  ArrayBuffer::New(isolate, value->len) :
								  ArrayBuffer::New(isolate, value->data, value->len);

		if (init) memcpy((char *) data->GetContents().Data(), (char *) value->data, value->len);

		return Uint8Array::New(data, 0, value->len);
	}

	char *HextoPChar(Local<Uint8Array> value, int index) {
		Local<ArrayBuffer> data = value->Buffer();
		char *buf = (char *) data->GetContents().Data();

		if (index < 0) index = (int) value->ByteOffset();
		buf += index;

		return buf;
	}

	char *StringtoPChar(Local<String> value) {
		size_t len = (size_t) value->Utf8Length();

		char *result = (char *) malloc(len + 1);

		value->WriteUtf8(result);

		return result;
	}

	nfn_buffer *HextoBuf(Local<Uint8Array> value) {
		nfn_buffer *buf = buffer_init(value->ByteLength());
		char *data = HextoPChar(value);

		memcpy((char *) buf->data, data, buf->max);
		buf->len = buf->max;

		return buf;
	}

//	do function

	void do_read(nfn_info *info, size_t len) {
		if (info->state & NFN_CLOSE) return;

		nfn_buffer *buf = info->read->buf;
		len = min(buf->max - buf->len, len);

		if (len < 1) return;

		len = (size_t) read(info->hwnd, (char *) buf->data + buf->len, len);

		if (len < 1) return;

		if (info->state & NFN_LOG)
			printf("[nfn bin] read %zu [buf: %s, len:%zu]\n", len, (char *) buf->data, buf->len);

		buf->len += len;

		uv_async_send(&info->read->work);
	}

	void do_write(nfn_info *info, const void *data, size_t len) {
		if (info->state & NFN_CLOSE) return;

		nfn_buffer *buf = info->write->buf;

		if (len > 0) {
			size_t tmp = len + buf->len > buf->max ? buf->max - buf->len : len;

			memcpy((char *) buf->data + buf->len, (char *) data, tmp);
			buf->len += tmp;

			if (len - tmp > 0) {
				do_write(info, NULL, 0);
				do_write(info, (char *) data + tmp, len - tmp);
			}
		} else if (buf->len > 0) {
			len = (size_t) write(info->hwnd, buf->data, buf->len);
			if (info->state & NFN_LOG)
				printf("[nfn bin] write %zu [buf: %s, len:%zu]\n", len, (char *) buf->data, buf->len);

			uv_async_send(&info->write->work);
		}
	}

	void do_finish(uv_handle_t *handle) {
		nfn_async *async = (nfn_async *) handle->data;

		buffer_free(async->buf);

		delete async;
	};

	void do_callback(Isolate *isolate, Local<Function> func, const char *tag, Local<Array> data) {
		HandleScope handle_scope(isolate);

		Local<Value> argv[2] = {
				String::NewFromUtf8(isolate, tag),
				data
		};
		func->Call(isolate->GetCurrentContext()->Global(), 2, argv);
	}

//	async function

	void async_work(uv_async_t *handle) {
		Isolate *isolate = Isolate::GetCurrent();
		HandleScope handle_scope(isolate);
		nfn_async *async = (nfn_async *) handle->data;

		if (*async->state & NFN_CLOSE) return;

		nfn_buffer *buf = async->buf;
		bool hex = *async->state & NFN_HEX;
		Local<Array> data = Array::New(isolate, 2);

		if (hex) data->Set(Number::New(isolate, 0), BuftoHex(isolate, buf));
		else data->Set(Number::New(isolate, 0), BuftoString(isolate, buf));

		data->Set(Number::New(isolate, 1), Number::New(isolate, buf->len));

		buf->len = 0;

		do_callback(isolate, Local<Function>::New(isolate, *async->func), async->tag, data);
	}

	void async_finish(nfn_async *async) {
		if (async) uv_close((uv_handle_t *) &async->work, do_finish);
	}

//	uv function

	void uv_work(uv_work_t *handle) {
		nfn_info *info = (nfn_info *) handle->data;
		nfn_uv *uv = info->uv;

		while (!(info->state & NFN_CLOSE)) {
			if (info->state & NFN_UV_LOCKED) usleep(10000);
			else {
				info->state |= NFN_UV_LOCKED;

				do_read(info, 4096);
				do_write(info, NULL, 0);

				if (info->state & NFN_CLOSE) return;

				info->state &= ~NFN_UV_LOCKED;
				usleep(uv->sp);
			}
		}
	}

	void uv_finish_cb(nfn_info *info) {
		async_finish(info->read);
		async_finish(info->write);
		if (info->state & NFN_LOG) printf("[nfn bin] free async\n");

		if (info->hwnd) close(info->hwnd);

		if (info->state & NFN_LOG) printf("[nfn bin] free end\n");

		Isolate *isolate = Isolate::GetCurrent();
		HandleScope handle_scope(isolate);

		do_callback(isolate,
					Local<Function>::New(isolate, info->func),
					info->state & NFN_RESET ? "reset" : "stop",
					Array::New(isolate, 0));

		delete info;
	}

	void uv_finish(uv_work_t *handle, int status) {
		nfn_info *info = (nfn_info *) handle->data;

		delete info->uv;
		if (info->state & NFN_LOG) printf("[nfn bin] free uv\n");

		uv_finish_cb(info);
	}

//	====== nfn_Core ======

	nfn_Core::~nfn_Core() { this->stop(); }

	bool nfn_Core::start(Isolate *isolate, Local<Object> opt, Local<Function> func) {
		bool result = false;
		nfn_info *info = this->_info;

		if (info) this->stop();

		info = this->info_init(isolate, opt);
		info->uv = NULL;
		info->read = NULL;
		info->write = NULL;

		info->hwnd = 0;
		info->state = 0;

		nfn_device *dev = this->info_set(isolate, opt);

		if (dev->log) info->state |= NFN_LOG;
		if (dev->hex) info->state |= NFN_HEX;
		if (!dev->manu) info->state |= NFN_UV;

		dev->hwnd = this->device_open(dev);
		if (info->state & NFN_LOG) printf("[nfn bin] Start name: %s  hwnd: %d\n", dev->name, dev->hwnd);

		result = dev->hwnd > 0;
		if (result) {
			result = this->device_set(dev);
			if (info->state & NFN_LOG) printf("[nfn bin] Start %s\n", result ? "OK" : "Err");

			if (result) this->_info = this->init(isolate, info, dev, func);
		}

		if (!result) delete info;

		free(dev->name);
		delete dev;

		return result;
	}

	void nfn_Core::stop(void) {
		nfn_info *info = this->_info;

		this->_info = NULL;

		if (!info || (info->state & NFN_CLOSE)) return;

		info->state |= NFN_CLOSE;
		if (!(info->state & NFN_UV)) uv_finish_cb(info);

		this->stop_custom(info);
	}

	void nfn_Core::reset(void) {
		nfn_info *info = this->_info;

		if (!(info->state & NFN_RESET)) info->state |= NFN_RESET;

		this->stop();
	}

	bool nfn_Core::config(Isolate *isolate, Local<Object> opt) {
		nfn_info *info = this->_info;

		if (!info || (info->state & NFN_CLOSE)) return false;

		Local<String> key[] = {
				String::NewFromUtf8(isolate, "log"),
				String::NewFromUtf8(isolate, "hex"),
				String::NewFromUtf8(isolate, "sleep")
		};
		Local<Value> vlv;

		vlv = opt->Get(key[0]);
		if (vlv->IsBoolean()) {
			if (vlv->BooleanValue()) info->state |= NFN_LOG;
			else info->state &= ~NFN_LOG;
		}

		vlv = opt->Get(key[1]);
		if (vlv->IsBoolean()) {
			if (vlv->BooleanValue()) info->state |= NFN_HEX;
			else info->state &= ~NFN_HEX;
		}

		vlv = opt->Get(key[2]);
		if (vlv->IsNumber() && info->uv) {
			info->uv->sp = (useconds_t) (vlv->Uint32Value() * 1000);
		}

		return this->config_custom(isolate, opt, info);
	}

	void nfn_Core::clear(void) {
		nfn_info *info = this->_info;

		if (!info || (info->state & NFN_CLOSE)) return;

		while (1) {
			if (info->state & NFN_CLOSE) return;

			if (info->state & NFN_UV_LOCKED) usleep(5000);
			else {
				info->state |= NFN_UV_LOCKED;

				info->read->buf->len = 0;
				info->write->buf->len = 0;

				info->state &= ~NFN_UV_LOCKED;

				return;
			}
		}
	}

	bool nfn_Core::read(size_t len) {
		nfn_info *info = this->_info;

		if (!info || (info->state & NFN_CLOSE)) return false;

		while (1) {
			if (info->state & NFN_CLOSE) return false;

			if (info->state & NFN_UV_LOCKED) usleep(5000);
			else {
				info->state |= NFN_UV_LOCKED;

				do_read(info, len);

				info->state &= ~NFN_UV_LOCKED;

				return true;
			}
		}
	}

	bool nfn_Core::write(Local<Value> data) {
		nfn_info *info = this->_info;

		if (!info || (info->state & NFN_CLOSE)) return false;

		nfn_buffer buf;
		if (data->IsString()) {
			Local<String> str = Local<String>::Cast(data);
			buf.data = StringtoPChar(str);
			buf.max = (size_t) str->Utf8Length();
		} else {
			Local<Uint8Array> arr = Local<Uint8Array>::Cast(data);
			buf.data = HextoPChar(arr);
			buf.max = arr->ByteLength();
		}

		do_write(info, buf.data, buf.max);

		if (data->IsString()) free(buf.data);

		return true;
	}

//	--------------------

	nfn_info *nfn_Core::init(Isolate *isolate, nfn_info *info, nfn_device *dev, Local<Function> func) {
		HandleScope handle_scope(isolate);

		info->state &= ~(NFN_CLOSE | NFN_RESET | NFN_UV_LOCKED);
		info->hwnd = dev->hwnd;
		info->func.Reset(isolate, func);

		this->init_custom(info, dev);

		uv_loop_t *loop = uv_default_loop();
		info->read = this->init_async(info, loop, "read", dev->rmax);
		info->write = this->init_async(info, loop, "write", dev->wmax);
		if (info->state & NFN_LOG) printf("[nfn bin] init async\n");

		Local<Array> data = Array::New(isolate, 2);
		data->Set(Number::New(isolate, 0), String::NewFromUtf8(isolate, dev->name));
		data->Set(Number::New(isolate, 1), this->info_get(isolate, dev));
		do_callback(isolate, Local<Function>::New(isolate, info->func), "start", data);

		if (info->state & NFN_UV) {
			info->uv = this->init_uv(info, uv_default_loop(), dev->sleep);
			if (info->state & NFN_LOG) printf("[nfn bin] init uv\n");
		}

		return info;
	}

	void nfn_Core::init_custom(nfn_info *info, nfn_device *dev) { }

	nfn_async *nfn_Core::init_async(nfn_info *info, uv_loop_t *loop, char *tag, size_t max) {
		nfn_async *async = new nfn_async;
		async->tag = tag;
		async->state = &info->state;
		async->buf = buffer_init(max);
		async->func = &info->func;
		async->work.data = async;

		uv_async_init(loop, &async->work, async_work);

		return async;
	}

	nfn_uv *nfn_Core::init_uv(nfn_info *info, uv_loop_t *loop, size_t sleep) {
		nfn_uv *uv = new nfn_uv;
		uv->sp = (useconds_t) (sleep * 1000);
		uv->work.data = info;

		uv_queue_work(loop, &uv->work, uv_work, uv_finish);

		return uv;
	}

	nfn_info *nfn_Core::info_init(Isolate *isolate, Local<Object> opt) {
		return new nfn_info;
	}

	nfn_device *nfn_Core::info_set(Isolate *isolate, Local<Object> opt) {
		nfn_device *dev = this->info_set_custom(isolate, opt);

		Local<String> key[] = {
				String::NewFromUtf8(isolate, "log"),
				String::NewFromUtf8(isolate, "hex"),
				String::NewFromUtf8(isolate, "manu"),
				String::NewFromUtf8(isolate, "sleep"),
				String::NewFromUtf8(isolate, "rmax"),
				String::NewFromUtf8(isolate, "wmax"),
				String::NewFromUtf8(isolate, "name")
		};
		Local<Value> vlv;
		size_t vli;

		vlv = opt->Get(key[0]);
		if (vlv->IsBoolean()) dev->log = vlv->BooleanValue();

		vlv = opt->Get(key[1]);
		if (vlv->IsBoolean()) dev->hex = vlv->BooleanValue();

		vlv = opt->Get(key[2]);
		if (vlv->IsBoolean()) dev->manu = vlv->BooleanValue();

		vlv = opt->Get(key[3]);
		if (vlv->IsNumber()) {
			vli = (size_t) vlv->NumberValue();
			dev->sleep = vli > 100 ? vli : 100;
		}

		vlv = opt->Get(key[4]);
		if (vlv->IsNumber()) {
			vli = (size_t) vlv->NumberValue();
			dev->rmax = vli > 0 ? min(vli, 4096) : 512;
		}

		vlv = opt->Get(key[5]);
		if (vlv->IsNumber()) {
			vli = (size_t) vlv->NumberValue();
			dev->wmax = vli > 0 ? min(vli, 4096) : 512;
		}

		vlv = opt->Get(key[6]);
		if (vlv->IsString()) dev->name = StringtoPChar(vlv->ToString());

		return dev;
	}

	nfn_device *nfn_Core::info_set_custom(Isolate *isolate, Local<Object> opt) {
		return new nfn_device;
	}

	void nfn_Core::stop_custom(nfn_info *info) { }

	bool nfn_Core::config_custom(Isolate *isolate, Local<Object> opt, nfn_info *info) {
		return true;
	}
}