/*
message_storage.c
Copyright (C) 2012  Belledonne Communications, Grenoble, France

This program 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 program 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 "private.h"
#include "linphone/core.h"


#ifdef SQLITE_STORAGE_ENABLED

#ifndef PRIu64
#define PRIu64 "I64u"
#endif

#ifndef _WIN32
#if !defined(__QNXNTO__) && !defined(__ANDROID__)
#include <ctype.h>
#include <langinfo.h>
#include <locale.h>
#include <iconv.h>
#include <string.h>
#endif
#else
#include <Windows.h>
#endif

#define MAX_PATH_SIZE 1024

#include "sqlite3.h"
#include <assert.h>


static char *utf8_convert(const char *filename){
	char db_file_utf8[MAX_PATH_SIZE] = "";
#if defined(_WIN32)
	wchar_t db_file_utf16[MAX_PATH_SIZE]={0};
	MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, filename, -1, db_file_utf16, MAX_PATH_SIZE);
	WideCharToMultiByte(CP_UTF8, 0, db_file_utf16, -1, db_file_utf8, sizeof(db_file_utf8), NULL, NULL);
#elif defined(__QNXNTO__) || defined(__ANDROID__)
	strncpy(db_file_utf8, filename, MAX_PATH_SIZE - 1);
#else
	char db_file_locale[MAX_PATH_SIZE] = {'\0'};
	char *inbuf=db_file_locale, *outbuf=db_file_utf8;
	size_t inbyteleft = MAX_PATH_SIZE, outbyteleft = MAX_PATH_SIZE;
	iconv_t cb;

	if (strcasecmp("UTF-8", nl_langinfo(CODESET)) == 0) {
		strncpy(db_file_utf8, filename, MAX_PATH_SIZE - 1);
	} else {
		strncpy(db_file_locale, filename, MAX_PATH_SIZE-1);
		cb = iconv_open("UTF-8", nl_langinfo(CODESET));
		if (cb != (iconv_t)-1) {
			int ret;
			ret = iconv(cb, &inbuf, &inbyteleft, &outbuf, &outbyteleft);
			if(ret == -1) db_file_utf8[0] = '\0';
			iconv_close(cb);
		}
	}
#endif
	return ms_strdup(db_file_utf8);
}


int _linphone_sqlite3_open(const char *db_file, sqlite3 **db) {
	char* errmsg = NULL;
	int ret;
	int flags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE;

#if TARGET_OS_IPHONE
	/* the secured filesystem of the iPHone doesn't allow writing while the app is in background mode, which is problematic.
	 * We workaround by asking that the open is made with no protection*/
	flags |= SQLITE_OPEN_FILEPROTECTION_NONE;
#endif

	/*since we plug our vfs into sqlite, we convert to UTF-8.
	 * On Windows, the filename has to be converted back to windows native charset.*/
	char *utf8_filename = utf8_convert(db_file);
	ret = sqlite3_open_v2(utf8_filename, db, flags, LINPHONE_SQLITE3_VFS);
	ms_free(utf8_filename);

	if (ret != SQLITE_OK) return ret;
	// Some platforms do not provide a way to create temporary files which are needed
	// for transactions... so we work in memory only
	// see http ://www.sqlite.org/compile.html#temp_store
	ret = sqlite3_exec(*db, "PRAGMA temp_store=MEMORY", NULL, NULL, &errmsg);
	if (ret != SQLITE_OK) {
		ms_error("Cannot set sqlite3 temporary store to memory: %s.", errmsg);
		sqlite3_free(errmsg);
	}

	/* the lines below have been disabled because they are likely an
	 * outdated hack */
#if 0 && TARGET_OS_IPHONE
	ret = sqlite3_exec(*db, "PRAGMA journal_mode = OFF", NULL, NULL, &errmsg);
	if (ret != SQLITE_OK) {
		ms_error("Cannot set sqlite3 journal_mode to off: %s.", errmsg);
		sqlite3_free(errmsg);
	}
#endif
	return ret;
}
#endif



#ifdef SQLITE_STORAGE_ENABLED


static LinphoneChatMessage * get_weak_message(LinphoneChatRoom *cr, unsigned int storage_id) {
	LinphoneChatMessage *cm;
	bctbx_list_t *item;
	for (item = cr->weak_messages; item != NULL; item = bctbx_list_next(item)) {
		cm = (LinphoneChatMessage *)bctbx_list_get_data(item);
		if (linphone_chat_message_get_storage_id(cm) == storage_id)
			return linphone_chat_message_ref(cm);
	}
	return NULL;
}

