/**
 * Copyright (C) 2020 Julien Chavanton
 *
 * This file is part of opensips, a free SIP server.
 *
 * This file is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version
 *
 *
 * This file is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#include "../../db/db.h"
#include "mqueue_api.h"

/** database connection */
db_con_t *mqueue_db_con = NULL;
db_func_t mq_dbf;

/** db parameters */
str mqueue_db_url = {0, 0};
str mq_db_key_column = str_init("key");
str mq_db_val_column = str_init("val");
str mq_db_id_column = str_init("id");

/**
 * initialize database connection
 */
int mqueue_db_init_con(void)
{
	LM_DBG("mqueue_db_url=[%.*s]\n", mqueue_db_url.len, mqueue_db_url.s);
	if(mqueue_db_url.len <= 0) {
		LM_ERR("failed to connect to the database, no db url\n");
		return -1;
	}
	/* binding to DB module */
	if(db_bind_mod(&mqueue_db_url, &mq_dbf)) {
		LM_ERR("database module not found\n");
		return -1;
	}

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

/**
 * open database connection
 */
int mqueue_db_open_con(void)
{
	if(mqueue_db_init_con() == 0) {
		mqueue_db_con = mq_dbf.init(&mqueue_db_url);
		if(mqueue_db_con == NULL) {
			LM_ERR("failed to connect to the database\n");
			return -1;
		}
		LM_DBG("database connection opened successfully\n");
		return 0;
	}
	return 0;
}

/**
 * close database connection
 */
int mqueue_db_close_con(void)
{
	if(mqueue_db_con != NULL && mq_dbf.close != NULL)
		mq_dbf.close(mqueue_db_con);
	mqueue_db_con = NULL;
	return 0;
}

int mqueue_db_load_queue(str *name)
{
	int ncols = 2;
	db_res_t *db_res = NULL;
	db_key_t db_cols[2] = {&mq_db_key_column, &mq_db_val_column};
	db_key_t db_ord = &mq_db_id_column;
	int mq_fetch_rows = 100;
	int ret = 0;
	str val = str_init("");
	str key = str_init("");
	int i;
	int cnt = 0;

	if(mqueue_db_open_con() != 0) {
		LM_ERR("no db connection\n");
		return -1;
	}

	if(mq_dbf.use_table(mqueue_db_con, name) < 0) {
		LM_ERR("failed to use_table\n");
		goto error;
	}

	LM_INFO("=============== loading queue table [%.*s] from database\n",
			name->len, name->s);

	if(DB_CAPABILITY(mq_dbf, DB_CAP_FETCH)) {
		if(mq_dbf.query(mqueue_db_con, 0, 0, 0, db_cols, 0, ncols, db_ord, 0)
				< 0) {
			LM_ERR("Error while querying db\n");
			goto error;
		}
		if(mq_dbf.fetch_result(mqueue_db_con, &db_res, mq_fetch_rows) < 0) {
			LM_ERR("Error while fetching result\n");
			if(db_res)
				mq_dbf.free_result(mqueue_db_con, db_res);
			goto error;
		} else {
			if(RES_ROW_N(db_res) == 0) {
				mq_dbf.free_result(mqueue_db_con, db_res);
				LM_DBG("Nothing to be loaded in queue\n");
				mqueue_db_close_con();
				return 0;
			}
		}
	} else {
		if((ret = mq_dbf.query(mqueue_db_con, NULL, NULL, NULL, db_cols, 0,
					ncols, 0, &db_res))
						!= 0
				|| RES_ROW_N(db_res) <= 0) {
			if(ret == 0) {
				mq_dbf.free_result(mqueue_db_con, db_res);
				mqueue_db_close_con();
				return 0;
			} else {
				goto error;
			}
		}
	}

	do {
		for(i = 0; i < RES_ROW_N(db_res); i++) {
			if(VAL_NULL(&RES_ROWS(db_res)[i].values[0])) {
				LM_ERR("mqueue [%.*s] row [%d] has NULL key string\n",
						name->len, name->s, i);
				goto error;
			}
			if(VAL_NULL(&RES_ROWS(db_res)[i].values[1])) {
				LM_ERR("mqueue [%.*s] row [%d] has NULL value string\n",
						name->len, name->s, i);
				goto error;
			}
			switch(RES_ROWS(db_res)[i].values[0].type) {
				case DB_STR:
					key.s = (RES_ROWS(db_res)[i].values[0].val.str_val.s);
					if(key.s == NULL) {
						LM_ERR("mqueue [%.*s] row [%d] has NULL key\n",
								name->len, name->s, i);
						goto error;
					}
					key.len = (RES_ROWS(db_res)[i].values[0].val.str_val.len);
					break;
				case DB_BLOB:
					key.s = (RES_ROWS(db_res)[i].values[0].val.blob_val.s);
					if(key.s == NULL) {
						LM_ERR("mqueue [%.*s] row [%d] has NULL key\n",
								name->len, name->s, i);
						goto error;
					}
					key.len = (RES_ROWS(db_res)[i].values[0].val.blob_val.len);
					break;
				case DB_STRING:
					key.s = (char *)(RES_ROWS(db_res)[i]
											 .values[0]
											 .val.string_val);
					if(key.s == NULL) {
						LM_ERR("mqueue [%.*s] row [%d] has NULL key\n",
								name->len, name->s, i);
						goto error;
					}
					key.len = strlen(key.s);
					break;
				default:
					LM_ERR("key type must be string (type=%d)\n",
							RES_ROWS(db_res)[i].values[0].type);
					goto error;
			}
			switch(RES_ROWS(db_res)[i].values[1].type) {
				case DB_STR:
					val.s = (RES_ROWS(db_res)[i].values[1].val.str_val.s);
					if(val.s == NULL) {
						LM_ERR("mqueue [%.*s] row [%d] has NULL value\n",
								name->len, name->s, i);
						goto error;
					}
					val.len = (RES_ROWS(db_res)[i].values[1].val.str_val.len);
					break;
				case DB_BLOB:
					val.s = (RES_ROWS(db_res)[i].values[1].val.blob_val.s);
					if(val.s == NULL) {
						LM_ERR("mqueue [%.*s] row [%d] has NULL value\n",
								name->len, name->s, i);
						goto error;
					}
					val.len = (RES_ROWS(db_res)[i].values[1].val.blob_val.len);
					break;
				case DB_STRING:
					val.s = (char *)(RES_ROWS(db_res)[i]
											 .values[1]
											 .val.string_val);
					if(val.s == NULL) {
						LM_ERR("mqueue [%.*s] row [%d] has NULL value\n",
								name->len, name->s, i);
						goto error;
					}
					val.len = strlen(val.s);
					break;
				default:
					LM_ERR("key type must be string (type=%d)\n",
							RES_ROWS(db_res)[i].values[1].type);
					goto error;
			}
			cnt++;
			LM_DBG("adding item[%d] key[%.*s] value[%.*s]\n", cnt, key.len,
					key.s, val.len, val.s);
			mq_item_add(name, &key, &val);
		}

		if(DB_CAPABILITY(mq_dbf, DB_CAP_FETCH)) {
			if(mq_dbf.fetch_result(mqueue_db_con, &db_res, mq_fetch_rows) < 0) {
				LM_ERR("Error while fetching!\n");
				goto error;
			}
		} else {
			break;
		}
	} while(RES_ROW_N(db_res) > 0);

	mq_dbf.free_result(mqueue_db_con, db_res);

	if(mq_dbf.delete(mqueue_db_con, 0, 0, 0, 0) < 0) {
		LM_ERR("failed to clear table\n");
		goto error;
	}

	LM_DBG("loaded %d values in queue\n", cnt);
	mqueue_db_close_con();
	return 0;
error:
	mqueue_db_close_con();
	return -1;
}

int mqueue_db_save_queue(str *name)
{
	int ncols = 2;
	db_key_t db_cols[2] = {&mq_db_key_column, &mq_db_val_column};
	db_val_t db_vals[2];
	int i;
	int mqueue_sz = 0;
	int ret = 0;

	if(mqueue_db_open_con() != 0) {
		LM_ERR("no db connection\n");
		return -1;
	}

	if(mq_dbf.use_table(mqueue_db_con, name) < 0) {
		LM_ERR("failed to use_table\n");
		goto error;
	}

	if(name->len <= 0 || name->s == NULL) {
		LM_ERR("bad mqueue name\n");
		goto error;
	}

	mqueue_sz = _mq_get_csize(name);

	if(mqueue_sz < 0) {
		LM_ERR("no such mqueue\n");
		goto error;
	}
	for(i = 0; i < mqueue_sz; i++) {
		ret = mq_head_fetch(name);
		if(ret != 0)
			break;
		str *key = NULL;
		str *val = NULL;
		key = get_mqk(name);
		val = get_mqv(name);
		LM_DBG("inserting mqueue[%.*s] name[%.*s] value[%.*s]\n", name->len,
				name->s, key->len, key->s, val->len, val->s);
		db_vals[0].type = DB_STR;
		db_vals[0].nul = 0;
		db_vals[0].val.str_val.s = key->s;
		db_vals[0].val.str_val.len = key->len;
		db_vals[1].type = DB_STR;
		db_vals[1].nul = 0;
		db_vals[1].val.str_val.s = val->s;
		db_vals[1].val.str_val.len = val->len;
		LM_DBG("mq_dbf.insert()\n");
		if(mq_dbf.insert(mqueue_db_con, db_cols, db_vals, ncols) < 0) {
			LM_ERR("failed to store key [%.*s] val [%.*s]\n", key->len, key->s,
					val->len, val->s);
		LM_DBG("done mq_dbf.insert()\n");
		}
	}

	LM_INFO("queue [%.*s] saved in db\n", name->len, name->s);
	mqueue_db_close_con();
	return 0;
error:
	mqueue_db_close_con();
	return -1;
}
