#include <queue>
#include <node.h>
#include <v8.h>
#include <node_buffer.h>
#include <stdio.h>
#include <cstring>
#include "uv.h"
#include "vita.h"
#include "vterror.h"

using namespace node;
using namespace std;

struct Baton {
	// Extract callback automatically as the last parameter
	// and VitaContext from the first internal pointer of This().
	Baton(const v8::Arguments& args) {
		v8::Handle<v8::Value> last = args[args.Length()-1];
		if (last != v8::Undefined() && last->IsFunction()) {
			callback = v8::Persistent<v8::Value>::New(last);
		} else {
			callback = v8::Persistent<v8::Value>::New(v8::Undefined());
		}
		hasBuf = 0;
	}

	~Baton() {
		this->callback.Dispose();
	}

	void call() {
		const unsigned argc = 3;

		v8::Handle<v8::Value> argv[argc];

		if (this->rc >= 0) {
			if (this->hasBuf) {
				argv[0] = v8::Undefined();
				// Create a node buffer object from a c buffer
				node::Buffer *slowBuffer = node::Buffer::New(this->length);
				memcpy(node::Buffer::Data(slowBuffer), this->buffer, this->length);
				v8::Local<v8::Object> globalObj = v8::Context::GetCurrent()->Global();
				v8::Local<v8::Function> bufferConstructor = v8::Local<v8::Function>::Cast(globalObj->Get(v8::String::New("Buffer")));
				v8::Handle<v8::Value> constructorArgs[3] = { slowBuffer->handle_, v8::Integer::New(this->length), v8::Integer::New(0) };
				v8::Local<v8::Object> actualBuffer = bufferConstructor->NewInstance(3, constructorArgs);
				argv[1] = actualBuffer;
				argv[2] = v8::Integer::New(this->rc);
			} else {
				argv[0] = v8::Undefined();
				argv[1] = v8::Integer::New(this->data);
				argv[2] = v8::Integer::New(this->rc);
			}
		} else {
			argv[0] = v8::Exception::Error( v8::String::Concat(
				v8::String::New("libvita failure: "),
				v8::String::New(vita_error_str(this->rc))));
			argv[1] = v8::Undefined();
			argv[2] = v8::Undefined();
		}

		if (this->callback->IsFunction()) {
			v8::Function::Cast(*(this->callback))->Call(v8::Context::GetCurrent()->Global(), argc, argv);
		}
	}

	v8::Persistent<v8::Value> callback;

	int group;
	int channel;
	int property;
	int data;
	char *buffer;
	int hasBuf;
	size_t length;
	int rc;
	bool agent;
};

namespace {
	uv_mutex_t waiting;
	uv_async_t sweep_queue;
	uv_thread_t usb_thread;
	std::queue<Baton*> pending;
	int devices = 0;

	inline VitaContext *context(const v8::Arguments& args) {
		return static_cast<VitaContext*>(args.This()->GetPointerFromInternalField(0));
	}
}

// This calls any pending Node callbacks.
void handle_signal(uv_async_t *handle, int status) {
	uv_mutex_lock(&waiting);

	// process any pending callbacks to node.
	while(!pending.empty()) {
		Baton *it = pending.front();
		uv_mutex_unlock(&waiting);
		it->call();

		uv_mutex_lock(&waiting);
		pending.pop();

		if (it->agent) continue;
		delete it;
	}

	uv_mutex_unlock(&waiting);
}

// Function passed to all vita functions that callback.
// Runs on the EventThread
void handle_data(int data, void *user) {
	Baton *baton = static_cast<Baton*>(user);
	baton->data = data;

	uv_mutex_lock(&waiting);
	// add to pending queue.
	pending.push(baton);

	uv_mutex_unlock(&waiting);

	// call handle_signal in the main thread
	uv_async_send(&sweep_queue);
}

// Function passed to all vita functions that callback.
// Runs on the EventThread
void handle_buf(char *buf, int length, void *user) {
	Baton *baton = static_cast<Baton*>(user);
	baton->buffer = buf;

	uv_mutex_lock(&waiting);
	// add to pending queue.
	pending.push(baton);

	uv_mutex_unlock(&waiting);

	// call handle_signal in the main thread
	uv_async_send(&sweep_queue);
}

