#include <typhoeus_easy.h>
	
static VALUE idAppend;
VALUE cTyphoeusEasy;

static void dealloc(CurlEasy *curl_easy) {
	if (curl_easy->request_chunk != NULL) {
		free(curl_easy->request_chunk);
	}
	
	if (curl_easy->headers != NULL) {
		curl_slist_free_all(curl_easy->headers);
	}
	
	curl_easy_cleanup(curl_easy->curl);
	
	free(curl_easy);
}

static VALUE easy_setopt_string(VALUE self, VALUE opt_name, VALUE parameter) {
	CurlEasy *curl_easy;
	Data_Get_Struct(self, CurlEasy, curl_easy);

	long opt = NUM2LONG(opt_name);
	curl_easy_setopt(curl_easy->curl, opt, StringValuePtr(parameter));
	return opt_name;
}

static VALUE easy_setopt_long(VALUE self, VALUE opt_name, VALUE parameter) {
	CurlEasy *curl_easy;
	Data_Get_Struct(self, CurlEasy, curl_easy);

	long opt = NUM2LONG(opt_name);
	curl_easy_setopt(curl_easy->curl, opt, NUM2LONG(parameter));
	return opt_name;	
}

static VALUE easy_getinfo_string(VALUE self, VALUE info) {
	char *info_string;
	CurlEasy *curl_easy;
	Data_Get_Struct(self, CurlEasy, curl_easy);

	long opt = NUM2LONG(info);
	curl_easy_getinfo(curl_easy->curl, opt, &info_string);
	
	return rb_str_new2(info_string);
}

static VALUE easy_getinfo_long(VALUE self, VALUE info) {
	long info_long;
	CurlEasy *curl_easy;
	Data_Get_Struct(self, CurlEasy, curl_easy);

	long opt = NUM2LONG(info);
	curl_easy_getinfo(curl_easy->curl, opt, &info_long);
	
	return LONG2NUM(info_long);
}

static VALUE easy_getinfo_double(VALUE self, VALUE info) {
	double info_double = 0;
	CurlEasy *curl_easy;
	Data_Get_Struct(self, CurlEasy, curl_easy);

	long opt = NUM2LONG(info);
	curl_easy_getinfo(curl_easy->curl, opt, &info_double);

	return rb_float_new(info_double);
}

static VALUE easy_perform(VALUE self) {
	CurlEasy *curl_easy;
	Data_Get_Struct(self, CurlEasy, curl_easy);
	curl_easy_perform(curl_easy->curl);

	return Qnil;
}

static size_t write_data_handler(char *stream, size_t size, size_t nmemb, VALUE val) {
	rb_funcall(val, idAppend, 1, rb_str_new(stream, size * nmemb));
	return size * nmemb;
}

static size_t read_callback(void *ptr, size_t size, size_t nmemb, void *data) {
	int realsize = size * nmemb;
	RequestChunk *mem = (RequestChunk *)data;

	if (realsize > mem->size - mem->read) {
		realsize = mem->size - mem->read;
	}
	
	if (realsize != 0) {
		memcpy(ptr, &(mem->memory[mem->read]), realsize);
		mem->read += realsize;
	}
	
	return realsize; 
}

static void set_response_handlers(VALUE easy, CURL *curl) {
	rb_iv_set(easy, "@response_body", rb_str_new2(""));
	rb_iv_set(easy, "@response_header", rb_str_new2(""));
	
  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, (curl_write_callback)&write_data_handler);
  curl_easy_setopt(curl, CURLOPT_WRITEDATA, rb_iv_get(easy, "@response_body"));	
  curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, (curl_write_callback)&write_data_handler);
  curl_easy_setopt(curl, CURLOPT_HEADERDATA, rb_iv_get(easy, "@response_header"));
}

