#include "../../lib/srdb1/db.h"
#include "ul_db.h"
#include "p_usrloc_mod.h"
#include "ul_db_layer.h"
#include "ul_db_api.h"

typedef struct res_list
{
	db1_con_t **h;
	db1_res_t *r;
	struct res_list *next;
} res_list_t;

static void add_res(db1_res_t *_r, db1_con_t **_h);
static db1_con_t **get_handle_by_res(db1_res_t *_r);
static void drop_res(db1_res_t *_r);

static ul_db_api_t p_ul_dbf;
static db_func_t dbf;

static res_list_t *used = NULL;
static res_list_t *unused = NULL;

static ul_domain_db_list_t *domain_db_list = NULL;

int ul_db_layer_init(void)
{
	if(bind_ul_db(&p_ul_dbf) < 0) {
		LM_ERR("could not bind ul_db_api.\n");
		return -1;
	}
	if(db_bind_mod(&default_db_url, &dbf) < 0) {
		LM_ERR("could not bind db.\n");
		return -1;
	}
	return 0;
}

void ul_db_layer_destroy(void)
{
	res_list_t *tmp, *del;
	tmp = used;
	while(tmp) {
		del = tmp;
		tmp = tmp->next;
		pkg_free(del);
	}
	tmp = unused;
	while(tmp) {
		del = tmp;
		tmp = tmp->next;
		pkg_free(del);
	}
	return;
}

int ul_db_layer_single_connect(udomain_t *domain, str *url)
{
	if((domain->dbh = dbf.init(url)) == NULL) {
		return -1;
	}
	return 1;
}

int ul_db_layer_insert(udomain_t *domain, str *user, str *sipdomain,
		db_key_t *_k, db_val_t *_v, int _n)
{
	ul_domain_db_t *d;
	switch(domain->dbt) {
		case DB_TYPE_CLUSTER:
			return p_ul_dbf.insert(domain->name, user, sipdomain, _k, _v, _n);
		case DB_TYPE_SINGLE:
			if(!domain->dbh) {
				if((d = ul_find_domain(domain->name->s)) == 0) {
					return -1;
				}
				if(ul_db_layer_single_connect(domain, &d->url) < 0) {
					return -1;
				}
			}
			if(dbf.use_table(domain->dbh, domain->name) < 0) {
				return -1;
			}
			return dbf.insert(domain->dbh, _k, _v, _n);
		default:
			return -1;
	}
}

int ul_db_layer_update(udomain_t *domain, str *user, str *sipdomain,
		db_key_t *_k, db_op_t *_o, db_val_t *_v, db_key_t *_uk, db_val_t *_uv,
		int _n, int _un)
{
	ul_domain_db_t *d;
	switch(domain->dbt) {
		case DB_TYPE_CLUSTER:
			return p_ul_dbf.update(domain->name, user, sipdomain, _k, _o, _v,
					_uk, _uv, _n, _un);
		case DB_TYPE_SINGLE:
			if(!domain->dbh) {
				if((d = ul_find_domain(domain->name->s)) == 0) {
					return -1;
				}
				if(ul_db_layer_single_connect(domain, &d->url) < 0) {
					return -1;
				}
			}
			if(dbf.use_table(domain->dbh, domain->name) < 0) {
				return -1;
			}
			return dbf.update(domain->dbh, _k, _o, _v, _uk, _uv, _n, _un);
		default:
			return -1;
	}
}

int ul_db_layer_replace(udomain_t *domain, str *user, str *sipdomain,
		db_key_t *_k, db_val_t *_v, int _n, int _un)
{
	ul_domain_db_t *d;
	switch(domain->dbt) {
		case DB_TYPE_CLUSTER: {
			return p_ul_dbf.replace(
					domain->name, user, sipdomain, _k, _v, _n, _un);
			;
		}
		case DB_TYPE_SINGLE:
			if(!domain->dbh) {
				if((d = ul_find_domain(domain->name->s)) == 0) {
					return -1;
				}
				if(ul_db_layer_single_connect(domain, &d->url) < 0) {
					return -1;
				}
			}
			if(dbf.use_table(domain->dbh, domain->name) < 0) {
				return -1;
			}
			return dbf.replace(domain->dbh, _k, _v, _n, _un, 0);
		default:
			return -1;
	}
}

