#include "api.h"
#include "buffer.h"
#include "my_global.h"
#include "mysql.h"
#include <stdio.h>
#include <string.h>

#define MY_TYPE		'MY'

#pragma warning(disable: 4244)

/////////////////////////////////////////////////////////////////////

class mySQL {
public:
	mySQL() : res(0), mysql(0) { }

	~mySQL() { if (mysql) mysql_close(mysql); if(res) mysql_free_result(res); }

	MYSQL *mysql;
	MYSQL_RES *res;
};

void my_destroy(void* ptr)
{
    delete (mySQL*)ptr;
    ptr = 0;
}

/////////////////////////////////////////////////////////////////////

void* my_create(void *ctx, int nargs, void** args)
{
	mySQL *o = new mySQL;
	o->mysql = mysql_init(0);
	if (!o->mysql) api_runtime_error(ctx, "failed to initialize MySQL client");
	return api_create_user(ctx, o, 0, my_destroy, MY_TYPE);
}

void* my_version(void *ctx, int nargs, void** args)
{
	if (nargs < 1) api_input_error(ctx);
	mySQL *o = (mySQL*)api_get_user(ctx, args[0], MY_TYPE);
	return api_create_string(ctx, mysql_get_client_info());
}

void* my_conn(void *ctx, int nargs, void** args)
{
	if (nargs < 4) api_input_error(ctx);
	mySQL *o = (mySQL*)api_get_user(ctx, args[0], MY_TYPE);
	const char *dbname = 0;
	if (nargs > 4) dbname = api_get_string(ctx, args[4]);
	if (!mysql_real_connect(
			o->mysql,
			api_get_string(ctx, args[1]),	// host
			api_get_string(ctx, args[2]),	// user
			api_get_string(ctx, args[3]),	// password
			dbname,
			0, 0, 0)) {
		return api_create_string(ctx, mysql_error(o->mysql));
	}
	return 0;
}

void* my_query(void *ctx, int nargs, void** args)
{
	if (nargs < 2) api_input_error(ctx);
	mySQL *o = (mySQL*)api_get_user(ctx, args[0], MY_TYPE);
	if (o->res) {
		mysql_free_result(o->res);
		o->res = 0;
	}
	if (mysql_query(o->mysql, api_get_string(ctx, args[1])) == 0) {
		o->res = mysql_store_result(o->mysql);
		int rows = 0;
		if (o->res) rows = mysql_num_rows(o->res);
		return api_create_integer(ctx, rows);
	}
	return api_create_string(ctx, mysql_error(o->mysql));
}

void* my_fetch(void *ctx, int nargs, void** args)
{
	if (nargs < 1) api_input_error(ctx);
	mySQL *o = (mySQL*)api_get_user(ctx, args[0], MY_TYPE);
	if (!o->res) return 0;
	MYSQL_ROW row = mysql_fetch_row(o->res);
	if (!row) return 0;
	int ncol = mysql_num_fields(o->res);
	if (ncol > 1) {
		void *arr = api_create_array(ctx, ncol);
		for (int i = 0; i < ncol; i++) {
			api_set_array_object2(ctx, arr, i, row[i] ? api_create_string(0, row[i]) : api_create_string(0, "NULL"));
		}
		return arr;
	}
	else if (ncol == 1) {
		return api_create_string(ctx, row[0] ? row[0] : "NULL");
	}
	return 0;
}

double cal2jul(const char *s)
{
	char c[5];
	strncpy(c, s, 4);
	c[4] = 0;
	int yy = atoi(c);
	strncpy(c, s+5, 2);
	c[2] = 0;
	int mm = atoi(c);
	strncpy(c, s+8, 2);
	int dd = atoi(c);
	yy -= mm < 3;
	mm += 12 * (mm < 3);
	int a = 365.25 * (yy + 4716),
		b = 30.6001 * (mm + 1);
	double d = a + b + dd - 2416556;
	if (strlen(s) > 18) {
		strncpy(c, s+11, 2);
		d += atof(c)/24.0;
		strncpy(c, s+14, 2);
		d += atof(c)/1440.0;
		strncpy(c, s+17, 2);
		d += atof(c)/86400.0;
	}
	return d;
}