// Base error handler for vita
// Runs on the EventThread?
void handle_error(int rc, void *user) {
	if (user) {
		Baton *baton = static_cast<Baton*>(user);
		baton->rc = rc;

		uv_mutex_lock(&waiting);
		// add to pending queue.
		pending.push(baton);

		uv_mutex_unlock(&waiting);

		// call handle_signal in the main thread
		uv_async_send(&sweep_queue);
	}
}

void EIO_EventThread(void *arg) {
	while (devices) {
		vita_poll(NULL, 100);
	}
}

// Constructor function for device.
// ====================================================================
v8::Handle<v8::Value> New(const v8::Arguments& args) {
	v8::HandleScope scope;

	if (!devices) {
		// init library
		vita_init(handle_error);
		uv_async_init(uv_default_loop(), &sweep_queue, handle_signal);
	}

	if (args.Length() < 1) {
		return v8::ThrowException(v8::Exception::TypeError(v8::String::New("First argument must be a string.")));
	}

	// Device url
	v8::String::Utf8Value url(args[0]->ToString());
	VitaContext *ctx = vita_open(*url);
	if (ctx) {
		devices++;
	}

	if (devices == 1) {
		uv_thread_create(&usb_thread, EIO_EventThread, NULL);
	}

	if (ctx == NULL) {
		return v8::ThrowException(v8::Exception::Error(v8::String::New("Failed to attach to device.")));
	}

	v8::Handle<v8::Object> self = args.This();
	self->SetPointerInInternalField(0, ctx);

	uv_ref((uv_handle_t*)&sweep_queue);

	return scope.Close(self);
}


// SetOutputs
// ====================================================================
v8::Handle<v8::Value> SetOutputs(const v8::Arguments& args) {
	v8::HandleScope scope;

	Baton *baton = new Baton(args);

	baton->group = args[0]->ToInteger()->Value();
	baton->channel = args[1]->ToInteger()->Value();
	baton->data = args[2]->ToInteger()->Value();

	VitaContext *ctx = context(args);
	baton->rc = vita_set_outputs(ctx, baton->group, baton->channel, baton->data);

	baton->call();

	return scope.Close(args.This());
};


// GetInputs
// ====================================================================
v8::Handle<v8::Value> GetInputs(const v8::Arguments& args) {
	v8::HandleScope scope;

	Baton *baton = new Baton(args);

	baton->group = args[0]->ToInteger()->Value();
	baton->channel = args[1]->ToInteger()->Value();

	VitaContext *ctx = context(args);
	baton->rc = vita_request_inputs(ctx, baton->group, baton->channel, handle_data, baton);

	if (baton->rc < 0) {
		// return error to node immediately.
		baton->call();
	}

	return scope.Close(args.This());
}

// SetProperty
// ====================================================================
v8::Handle<v8::Value> SetProperty(const v8::Arguments& args) {
	v8::HandleScope scope;
	VitaContext *ctx = context(args);

	Baton *baton = new Baton(args);

	baton->group = args[0]->ToInteger()->Value();
	baton->channel = args[1]->ToInteger()->Value();
	baton->property = args[2]->ToInteger()->Value();
	baton->data = args[3]->ToInteger()->Value();

	baton->rc = vita_set_property(ctx, baton->group, baton->channel, baton->property, baton->data);

	baton->call();

	return scope.Close(args.This());
};

// SetPropertyBuf
// ====================================================================
v8::Handle<v8::Value> SetPropertyBuf(const v8::Arguments& args) {
	v8::HandleScope scope;
	VitaContext *ctx = context(args);

	Baton *baton = new Baton(args);

	baton->group = args[0]->ToInteger()->Value();
	baton->channel = args[1]->ToInteger()->Value();
	baton->property = args[2]->ToInteger()->Value();

	v8::Handle<v8::Object> bufferObj = args[3]->ToObject();
	baton->buffer = node::Buffer::Data(bufferObj);
	baton->length = node::Buffer::Length(bufferObj);
	//printf("%x\n", baton->buffer[0]);
	//printf("%x\n", baton->buffer[1]);

	baton->rc = vita_set_property_buf(ctx, baton->group, baton->channel, baton->property, baton->buffer, baton->length);

	baton->call();

	return scope.Close(args.This());
};

