#include "../../lib/srdb1/db.h"
#include "usrloc.h"
#include "usrloc_db.h"
#include "bin_utils.h"
#include "udomain.h"
#include "math.h"
#include "subscribe.h"
#include "../../lib/ims/useful_defs.h"
#include "../../core/parser/parse_param.h"

str id_col = str_init(ID_COL); /*!< Name of column containing ID (gen. auto_increment field */
str impu_id_col = str_init(IMPU_ID_COL); /*!< Name of column containing impu ID in mapping table */
str contact_id_col = str_init(CONTACT_ID_COL); /*!< Name of column containing contact ID in mapping table */
str impu_col = str_init(IMPU_COL); /*!< Name of column containing impu in impu table */
str reg_state_col = str_init(REGSTATE_COL); /*!< Name of column containing reg state for aor */
str barring_col = str_init(BARRING_COL); /*!< Name of column containing aor barring */
str ccf1_col = str_init(CCF1_COL); /*!< Name of column containing ccf1 */
str ccf2_col = str_init(CCF2_COL); /*!< Name of column containing ccf2 */
str ecf1_col = str_init(ECF1_COL); /*!< Name of column containing ecf1 */
str ecf2_col = str_init(ECF2_COL); /*!< Name of column containing ecf2 */
str ims_sub_data_col = str_init(IMS_SUB_COL); /*!< Name of column containing ims_subscription data */
str contact_col = str_init(CONTACT_COL); /*!< Name of column containing contact addresses */
str params_col = str_init(PARAMS_COL); /*!< Name of column containing contact addresses */
str expires_col = str_init(EXPIRES_COL); /*!< Name of column containing expires values */
str q_col = str_init(Q_COL); /*!< Name of column containing q values */
str callid_col = str_init(CALLID_COL); /*!< Name of column containing callid string */
str cseq_col = str_init(CSEQ_COL); /*!< Name of column containing cseq values */
str flags_col = str_init(FLAGS_COL); /*!< Name of column containing internal flags */
str cflags_col = str_init(CFLAGS_COL); /*!< Name of column containing contact flags */
str user_agent_col = str_init(USER_AGENT_COL); /*!< Name of column containing user agent string */
str received_col = str_init(RECEIVED_COL); /*!< Name of column containing transport info of REGISTER */
str path_col = str_init(PATH_COL); /*!< Name of column containing the Path header */
str sock_col = str_init(SOCK_COL); /*!< Name of column containing the received socket */
str methods_col = str_init(METHODS_COL); /*!< Name of column containing the supported methods */
str last_mod_col = str_init(LAST_MOD_COL); /*!< Name of column containing the last modified date */

/*impu table*/
str impu_table = str_init(IMPU_TABLE);

/*contact table*/
str contact_table = str_init(CONTACT_TABLE);

/*impu contact table*/
str impu_contact_table = str_init(IMPU_CONTACT_TABLE);

/*subscriber table*/
str subscriber_table = str_init(SUBSCRIBER_TABLE);
str sub_event_col = str_init(SUB_EVENT_COL);
str sub_expires_col = str_init(SUB_EXPIRES_COL);
str sub_version_col = str_init(SUB_VERSION_COL);
str sub_watcher_uri_col = str_init(SUB_WATCHER_URI_COL);
str sub_watcher_contact_col = str_init(SUB_WATCHER_CONTACT_COL);
str sub_presentity_uri_col = str_init(SUB_PRESENTITY_URI_COL);
str sub_local_cseq_col = str_init(SUB_LOCAL_CSEQ_COL);
str sub_call_id_col = str_init(SUB_CALL_ID_COL);
str sub_from_tag_col = str_init(SUB_FROM_TAG_COL);
str sub_to_tag_col = str_init(SUB_TO_TAG_COL);
str sub_record_route_col = str_init(SUB_RECORD_ROUTE_COL);
str sub_sockinfo_str_col = str_init(SUB_SOCKINFO_STR_COL);

/*impu_subscriber table*/
str impu_subscriber_table = str_init(IMPU_SUBSCRIBER_TABLE);
str subscriber_id_col = str_init(SUBSCRIBER_ID_COL);

str query_buffer 		= { 0, 0 };
int query_buffer_len		= 0;

char* check_contact_links_query = "SELECT * FROM impu_contact WHERE contact_id = (SELECT contact.id from contact WHERE contact.contact='%.*s')";
int check_contact_links_query_len;

char* impu_contact_insert_query = "INSERT INTO impu_contact (impu_id, contact_id) (SELECT I.id, C.id FROM impu I, contact C WHERE I.impu='%.*s' and C.contact='%.*s')";
int impu_contact_insert_query_len;
char* impu_contact_delete_query = "DELETE impu_contact FROM impu_contact INNER JOIN impu ON impu_contact.impu_id = impu.id INNER JOIN contact ON contact.id = impu_contact.contact_id where impu.impu = '%.*s' and contact.contact = '%.*s'";
int impu_contact_delete_query_len;

char* impu_subscriber_insert_query = "INSERT INTO impu_subscriber (impu_id, subscriber_id) (SELECT I.id, S.id FROM impu I, subscriber S WHERE I.impu='%.*s' and S.event='%.*s' and S.watcher_contact='%.*s' and S.presentity_uri='%.*s')";
int impu_subscriber_insert_query_len;
char* impu_subscriber_delete_query = "DELETE impu_subscriber FROM impu_subscriber INNER JOIN impu on impu_subscriber.impu_id=impu.id INNER JOIN subscriber on impu_subscriber.subscriber_id=subscriber.id WHERE impu.impu='%.*s' AND subscriber.event='%.*s' and subscriber.watcher_contact='%.*s' and subscriber.presentity_uri='%.*s'";
int impu_subscriber_delete_query_len;

char* delete_unlinked_contact_query = "DELETE from contact";
int delete_unlinked_contact_query_len;



extern db1_con_t* ul_dbh;
extern db_func_t ul_dbf;
extern int ul_fetch_rows;

int init_db(const str *db_url, int db_update_period, int fetch_num_rows) {
	/* Find a database module */
	if (db_bind_mod(db_url, &ul_dbf) < 0) {
		LM_ERR("Unable to bind to a database driver\n");
		return -1;
	}

	if (connect_db(db_url) != 0) {
		LM_ERR("unable to connect to the database\n");
		return -1;
	}

	if (!DB_CAPABILITY(ul_dbf, DB_CAP_ALL)) {
		LM_ERR("database module does not implement all functions needed by the module\n");
		return -1;
	}

	ul_dbf.close(ul_dbh);
	ul_dbh = 0;

	return 0;
}