static ORTP_INLINE LinphoneChatMessage* get_transient_message(LinphoneChatRoom* cr, unsigned int storage_id){
	bctbx_list_t* transients = cr->transient_messages;
	LinphoneChatMessage* chat;
	while( transients ){
		chat = (LinphoneChatMessage*)transients->data;
		if(chat->storage_id == storage_id){
			return linphone_chat_message_ref(chat);
		}
		transients = transients->next;
	}
	return NULL;
}

/* DB layout:
 * | 0  | storage_id
 * | 1  | type
 * | 2  | subtype
 * | 3  | name
 * | 4  | encoding
 * | 5  | size
 * | 6  | data (currently not stored)
 * | 7  | key size
 * | 8  | key
 */
// Callback for sql request when getting linphone content
static int callback_content(void *data, int argc, char **argv, char **colName) {
	LinphoneChatMessage *message = (LinphoneChatMessage *)data;

	if (message->file_transfer_information) {
		linphone_content_unref(message->file_transfer_information);
		message->file_transfer_information = NULL;
	}
	message->file_transfer_information = linphone_content_new();
	if (argv[1]) linphone_content_set_type(message->file_transfer_information, argv[1]);
	if (argv[2]) linphone_content_set_subtype(message->file_transfer_information, argv[2]);
	if (argv[3]) linphone_content_set_name(message->file_transfer_information, argv[3]);
	if (argv[4]) linphone_content_set_encoding(message->file_transfer_information, argv[4]);
	linphone_content_set_size(message->file_transfer_information, (size_t)atoi(argv[5]));
	if (argv[8]) linphone_content_set_key(message->file_transfer_information, argv[8], (size_t)atol(argv[7]));

	return 0;
}

static void fetch_content_from_database(sqlite3 *db, LinphoneChatMessage *message, int content_id) {
	char* errmsg = NULL;
	int ret;
	char * buf;

	buf = sqlite3_mprintf("SELECT * FROM content WHERE id = %i", content_id);
	ret = sqlite3_exec(db, buf, callback_content, message, &errmsg);
	if (ret != SQLITE_OK) {
		ms_error("Error in creation: %s.", errmsg);
		sqlite3_free(errmsg);
	}
	sqlite3_free(buf);
}



// Called when fetching all conversations from database
static int callback_all(void *data, int argc, char **argv, char **colName){
	LinphoneCore* lc = (LinphoneCore*) data;
	char* address = argv[0];
	LinphoneAddress *addr = linphone_address_new(address);
	if (addr){
		linphone_core_get_chat_room(lc, addr);
		linphone_address_unref(addr);
	}
	return 0;
}

/* DB layout:
 * | 0  | storage_id
 * | 1  | localContact
 * | 2  | remoteContact
 * | 3  | direction flag (LinphoneChatMessageDir)
 * | 4  | message (text content of the message)
 * | 5  | time (unused now, used to be string-based timestamp, replaced by the utc timestamp)
 * | 6  | read flag (no longer used, replaced by the LinphoneChatMessageStateDisplayed state)
 * | 7  | status (LinphoneChatMessageState)
 * | 8  | external body url (deprecated file transfer system)
 * | 9  | utc timestamp
 * | 10 | app data text
 * | 11 | linphone content id (LinphoneContent describing a file transfer)
 * | 12 | message id (used for IMDN)
 * | 13 | content type (of the message field [must be text representable])
 * | 14 | secured flag
 */
static int create_chat_message(void *data, int argc, char **argv, char **colName){
	LinphoneChatRoom *cr = (LinphoneChatRoom *)data;
	unsigned int storage_id = (unsigned int)atoi(argv[0]);
	LinphoneChatMessage* new_message;

	/* Check if the message exists in the weak messages list, in which case we should return that one. */
	new_message = get_weak_message(cr, storage_id);
	if (new_message == NULL) {
		/* Check if the message exists in the transient list, in which case we should return that one. */
		new_message = get_transient_message(cr, storage_id);
	}
	if (new_message == NULL) {
		new_message = linphone_chat_room_create_message(cr, argv[4]);

		if(atoi(argv[3])==LinphoneChatMessageIncoming){
			new_message->dir=LinphoneChatMessageIncoming;
			linphone_chat_message_set_from(new_message,linphone_chat_room_get_peer_address(cr));
			new_message->to = NULL; /*will be filled at the end */
		} else {
			new_message->dir=LinphoneChatMessageOutgoing;
			new_message->from = NULL; /*will be filled at the end */
			linphone_chat_message_set_to(new_message,linphone_chat_room_get_peer_address(cr));
		}

		new_message->time = (time_t)atol(argv[9]);
		new_message->is_read=atoi(argv[6]);
		new_message->state=atoi(argv[7]);
		new_message->storage_id=storage_id;
		new_message->external_body_url= ms_strdup(argv[8]);
		new_message->appdata = ms_strdup(argv[10]);
		new_message->message_id = ms_strdup(argv[12]);
		linphone_chat_message_set_content_type(new_message, argv[13]);
		new_message->is_secured = (bool_t)atoi(argv[14]);

		if (argv[11] != NULL) {
			int id = atoi(argv[11]);
			if (id >= 0) {
				fetch_content_from_database(cr->lc->db, new_message, id);
			}
		}

		/* Fix content type for old messages that were stored without it */
		if (new_message->content_type == NULL) {
			if (new_message->file_transfer_information != NULL) {
				new_message->content_type = ms_strdup("application/vnd.gsma.rcs-ft-http+xml");
			} else if (new_message->external_body_url != NULL) {
				new_message->content_type = ms_strdup("message/external-body");
			} else {
				new_message->content_type = ms_strdup("text/plain");
			}
		}

		/* Add the new message to the weak messages list. */
		linphone_chat_room_add_weak_message(cr, new_message);
	}
	cr->messages_hist=bctbx_list_prepend(cr->messages_hist,new_message);

	return 0;
}