void* my_data(void *ctx, int nargs, void** args)
{
	if (nargs < 3) api_input_error(ctx);
	mySQL *o = (mySQL*)api_get_user(ctx, args[0], MY_TYPE);
	double *d = (double*)api_get_ptr(ctx, args[1]);
	int nd = api_get_integer(ctx, args[2]);
	bool flag = false;
	if (nargs > 3) flag = api_get_integer(ctx, args[3])!=0;
	if (!o->res) return api_create_integer(ctx, 0);
	MYSQL_ROW row = mysql_fetch_row(o->res);
	if (!row) return api_create_integer(ctx, 0);
	int ncol = mysql_num_fields(o->res);
	if (ncol <= 0) return api_create_integer(ctx, 0);
	int k = 0;
	while (row) {
		if (ncol > 1) {
			for (int i = 0; i < ncol; i++) {
				if (k >= nd) return api_create_integer(ctx, k);
				if (flag && i==0) {
					d[k++] = cal2jul(row[0]);
				}
				else {
					if (row[i])
						d[k++] = atof(row[i]);
					else
						d[k++] = -1.e30;
				}
			}
		}
		else {
			if (k >= nd) return api_create_integer(ctx, k);
			if (flag) {
				d[k++] = cal2jul(row[0]);
			}
			else {
				if (row[0])
					d[k++] = atof(row[0]);
				else
					d[k++] = -1.e30;
			}
		}
		row = mysql_fetch_row(o->res);
	}
	return api_create_integer(ctx, k);;
}

void* my_print(void *ctx, int nargs, void** args)
{
	if (nargs < 1) api_input_error(ctx);
	mySQL *o = (mySQL*)api_get_user(ctx, args[0], MY_TYPE);
	if (!o->res) return 0;
	MYSQL_ROW row = mysql_fetch_row(o->res);
	if (!row) return 0;
	int ncol = mysql_num_fields(o->res);
	while (row) {
		for (int i = 0; i < ncol; i++) {
			if (i == ncol-1)
				fprintf(stdout, "%s\n", row[i] ? row[i] : "NULL");
			else
				fprintf(stdout, "%s, ", row[i] ? row[i] : "NULL");
		}
		row = mysql_fetch_row(o->res);
	}
	return 0;
}

void* my_escape(void *ctx, int nargs, void** args)
{
	if (nargs < 2) api_input_error(ctx);
	mySQL *o = (mySQL*)api_get_user(ctx, args[0], MY_TYPE);
	if (nargs == 2) {
		const char *s = api_get_string(ctx, args[1]);
		int n = strlen(s);
		zsBuffer buf(2*n+2);
		mysql_real_escape_string(o->mysql, buf.u.pchar, s, n);
		return api_create_string(ctx, buf.u.pchar);
	}
	void *ptr = api_get_ptr(ctx, args[1]);
	int n = api_get_integer(ctx, args[2]);
	zsBuffer buf(2*n+2);
	mysql_real_escape_string(o->mysql, buf.u.pchar, (const char*)ptr, n);
	return api_create_string(ctx, buf.u.pchar);
}

/////////////////////////////////////////////////////////////////////

class zsRegPrimitive
{
public:
	zsRegPrimitive()
	{
		api_add_primitive("mysql",		0,			my_create);
		api_add_primitive("version",	MY_TYPE,	my_version);
		api_add_primitive("connect",	MY_TYPE,	my_conn);
		api_add_primitive("query",		MY_TYPE,	my_query);
		api_add_primitive("fetch",		MY_TYPE,	my_fetch);
		api_add_primitive("data",		MY_TYPE,	my_data);
		api_add_primitive("print",		MY_TYPE,	my_print);
		api_add_primitive("escate",		MY_TYPE,	my_escape);
	}

	~zsRegPrimitive() { }
};

zsRegPrimitive my_register;