int connect_db(const str *db_url) {
	if (ul_dbh) { /* we've obviously already connected... */
		LM_WARN("DB connection already open... continuing\n");
		return 0;
	}

	if ((ul_dbh = ul_dbf.init(db_url)) == 0)
		return -1;

	LM_DBG("Successfully connected to DB and returned DB handle ptr %p\n", ul_dbh);
	return 0;
}

void destroy_db() {
	/* close the DB connection */
	if (ul_dbh) {
		ul_dbf.close(ul_dbh);
		ul_dbh = 0;
	}
}

int use_location_scscf_table(str* domain) {
	if (!ul_dbh) {
		LM_ERR("invalid database handle\n");
		return -1;
	}

	if (ul_dbf.use_table(ul_dbh, domain) < 0) {
		LM_ERR("Error in use_table\n");
		return -1;
	}

	return 0;
}

int db_insert_impurecord(struct udomain* _d, str* public_identity,
		int reg_state, int barring, ims_subscription** s, str* ccf1, str* ccf2,
		str* ecf1, str* ecf2, struct impurecord** _r) {
	int i;
	bin_data x;
	db_key_t key[8];
	db_val_t val[8];
	str bin_str;

	LM_DBG("DB: Inserting/Updating IMPU [%.*s]\n", public_identity->len, public_identity->s);

	//serialise ims_subscription
	if (s) {
	    if (!bin_alloc(&x, 256)) {
		    LM_DBG("unable to allocate buffer for binary serialisation\n");
		    return -1;
	    }
	    if (!bin_encode_ims_subscription(&x, (*s))) {
		    LM_DBG("Unable to serialise ims_subscription data\n");
		    bin_free(&x);
		    return -1;
	    }
	    bin_str.s = x.s;
	    bin_str.len = x.len;
	}

	key[0] = &impu_col;
	key[1] = &barring_col;
	key[2] = &reg_state_col;

	val[0].type = DB1_STR;
	val[0].nul = 0;
	val[0].val.str_val = *public_identity;
	val[1].type = DB1_INT;
	val[1].nul = 0;
	val[1].val.int_val = barring;
	val[2].type = DB1_INT;
	val[2].nul = 0;
	val[2].val.int_val = reg_state;

	i = 3;

	if (ccf1 && ccf1->s && ccf1->len >= 0) {
		key[i] = &ccf1_col;
		val[i].type = DB1_STR;
		val[i].nul = 0;
		val[i].val.str_val = *ccf1;
		i++;
	}
	if (ecf1 && ecf1->s && ecf1->len >= 0) {
		key[i] = &ecf1_col;
		val[i].type = DB1_STR;
		val[i].nul = 0;
		val[i].val.str_val = *ecf1;
		i++;
	}
	if (ccf2 && ccf2->s && ccf2->len >= 0) {
		key[i] = &ccf2_col;
		val[i].type = DB1_STR;
		val[i].nul = 0;
		val[i].val.str_val = *ccf2;
		i++;
	}
	if (ecf2 && ecf2->s && ecf2->len >= 0) {
		key[i] = &ecf2_col;
		val[i].type = DB1_STR;
		val[i].nul = 0;
		val[i].val.str_val = *ecf2;
		i++;
	}
	key[i] = &ims_sub_data_col;
	val[i].type = DB1_BLOB;
	if (s) {
	    val[i].nul = 0;
	    val[i].val.blob_val = bin_str;
	} else {
	    val[i].nul = 1;
	}

	i++;

	if (ul_dbf.use_table(ul_dbh, &impu_table) != 0) {
		LM_ERR("Unable to use table [%.*s]\n", impu_table.len, impu_table.s);
		bin_free(&x);
		return -1;
	}
	if (ul_dbf.insert_update(ul_dbh, key, val, i) != 0) {
		LM_ERR("Unable to insert impu into table [%.*s]\n", public_identity->len, public_identity->s);
		bin_free(&x);
		return -1;
	}

	if (s)
	    bin_free(&x);

	return 0;
}

int db_delete_impurecord(udomain_t* _d, struct impurecord* _r) {
	db_key_t key[1];
	db_val_t val[1];

	LM_DBG("DB: deleting IMPU [%.*s]\n", _r->public_identity.len, _r->public_identity.s);

	key[0] = &impu_col;
	val[0].type = DB1_STR;
	val[0].nul = 0;
	val[0].val.str_val = _r->public_identity;

	if (ul_dbf.use_table(ul_dbh, &impu_table) != 0) {
		LM_ERR("Unable to use table [%.*s]\n", impu_table.len, impu_table.s);
		return -1;
	}
	if (ul_dbf.delete(ul_dbh, key, 0, val, 1) != 0) {
		LM_ERR("Unable to delete impu [%.*s] from DB\n", _r->public_identity.len, _r->public_identity.s);
		return -1;
	}

	return 0;
}

static int MAX_PARAMS_SIZE = 1000;
static str param_name_and_body = {"%.*s=%.*s;", 1};
static str param_name_and_body_last = {"%.*s=%.*s", 1};
static str param_name_no_body = {"%.*s;", 1};
static str param_name_no_body_last = {"%.*s", 1};

