
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fstream>
#include <list>
#include <string>

#include "json.h"

/***********************************************************************
 ***********************************************************************
 *
 * JSONParse
 *
 ***********************************************************************
 ***********************************************************************
 */

enum {
	STEP_NONE = 0,
	STEP_STARTNAME,
	STEP_NAME,
	STEP_STARTVALUE,
	STEP_VALUE,
	STEP_END
};


/*
 * clear out all data
 */
void JSONParse::Clear() {
	jsondata = "";
	names.clear();
}

/*
 * read every element and keep only this in memory.
 */
int JSONParse::Set(string json) {
	int i;
	int step;
	int level;
	bool ignorenext;

	JSONElement jelement;

	Clear();

	// find start and read until end
	for (step = STEP_NONE, i = 0, ignorenext = false; (unsigned int)i < json.length(); i++) {
		// need to copy next character
		if (ignorenext) {
			ignorenext = false;
			if (step == STEP_NAME)  jelement.name += json[i];
			if (step == STEP_VALUE) jelement.value += json[i];
		}

		// searching for startname
		else if (step == STEP_NONE) {
			if (json[i] == '{') {
				step = STEP_STARTNAME;
				continue;
			}
		}

		// searching for startname
		else if (step == STEP_STARTNAME) {
			if (json[i] == '"') {
				step = STEP_NAME;
				continue;
			}
		}

		// copy name
		else if (step == STEP_NAME) {
			if (json[i] == '"') {
				step = STEP_STARTVALUE;
				continue;
			}
			else {
				jelement.name += json[i];
				continue;
			}
		}

		// searching for startvalue
		else if (step == STEP_STARTVALUE) {
			if (json[i] == '"') {
				step = STEP_VALUE;
				jelement.type = JSON_T_STRING;
				continue;
			}
			if (json[i] == '{') {
				step = STEP_VALUE;
				level = 0;
				jelement.type = JSON_T_OBJECT;
				jelement.value = "{";
				continue;
			}
			if (json[i] == '[') {
				step = STEP_VALUE;
				level = 0;
				jelement.type = JSON_T_ARRAY;
				jelement.value = "[";
				continue;
			}
			if ((json[i] >= '0' && json[i] <= '9') ||
				(json[i] == '+' || json[i] == '-')) {
				step = STEP_VALUE;
				level = 0;
				jelement.type = JSON_T_NUMBER;
				jelement.value = json[i];
				continue;
			}
		}

		// copy value
		else if (step == STEP_VALUE) {
			if (jelement.type == JSON_T_STRING) {
				if (json[i] == '"') step = STEP_END;
				else jelement.value += json[i];
				continue;
			}
			else if (jelement.type == JSON_T_OBJECT) {
				if (json[i] == '}' && level == 0) {
					jelement.value += json[i];
					step = STEP_END;
				}
				else {
					if (json[i] == '{') level++;	// increase level
					if (json[i] == '}') level--;	// decrease level
					jelement.value += json[i];
				}
				continue;
			}
			else if (jelement.type == JSON_T_ARRAY) {
				if (json[i] == ']' && level == 0) {
					jelement.value += json[i];
					step = STEP_END;
				}
				else {
					if (json[i] == '[') level++;	// increase level
					if (json[i] == ']') level--;	// decrease level
					jelement.value += json[i];
				}
				continue;
			}
			else if (jelement.type == JSON_T_NUMBER) {
				if ((json[i] < '0' || json[i] > '9') && json[i] != '.' &&
					json[i] != '+' && json[i] != 'e' && json[i] != 'E') step = STEP_END;
				else {
					jelement.value += json[i];
					continue;
				}
			}
		}

		// another element?
		if (step == STEP_END) {
			if (json[i] == ',') {
				if (jelement.type != JSON_T_NONE) {
					names.push_back (jelement);
				}
				jelement.Clear();
				step = STEP_STARTNAME;
			}
			continue;
		}
	}
	if (jelement.type != JSON_T_NONE) {
		names.push_back (jelement);
	}

	return 0;
};