// GetPropertyBuf
// ====================================================================
v8::Handle<v8::Value> GetPropertyBuf(const v8::Arguments& args) {
	v8::HandleScope scope;
	VitaContext *ctx = context(args);

	Baton *baton = new Baton(args);

	baton->group = args[0]->ToInteger()->Value();
	baton->channel = args[1]->ToInteger()->Value();
	baton->property = args[2]->ToInteger()->Value();
	baton->length = args[3]->ToInteger()->Value();
	baton->hasBuf = 1;

	//v8::Handle<v8::Object> bufferObj = args[3]->ToObject();
	//baton->buffer = node::Buffer::Data(bufferObj);
	//baton->length = node::Buffer::Length(bufferObj);

	baton->rc = vita_get_property_buf(ctx, baton->group, baton->channel, baton->property, baton->length, handle_buf, baton);

	if (baton->rc < 0) baton->call();

	return scope.Close(args.This());
};

// GetProperty
// ====================================================================
v8::Handle<v8::Value> GetProperty(const v8::Arguments& args) {
	v8::HandleScope scope;
	VitaContext *ctx = context(args);

	Baton *baton = new Baton(args);

	baton->group = args[0]->ToInteger()->Value();
	baton->channel = args[1]->ToInteger()->Value();
	baton->property = args[2]->ToInteger()->Value();

	baton->rc = vita_request_property(ctx, baton->group, baton->channel, baton->property, handle_data, baton);

	if (baton->rc < 0) {
		// return error to node immediately.
		baton->call();
	}

	return scope.Close(args.This());
}

// Agents
// ====================================================================
v8::Handle<v8::Value> AddAgent(const v8::Arguments& args) {
	v8::HandleScope scope;
	VitaContext *ctx = context(args);

	Baton *baton = new Baton(args);

	baton->group = args[0]->ToInteger()->Value();
	baton->channel = args[1]->ToInteger()->Value();
	baton->property = args[2]->ToInteger()->Value();
	baton->agent = true;

	baton->rc = vita_start_agent(ctx, baton->group, baton->channel, baton->property, handle_data, baton);

	if (baton->rc < 0) {
		baton->call();
	}

	return scope.Close(v8::Integer::New(baton->rc));
}

v8::Handle<v8::Value> StopAgent(const v8::Arguments& args) {
	v8::HandleScope scope;
	VitaContext *ctx = context(args);
	Baton *baton = new Baton(args);

	baton->rc = vita_stop_agent(ctx, 0);

	if (baton->rc < 0) {
		baton->call();
	}

	return scope.Close(args.This());
}

// close
// ====================================================================
v8::Handle<v8::Value> Close(const v8::Arguments& args) {
	v8::HandleScope scope;
	VitaContext *ctx = context(args);
	int rc = vita_close(ctx);

	if (rc == 0) {
		devices--;

		if (!devices) {
			uv_thread_join(&usb_thread);
			uv_close((uv_handle_t*)&sweep_queue, NULL);
			vita_destroy();
		}
	}

	return scope.Close(args.This());
}

// Module Init
// ====================================================================
void init(v8::Handle<v8::Object> target) {
	v8::HandleScope scope;

	uv_mutex_init(&waiting);

	v8::Persistent<v8::FunctionTemplate> ctor = v8::Persistent<v8::FunctionTemplate>::New(v8::FunctionTemplate::New(New));
	ctor->InstanceTemplate()->SetInternalFieldCount(1);
	ctor->SetClassName(v8::String::NewSymbol("create"));

	NODE_SET_PROTOTYPE_METHOD(ctor, "setOutputs", SetOutputs);
	NODE_SET_PROTOTYPE_METHOD(ctor, "getInputs", GetInputs);

	NODE_SET_PROTOTYPE_METHOD(ctor, "setProperty", SetProperty);
	NODE_SET_PROTOTYPE_METHOD(ctor, "setPropertyBuf", SetPropertyBuf);
	NODE_SET_PROTOTYPE_METHOD(ctor, "getPropertyBuf", GetPropertyBuf);
	NODE_SET_PROTOTYPE_METHOD(ctor, "requestProperty", GetProperty);

	NODE_SET_PROTOTYPE_METHOD(ctor, "addAgent", AddAgent);
	NODE_SET_PROTOTYPE_METHOD(ctor, "stopAgent", StopAgent);
	//NODE_SET_PROTOTYPE_METHOD(ctor, "removeAgent", StopAgent);

	NODE_SET_PROTOTYPE_METHOD(ctor, "close", Close);

	target->Set(v8::String::NewSymbol("Vita"), ctor->GetFunction());
}

NODE_MODULE(vita, init)