int db_insert_ucontact(impurecord_t* _r, ucontact_t* _c) {

	str param_buf, param_pad;
	param_t * tmp;
	char param_bufc[MAX_PARAMS_SIZE], param_padc[MAX_PARAMS_SIZE];
	param_buf.s = param_bufc;
	param_buf.len = 0;
	param_pad.s = param_padc;
	param_pad.len = 0;
	char* format_name_and_body, *format_name_no_body;

	db_key_t key[7];
	db_val_t val[7];

	LM_DBG("DB: inserting ucontact [%.*s]\n", _c->c.len, _c->c.s);

	tmp = _c->params;
	while (tmp) {
		if (tmp->name.len > 0) {			    
			if (tmp->next == 0) {
				format_name_and_body = param_name_and_body_last.s;
				format_name_no_body = param_name_no_body_last.s;
			} else {
				format_name_and_body = param_name_and_body.s;
				format_name_no_body = param_name_no_body.s;
			}
			if (tmp->body.len > 0) {
				sprintf(param_pad.s, format_name_and_body, tmp->name.len, tmp->name.s, tmp->body.len, tmp->body.s);
			} else {
				sprintf(param_pad.s, format_name_no_body, tmp->name.len, tmp->name.s);
			}
			param_pad.len = strlen(param_pad.s);
			STR_APPEND(param_buf, param_pad);
		}
		tmp = tmp->next;
	}
	LM_DBG("Converted params to string to insert into db: [%.*s]\n", param_buf.len, param_buf.s);


	key[0] = &contact_col;
	key[1] = &params_col;
	key[2] = &path_col;
	key[3] = &user_agent_col;
	key[4] = &received_col;
	key[5] = &expires_col;
	key[6] = &callid_col;

	val[0].type = DB1_STR;
	val[0].nul = 0;
	val[0].val.str_val = _c->c;

	val[1].type = DB1_STR;
	if (param_buf.len > 0) {
		val[1].nul = 0;
		val[1].val.str_val = param_buf;
	} else {
		val[1].nul = 1;
	}

	val[2].type = DB1_STR;
	val[2].nul = 0;
	val[2].val.str_val = _c->path;

	val[3].type = DB1_STR;
	val[3].nul = 0;
	val[3].val.str_val = _c->user_agent;

	val[4].type = DB1_STR;
	val[4].nul = 0;
	val[4].val.str_val = _c->received;

	val[5].type = DB1_DATETIME;
	val[5].nul = 0;
	val[5].val.time_val = _c->expires;

	val[6].type = DB1_STR;
	val[6].nul = 0;
	val[6].val.str_val = _c->callid;

	if (ul_dbf.use_table(ul_dbh, &contact_table) != 0) {
		LM_ERR("Unable to use table [%.*s]\n", contact_table.len, contact_table.s);
		return -1;
	}
	if (ul_dbf.insert_update(ul_dbh, key, val, 7) != 0) {
		LM_ERR("Failed to insert/update contact record for [%.*s]\n", _c->c.len, _c->c.s);
		return -1;
	}

	return 0;
}

int db_delete_ucontact(ucontact_t* _c) {
	db_key_t key[1];
	db_val_t val[1];

	LM_DBG("Deleting ucontact [%.*s]\n",_c->c.len, _c->c.s);

	/* get contact id from DB */
	if (ul_dbf.use_table(ul_dbh, &contact_table) != 0) {
		LM_ERR("Unable to use table [%.*s]\n", contact_table.len, contact_table.s);
		return -1;
	}
	key[0] = &contact_col;
	val[0].type = DB1_STR;
	val[0].nul = 0;
	val[0].val.str_val = _c->c;
	if (ul_dbf.delete(ul_dbh, key, 0, val, 1) != 0) {
		LM_ERR("Unable to delete contact [%.*s] from DB\n", _c->c.len, _c->c.s);
		return -1;
	}

	return 0;
}

int db_insert_subscriber(impurecord_t* _r, reg_subscriber* _reg_subscriber) {
	int col_num = 12;
	db_key_t key[col_num];
	db_val_t val[col_num];

	LM_DBG("DB: inserting subscriber [%.*s]\n", _reg_subscriber->presentity_uri.len, _reg_subscriber->presentity_uri.s);

	key[0] = &sub_watcher_uri_col;
	key[1] = &sub_watcher_contact_col;
	key[2] = &sub_presentity_uri_col;
	key[3] = &sub_event_col;
	key[4] = &sub_expires_col;
	key[5] = &sub_version_col;
	key[6] = &sub_local_cseq_col;
	key[7] = &sub_call_id_col;
	key[8] = &sub_from_tag_col;
	key[9] = &sub_to_tag_col;
	key[10] = &sub_record_route_col;
	key[11] = &sub_sockinfo_str_col;

	val[0].type = DB1_STR;
	val[0].nul = 0;
	val[0].val.str_val = _reg_subscriber->watcher_uri;

	val[1].type = DB1_STR;
	val[1].nul = 0;
	val[1].val.str_val = _reg_subscriber->watcher_contact;

	val[2].type = DB1_STR;
	val[2].nul = 0;
	val[2].val.str_val = _reg_subscriber->presentity_uri;

	val[3].type = DB1_INT;
	val[3].nul = 0;
	val[3].val.int_val = _reg_subscriber->event;

	val[4].type = DB1_DATETIME;
	val[4].nul = 0;
	val[4].val.time_val = _reg_subscriber->expires;

	val[5].type = DB1_INT;
	val[5].nul = 0;
	val[5].val.int_val = _reg_subscriber->version;

	val[6].type = DB1_INT;
	val[6].nul = 0;
	val[6].val.int_val = _reg_subscriber->local_cseq;

	val[7].type = DB1_STR;
	val[7].nul = 0;
	val[7].val.str_val = _reg_subscriber->call_id;

	val[8].type = DB1_STR;
	val[8].nul = 0;
	val[8].val.str_val = _reg_subscriber->from_tag;

	val[9].type = DB1_STR;
	val[9].nul = 0;
	val[9].val.str_val = _reg_subscriber->to_tag;

	val[10].type = DB1_STR;
	val[10].nul = 0;
	val[10].val.str_val = _reg_subscriber->record_route;

	val[11].type = DB1_STR;
	val[11].nul = 0;
	val[11].val.str_val = _reg_subscriber->sockinfo_str;

	if (ul_dbf.use_table(ul_dbh, &subscriber_table) != 0) {
		LM_ERR("Unable to use table [%.*s]\n", subscriber_table.len, subscriber_table.s);
		return -1;
	}
	if (ul_dbf.insert_update(ul_dbh, key, val, col_num) != 0) {
		LM_ERR("Failed to insert/update subscriber record for [%.*s]\n", _reg_subscriber->presentity_uri.len, _reg_subscriber->presentity_uri.s);
		return -1;
	}

	return 0;
}