int ul_db_layer_delete(udomain_t *domain, str *user, str *sipdomain,
		db_key_t *_k, db_op_t *_o, db_val_t *_v, int _n)
{
	ul_domain_db_t *d;
	switch(domain->dbt) {
		case DB_TYPE_CLUSTER:
			return p_ul_dbf.delete(
					domain->name, user, sipdomain, _k, _o, _v, _n);
		case DB_TYPE_SINGLE:
			if(!domain->dbh) {
				if((d = ul_find_domain(domain->name->s)) == 0) {
					return -1;
				}
				if(ul_db_layer_single_connect(domain, &d->url) < 0) {
					return -1;
				}
			}
			if(dbf.use_table(domain->dbh, domain->name) < 0) {
				return -1;
			}
			return dbf.delete(domain->dbh, _k, _o, _v, _n);
		default:
			return -1;
	}
}

int ul_db_layer_query(udomain_t *domain, str *user, str *sipdomain,
		db_key_t *_k, db_op_t *_op, db_val_t *_v, db_key_t *_c, int _n, int _nc,
		db_key_t _o, db1_res_t **_r)
{
	ul_domain_db_t *d;
	db1_con_t **h;
	int ret;
	switch(domain->dbt) {
		case DB_TYPE_CLUSTER: {
			if((ret = p_ul_dbf.query(domain->name, user, sipdomain, &h, _k, _op,
						_v, _c, _n, _nc, _o, _r))
							< 0
					|| (!_r)) {
				return -1;
			}
			add_res(*_r, h);
			return ret;
		}
		case DB_TYPE_SINGLE:
			if(!domain->dbh) {
				if((d = ul_find_domain(domain->name->s)) == 0) {
					return -1;
				}
				if(ul_db_layer_single_connect(domain, &d->url) < 0) {
					return -1;
				}
			}
			if(dbf.use_table(domain->dbh, domain->name) < 0) {
				return -1;
			}
			return dbf.query(domain->dbh, _k, _op, _v, _c, _n, _nc, _o, _r);
		default:
			return -1;
	}
}

int ul_db_layer_raw_query(udomain_t *domain, str *_s, db1_res_t **_r)
{
	ul_domain_db_t *d;
	switch(domain->dbt) {
		case DB_TYPE_CLUSTER: {
			return -1; // not useful in this mode
		}
		case DB_TYPE_SINGLE:
			if(!domain->dbh) {
				if((d = ul_find_domain(domain->name->s)) == 0) {
					return -1;
				}
				if(ul_db_layer_single_connect(domain, &d->url) < 0) {
					return -1;
				}
			}
			if(dbf.use_table(domain->dbh, domain->name) < 0) {
				return -1;
			}
			return dbf.raw_query(domain->dbh, _s, _r);
		default:
			return -1;
	}
}

int ul_db_layer_free_result(udomain_t *domain, db1_res_t *res)
{
	db1_con_t **h;
	int ret;
	switch(domain->dbt) {
		case DB_TYPE_CLUSTER:
			if((h = get_handle_by_res(res)) == NULL) {
				return -1;
			}
			ret = p_ul_dbf.free_result(h, res);
			drop_res(res);
			return ret;
		case DB_TYPE_SINGLE:
			return dbf.free_result(domain->dbh, res);
		default:
			return -1;
	}
}

