#include <glib-object.h>
#include <json-glib/json-glib.h>

#include "{{classname}}.h"
#include "NetClient.h"
#include "Helpers.h"
#include "Error.h"
#include "RequestInfo.h"

using namespace std;
using namespace Tizen::{{prefix}};

{{#operations}}

{{classname}}::{{classname}}()
{

}

{{classname}}::~{{classname}}()
{

}

static gboolean __{{classname}}responseHandler(gpointer data)
{
	RequestInfo *request = static_cast<RequestInfo*>(data);
	g_thread_join(request->thread);

	// invoke the callback function
	bool retval = request->processor(*(request->p_chunk), *(request->code), request->errormsg, request->userData, request->handler);

	delete request;
	return FALSE;
}

static gpointer __{{classname}}threadFunc(gpointer data)
{
	RequestInfo *request = static_cast<RequestInfo*>(data);

	// handle the request
	NetClient::easycurl(request->host, request->path, request->method, request->queryParams,
	request->mBody, request->headerList, request->p_chunk, request->code, request->errormsg);

	request->thread = g_thread_self();
	g_idle_add(__{{classname}}responseHandler, static_cast<gpointer>(request));

	return NULL;
}


{{#operation}}
static bool {{nickname}}Processor(MemoryStruct_s p_chunk, long code, char* errormsg, void* userData,
	void(* voidHandler)())
{
	{{#returnType}}void(* handler)({{#returnContainer}}{{#isListContainer}}{{returnType}}<{{returnBaseType}}>{{/isListContainer}}{{#isMapContainer}}{{returnType}}<string,string>{{/isMapContainer}}{{/returnContainer}}{{^returnContainer}}{{returnType}}{{/returnContainer}}, Error, void* )
	= reinterpret_cast<void(*)({{#returnContainer}}{{#isListContainer}}{{returnType}}<{{returnBaseType}}>{{/isListContainer}}{{#isMapContainer}}{{returnType}}<string,string>{{/isMapContainer}}{{/returnContainer}}{{^returnContainer}}{{returnType}}{{/returnContainer}}, Error, void* )> (voidHandler);{{/returnType}}
	{{^returnType}}void(* handler)(Error, void* ) = reinterpret_cast<void(*)(Error, void* )> (voidHandler);{{/returnType}}
	JsonNode* pJson;
	char * data = p_chunk.memory;

	{{#returnType}}{{#returnContainer}}{{#isListContainer}}{{returnType}}<{{returnBaseType}}>{{/isListContainer}}{{#isMapContainer}}{{returnType}}<string,string>{{/isMapContainer}} out;{{/returnContainer}}
	{{^returnContainer}}{{returnType}} out;{{/returnContainer}}{{/returnType}}

	if (code >= 200 && code < 300) {
		Error error(code, string("No Error"));


		{{^returnType}}
		handler(error, userData);
		return true;
		{{/returnType}}

		{{#returnContainer}}
		{{#isListContainer}}
		pJson = json_from_string(data, NULL);
		JsonArray * jsonarray = json_node_get_array (pJson);
		guint length = json_array_get_length (jsonarray);
		for(guint i = 0; i < length; i++){
			JsonNode* myJson = json_array_get_element (jsonarray, i);
			char * singlenodestr = json_to_string(myJson, false);
			{{returnBaseType}} singlemodel;
			singlemodel.fromJson(singlenodestr);
			out.push_front(singlemodel);
			g_free(static_cast<gpointer>(singlenodestr));
			json_node_free(myJson);
		}
		json_array_unref (jsonarray);
		json_node_free(pJson);
		{{/isListContainer}}
		{{/returnContainer}}

		{{^returnContainer}}
		{{#returnType}}
		if (isprimitive("{{returnType}}")) {
			pJson = json_from_string(data, NULL);
			jsonToValue(&out, pJson, "{{returnType}}", "{{returnBaseType}}");
			json_node_free(pJson);

			if ("{{returnType}}" == "std::string") {
				string* val = (std::string*)(&out);
				if (val->empty() && p_chunk.size>4) {
					*val = string(p_chunk.memory, p_chunk.size);
				}
			}
		} else {
			{{#responses}}{{^primitiveType}}
			out.fromJson(data);
			char *jsonStr =  out.toJson();
			printf("\n%s\n", jsonStr);
			g_free(static_cast<gpointer>(jsonStr));
			{{/primitiveType}}{{/responses}}
		}
		handler(out, error, userData);
		return true;
		//TODO: handle case where json parsing has an error
		{{/returnType}}
		{{/returnContainer}}

	} else {
		Error error;
		if (errormsg != NULL) {
			error = Error(code, string(errormsg));
		} else if (p_chunk.memory != NULL) {
			error = Error(code, string(p_chunk.memory));
		} else {
			error = Error(code, string("Unknown Error"));
		}
		{{#returnType}} handler(out, error, userData);
		return false;
		{{/returnType}}{{^returnType}}handler(error, userData);
		return false;
		{{/returnType}}
	}
}

static bool {{nickname}}Helper(char * accessToken,
	{{#allParams}}{{#isContainer}}{{{dataType}}}<{{baseType}}> {{paramName}}{{/isContainer}}{{^isContainer}}{{{dataType}}} {{paramName}}{{/isContainer}}{{#hasMore}}, {{/hasMore}}{{/allParams}}{{#hasParams}}, {{/hasParams}}
	{{#returnType}}void(* handler)({{#returnContainer}}{{#isListContainer}}{{returnType}}<{{returnBaseType}}>{{/isListContainer}}{{#isMapContainer}}{{returnType}}<string,string>{{/isMapContainer}}{{/returnContainer}}{{^returnContainer}}{{returnType}}{{/returnContainer}}, Error, void* ){{/returnType}}
	{{^returnType}}void(* handler)(Error, void* ) {{/returnType}}, void* userData, bool isAsync)
{

	//TODO: maybe delete headerList after its used to free up space?
	struct curl_slist *headerList = NULL;

	{{#headerParams}}{{#collectionFormat}}
	for ({{{dataType}}}
	<{{baseType}}>::iterator queryIter = {{paramName}}.begin(); queryIter != {{paramName}}.end(); ++queryIter) {
		string itemAt = stringify(&(*queryIter), "{{baseType}}");
		{{^required}}
		if( itemAt.empty()){
			continue;
		}
		{{/required}}
		itemAt.insert(0, "{{paramName}}: ");
		headerList = curl_slist_append(headerList, itemAt);
	}
	{{/collectionFormat}}{{^collectionFormat}}
	{
		string headerString("{{baseName}}: ");
		headerString.append(stringify(&{{paramName}}, "{{dataType}}"));
		headerList = curl_slist_append(headerList, headerString.c_str());
	}
	{{/collectionFormat}}
	{{/headerParams}}

	string accessHeader = "Authorization: Bearer ";
	accessHeader.append(accessToken);
	headerList = curl_slist_append(headerList, accessHeader.c_str());
	{{#hasConsumes}}
	{{#consumes}}
	headerList = curl_slist_append(headerList, "Content-Type: {{{mediaType}}}");
	{{/consumes}}
	{{/hasConsumes}}
	{{^hasConsumes}}
	headerList = curl_slist_append(headerList, "Content-Type: application/json");
	{{/hasConsumes}}

	map <string, string> queryParams;
	string itemAtq;
	{{#queryParams}}{{#collectionFormat}}
	for ({{{dataType}}}
	<{{baseType}}>::iterator queryIter = {{paramName}}.begin(); queryIter != {{paramName}}.end(); ++queryIter) {
		string itemAt = stringify(&(*queryIter), "{{baseType}}");
		{{^required}}
		if( itemAt.empty()){
			continue;
		}
		{{/required}}
		queryParams.insert(pair<string, string>("{{paramName}}", itemAt));
	}
	{{/collectionFormat}}{{^collectionFormat}}

	itemAtq = stringify(&{{paramName}}, "{{dataType}}");
	queryParams.insert(pair<string, string>("{{baseName}}", itemAtq));
	{{^required}}
	if( itemAtq.empty()==true){
		queryParams.erase("{{baseName}}");
	}
	{{/required}}
	{{/collectionFormat}}
	{{/queryParams}}

	string mBody = "";
	JsonNode* node;
	JsonArray* json_array;
	{{#allParams}}
	{{#isBodyParam}}
	{{#isContainer}}
	//TODO: Map Container
	{{#isListContainer}}
	if (isprimitive("{{baseType}}")) {
		node = converttoJson(&{{paramName}}, "{{baseType}}", "array");
	} {{^isFloat}}{{^isInteger}}{{^isDate}}{{^isLong}}{{^isString}}{{^isDateTime}}{{^isDouble}}{{^isBoolean}}else {
		node = json_node_alloc();
		json_array = json_array_new();
		for ({{{dataType}}}
			<{{baseType}}>::iterator bodyIter = {{paramName}}.begin(); bodyIter != {{paramName}}.end(); ++bodyIter) {
			{{baseType}} itemAt = (*bodyIter);
			char *jsonStr =  itemAt.toJson();
			JsonNode *node_temp = json_from_string(jsonStr, NULL);
			g_free(static_cast<gpointer>(jsonStr));
			json_array_add_element(json_array, node_temp);
		}
		json_node_init_array(node, json_array);
		json_array_unref(json_array);
	}
	{{/isBoolean}}{{/isDouble}}{{/isDateTime}}{{/isString}}{{/isLong}}{{/isDate}}{{/isInteger}}{{/isFloat}}
	{{/isListContainer}}



	{{#isMapContainer}}
	//IF CODE IS HERE, SOMETHING IS SLIGHTLY WRONG!
	if (isprimitive("{{baseType}}")) {
		node = converttoJson(&{{paramName}}, "{{baseType}}", "array");
	} {{^isInteger}}{{^isDate}}{{^isLong}}{{^isBoolean}}{{^isFloat}}{{^isString}}{{^isDateTime}}{{^isDouble}}else {

		node = json_node_alloc();
		json_array = json_array_new();
		for ({{{dataType}}}
			<{{baseType}}>::iterator bodyIter = {{paramName}}.begin(); bodyIter != {{paramName}}.end(); ++bodyIter) {
			{{baseType}} itemAt = (*bodyIter);
			char *jsonStr =  itemAt.toJson();
			JsonNode *node_temp = json_from_string(jsonStr, NULL);
			g_free(static_cast<gpointer>(jsonStr));
			json_array_add_element(json_array, node_temp);
		}
		json_node_init_array(node, json_array);
		json_array_unref(json_array);
	}
	{{/isDouble}}{{/isDateTime}}{{/isString}}{{/isFloat}}{{/isBoolean}}{{/isLong}}{{/isDate}}{{/isInteger}}
	{{/isMapContainer}}
	{{/isContainer}}

	{{^isContainer}}
	if (isprimitive("{{baseType}}")) {
		node = converttoJson(&{{paramName}}, "{{baseType}}", "");
	}
	{{^isPrimitiveType}}{{^isInteger}}{{^isDate}}{{^isLong}}{{^isBoolean}}{{^isFloat}}{{^isString}}{{^isDateTime}}{{^isDouble}}
	char *jsonStr =  {{paramName}}.toJson();
	node = json_from_string(jsonStr, NULL);
	g_free(static_cast<gpointer>(jsonStr));
	{{/isDouble}}{{/isDateTime}}{{/isString}}{{/isFloat}}{{/isBoolean}}{{/isLong}}{{/isDate}}{{/isInteger}}{{/isPrimitiveType}}

	{{/isContainer}}
	char *jsonStr1 =  json_to_string(node, false);
	mBody.append(jsonStr1);
	g_free(static_cast<gpointer>(jsonStr1));
	{{/isBodyParam}}
	{{/allParams}}

	string url("{{{path}}}");
	int pos;

	{{#pathParams}}
	string s_{{paramName}}("{");
	s_{{paramName}}.append("{{{baseName}}}");
	s_{{paramName}}.append("}");
	pos = url.find(s_{{paramName}});
	url.erase(pos, s_{{paramName}}.length());
	url.insert(pos, stringify(&{{paramName}}, "{{dataType}}"));
	{{/pathParams}}

	//TODO: free memory of errormsg, memorystruct
	MemoryStruct_s* p_chunk = new MemoryStruct_s();
	long code;
	char* errormsg = NULL;
	string myhttpmethod("{{httpMethod}}");

	if(strcmp("PUT", "{{httpMethod}}") == 0){
		if(strcmp("", mBody.c_str()) == 0){
			mBody.append("{}");
		}
	}

	if(!isAsync){
		NetClient::easycurl({{classname}}::getBasePath(), url, myhttpmethod, queryParams,
			mBody, headerList, p_chunk, &code, errormsg);
		bool retval = {{nickname}}Processor(*p_chunk, code, errormsg, userData,reinterpret_cast<void(*)()>(handler));

		curl_slist_free_all(headerList);
		if (p_chunk) {
			if(p_chunk->memory) {
				free(p_chunk->memory);
			}
			delete (p_chunk);
		}
		if (errormsg) {
			free(errormsg);
		}
		return retval;
	} else{
		GThread *thread = NULL;
		RequestInfo *requestInfo = NULL;

		requestInfo = new(nothrow) RequestInfo ({{classname}}::getBasePath(), url, myhttpmethod, queryParams,
			mBody, headerList, p_chunk, &code, errormsg, userData, reinterpret_cast<void(*)()>(handler), {{nickname}}Processor);;
		if(requestInfo == NULL)
			return false;

		thread = g_thread_new(NULL, __{{classname}}threadFunc, static_cast<gpointer>(requestInfo));
		return true;
	}
}




bool {{classname}}::{{nickname}}Async(char * accessToken,
	{{#allParams}}{{#isContainer}}{{{dataType}}}<{{baseType}}> {{paramName}}{{/isContainer}}{{^isContainer}}{{{dataType}}} {{paramName}}{{/isContainer}}{{#hasMore}}, {{/hasMore}}{{/allParams}}{{#hasParams}}, {{/hasParams}}
	{{#returnType}}void(* handler)({{#returnContainer}}{{#isListContainer}}{{returnType}}<{{returnBaseType}}>{{/isListContainer}}{{#isMapContainer}}{{returnType}}<string,string>{{/isMapContainer}}{{/returnContainer}}{{^returnContainer}}{{returnType}}{{/returnContainer}}, Error, void* ){{/returnType}}
	{{^returnType}}void(* handler)(Error, void* ) {{/returnType}}, void* userData)
{
	return {{nickname}}Helper(accessToken,
	{{#allParams}}{{#isContainer}}{{paramName}}{{/isContainer}}{{^isContainer}}{{paramName}}{{/isContainer}}{{#hasMore}}, {{/hasMore}}{{/allParams}}{{#hasParams}}, {{/hasParams}}
	handler, userData, true);
}

bool {{classname}}::{{nickname}}Sync(char * accessToken,
	{{#allParams}}{{#isContainer}}{{{dataType}}}<{{baseType}}> {{paramName}}{{/isContainer}}{{^isContainer}}{{{dataType}}} {{paramName}}{{/isContainer}}{{#hasMore}}, {{/hasMore}}{{/allParams}}{{#hasParams}}, {{/hasParams}}
	{{#returnType}}void(* handler)({{#returnContainer}}{{#isListContainer}}{{returnType}}<{{returnBaseType}}>{{/isListContainer}}{{#isMapContainer}}{{returnType}}<string,string>{{/isMapContainer}}{{/returnContainer}}{{^returnContainer}}{{returnType}}{{/returnContainer}}, Error, void* ){{/returnType}}
	{{^returnType}}void(* handler)(Error, void* ) {{/returnType}}, void* userData)
{
	return {{nickname}}Helper(accessToken,
	{{#allParams}}{{#isContainer}}{{paramName}}{{/isContainer}}{{^isContainer}}{{paramName}}{{/isContainer}}{{#hasMore}}, {{/hasMore}}{{/allParams}}{{#hasParams}}, {{/hasParams}}
	handler, userData, false);
}

{{/operation}}
{{/operations}}