int db_delete_subscriber(impurecord_t* _r, reg_subscriber* _reg_subscriber) {
	db_key_t key[3];
	db_val_t val[3];

	LM_DBG("Deleting subscriber binding [%.*s] on impu [%.*s]\n",
			_reg_subscriber->presentity_uri.len, _reg_subscriber->presentity_uri.s,
			_r->public_identity.len, _r->public_identity.s);

	/* get subscriber id from DB */
	if (ul_dbf.use_table(ul_dbh, &subscriber_table) != 0) {
		LM_ERR("Unable to use table [%.*s]\n", subscriber_table.len, subscriber_table.s);
		return -1;
	}
	key[0] = &sub_event_col;
	val[0].type = DB1_INT;
	val[0].nul = 0;
	val[0].val.int_val = _reg_subscriber->event;

	key[1] = &sub_watcher_contact_col;
	val[1].type = DB1_STR;
	val[1].nul = 0;
	val[1].val.str_val = _reg_subscriber->watcher_contact;

	key[2] = &sub_presentity_uri_col;
	val[2].type = DB1_STR;
	val[2].nul = 0;
	val[2].val.str_val = _reg_subscriber->presentity_uri;

	if (ul_dbf.delete(ul_dbh, key, 0, val, 3) != 0) {
		LM_ERR("Unable to delete subscriber [%.*s] from DB\n", _reg_subscriber->presentity_uri.len, _reg_subscriber->presentity_uri.s);
		return -1;
	}

	return 0;

}


int int_to_str_len(int i) {
	if (i < 0)
		i = -i;
	if (i < 10)
		return 1;
	if (i < 100)
		return 2;
	if (i < 1000)
		return 3;
	if (i < 10000)
		return 4;
	if (i < 100000)
		return 5;
	if (i < 1000000)
		return 6;
	if (i < 10000000)
		return 7;
	if (i < 100000000)
		return 8;
	if (i < 1000000000)
		return 9;
	return 10;
}

static inline int dbrow2contact(db_val_t* val, ucontact_info_t* ci) {
	static str path, user_agent, callid, params;
	param_hooks_t hooks;

	// Set ci to 0:
	memset( ci, 0, sizeof(ucontact_info_t));

	/* params */
	if (!VAL_NULL(val + 1)) {
		params.s = (char*)VAL_STRING(val + 1);
		params.len = strlen(params.s);
		if (parse_params(&params, CLASS_CONTACT, &hooks, &ci->params) < 0) {
			LM_WARN("Error while parsing parameters: %.*s\n", params.len, params.s);
		}
	}

	/* path */
	if (!VAL_NULL(val + 2)) {
		path.s = (char*)VAL_STRING(val + 2);
		path.len = strlen(path.s);
	}
	ci->path = &path;

	/* user-agent */
	if (!VAL_NULL(val + 3)) {
		user_agent.s = (char*)VAL_STRING(val + 3);
		user_agent.len = strlen(user_agent.s);
	}
	ci->user_agent = &user_agent;

	/* received */
	if (!VAL_NULL(val + 4)) {
		ci->received.s = (char*)VAL_STRING(val + 4);
		ci->received.len = strlen(ci->received.s);
	}

	/* expires */
	if (!VAL_NULL(val + 5)) {
		ci->expires = VAL_TIME(val + 5);
	}
	/* callid */
	if (!VAL_NULL(val + 6)) {
		callid.s = (char*) VAL_STRING(val + 6);
		callid.len = strlen(callid.s);
	}
	ci->callid = &callid;

	return 0;
}

static inline int dbrow2subscriber(db_val_t* val, subscriber_data_t* subscriber_data) {
	static str presentity_uri, watcher_uri, watcher_contact, call_id, from_tag, to_tag, record_route, sockinfo_str;

	/*presentity uri*/
	if (!VAL_NULL(val)) {
	    presentity_uri.s = (char*) VAL_STRING(val);
	    presentity_uri.len = strlen(presentity_uri.s);
	}
	subscriber_data->presentity_uri = &presentity_uri;
	LM_DBG("presentity_uri: [%.*s]", subscriber_data->presentity_uri->len, subscriber_data->presentity_uri->s);

	/*watcher_uri*/
	if (!VAL_NULL(val + 1)) {
	    watcher_uri.s = (char*) VAL_STRING(val + 1);
	    watcher_uri.len = strlen(watcher_uri.s);
	}
	subscriber_data->watcher_uri = &watcher_uri;
	LM_DBG("watcher_uri: [%.*s]", subscriber_data->watcher_uri->len, subscriber_data->watcher_uri->s);

	/*watcher_contact*/
	if (!VAL_NULL(val + 2)) {
	    watcher_contact.s = (char*) VAL_STRING(val + 2);
	    watcher_contact.len = strlen(watcher_contact.s);
	}
	subscriber_data->watcher_contact = &watcher_contact;
	LM_DBG("watcher_contact: [%.*s]", subscriber_data->watcher_contact->len, subscriber_data->watcher_contact->s);

	/*event*/
	if (!VAL_NULL(val + 3)) {
	    subscriber_data->event = VAL_INT(val + 3);
	}
	LM_DBG("event: [%d]", subscriber_data->event);

	/* expires */
	if (!VAL_NULL(val + 4)) {
		subscriber_data->expires = VAL_TIME(val + 4);
	}
	LM_DBG("expires: [%d]", subscriber_data->expires);

	/*event*/
	if (!VAL_NULL(val + 5)) {
	    subscriber_data->version = VAL_INT(val + 5);
	}
	LM_DBG("version: [%d]", subscriber_data->version);

	/*local_cseq*/
	if (!VAL_NULL(val + 6)) {
	    subscriber_data->local_cseq = VAL_INT(val + 6);
	}
	LM_DBG("local_cseq: [%d]", subscriber_data->local_cseq);

	/* callid */
	if (!VAL_NULL(val + 7)) {
		call_id.s = (char*) VAL_STRING(val + 7);
		call_id.len = strlen(call_id.s);
	}
	subscriber_data->callid = &call_id;
	LM_DBG("callid: [%.*s]", subscriber_data->callid->len, subscriber_data->callid->s);

	/* ftag */
	if (!VAL_NULL(val + 8)) {
		from_tag.s = (char*) VAL_STRING(val + 8);
		from_tag.len = strlen(from_tag.s);
	}
	subscriber_data->ftag = &from_tag;
	LM_DBG("ftag: [%.*s]", subscriber_data->ftag->len, subscriber_data->ftag->s);

	/* ttag */
	if (!VAL_NULL(val + 9)) {
		to_tag.s = (char*) VAL_STRING(val + 9);
		to_tag.len = strlen(to_tag.s);
	}
	subscriber_data->ttag = &to_tag;
	LM_DBG("ttag: [%.*s]", subscriber_data->ttag->len, subscriber_data->ttag->s);

	/* record_route */
	if (!VAL_NULL(val + 10)) {
		record_route.s = (char*) VAL_STRING(val + 10);
		record_route.len = strlen(record_route.s);
	}
	subscriber_data->record_route = &record_route;
	LM_DBG("record_route: [%.*s]", subscriber_data->record_route->len, subscriber_data->record_route->s);

	/* sockinfo_str */
	if (!VAL_NULL(val + 11)) {
		sockinfo_str.s = (char*) VAL_STRING(val + 11);
		sockinfo_str.len = strlen(sockinfo_str.s);
	}
	subscriber_data->sockinfo_str = &sockinfo_str;
	LM_DBG("sockinfo_str: [%.*s]", subscriber_data->sockinfo_str->len, subscriber_data->sockinfo_str->s);

	return 0;
}