void linphone_sql_request_message(sqlite3 *db,const char *stmt,LinphoneChatRoom *cr){
	char* errmsg=NULL;
	int ret;
	ret=sqlite3_exec(db,stmt,create_chat_message,cr,&errmsg);
	if(ret != SQLITE_OK) {
		ms_error("Error in creation: %s.", errmsg);
		sqlite3_free(errmsg);
	}
}

int linphone_sql_request(sqlite3* db,const char *stmt){
	char* errmsg=NULL;
	int ret;
	ret=sqlite3_exec(db,stmt,NULL,NULL,&errmsg);
	if(ret != SQLITE_OK) {
		ms_error("linphone_sql_request: statement %s -> error sqlite3_exec(): %s.", stmt, errmsg);
		sqlite3_free(errmsg);
	}
	return ret;
}

// Process the request to fetch all chat contacts
void linphone_sql_request_all(sqlite3* db,const char *stmt, LinphoneCore* lc){
	char* errmsg=NULL;
	int ret;
	ret=sqlite3_exec(db,stmt,callback_all,lc,&errmsg);
	if(ret != SQLITE_OK) {
		ms_error("linphone_sql_request_all: error sqlite3_exec(): %s.", errmsg);
		sqlite3_free(errmsg);
	}
}

static int linphone_chat_message_store_content(LinphoneChatMessage *msg) {
	LinphoneCore *lc = linphone_chat_room_get_core(msg->chat_room);
	int id = -1;
	if (lc->db) {
		LinphoneContent *content = msg->file_transfer_information;
		char *buf = sqlite3_mprintf("INSERT INTO content VALUES(NULL,%Q,%Q,%Q,%Q,%i,%Q,%lld,%Q);",
						linphone_content_get_type(content),
						linphone_content_get_subtype(content),
						linphone_content_get_name(content),
						linphone_content_get_encoding(content),
						linphone_content_get_size(content),
						NULL,
						(int64_t)linphone_content_get_key_size(content),
						linphone_content_get_key(content)
					);
		linphone_sql_request(lc->db, buf);
		sqlite3_free(buf);
		id = (unsigned int) sqlite3_last_insert_rowid (lc->db);
	}
	return id;
}

unsigned int linphone_chat_message_store(LinphoneChatMessage *msg){
	LinphoneCore *lc=linphone_chat_room_get_core(msg->chat_room);
	int id = 0;

	if (lc->db){
		int content_id = -1;
		char *peer;
		char *local_contact;
		char *buf;
		if (msg->file_transfer_information) {
			content_id = linphone_chat_message_store_content(msg);
		}

		peer=linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(msg->chat_room));
		local_contact=linphone_address_as_string_uri_only(linphone_chat_message_get_local_address(msg));
		buf = sqlite3_mprintf("INSERT INTO history VALUES(NULL,%Q,%Q,%i,%Q,%Q,%i,%i,%Q,%lld,%Q,%i,%Q,%Q,%i);",
						local_contact,
						peer,
						msg->dir,
						msg->message,
						"-1", /* use UTC field now */
						FALSE, /* use state == LinphoneChatMessageStateDisplayed now */
						msg->state,
						msg->external_body_url,
						(int64_t)msg->time,
						msg->appdata,
						content_id,
						msg->message_id,
						msg->content_type,
						(int)msg->is_secured
					);
		linphone_sql_request(lc->db,buf);
		sqlite3_free(buf);
		ms_free(local_contact);
		ms_free(peer);
		id = (unsigned int) sqlite3_last_insert_rowid (lc->db);
	}
	return id;
}