int ul_add_domain_db(str *d, int t, str *url)
{
	ul_domain_db_list_t *new_d = NULL;
	LM_DBG("%.*s, type: %s\n", d->len, d->s,
			t == DB_TYPE_SINGLE ? "SINGLE" : "CLUSTER");
	if((new_d = pkg_malloc(sizeof(ul_domain_db_list_t))) == NULL) {
		return -1;
	}
	memset(new_d, 0, sizeof(ul_domain_db_list_t));

	if(!d || !d->s)
		goto error;

	if((new_d->domain.name.s = pkg_malloc(d->len + 1)) == NULL) {
		goto error;
	}

	if(t == DB_TYPE_SINGLE) {
		if(url) {
			LM_DBG("url: %.*s", url->len, url->s);
			if((new_d->domain.url.s = pkg_malloc(url->len + 1)) == NULL) {
				goto error;
			}

			strncpy(new_d->domain.url.s, url->s, url->len);
			new_d->domain.url.s[url->len] = '\0';
			new_d->domain.url.len = url->len;
		} else {
			if((new_d->domain.url.s = pkg_malloc(default_db_url.len + 1))
					== NULL) {
				goto error;
			}
			strcpy(new_d->domain.url.s, default_db_url.s);
			new_d->domain.url.len = default_db_url.len;
		}
	}
	strncpy(new_d->domain.name.s, d->s, d->len);
	new_d->domain.name.len = d->len;
	new_d->domain.dbt = t;
	new_d->next = domain_db_list;
	domain_db_list = new_d;
	return 1;
error:
	pkg_free(new_d);
	return -1;
}

ul_domain_db_t *ul_find_domain(const char *s)
{
	ul_domain_db_list_t *tmp;
	str d;
	if(!domain_db_list) {
		if(parse_domain_db(&domain_db) < 0) {
			LM_ERR("could not parse domain parameter.\n");
			return NULL;
		}
	}
	tmp = domain_db_list;
	while(tmp) {
		LM_DBG("searched domain: %s, actual domain: %.*s, length: %i, type: "
			   "%s\n",
				s, tmp->domain.name.len, tmp->domain.name.s,
				tmp->domain.name.len,
				tmp->domain.dbt == DB_TYPE_SINGLE ? "SINGLE" : "CLUSTER");
		if((strlen(s) == tmp->domain.name.len)
				&& (memcmp(s, tmp->domain.name.s, tmp->domain.name.len) == 0)) {
			return &tmp->domain;
		}
		tmp = tmp->next;
	}
	if((d.s = pkg_malloc(strlen(s) + 1)) == NULL) {
		return NULL;
	}
	strcpy(d.s, s);
	d.len = strlen(s);
	if(ul_add_domain_db(&d, default_dbt, &default_db_url)) {
		pkg_free(d.s);
		return ul_find_domain(s);
	}
	pkg_free(d.s);
	return NULL;
}

void free_all_udomains(void)
{
	ul_domain_db_list_t *tmp, *del;
	tmp = domain_db_list;
	while(tmp) {
		del = tmp;
		tmp = tmp->next;
		pkg_free(del->domain.name.s);
		if(del->domain.dbt == DB_TYPE_SINGLE) {
			pkg_free(del->domain.url.s);
		}
		pkg_free(del);
	}
	return;
}