int delete_all_unlinked_contacts() {
	int len;
	db1_res_t* rs;

	len = strlen(delete_unlinked_contact_query) +1;
	if (!query_buffer_len || query_buffer_len < len) {
		if (query_buffer.s) {
			pkg_free(query_buffer.s);
		}
		query_buffer.s = (char*) pkg_malloc(len);
		if (!query_buffer.s) {
			LM_ERR("no more pkg mem\n");
			return -1;
		}
		query_buffer_len = len;
	}
	snprintf(query_buffer.s,query_buffer_len,"%s",delete_unlinked_contact_query);
	query_buffer.len = strlen(query_buffer.s);

	if (ul_dbf.raw_query(ul_dbh, &query_buffer, &rs) != 0) {
		return -1;
	}
	ul_dbf.free_result(ul_dbh,rs);
	return 0;
}

int preload_udomain(db1_con_t* _c, udomain_t* _d) {
    db_key_t col[9];
    db_row_t* row;
    db_row_t* contact_row;
    db_row_t* subscriber_row;
    db1_res_t* rs;
    db1_res_t* contact_rs;
    db1_res_t* subscriber_rs;
    db_val_t* vals;
    db_val_t* contact_vals;
    db_val_t* subscriber_vals;
    int barring = 0, reg_state = 0, impu_id, n, nn, i, j, len;
    str query_contact, query_subscriber, impu, ccf1 = {0, 0}, ecf1 = {0, 0}, ccf2 = {0, 0}, ecf2 = {
	0, 0
    }, blob = {0, 0}, contact = {0, 0}, presentity_uri = {0, 0};
    bin_data x;
    ims_subscription* subscription = 0;
	ims_subscription* found_subscription = 0;
    impurecord_t* impurecord;
    int impu_id_len;
    ucontact_t* c;
    ucontact_info_t contact_data;
    subscriber_data_t subscriber_data;
    reg_subscriber *reg_subscriber;
	int must_unref_subscription = 0;

    /*
     * the two queries - get the IMPUs, then get associated contacts for each IMPU:
     * SELECT impu.impu,impu.barring,impu.reg_state,impu.ccf1,impu.ccf2,impu.ecf1,impu.ecf2,impu.ims_subscription_data FROM impu;
     * SELECT c.contact,c.path,c.user_agent,c.received,c.expires FROM impu_contact m LEFT JOIN contact c ON c.id=m.contact_id WHERE m.impu_id=20;
     */

    char *p_contact =
	    "SELECT c.contact,c.params,c.path,c.user_agent,c.received,c.expires,c.callid FROM impu_contact m LEFT JOIN contact c ON c.id=m.contact_id WHERE m.impu_id=";

    char *p_subscriber =
	    "SELECT s.presentity_uri,s.watcher_uri,s.watcher_contact,s.event,s.expires,s.version,s.local_cseq,s.call_id,s.from_tag,"
	    "s.to_tag,s.record_route,s.sockinfo_str FROM impu_subscriber m LEFT JOIN subscriber s ON s.id=m.subscriber_id WHERE m.impu_id=";

    query_contact.s = p_contact;
    query_contact.len = strlen(query_contact.s);

    query_subscriber.s = p_subscriber;
    query_subscriber.len = strlen(query_subscriber.s);


    col[0] = &impu_col;
    col[1] = &barring_col;
    col[2] = &reg_state_col;
    col[3] = &ccf1_col;
    col[4] = &ecf1_col;
    col[5] = &ccf2_col;
    col[6] = &ecf2_col;
    col[7] = &ims_sub_data_col;
    col[8] = &id_col;

    if (ul_dbf.use_table(_c, &impu_table) != 0) {
	LM_ERR("SQL use table failed\n");
	return -1;
    }
    if (ul_dbf.query(_c, NULL, 0, NULL, col, 0, 9, NULL, &rs) != 0) {
	LM_ERR("Unable to query DB to preload S-CSCF usrloc\n");
	return -1;
    }

    if (RES_ROW_N(rs) == 0) {
		LM_DBG("IMPU table is empty, removing all unlinked contacts \n");
		ul_dbf.free_result(_c, rs);
		if (delete_all_unlinked_contacts() != 0) {
			LM_ERR("Not able to delete unlinked contacts\n");
		}
		return 0;
    }

    LM_DBG("preloading S-CSCF usrloc...\n");
    LM_DBG("%d rows returned in preload\n", RES_ROW_N(rs));

    n = 0;
    do {
	n++;
	LM_DBG("loading S-CSCF usrloc records - cycle [%d]\n", n);
	for (i = 0; i < RES_ROW_N(rs); i++) {
	    impu_id = -1;

	    row = RES_ROWS(rs) + i;
	    LM_DBG("Fetching IMPU row %d\n", i + 1);
	    vals = ROW_VALUES(row);

	    impu.s = (char*) VAL_STRING(vals);
	    if (VAL_NULL(vals) || !impu.s || !impu.s[0]) {
		impu.len = 0;
		impu.s = 0;
	    } else {
		impu.len = strlen(impu.s);
	    }
	    LM_DBG("IMPU from DB is [%.*s]\n", impu.len, impu.s);
	    if (!VAL_NULL(vals + 1)) {
		barring = VAL_INT(vals + 1);
	    }
	    if (!VAL_NULL(vals + 2)) {
		reg_state = VAL_INT(vals + 2);
	    }
	    if (!VAL_NULL(vals + 3)) {
		ccf1.s = (char*) VAL_STRING(vals + 3);
		ccf1.len = strlen(ccf1.s);
	    }
	    LM_DBG("CCF1 from DB is [%.*s]\n", ccf1.len, ccf1.s);
	    if (!VAL_NULL(vals + 4)) {
		ecf1.s = (char*) VAL_STRING(vals + 3);
		ecf1.len = strlen(ecf1.s);
	    }
	    LM_DBG("ECF1 from DB is [%.*s]\n", ecf1.len, ecf1.s);
	    if (!VAL_NULL(vals + 5)) {
		ccf2.s = (char*) VAL_STRING(vals + 5);
		ccf2.len = strlen(ccf2.s);
	    }
	    LM_DBG("CCF2 from DB is [%.*s]\n", ccf2.len, ccf2.s);
	    if (!VAL_NULL(vals + 6)) {
		ecf2.s = (char*) VAL_STRING(vals + 6);
		ecf2.len = strlen(ecf2.s);
	    }
	    LM_DBG("ECF2 from DB is [%.*s]\n", ecf2.len, ecf2.s);

	    if (!VAL_NULL(vals + 7)) {
		blob = VAL_BLOB(vals + 7);
		bin_alloc(&x, blob.len);
		memcpy(x.s, blob.s, blob.len);
		x.len = blob.len;
		x.max = 0;
		subscription = bin_decode_ims_subscription(&x);
		bin_free(&x);
	    }
	    if (!VAL_NULL(vals + 8)) {
		impu_id = VAL_INT(vals + 8);
	    }

		int leave_slot_locked = 1;
		int res = get_subscription(&subscription->private_identity, &found_subscription, leave_slot_locked); //leave slot locked in case we need to add.... don't want racing adds
        if (res != 0) {
            LM_DBG("No subscription yet for [%.*s]... adding\n", subscription->private_identity.len, subscription->private_identity.s);
            ref_subscription_unsafe(subscription); //we reference coz we are using it - will be unreferenced later.
            add_subscription_unsafe(subscription);
            unlock_subscription_slot(subscription->sl);
        } else {
            //TODO: we may want to do a deep comparison of the subscription and update....
            if (compare_subscription(subscription, found_subscription) != 0) {
				unref_subscription_unsafe(subscription);	/*we don't need this one from the DB - don't leak it */
                subscription = found_subscription;
            } else {
                // Treat it as a new Subscription - it's not the same as the previous one
                ref_subscription_unsafe(subscription); //we reference coz we are using it - will be unreferenced later.
                add_subscription_unsafe(subscription);
            }
			ref_subscription_unsafe(subscription);	/*assume we will add to impu - if not, we will unref it*/
			unlock_subscription_slot(subscription->sl);
        }

	    /* insert impu into memory */
	    lock_udomain(_d, &impu);
	    if (get_impurecord_unsafe(_d, &impu, &impurecord) != 0) {
			if (mem_insert_impurecord(_d, &impu, &subscription->private_identity,reg_state, barring,
				&subscription, &ccf1, &ccf2, &ecf1, &ecf2, &impurecord)
				!= 0) {
				LM_ERR("Unable to insert IMPU into memory [%.*s]\n", impu.len, impu.s);
			}
			/* run the INSERTion callback so REGISTRAR can get into sync - ie subscribe for callbacks on the IMPU.... for NOTIFYs for example*/
			run_ul_callbacks(NULL, UL_IMPU_INSERT, impurecord, NULL);
	    } else {
			/* unref the unused subscription*/
			must_unref_subscription = 1;
		}

	    /* add contacts */
	    if (impu_id < 0) {
			LM_ERR("impu_id has not been set [%.*s] - we cannot read contacts or subscribers from DB....aborting preload\n", impu.len, impu.s);
			//TODO: check frees
			unlock_udomain(_d, &impu);
			unref_subscription(subscription);
			continue;
	    }
	    impu_id_len = int_to_str_len(impu_id);
	    len = query_contact.len + impu_id_len + 1/*nul*/;
	    if (!query_buffer_len || query_buffer_len < len) {
		if (query_buffer.s) {
		    pkg_free(query_buffer.s);
		}
		query_buffer.s = (char*) pkg_malloc(len);
		if (!query_buffer.s) {
		    LM_ERR("mo more pkg mem\n");
		    //TODO: check free
		    unlock_udomain(_d, &impu);
			unref_subscription(subscription);
		    return -1;
		}
		query_buffer_len = len;
	    }
	    memcpy(query_buffer.s, query_contact.s, query_contact.len);
	    p_contact = query_buffer.s + query_contact.len;
	    snprintf(p_contact, impu_id_len + 1, "%d", impu_id);
	    query_buffer.len = query_contact.len + impu_id_len;
	    if (ul_dbf.raw_query(_c, &query_buffer, &contact_rs) != 0) {
		LM_ERR("Unable to query DB for contacts associated with impu [%.*s]\n",
			impu.len, impu.s);
		ul_dbf.free_result(_c, contact_rs);
	    } else {
		if (RES_ROW_N(contact_rs) == 0) {
		    LM_DBG("no contacts associated with impu [%.*s]\n", impu.len, impu.s);
		    ul_dbf.free_result(_c, contact_rs);
		} else {
		    nn = 0;
		    do {
			nn++;
			LM_DBG("loading S-CSCF contact - cycle [%d]\n", nn);
			for (j = 0; j < RES_ROW_N(contact_rs); j++) {
			    contact_row = RES_ROWS(contact_rs) + j;
			    contact_vals = ROW_VALUES(contact_row);

			    if (!VAL_NULL(contact_vals)) {
				contact.s = (char*) VAL_STRING(contact_vals);
				contact.len = strlen(contact.s);
			    }

                            if (contact.len <=0 || !contact.s){
                                   LM_ERR("Unable to insert contact [%.*s] for IMPU [%.*s] into memory... continuing...\n",
					    contact.len, contact.s,
					    impu.len, impu.s);
                                   continue;
                            }

			    if (dbrow2contact(contact_vals, &contact_data) != 0) {
				LM_ERR("unable to convert contact row from DB into valid data... moving on\n");
				continue;
			    }

			    if (get_scontact(&contact, contact_data.callid, contact_data.path, contact_data.cseq, &c) != 0) {
				LM_DBG("Contact doesn't exist yet, creating new one [%.*s]\n", contact.len, contact.s);
				if ((c = mem_insert_scontact(impurecord, &contact, &contact_data)) == 0) {
				    LM_ERR("Unable to insert contact [%.*s] for IMPU [%.*s] into memory... continuing...\n",
					    contact.len, contact.s,
					    impu.len, impu.s);
				    continue;
				}
			    }
			    link_contact_to_impu(impurecord, c, 0);
			    release_scontact(c);
			}
			if (DB_CAPABILITY(ul_dbf, DB_CAP_FETCH)) {
			    if (ul_dbf.fetch_result(_c, &contact_rs, ul_fetch_rows) < 0) {
				LM_ERR("fetching rows failed\n");
				ul_dbf.free_result(_c, contact_rs);
				unlock_udomain(_d, &impu);
				return -1;
			    }
			} else {
			    break;
			}
		    } while (RES_ROW_N(contact_rs) > 0);
		    ul_dbf.free_result(_c, contact_rs);
		}
	    }

	    /* add subscriber */
	    impu_id_len = int_to_str_len(impu_id);
	    len = query_subscriber.len + impu_id_len + 1/*nul*/;
	    if (!query_buffer_len || query_buffer_len < len) {
		if (query_buffer.s) {
		    pkg_free(query_buffer.s);
		}
		query_buffer.s = (char*) pkg_malloc(len);
		if (!query_buffer.s) {
		    LM_ERR("mo more pkg mem\n");
		    //TODO: check free
		    unlock_udomain(_d, &impu);
		    return -1;
		}
		query_buffer_len = len;
	    }
	    memcpy(query_buffer.s, query_subscriber.s, query_subscriber.len);
	    p_subscriber = query_buffer.s + query_subscriber.len;
	    snprintf(p_subscriber, impu_id_len + 1, "%d", impu_id);
	    query_buffer.len = query_subscriber.len + impu_id_len;
	    if (ul_dbf.raw_query(_c, &query_buffer, &subscriber_rs) != 0) {
		LM_ERR("Unable to query DB for subscriber associated with impu [%.*s]\n",
			impu.len, impu.s);
		ul_dbf.free_result(_c, subscriber_rs);
		unlock_udomain(_d, &impu);
		unref_subscription(subscription);
		continue;
	    }
	    if (RES_ROW_N(subscriber_rs) == 0) {
		LM_DBG("no subscriber associated with impu [%.*s]\n", impu.len, impu.s);
		ul_dbf.free_result(_c, subscriber_rs);
		unlock_udomain(_d, &impu);
		unref_subscription(subscription);
		continue;
	    }

	    nn = 0;
	    do {
		nn++;
		LM_DBG("loading S-CSCF subscriber - cycle [%d]\n", nn);
		for (j = 0; j < RES_ROW_N(subscriber_rs); j++) {
		    subscriber_row = RES_ROWS(subscriber_rs) + j;
		    subscriber_vals = ROW_VALUES(subscriber_row);

		    /*presentity uri*/
		    if (!VAL_NULL(subscriber_vals)) {
			presentity_uri.s = (char*) VAL_STRING(subscriber_vals);
			presentity_uri.len = strlen(presentity_uri.s);
		    }

		    if (dbrow2subscriber(subscriber_vals, &subscriber_data) != 0) {
			LM_ERR("unable to convert subscriber row from DB into valid subscriberdata... moving on\n");
			continue;
		    }

		    if (add_subscriber(impurecord, &subscriber_data, &reg_subscriber, 1 /*db_load*/) != 0) {
			LM_ERR("Unable to insert subscriber with presentity_uri [%.*s] for IMPU [%.*s] into memory... continuing...\n",
				presentity_uri.len, presentity_uri.s,
				impu.len, impu.s);
		    }
		}
		if (DB_CAPABILITY(ul_dbf, DB_CAP_FETCH)) {
		    if (ul_dbf.fetch_result(_c, &subscriber_rs, ul_fetch_rows) < 0) {
			LM_ERR("fetching rows failed\n");
			ul_dbf.free_result(_c, subscriber_rs);
			unlock_udomain(_d, &impu);
			return -1;
		    }
		} else {
		    break;
		}
	    } while (RES_ROW_N(subscriber_rs) > 0);
	    ul_dbf.free_result(_c, subscriber_rs);

	    unlock_udomain(_d, &impu);
		if (must_unref_subscription)
			unref_subscription(subscription);

	}

	if (DB_CAPABILITY(ul_dbf, DB_CAP_FETCH)) {
			if (ul_dbf.fetch_result(_c, &rs, ul_fetch_rows) < 0) {
				LM_ERR("fetching rows (1) failed\n");
				ul_dbf.free_result(_c, rs);
				return -1;
			}
		} else {
			break;
		}
	} while (RES_ROW_N(rs) > 0);

	ul_dbf.free_result(_c, rs);

	LM_DBG("Completed preload_udomain");

	return 0;
}