void linphone_chat_message_store_update(LinphoneChatMessage *msg) {
	LinphoneCore *lc = linphone_chat_room_get_core(msg->chat_room);

	if (lc->db) {
		char *peer;
		char *local_contact;
		char *buf;

		peer = linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(msg->chat_room));
		local_contact = linphone_address_as_string_uri_only(linphone_chat_message_get_local_address(msg));
		buf = sqlite3_mprintf("UPDATE history SET"
			" localContact = %Q,"
			" remoteContact = %Q,"
			" message = %Q,"
			" status = %i,"
			" appdata = %Q,"
			" messageId = %Q,"
			" content_type = %Q"
			" WHERE (id = %u);",
			local_contact,
			peer,
			msg->message,
			msg->state,
			msg->appdata,
			msg->message_id,
			msg->content_type,
			msg->storage_id
		);
		linphone_sql_request(lc->db, buf);
		sqlite3_free(buf);
		ms_free(local_contact);
		ms_free(peer);
	}
}

void linphone_chat_message_store_state(LinphoneChatMessage *msg){
	LinphoneCore *lc=msg->chat_room->lc;
	if (lc->db){
		char *buf=sqlite3_mprintf("UPDATE history SET status=%i WHERE (id = %u);",
								  msg->state,msg->storage_id);
		linphone_sql_request(lc->db,buf);
		sqlite3_free(buf);
	}
}

void linphone_chat_message_store_appdata(LinphoneChatMessage* msg){
	LinphoneCore *lc=msg->chat_room->lc;
	if (lc->db){
		char *buf=sqlite3_mprintf("UPDATE history SET appdata=%Q WHERE id=%u;",
								  msg->appdata,msg->storage_id);
		linphone_sql_request(lc->db,buf);
		sqlite3_free(buf);
	}
}

void linphone_chat_room_mark_as_read(LinphoneChatRoom *cr){
	LinphoneCore *lc=linphone_chat_room_get_core(cr);
	bctbx_list_t *item;
	char *peer;
	char *buf;

	if (lc->db==NULL) return ;

	// optimization: do not modify the database if no message is marked as unread
	if(linphone_chat_room_get_unread_messages_count(cr) == 0) return;

	peer=linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr));
	buf = sqlite3_mprintf("SELECT * FROM history WHERE remoteContact = %Q AND direction = %i AND status != %i", peer, LinphoneChatMessageIncoming, LinphoneChatMessageStateDisplayed);
	linphone_sql_request_message(lc->db, buf, cr);
	sqlite3_free(buf);
	for (item = cr->messages_hist; item != NULL; item = bctbx_list_next(item)) {
		LinphoneChatMessage *cm = (LinphoneChatMessage *)bctbx_list_get_data(item);
		linphone_chat_message_send_display_notification(cm);
	}
	bctbx_list_free_with_data(cr->messages_hist, (bctbx_list_free_func)linphone_chat_message_unref);
	cr->messages_hist = NULL;
	buf=sqlite3_mprintf("UPDATE history SET status=%i WHERE remoteContact=%Q AND direction=%i;",
		LinphoneChatMessageStateDisplayed, peer, LinphoneChatMessageIncoming);
	linphone_sql_request(lc->db,buf);
	sqlite3_free(buf);
	ms_free(peer);

	if (cr->pending_message) {
		linphone_chat_message_set_state(cr->pending_message, LinphoneChatMessageStateDisplayed);
		linphone_chat_message_send_display_notification(cr->pending_message);
	}

	cr->unread_count = 0;
}

void linphone_chat_room_update_url(LinphoneChatRoom *cr, LinphoneChatMessage *msg) {
	LinphoneCore *lc=linphone_chat_room_get_core(cr);
	char *buf;

	if (lc->db==NULL) return ;

	buf=sqlite3_mprintf("UPDATE history SET url=%Q WHERE id=%u;",msg->external_body_url,msg->storage_id);
	linphone_sql_request(lc->db,buf);
	sqlite3_free(buf);
}

static int linphone_chat_room_get_messages_count(LinphoneChatRoom *cr, bool_t unread_only){
	LinphoneCore *lc=linphone_chat_room_get_core(cr);
	int numrows=0;
	char *peer;
	char *buf;
	char *option = NULL;
	sqlite3_stmt *selectStatement;
	int returnValue;

	if (lc->db==NULL) return 0;

	// optimization: do not read database if the count is already available in memory
	if(unread_only && cr->unread_count >= 0) return cr->unread_count;

	peer=linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr));
	if (unread_only) {
		option = bctbx_strdup_printf("AND status!=%i AND direction=%i", LinphoneChatMessageStateDisplayed, LinphoneChatMessageIncoming);
	}
	buf=sqlite3_mprintf("SELECT count(*) FROM history WHERE remoteContact = %Q %s;",peer,unread_only?option:"");
	returnValue = sqlite3_prepare_v2(lc->db,buf,-1,&selectStatement,NULL);
	if (returnValue == SQLITE_OK){
		if(sqlite3_step(selectStatement) == SQLITE_ROW){
			numrows= sqlite3_column_int(selectStatement, 0);
		}
	}
	sqlite3_finalize(selectStatement);
	sqlite3_free(buf);
	ms_free(peer);

	/* no need to test the sign of cr->unread_count here
	 * because it has been tested above */
	if(unread_only) {
		cr->unread_count = numrows;
		if (option) bctbx_free(option);
	}

	return numrows;
}

