#include "ro_db_handler.h"
#include "../../lib/srdb1/db.h"

static db1_con_t *ro_db_handle = 0; /* database connection handle */
static db_func_t ro_dbf;

str ro_session_table_name = str_init(RO_SESSION_TABLE_NAME);
str id_column = str_init(ID_COL);
str h_entry_column = str_init(HASH_ENTRY_COL);
str h_id_column = str_init(HASH_ID_COL);
str session_id_column = str_init(SESSION_ID_COL);
str dlg_h_entry_column = str_init(DLG_HASH_ENTRY_COL);
str dlg_h_id_column = str_init(DLG_HASH_ID_COL);
str direction_column = str_init(DIRECTION_COL);
str asserted_column = str_init(ASSERTED_ID_COL);
str callee_column = str_init(CALLEE_COL);
str start_time_col = str_init(START_TIME_COL);
str last_event_ts_column = str_init(LAST_EVENT_TS_COL);
str reserved_sec_column = str_init(RESERVED_SECS_COL);
str valid_for_column = str_init(VALID_FOR_COL);
str state_column = str_init(STATE_COL);
str incoming_trunk_id_column = str_init(INCOMING_TRUNK_ID_COL);
str outgoing_trunk_id_column = str_init(OUTGOING_TRUNK_ID_COL);
str rating_group_column = str_init(RATING_GROUP_COL);
str service_identifier_column = str_init(SERVICE_IDENTIFIER_COL);

typedef enum ro_session_field_idx
{
	ID_COL_IDX = 0,
	HASH_ENTRY_COL_IDX,
	HASH_ID_COL_IDX,
	SESSION_ID_COL_IDX,
	DLG_HASH_ENTRY_COL_IDX,
	DLG_HASH_ID_COL_IDX,
	DIRECTION_COL_IDX,
	ASSERTED_ID_COL_IDX,
	CALLEE_COL_IDX,
	START_TIME_COL_IDX,
	LAST_EVENT_TS_COL_IDX,
	RESERVED_SECS_COL_IDX,
	VALID_FOR_COL_IDX,
	STATE_COL_IDX,
	INCOMING_TRUNK_ID_COL_IDX,
	OUTGOING_TRUNK_ID_COL_IDX,
	RATING_GROUP_COL_IDX,
	SERVICE_IDENTIFIER_COL_IDX

} ro_session_field_idx_t;

#define GET_FIELD_IDX(_val, _idx) (_val + _idx)

#define SET_STR_VALUE(_val, _str)         \
	do {                                  \
		VAL_STR((_val)).s = (_str).s;     \
		VAL_STR((_val)).len = (_str).len; \
	} while(0);

#define SET_NULL_FLAG(_vals, _i, _max, _flag)   \
	do {                                        \
		for((_i) = 0; (_i) < (_max); (_i)++)    \
			VAL_NULL((_vals) + (_i)) = (_flag); \
	} while(0);

#define SET_PROPER_NULL_FLAG(_str, _vals, _index) \
	do {                                          \
		if((_str).len == 0)                       \
			VAL_NULL((_vals) + (_index)) = 1;     \
		else                                      \
			VAL_NULL((_vals) + (_index)) = 0;     \
	} while(0);

#define GET_STR_VALUE(_res, _values, _index, _not_null, _unref)   \
	do {                                                          \
		if(VAL_NULL((_values) + (_index))) {                      \
			if(_not_null) {                                       \
				if(_unref)                                        \
					unref_dlg(dlg, 1);                            \
				goto next_dialog;                                 \
			} else {                                              \
				(_res).s = 0;                                     \
				(_res).len = 0;                                   \
			}                                                     \
		} else {                                                  \
			(_res).s = VAL_STR((_values) + (_index)).s;           \
			(_res).len = strlen(VAL_STR((_values) + (_index)).s); \
		}                                                         \
	} while(0);

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

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

	if(db_check_table_version(
			   &ro_dbf, ro_db_handle, &ro_session_table_name, RO_TABLE_VERSION)
			< 0) {
		DB_TABLE_VERSION_ERROR(ro_session_table_name);
		goto dberror;
	}

	//	if( (dlg_db_mode==DB_MODE_DELAYED) &&
	//	(register_timer( dialog_update_db, 0, db_update_period)<0 )) {
	//		LM_ERR("failed to register update db\n");
	//		return -1;
	//	}

	if((load_ro_info_from_db(dlg_hash_size, fetch_num_rows)) != 0) {
		LM_ERR("unable to load the dialog data\n");
		goto dberror;
	}

	ro_dbf.close(ro_db_handle);
	ro_db_handle = 0;

	return 0;

dberror:
	ro_dbf.close(ro_db_handle);
	ro_db_handle = 0;
	return -1;
}