int db_link_contact_to_impu(impurecord_t* _r, ucontact_t* _c) {
    int len;
    db1_res_t* rs;

    LM_DBG("DB: linking contact to IMPU\n");

    len = strlen(impu_contact_insert_query) + _r->public_identity.len + _c->c.len + 1;

    if (!query_buffer_len || query_buffer_len < len) {
	if (query_buffer.s) {
	    pkg_free(query_buffer.s);
	}
	query_buffer.s = (char*) pkg_malloc(len);
	if (!query_buffer.s) {
	    LM_ERR("no more pkg mem\n");
	    return -1;
	}
	query_buffer_len = len;

    }

    query_buffer.len = snprintf(query_buffer.s, query_buffer_len, impu_contact_insert_query, _r->public_identity.len, _r->public_identity.s, _c->c.len, _c->c.s);

    LM_DBG("QUERY IS [%.*s] and len is %d\n", query_buffer.len, query_buffer.s, query_buffer.len);
    if (ul_dbf.raw_query(ul_dbh, &query_buffer, &rs) != 0) {
	LM_ERR("Unable to link impu-contact in DB - impu [%.*s], contact [%.*s]\n", _r->public_identity.len, _r->public_identity.s, _c->c.len, _c->c.s);
	return -1;
    }
    ul_dbf.free_result(ul_dbh, rs);
    LM_DBG("Query success\n");

    return 0;
}