int parse_domain_db(str *_d)
{
#define STATE_START 0
#define STATE_DOMAIN 1
#define STATE_TYPE 2
#define STATE_URL 3
	char *domains;
	char *end;
	str d = {NULL, 0}, u = {NULL, 0}, t = {NULL, 0};
	int type = 0;
	int state;

	if(_d->len == 0) {
		return -1;
	}
	domains = _d->s;
	end = domains + _d->len;

	state = STATE_START;
	while(domains <= end) {
		switch(*domains) {
			case '=':
				switch(state) {
					case STATE_START:
						return -1;
					case STATE_DOMAIN:
						LM_DBG("found domain %.*s\n", d.len, d.s);
						state = STATE_TYPE;
						t.s = domains + 1;
						t.len = 0;
						break;
					case STATE_TYPE:
						return -1;
					case STATE_URL:
						return -1;
					default:
						return -1;
				}
				break;
			case ';':
				switch(state) {
					case STATE_START:
						return 1;
					case STATE_DOMAIN:
						return -1;
					case STATE_TYPE:
						LM_DBG("found type %.*s\n", t.len, t.s);
						if(strncmp(t.s, DB_TYPE_CLUSTER_STR,
								   strlen(DB_TYPE_CLUSTER_STR))
								== 0) {
							type = DB_TYPE_CLUSTER;
						} else {
							type = DB_TYPE_SINGLE;
						}
						state = STATE_URL;
						u.s = domains + 1;
						u.len = 0;
						break;
					case STATE_URL:
						return -1;
						break;
					default:
						break;
				}
				break;
			case ',':
				switch(state) {
					case STATE_START:
						return -1;
					case STATE_DOMAIN:
						return -1;
					case STATE_TYPE:
						LM_DBG("found type %.*s\n", t.len, t.s);
						if(strncmp(t.s, DB_TYPE_CLUSTER_STR,
								   strlen(DB_TYPE_CLUSTER_STR))
								== 0) {
							type = DB_TYPE_CLUSTER;
						} else {
							type = DB_TYPE_SINGLE;
						}
						ul_add_domain_db(&d, type, NULL);
						state = STATE_START;
						break;
					case STATE_URL:
						LM_DBG("found url %.*s\n", u.len, u.s);
						state = STATE_START;
						ul_add_domain_db(&d, type, &u);
						break;
					default:
						return -1;
				}
				break;
			case '\0':
				switch(state) {
					case STATE_START:
						return 1;
					case STATE_DOMAIN:
						return -1;
					case STATE_TYPE:
						LM_DBG("found type %.*s\n", t.len, t.s);
						if(strncmp(t.s, DB_TYPE_CLUSTER_STR,
								   strlen(DB_TYPE_CLUSTER_STR))
								== 0) {
							type = DB_TYPE_CLUSTER;
						} else {
							type = DB_TYPE_SINGLE;
						}
						ul_add_domain_db(&d, type, NULL);
						return 1;
					case STATE_URL:
						LM_DBG("found url %.*s\n", u.len, u.s);
						ul_add_domain_db(&d, type, &u);
						return 1;
					default:
						return -1;
				}
				break;
			default:
				switch(state) {
					case STATE_START:
						state = STATE_DOMAIN;
						d.s = domains;
						d.len = 1;
						break;
					case STATE_DOMAIN:
						d.len++;
						break;
					case STATE_TYPE:
						t.len++;
						break;
					case STATE_URL:
						u.len++;
						break;
					default:
						break;
				}
				break;
		}
		domains++;
	}
	return 1;
}

static void add_res(db1_res_t *_r, db1_con_t **_h)
{
	res_list_t *new_res;
	if(!unused) {
		if((new_res = pkg_malloc(sizeof(res_list_t))) == NULL) {
			return;
		}
		memset(new_res, 0, sizeof(res_list_t));
	} else {
		new_res = unused;
		unused = unused->next;
	}
	new_res->h = _h;
	new_res->r = _r;
	new_res->next = used;
	used = new_res;
	return;
}

static db1_con_t **get_handle_by_res(db1_res_t *_r)
{
	res_list_t *tmp = used;
	while(tmp) {
		if(_r == tmp->r) {
			return tmp->h;
		}
		tmp = tmp->next;
	}
	return NULL;
}

static void drop_res(db1_res_t *_r)
{
	res_list_t *prev = NULL;
	res_list_t *tmp;
	tmp = used;
	while(tmp) {
		if(_r == tmp->r) {
			if(prev == NULL) {
				used = tmp->next;
			} else {
				prev->next = tmp->next;
			}
			tmp->next = unused;
			unused = tmp;
			return;
		}
		prev = tmp;
		tmp = tmp->next;
	}
	return;
}