int load_ro_info_from_db(int hash_size, int fetch_num_rows)
{
	LM_WARN("not supported yet\n");
	return 0;
}

int ro_connect_db(const str *db_url)
{
	if(ro_db_handle) {
		LM_CRIT("BUG - db connection found already open\n");
		return -1;
	}
	if((ro_db_handle = ro_dbf.init(db_url)) == 0)
		return -1;

	/* use default table */
	if(ro_dbf.use_table(ro_db_handle, &ro_session_table_name) != 0) {
		LM_ERR("Error in use table for table name [%.*s]\n",
				ro_session_table_name.len, ro_session_table_name.s);
		return -1;
	}

	return 0;
}

void db_set_int_val(db_val_t *values, int index, int val)
{
	VAL_TYPE(GET_FIELD_IDX(values, index)) = DB1_INT;
	VAL_NULL(GET_FIELD_IDX(values, index)) = 0;
	VAL_INT(GET_FIELD_IDX(values, index)) = val;
}

void db_set_str_val(db_val_t *values, int index, str *val)
{
	VAL_TYPE(GET_FIELD_IDX(values, index)) = DB1_STR;
	VAL_NULL(GET_FIELD_IDX(values, index)) = 0;
	SET_STR_VALUE(GET_FIELD_IDX(values, index), *val);
}

void db_set_datetime_val(db_val_t *values, int index, time_t val)
{
	VAL_TYPE(GET_FIELD_IDX(values, index)) = DB1_DATETIME;
	VAL_NULL(GET_FIELD_IDX(values, index)) = 0;
	VAL_TIME(GET_FIELD_IDX(values, index)) = val;
}