int db_check_if_contact_is_linked(ucontact_t* _c) {
	int len;
	db1_res_t* rs;
	int n_res_row = 0;

	len = strlen(check_contact_links_query) + _c->c.len + 1;

	if (!query_buffer_len || query_buffer_len < len) {
		if (query_buffer.s) {
			pkg_free(query_buffer.s);
		}
		query_buffer.s = (char*) pkg_malloc(len);
		if (!query_buffer.s) {
			LM_ERR("no more pkg mem\n");
			return -1;
		}
		query_buffer_len = len;

	}

	snprintf(query_buffer.s,query_buffer_len,check_contact_links_query,_c->c.len,_c->c.s);
	query_buffer.len = strlen(query_buffer.s);
	if (ul_dbf.raw_query(ul_dbh, &query_buffer, &rs) != 0) {
		LM_ERR("Unable to query DB to check if contact[%.*s] is linked\n", _c->c.len, _c->c.s);
		return -1;
	}
	n_res_row = RES_ROW_N(rs);
	ul_dbf.free_result(ul_dbh, rs);
	return n_res_row;
}

int db_unlink_contact_from_impu(impurecord_t* _r, ucontact_t* _c) {
    int len;
    db1_res_t* rs;

    LM_DBG("DB: un-linking contact to IMPU\n");

    len = strlen(impu_contact_delete_query) + _r->public_identity.len + _c->c.len + 1;

    if (!query_buffer_len || query_buffer_len < len) {
	if (query_buffer.s) {
	    pkg_free(query_buffer.s);
	}
	query_buffer.s = (char*) pkg_malloc(len);
	if (!query_buffer.s) {
	    LM_ERR("no more pkg mem\n");
	    return -1;
	}
	query_buffer_len = len;

    }

    snprintf(query_buffer.s, query_buffer_len, impu_contact_delete_query, _r->public_identity.len, _r->public_identity.s, _c->c.len, _c->c.s);
    query_buffer.len = strlen(query_buffer.s);//len;

    if (ul_dbf.raw_query(ul_dbh, &query_buffer, &rs) != 0) {
	LM_ERR("Unable to un-link impu-contact in DB - impu [%.*s], contact [%.*s]\n", _r->public_identity.len, _r->public_identity.s, _c->c.len, _c->c.s);
	return -1;
    }
    ul_dbf.free_result(ul_dbh, rs);
    LM_DBG("Delete query success\n");

    return 0;
}