int linphone_chat_room_get_unread_messages_count(LinphoneChatRoom *cr){
	return linphone_chat_room_get_messages_count(cr, TRUE);
}

int linphone_chat_room_get_history_size(LinphoneChatRoom *cr){
	return linphone_chat_room_get_messages_count(cr, FALSE);
}

void linphone_chat_room_delete_message(LinphoneChatRoom *cr, LinphoneChatMessage *msg) {
	LinphoneCore *lc=cr->lc;
	char *buf;

	if (lc->db==NULL) return ;

	buf=sqlite3_mprintf("DELETE FROM history WHERE id = %u;", msg->storage_id);
	linphone_sql_request(lc->db,buf);
	sqlite3_free(buf);

	/* Invalidate unread_count when we modify the database, so that next
	 time we need it it will be recomputed from latest database state */
	cr->unread_count = -1;
}

void linphone_chat_room_delete_history(LinphoneChatRoom *cr){
	LinphoneCore *lc=cr->lc;
	char *peer;
	char *buf;

	if (lc->db==NULL) return ;

	peer=linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr));
	buf=sqlite3_mprintf("DELETE FROM history WHERE remoteContact = %Q;",peer);
	linphone_sql_request(lc->db,buf);
	sqlite3_free(buf);
	ms_free(peer);

	if(cr->unread_count > 0) cr->unread_count = 0;
}

bctbx_list_t *linphone_chat_room_get_history_range(LinphoneChatRoom *cr, int startm, int endm){
	LinphoneCore *lc=linphone_chat_room_get_core(cr);
	bctbx_list_t *ret;
	char *buf,*buf2;
	char *peer;
	uint64_t begin,end;
	int buf_max_size = 512;

	if (lc->db==NULL) return NULL;
	peer = linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr));

	cr->messages_hist = NULL;

	/*since we want to append query parameters depending on arguments given, we use malloc instead of sqlite3_mprintf*/
	buf=ms_malloc(buf_max_size);
	buf=sqlite3_snprintf(buf_max_size-1,buf,"SELECT * FROM history WHERE remoteContact = %Q ORDER BY id DESC",peer);


	if (startm<0) startm=0;

	if ((endm>0&&endm>=startm) || (startm == 0 && endm == 0) ){
		buf2=ms_strdup_printf("%s LIMIT %i ",buf,endm+1-startm);
		ms_free(buf);
		buf = buf2;
	}else if(startm>0){
		ms_message("%s(): end is lower than start (%d < %d). Assuming no end limit.",__FUNCTION__,endm,startm);
		buf2=ms_strdup_printf("%s LIMIT -1",buf);
		ms_free(buf);
		buf = buf2;
	}

	if (startm>0){
		buf2=ms_strdup_printf("%s OFFSET %i ",buf,startm);
		ms_free(buf);
		buf = buf2;
	}

	begin=ortp_get_cur_time_ms();
	linphone_sql_request_message(lc->db,buf,cr);
	end=ortp_get_cur_time_ms();

	if (endm+1-startm > 1) {
		//display message only if at least 2 messages are loaded
		ms_message("%s(): completed in %i ms",__FUNCTION__, (int)(end-begin));
	}
	ms_free(buf);

	if (cr->messages_hist) {
		//fill local addr with core identity instead of per message
		LinphoneAddress* local_addr = linphone_address_new(linphone_core_get_identity(cr->lc));
		bctbx_list_t* it = cr->messages_hist;
		while (it) {
			LinphoneChatMessage* msg = it->data;
			if (msg->dir == LinphoneChatMessageOutgoing) {
				if (msg->from != NULL) linphone_address_unref(msg->from);
				msg->from = linphone_address_ref(local_addr);
			} else {
				msg->to = linphone_address_ref(local_addr);
			}
			it = it->next;
		}
		linphone_address_unref(local_addr);
	}

	ret=cr->messages_hist;
	cr->messages_hist=NULL;
	ms_free(peer);
	return ret;
}