static VALUE easy_reset(VALUE self) {
	CurlEasy *curl_easy;
	Data_Get_Struct(self, CurlEasy, curl_easy);

	if (curl_easy->request_chunk != NULL) {
		free(curl_easy->request_chunk);
		curl_easy->request_chunk = NULL;
	}
	
	if (curl_easy->headers != NULL) {
		curl_slist_free_all(curl_easy->headers);
		curl_easy->headers = NULL;
	}

	curl_easy_reset(curl_easy->curl);

	set_response_handlers(self, curl_easy->curl);
	
	return Qnil;	
}

static VALUE easy_add_header(VALUE self, VALUE header) {
	CurlEasy *curl_easy;
	Data_Get_Struct(self, CurlEasy, curl_easy);
	
	curl_easy->headers = curl_slist_append(curl_easy->headers, RSTRING_PTR(header));
	return header;
}

static VALUE easy_set_headers(VALUE self) {
	CurlEasy *curl_easy;
	Data_Get_Struct(self, CurlEasy, curl_easy);

	curl_easy_setopt(curl_easy->curl, CURLOPT_HTTPHEADER, curl_easy->headers);
	
	return Qnil;
}

static VALUE easy_set_request_body(VALUE self, VALUE data, VALUE content_length_header) {
	CurlEasy *curl_easy;
	Data_Get_Struct(self, CurlEasy, curl_easy);

	curl_easy->request_chunk = ALLOC(RequestChunk);
	curl_easy->request_chunk->size = RSTRING_LEN(data);
	curl_easy->request_chunk->memory = StringValuePtr(data);
	curl_easy->request_chunk->read = 0;

  curl_easy_setopt(curl_easy->curl, CURLOPT_READFUNCTION, (curl_read_callback)read_callback);
  curl_easy_setopt(curl_easy->curl, CURLOPT_READDATA, curl_easy->request_chunk);
	curl_easy_setopt(curl_easy->curl, CURLOPT_INFILESIZE, RSTRING_LEN(data));

	return Qnil;
}

static VALUE easy_escape(VALUE self, VALUE data, VALUE length) {
	CurlEasy *curl_easy;
	Data_Get_Struct(self, CurlEasy, curl_easy);

	return rb_str_new2(curl_easy_escape(curl_easy->curl, StringValuePtr(data), NUM2INT(length)));
}

static VALUE version(VALUE self) {
	return rb_str_new2(curl_version());
}

static VALUE new(int argc, VALUE *argv, VALUE klass) {
	CURL *curl = curl_easy_init();
	CurlEasy *curl_easy = ALLOC(CurlEasy);
	curl_easy->curl = curl;
	curl_easy->headers = NULL;
	curl_easy->request_chunk = NULL;
	VALUE easy = Data_Wrap_Struct(cTyphoeusEasy, 0, dealloc, curl_easy);

	set_response_handlers(easy, curl);

	rb_obj_call_init(easy, argc, argv);

	return easy;
}

void init_typhoeus_easy() {
	VALUE klass = cTyphoeusEasy = rb_define_class_under(mTyphoeus, "Easy", rb_cObject);
	idAppend = rb_intern("<<");
	rb_define_singleton_method(klass, "new", new, -1);
	rb_define_private_method(klass, "easy_setopt_string", easy_setopt_string, 2);
	rb_define_private_method(klass, "easy_setopt_long", easy_setopt_long, 2);
	rb_define_private_method(klass, "easy_getinfo_string", easy_getinfo_string, 1);
	rb_define_private_method(klass, "easy_getinfo_long", easy_getinfo_long, 1);
	rb_define_private_method(klass, "easy_getinfo_double", easy_getinfo_double, 1);
	rb_define_private_method(klass, "easy_perform", easy_perform, 0);
	rb_define_private_method(klass, "easy_reset", easy_reset, 0);
	rb_define_private_method(klass, "easy_set_request_body", easy_set_request_body, 1);
	rb_define_private_method(klass, "easy_set_headers", easy_set_headers, 0);
	rb_define_private_method(klass, "easy_add_header", easy_add_header, 1);
	rb_define_private_method(klass, "easy_escape", easy_escape, 2);
	rb_define_private_method(klass, "version", version, 0);
}