int JSONParse::GetValue(string varname, string *dest) {
	list<JSONElement>::iterator iter;

	if (dest == NULL) return 0;
	*dest = "";

	for (iter = names.begin(); iter != names.end(); iter++) {
		if (varname.compare(iter->name) == 0) {
			*dest = iter->value;
			return 1;
		}
	}

	return 0;
};


int JSONParse::GetValueInt(string varname, int *dest) {
	string s;
	int res = GetValue(varname, &s);
	if (res) {
		*dest = atoi (s.c_str());
		return 1;
	}
	return 0;
};


int JSONParse::GetValueDouble(string varname, double *dest) {
	string s;
	int res = GetValue(varname, &s);
	if (res) {
		*dest = atof (s.c_str());
		return 1;
	}
	return 0;
};


int JSONParse::GetValueInt64(string varname, int64_t *dest) {
	string s;
	int res = GetValue(varname, &s);
	if (res) {
		*dest = atol (s.c_str());
		return 1;
	}
	return 0;
};


int JSONParse::GetObjectJson(string varname, JSONParse *dest) {
	list<JSONElement>::iterator iter;

	if (dest == NULL) return 0;

	for (iter = names.begin(); iter != names.end(); iter++) {
		if (varname.compare(iter->name) == 0) {
			dest->Set(iter->value);
			return 1;
		}
	}

	return 0;
};


#define MAXRECURSIVE 255
int JSONParse::GetIdx(string src, int idx, string *dest) {
	char recursive[MAXRECURSIVE];
	int i = 0, rcnt = 0, cnt = 0;

	(*dest) = "";

	for (i = 0; i < MAXRECURSIVE; i++) recursive[i] = 0;
	for (i = 0; (unsigned int) i < src.length() && rcnt < MAXRECURSIVE && cnt <= idx; i++) {
		if (src[i] == '[') {
			recursive[rcnt++] = src[i];
			continue;
		}
		else if (src[i] == '{' && recursive[rcnt] != '"') recursive[++rcnt] = src[i];
		else if (src[i] == '}' && recursive[rcnt] == '{') rcnt--;
		else if (src[i] == '"' && recursive[rcnt] == '"') rcnt--;
		else if (src[i] == '"') recursive[++rcnt] = src[i];
		else if (src[i] == ',' && rcnt == 1) {
			cnt++;
			continue;
		}
		else if (src[i] == ']' && rcnt == 1) {
			continue;
		}

		if (rcnt > 0 && cnt == idx) {
			(*dest) += src[i];
			if (src[i] == '\\') (*dest) += src[i];
		}
		else {
			if (src[i] == '\\')i++;
		}
	}

	//
	// final checks
	if (cnt == 0 && idx == 0 && 			// empty source/array?
			dest->size() == 0) return 0;	//
	if (cnt >= idx) return 1;				// found the right element
	return 0;								// element not found
}
#undef MAXRECURSIVE

int JSONParse::GetValueIdx(string varname, int idx, string *dest) {
	list<JSONElement>::iterator iter;

	if (dest == NULL) return 0;

	for (iter = names.begin(); iter != names.end(); iter++) {
		if (varname.compare(iter->name) == 0) {
			return GetIdx(iter->value, idx, dest);
		}
	}

	return 0;
};

int JSONParse::GetObjectIdx(string varname, int idx, JSONParse *dest) {
	list<JSONElement>::iterator iter;
	string deststr;
	int ret = 0;

	if (dest == NULL) return 0;

	for (iter = names.begin(); iter != names.end(); iter++) {
		if (varname.compare(iter->name) == 0) {
			ret = GetIdx(iter->value, idx, &deststr);
			if (ret == 1) dest->Set(deststr);
			return ret;
		}
	}

	return 0;
};

list<JSONElement> JSONParse::GetElements() {
	list<JSONElement> l;
	list<JSONElement>::iterator iter;

	l.clear();
	for (iter = names.begin(); iter != names.end(); iter++) {
		l.push_back(*iter);
	}

	return l;
};


void JSONParse::AddObject (JSONElement element) {
	names.push_back (element);
};