int update_ro_dbinfo_unsafe(struct ro_session *ro_session)
{
	if((ro_session->flags & RO_SESSION_FLAG_NEW) != 0
			&& (ro_session->flags & RO_SESSION_FLAG_INSERTED) == 0) {

		db_val_t values[RO_SESSION_TABLE_COL_NUM];
		db_key_t insert_keys[RO_SESSION_TABLE_COL_NUM] = {&id_column,
				&h_entry_column, &h_id_column, &session_id_column,
				&dlg_h_entry_column, &dlg_h_id_column, &direction_column,
				&asserted_column, &callee_column, &start_time_col,
				&last_event_ts_column, &reserved_sec_column, &valid_for_column,
				&state_column, &incoming_trunk_id_column,
				&outgoing_trunk_id_column, &rating_group_column,
				&service_identifier_column};

		VAL_TYPE(GET_FIELD_IDX(values, ID_COL_IDX)) = DB1_INT;
		VAL_NULL(GET_FIELD_IDX(values, ID_COL_IDX)) = 1;

		db_set_int_val(values, HASH_ENTRY_COL_IDX, ro_session->h_entry);
		db_set_int_val(values, HASH_ID_COL_IDX, ro_session->h_id);
		db_set_str_val(values, SESSION_ID_COL_IDX, &ro_session->ro_session_id);
		db_set_int_val(values, DLG_HASH_ENTRY_COL_IDX, ro_session->dlg_h_entry);
		db_set_int_val(values, DLG_HASH_ID_COL_IDX, ro_session->dlg_h_id);
		db_set_int_val(values, DIRECTION_COL_IDX, ro_session->direction);
		db_set_str_val(
				values, ASSERTED_ID_COL_IDX, &ro_session->asserted_identity);
		db_set_str_val(
				values, CALLEE_COL_IDX, &ro_session->called_asserted_identity);
		db_set_datetime_val(
				values, START_TIME_COL_IDX, ro_session->start_time / 1000000);
		db_set_datetime_val(values, LAST_EVENT_TS_COL_IDX,
				ro_session->last_event_timestamp / 1000000);
		db_set_int_val(
				values, RESERVED_SECS_COL_IDX, ro_session->reserved_secs);
		db_set_int_val(values, VALID_FOR_COL_IDX, ro_session->valid_for);
		db_set_int_val(values, STATE_COL_IDX, ro_session->active);
		db_set_str_val(values, INCOMING_TRUNK_ID_COL_IDX,
				&ro_session->incoming_trunk_id);
		db_set_str_val(values, OUTGOING_TRUNK_ID_COL_IDX,
				&ro_session->outgoing_trunk_id);
		db_set_int_val(values, RATING_GROUP_COL_IDX, ro_session->rating_group);
		db_set_int_val(values, SERVICE_IDENTIFIER_COL_IDX,
				ro_session->service_identifier);


		LM_DBG("Inserting ro_session into database\n");
		if((ro_dbf.insert(
				   ro_db_handle, insert_keys, values, RO_SESSION_TABLE_COL_NUM))
				!= 0) {
			LM_ERR("could not add new Ro session into DB.... continuing\n");
			goto error;
		}
		ro_session->flags &= ~(RO_SESSION_FLAG_NEW | RO_SESSION_FLAG_CHANGED);
		ro_session->flags |= RO_SESSION_FLAG_INSERTED;

	} else if((ro_session->flags & RO_SESSION_FLAG_CHANGED) != 0
			  && (ro_session->flags & RO_SESSION_FLAG_INSERTED) != 0) {

		db_val_t values[RO_SESSION_TABLE_COL_NUM - 1];
		db_key_t update_keys[RO_SESSION_TABLE_COL_NUM - 1] = {&h_entry_column,
				&h_id_column, &session_id_column, &dlg_h_entry_column,
				&dlg_h_id_column, &direction_column, &asserted_column,
				&callee_column, &start_time_col, &last_event_ts_column,
				&reserved_sec_column, &valid_for_column, &state_column,
				&incoming_trunk_id_column, &outgoing_trunk_id_column,
				&rating_group_column, &service_identifier_column};

		db_set_int_val(values, HASH_ENTRY_COL_IDX - 1, ro_session->h_entry);
		db_set_int_val(values, HASH_ID_COL_IDX - 1, ro_session->h_id);
		db_set_str_val(
				values, SESSION_ID_COL_IDX - 1, &ro_session->ro_session_id);
		db_set_int_val(
				values, DLG_HASH_ENTRY_COL_IDX - 1, ro_session->dlg_h_entry);
		db_set_int_val(values, DLG_HASH_ID_COL_IDX - 1, ro_session->dlg_h_id);
		db_set_int_val(values, DIRECTION_COL_IDX - 1, ro_session->direction);
		db_set_str_val(values, ASSERTED_ID_COL_IDX - 1,
				&ro_session->asserted_identity);
		db_set_str_val(values, CALLEE_COL_IDX - 1,
				&ro_session->called_asserted_identity);
		db_set_datetime_val(values, START_TIME_COL_IDX - 1,
				ro_session->start_time / 1000000);
		db_set_datetime_val(values, LAST_EVENT_TS_COL_IDX - 1,
				ro_session->last_event_timestamp / 1000000);
		db_set_int_val(
				values, RESERVED_SECS_COL_IDX - 1, ro_session->reserved_secs);
		db_set_int_val(values, VALID_FOR_COL_IDX - 1, ro_session->valid_for);
		db_set_int_val(values, STATE_COL_IDX - 1, ro_session->active);
		db_set_str_val(values, INCOMING_TRUNK_ID_COL_IDX - 1,
				&ro_session->incoming_trunk_id);
		db_set_str_val(values, OUTGOING_TRUNK_ID_COL_IDX - 1,
				&ro_session->outgoing_trunk_id);
		db_set_int_val(
				values, RATING_GROUP_COL_IDX - 1, ro_session->rating_group);
		db_set_int_val(values, SERVICE_IDENTIFIER_COL_IDX - 1,
				ro_session->service_identifier);

		LM_DBG("Updating ro_session in database\n");
		if((ro_dbf.update(ro_db_handle, update_keys /*match*/, 0 /*match*/,
				   values /*match*/, update_keys /*update*/, values /*update*/,
				   3 /*match*/, 13 /*update*/))
				!= 0) {
			LM_ERR("could not update Ro session information in DB... "
				   "continuing\n");
			goto error;
		}
		ro_session->flags &= ~RO_SESSION_FLAG_CHANGED;
	} else if((ro_session->flags & RO_SESSION_FLAG_DELETED) != 0) {
		db_val_t values[3];
		db_key_t match_keys[3] = {
				&h_entry_column, &h_id_column, &session_id_column};

		db_set_int_val(values, HASH_ENTRY_COL_IDX - 1, ro_session->h_entry);
		db_set_int_val(values, HASH_ID_COL_IDX - 1, ro_session->h_id);
		db_set_str_val(
				values, SESSION_ID_COL_IDX - 1, &ro_session->ro_session_id);

		if(ro_dbf.delete(ro_db_handle, match_keys, 0, values, 3) < 0) {
			LM_ERR("failed to delete ro session database information... "
				   "continuing\n");
			return -1;
		}
	} else {
		LM_WARN("Asked to update Ro session in strange state [%d]\n",
				ro_session->flags);
	}

	return 0;

error:
	return -1;
}

int update_ro_dbinfo(struct ro_session *ro_session)
{
	struct ro_session_entry entry;
	/* lock the entry */
	entry = (ro_session_table->entries)[ro_session->h_entry];
	ro_session_lock(ro_session_table, &entry);
	if(update_ro_dbinfo_unsafe(ro_session) != 0) {
		LM_ERR("failed to update ro_session in DB\n");
		ro_session_unlock(ro_session_table, &entry);
		return -1;
	}
	ro_session_unlock(ro_session_table, &entry);
	return 0;
}