bctbx_list_t *linphone_chat_room_get_history(LinphoneChatRoom *cr,int nb_message){
	return linphone_chat_room_get_history_range(cr, 0, nb_message-1);
}


bctbx_list_t* linphone_chat_room_find_messages(LinphoneChatRoom *cr, const char *message_id) {
	LinphoneCore *lc = linphone_chat_room_get_core(cr);
	char *buf;
	char *peer;
	bctbx_list_t* messages;
	
	if (lc->db == NULL) return NULL;
	peer = linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr));
	cr->messages_hist = NULL;
	buf = sqlite3_mprintf("SELECT * FROM history WHERE remoteContact = %Q AND messageId = %Q", peer, message_id);
	linphone_sql_request_message(lc->db, buf, cr);
	sqlite3_free(buf);
	ms_free(peer);
	messages = cr->messages_hist;
	cr->messages_hist = NULL;
	return messages;
}

LinphoneChatMessage * linphone_chat_room_find_message_with_dir(LinphoneChatRoom *cr, const char *message_id, LinphoneChatMessageDir dir) {
	bctbx_list_t* messages = linphone_chat_room_find_messages(cr, message_id);
	bctbx_list_t* it;
	LinphoneChatMessage *ret = NULL;
	for (it = messages; it != NULL; it = it->next) {
		LinphoneChatMessage * cm = (LinphoneChatMessage*)it->data;
		if (cm->dir == dir) {
			linphone_chat_message_ref(cm);
			ret = cm;
			break;
		}
	}
	if (messages)
		bctbx_list_free_with_data(messages, (bctbx_list_free_func)linphone_chat_message_unref);
	
	return ret;

}

LinphoneChatMessage * linphone_chat_room_find_message(LinphoneChatRoom *cr, const char *message_id) {
	bctbx_list_t* messages = linphone_chat_room_find_messages(cr, message_id);
	LinphoneChatMessage *cm = NULL;
	if (messages) {
		cm = (LinphoneChatMessage *)bctbx_list_nth_data(messages, 0);
		linphone_chat_message_ref(cm);
		bctbx_list_free_with_data(messages, (bctbx_list_free_func)linphone_chat_message_unref);
	}
	return cm;
}

static void linphone_create_history_table(sqlite3* db){
	char* errmsg=NULL;
	int ret;
	ret=sqlite3_exec(db,"CREATE TABLE IF NOT EXISTS history ("
							 "id            INTEGER PRIMARY KEY AUTOINCREMENT,"
							 "localContact  TEXT NOT NULL,"
							 "remoteContact TEXT NOT NULL,"
							 "direction     INTEGER,"
							 "message       TEXT,"
							 "time          TEXT NOT NULL,"
							 "read          INTEGER,"
							 "status        INTEGER"
						");",
			0,0,&errmsg);
	if(ret != SQLITE_OK) {
		ms_error("Error in creation: %s.\n", errmsg);
		sqlite3_free(errmsg);
	}
}