int db_unlink_subscriber_from_impu(impurecord_t* _r, reg_subscriber* _reg_subscriber) {
    int len;
    db1_res_t* rs;
    char event[11];
    int event_len;

    LM_DBG("DB: un-linking subscriber to IMPU\n");

    event_len = int_to_str_len(_reg_subscriber->event);
    snprintf(event, event_len + 1, "%d", _reg_subscriber->event);

    len = strlen(impu_subscriber_delete_query) + _r->public_identity.len + _reg_subscriber->watcher_contact.len + _reg_subscriber->presentity_uri.len + strlen(event) + 1;

    if (!query_buffer_len || query_buffer_len < len) {
	if (query_buffer.s) {
	    pkg_free(query_buffer.s);
	}
	query_buffer.s = (char*) pkg_malloc(len);
	if (!query_buffer.s) {
	    LM_ERR("no more pkg mem\n");
	    return -1;
	}
	query_buffer_len = len;

    }

    snprintf(query_buffer.s, query_buffer_len, impu_subscriber_delete_query, _r->public_identity.len, _r->public_identity.s, strlen(event), event,
	    _reg_subscriber->watcher_contact.len, _reg_subscriber->watcher_contact.s, _reg_subscriber->presentity_uri.len, _reg_subscriber->presentity_uri.s);
    query_buffer.len = strlen(query_buffer.s);//len;

    if (ul_dbf.raw_query(ul_dbh, &query_buffer, &rs) != 0) {
	LM_ERR("Unable to un-link impu-subscriber in DB - impu [%.*s], subscriber [%.*s]\n", _r->public_identity.len, _r->public_identity.s, _reg_subscriber->presentity_uri.len, _reg_subscriber->presentity_uri.s);
	return -1;
    }
    ul_dbf.free_result(ul_dbh, rs);
    LM_DBG("Delete query success\n");

    return 0;
}

int db_link_subscriber_to_impu(impurecord_t* _r, reg_subscriber* _reg_subscriber) {
    int len;
    db1_res_t* rs;
    char event[11];
    int event_len;

    LM_DBG("DB: linking subscriber to IMPU\n");

    event_len = int_to_str_len(_reg_subscriber->event);
    snprintf(event, event_len + 1, "%d", _reg_subscriber->event);

    len = strlen(impu_subscriber_insert_query) + _r->public_identity.len + _reg_subscriber->watcher_contact.len + _reg_subscriber->presentity_uri.len + strlen(event) + 1;

    if (!query_buffer_len || query_buffer_len < len) {
	if (query_buffer.s) {
	    pkg_free(query_buffer.s);
	}
	query_buffer.s = (char*) pkg_malloc(len);
	if (!query_buffer.s) {
	    LM_ERR("no more pkg mem\n");
	    return -1;
	}
	query_buffer_len = len;

    }

    snprintf(query_buffer.s, query_buffer_len, impu_subscriber_insert_query, _r->public_identity.len, _r->public_identity.s, strlen(event), event,
	    _reg_subscriber->watcher_contact.len, _reg_subscriber->watcher_contact.s, _reg_subscriber->presentity_uri.len, _reg_subscriber->presentity_uri.s);
    query_buffer.len = strlen(query_buffer.s);//len;

    LM_DBG("QUERY IS [%.*s] and len is %d\n", query_buffer.len, query_buffer.s, query_buffer.len);
    if (ul_dbf.raw_query(ul_dbh, &query_buffer, &rs) != 0) {
	LM_ERR("Unable to link impu-subscriber in DB - impu [%.*s], subscriber [%.*s]\n", _r->public_identity.len, _r->public_identity.s, _reg_subscriber->presentity_uri.len, _reg_subscriber->presentity_uri.s);
	return -1;
    }
    LM_DBG("Query success\n");
    ul_dbf.free_result(ul_dbh, rs);

    return 0;
}