void JSONParse::AddObject (string name, JSONParse jp) {
	JSONElement je;
	je.SetObject(name, jp.ToString());
	names.push_back(je);
};


void JSONParse::AddObject (string name, int val) {
	JSONElement je;
	je.Set(name, val);
	names.push_back(je);
};


void JSONParse::AddObject (string name, int64_t val) {
	JSONElement je;
	je.Set(name, val);
	names.push_back(je);
};


void JSONParse::AddObject (string name, string val) {
	JSONElement je;
	je.Set(name, val);
	names.push_back(je);
};


void JSONParse::AddObject (string name, double val) {
	JSONElement je;
	je.Set(name, val);
	names.push_back(je);
};


string JSONParse::ToString() {
	list<JSONElement>::iterator iter;
	string output;
	int level, i;

	output = "{";

	for (level = 1, iter = names.begin(); iter != names.end(); iter++) {
		if (iter != names.begin()) output += ",";
		output += "\n";
		for (i = 0; i < 4*level; i++) output += " ";
		output += iter->GetString();
	}

	output += "\n}\n";

	return output;
};



/*
 * Load/Save elements to a file.
 * Return Value: -1 .. on Error, errno will be set
 *                0 .. on Success
 */
int JSONParse::LoadFromFile(string filename) {
	int fd;
	struct stat fs;
	char *buffer;

	if (stat(filename.c_str(), &fs) != 0) return -1;
	buffer = (char *) malloc (fs.st_size+1);
	memset (buffer, 0x0, fs.st_size+1);

	fd = open(filename.c_str(), O_RDONLY);
	if (fd < 0) return -1;
	read (fd, buffer, fs.st_size);
	close (fd);

	Set(buffer);
	free (buffer);

	return 0;
};

int JSONParse::SaveToFile(string filename) {
	ofstream out(filename);

	if (!out) return -1;

	out << ToString();
	out.close();

	return 0;
};




/***********************************************************************
 ***********************************************************************
 *
 * JSONElement
 *
 ***********************************************************************
 ***********************************************************************
 */


void JSONElement::Set (string n, double v) {
	name = n;
	value = to_string(v);
	type = JSON_T_NUMBER;
};


void JSONElement::Set (string n, int v) {
	name = n;
	value = to_string(v);
	type = JSON_T_NUMBER;
};


void JSONElement::Set (string n, int64_t v) {
	name = n;
	value = to_string(v);
	type = JSON_T_NUMBER;
};


void JSONElement::Set (string n, string v) {
	name = n;
	value = v;
	type = JSON_T_STRING;
};


void JSONElement::SetArray (string n, list<JSONElement> *l) {
	list<JSONElement>::iterator iter;

	name = n;
	value = "[";
	type = JSON_T_STRING;

	for (iter = l->begin(); iter != l->end(); iter++) {
		if (iter != l->begin()) value += ",";
		value += iter->GetString();
	}
	value += "]";
};


void JSONElement::SetObject (string n, string s) {
	name = n;
	value = s;
	type = JSON_T_OBJECT;
};


string JSONElement::GetString () {
	string output = "";
	string filename = __FILE__;

	switch (type) {
	case(JSON_T_NUMBER):
				output += "\"" + name + "\" : " + value;
				break;
	case(JSON_T_STRING):
				if (value.length()==0) {
					output += "\"" + name + "\" : \"\"";
				}
				else if (value[0] != '"') {
					output += "\"" + name + "\" : \"" + value + "\"";
				}
				else output += "\"" + name + "\" : " + value;
				break;
	case(JSON_T_OBJECT):
				output += "\"" + name + "\" : " + value;
				break;
	case(JSON_T_ARRAY):
				if (value.length()==0) {
					output += "\"" + name + "\" : []";
				}
				else if (value[0] != '[') {
					output += "\"" + name + "\" : [" + value + "]";
				}
				else output += "\"" + name + "\" : " + value;
				break;
	default:
		output += "\"error\" : \""+ filename + ":" + to_string(__LINE__) + " JSONElement unknown type error\"("+to_string(type)+")";
		break;
	}

	return output;
};