static const char *days[]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
static const char *months[]={"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};
static time_t parse_time_from_db( const char* time ){
	/* messages used to be stored in the DB by using string-based time */
	struct tm ret={0};
	char tmp1[80]={0};
	char tmp2[80]={0};
	int i,j;
	time_t parsed = 0;

	if( sscanf(time,"%3c %3c%d%d:%d:%d %d",tmp1,tmp2,&ret.tm_mday,
			   &ret.tm_hour,&ret.tm_min,&ret.tm_sec,&ret.tm_year) == 7 ){
		ret.tm_year-=1900;
		for(i=0;i<7;i++) {
			if(strcmp(tmp1,days[i])==0) ret.tm_wday=i;
		}
		for(j=0;j<12;j++) {
			if(strcmp(tmp2,months[j])==0) ret.tm_mon=j;
		}
		ret.tm_isdst=-1;
		parsed = mktime(&ret);
	}
	return parsed;
}


static int migrate_messages_timestamp(void* data,int argc, char** argv, char** column_names) {
	time_t new_time = parse_time_from_db(argv[1]);
	if( new_time ){
		/* replace 'time' by -1 and set 'utc' to the timestamp */
		char *buf =	sqlite3_mprintf("UPDATE history SET utc=%lld,time='-1' WHERE id=%i;", (int64_t)new_time, atoi(argv[0]));
		if( buf) {
			linphone_sql_request((sqlite3*)data, buf);
			sqlite3_free(buf);
		}
	} else {
		ms_warning("Cannot parse time %s from id %s", argv[1], argv[0]);
	}
	return 0;
}

static void linphone_migrate_timestamps(sqlite3* db){
	int ret;
	char* errmsg = NULL;
	uint64_t begin=ortp_get_cur_time_ms();

	linphone_sql_request(db,"BEGIN TRANSACTION");

	ret = sqlite3_exec(db,"SELECT id,time,direction FROM history WHERE time != '-1';", migrate_messages_timestamp, db, &errmsg);
	if( ret != SQLITE_OK ){
		ms_warning("Error migrating outgoing messages: %s.\n", errmsg);
		sqlite3_free(errmsg);
		linphone_sql_request(db, "ROLLBACK");
	} else {
		uint64_t end;
		linphone_sql_request(db, "COMMIT");
		end=ortp_get_cur_time_ms();
		ms_message("Migrated message timestamps to UTC in %lu ms",(unsigned long)(end-begin));
	}
}

static void linphone_update_history_table(sqlite3* db) {
	char* errmsg=NULL;
	char *buf;
	int ret;

	// for image url storage
	ret=sqlite3_exec(db,"ALTER TABLE history ADD COLUMN url TEXT;",NULL,NULL,&errmsg);
	if(ret != SQLITE_OK) {
		ms_message("Table already up to date: %s.", errmsg);
		sqlite3_free(errmsg);
	} else {
		ms_debug("Table history updated successfully for URL.");
	}

	// for UTC timestamp storage
	ret = sqlite3_exec(db, "ALTER TABLE history ADD COLUMN utc INTEGER;", NULL,NULL,&errmsg);
	if( ret != SQLITE_OK ){
		ms_message("Table already up to date: %s.", errmsg);
		sqlite3_free(errmsg);
	} else {
		ms_debug("Table history updated successfully for UTC.");
		// migrate from old text-based timestamps to unix time-based timestamps
		linphone_migrate_timestamps(db);
	}

	// new field for app-specific storage
	ret=sqlite3_exec(db,"ALTER TABLE history ADD COLUMN appdata TEXT;",NULL,NULL,&errmsg);
	if(ret != SQLITE_OK) {
		ms_message("Table already up to date: %s.", errmsg);
		sqlite3_free(errmsg);
	} else {
		ms_debug("Table history updated successfully for app-specific data.");
	}

	// new field for linphone content storage
	ret=sqlite3_exec(db,"ALTER TABLE history ADD COLUMN content INTEGER;",NULL,NULL,&errmsg);
	if(ret != SQLITE_OK) {
		ms_message("Table already up to date: %s.", errmsg);
		sqlite3_free(errmsg);
	} else {
		ms_debug("Table history updated successfully for content data.");
		ret = sqlite3_exec(db,"CREATE TABLE IF NOT EXISTS content ("
							"id INTEGER PRIMARY KEY AUTOINCREMENT,"
							"type TEXT,"
							"subtype TEXT,"
							"name TEXT,"
							"encoding TEXT,"
							"size INTEGER,"
							"data BLOB"
						");",
			0,0,&errmsg);
		if(ret != SQLITE_OK) {
			ms_error("Error in creation: %s.\n", errmsg);
			sqlite3_free(errmsg);
		} else {
			ms_debug("Table content successfully created.");
		}
	}

	// new fields for content key storage when using lime
	ret=sqlite3_exec(db,"ALTER TABLE content ADD COLUMN key_size INTEGER;",NULL,NULL,&errmsg);
	if(ret != SQLITE_OK) {
		ms_message("Table already up to date: %s.", errmsg);
		sqlite3_free(errmsg);
	} else {
		ret=sqlite3_exec(db,"ALTER TABLE content ADD COLUMN key TEXT;",NULL,NULL,&errmsg);
		if(ret != SQLITE_OK) {
			ms_message("Table already up to date: %s.", errmsg);
			sqlite3_free(errmsg);
		} else {
			ms_debug("Table history content successfully for lime key storage data.");
		}
	}

	// new field for message_id
	ret = sqlite3_exec(db, "ALTER TABLE history ADD COLUMN messageId TEXT;", NULL, NULL, &errmsg);
	if (ret != SQLITE_OK) {
		ms_message("Table already up to date: %s", errmsg);
		sqlite3_free(errmsg);
	} else {
		ms_message("Table history updated successfully for messageId data.");
	}

	// Convert is_read to LinphoneChatMessageStateDisplayed
	buf = sqlite3_mprintf("UPDATE history SET status=%i WHERE read=1 AND direction=%i;", LinphoneChatMessageStateDisplayed, LinphoneChatMessageIncoming);
	linphone_sql_request(db, buf);
	sqlite3_free(buf);

	/* New field for content type */
	ret = sqlite3_exec(db, "ALTER TABLE history ADD COLUMN content_type TEXT;", NULL, NULL, &errmsg);
	if (ret != SQLITE_OK) {
		ms_message("Table already up to date: %s", errmsg);
		sqlite3_free(errmsg);
	} else {
		ms_message("Table history updated successfully for content_type data.");
	}

	// new field for secured flag
	ret = sqlite3_exec(db, "ALTER TABLE history ADD COLUMN is_secured INTEGER DEFAULT 0;", NULL, NULL, &errmsg);
	if (ret != SQLITE_OK) {
		ms_message("Table already up to date: %s", errmsg);
		sqlite3_free(errmsg);
	} else {
		ms_message("Table history updated successfully for is_secured data.");
	}
}

static void linphone_fix_outgoing_messages_state(sqlite3* db) {
	/* Convert Idle and InProgress states of outgoing messages to NotDelivered */
	char *buf = sqlite3_mprintf("UPDATE history SET status=%i WHERE direction=%i AND (status=%i OR status=%i);",
		LinphoneChatMessageStateNotDelivered, LinphoneChatMessageOutgoing, LinphoneChatMessageStateIdle, LinphoneChatMessageStateInProgress);
	linphone_sql_request(db, buf);
	sqlite3_free(buf);
}

void linphone_message_storage_init_chat_rooms(LinphoneCore *lc) {
	char *buf;

	if (lc->db==NULL) return;
	buf=sqlite3_mprintf("SELECT remoteContact FROM history GROUP BY remoteContact;");
	linphone_sql_request_all(lc->db,buf,lc);
	sqlite3_free(buf);
}

static void _linphone_message_storage_profile(void*data,const char*statement, sqlite3_uint64 duration){
	ms_warning("SQL statement '%s' took %" PRIu64 " microseconds", statement, (uint64_t)(duration / 1000LL) );
}

static void linphone_message_storage_activate_debug(sqlite3* db, bool_t debug){
	if( debug  ){
		sqlite3_profile(db, _linphone_message_storage_profile, NULL );
	} else {
		sqlite3_profile(db, NULL, NULL );
	}
}

void linphone_core_message_storage_set_debug(LinphoneCore *lc, bool_t debug){

	lc->debug_storage = debug;

	if( lc->db ){
		linphone_message_storage_activate_debug(lc->db, debug);
	}
}

void linphone_core_message_storage_init(LinphoneCore *lc){
	int ret;
	const char *errmsg;
	sqlite3 *db = NULL;

	linphone_core_message_storage_close(lc);

	ret=_linphone_sqlite3_open(lc->chat_db_file,&db);
	if(ret != SQLITE_OK) {
		errmsg=sqlite3_errmsg(db);
		ms_error("Error in the opening: %s.\n", errmsg);
		sqlite3_close(db);
		return;
	}

	linphone_message_storage_activate_debug(db, lc->debug_storage);

	linphone_create_history_table(db);
	linphone_update_history_table(db);
	linphone_fix_outgoing_messages_state(db);
	lc->db=db;

	// Create a chatroom for each contact in the chat history
	linphone_message_storage_init_chat_rooms(lc);
}

void linphone_core_message_storage_close(LinphoneCore *lc){
	if (lc->db){
		sqlite3_close(lc->db);
		lc->db=NULL;
	}
}

#else

unsigned int linphone_chat_message_store(LinphoneChatMessage *cr){
	return 0;
}

void linphone_chat_message_store_state(LinphoneChatMessage *cr){
}

void linphone_chat_message_store_appdata(LinphoneChatMessage *msg){
}

void linphone_chat_room_mark_as_read(LinphoneChatRoom *cr){
}

bctbx_list_t *linphone_chat_room_get_history(LinphoneChatRoom *cr,int nb_message){
	return NULL;
}

bctbx_list_t *linphone_chat_room_get_history_range(LinphoneChatRoom *cr, int begin, int end){
	return NULL;
}

LinphoneChatMessage * linphone_chat_room_find_message(LinphoneChatRoom *cr, const char *message_id) {
	return NULL;
}

void linphone_chat_room_delete_message(LinphoneChatRoom *cr, LinphoneChatMessage *msg) {
}

void linphone_chat_room_delete_history(LinphoneChatRoom *cr){
}

void linphone_message_storage_init_chat_rooms(LinphoneCore *lc) {
}

void linphone_core_message_storage_init(LinphoneCore *lc){
}

void linphone_core_message_storage_close(LinphoneCore *lc){
}

void linphone_chat_room_update_url(LinphoneChatRoom *cr, LinphoneChatMessage *msg) {
}

int linphone_chat_room_get_unread_messages_count(LinphoneChatRoom *cr){
	return 0;
}

int linphone_chat_room_get_history_size(LinphoneChatRoom *cr){
	return 0;
}

void linphone_chat_message_store_update(LinphoneChatMessage *msg){
}

#endif
