/**
 * An object to store the bookmarks in the HTML5 database.
 * Copyright 2008-2009 Palm, Inc. All rights reserved.
 */
/**
 * @constructor
 *
 * @param {Object} options
 * @param {function} onSuccess function to call if database is successfully opened/created.
 * @param {function} onFailure fucntion to call if there is an error opening the database.
 */
function BookmarkStore(options, onSuccess, onFailure){
	
	try {
		this.displayName = options.displayName || this.name;
		this.estimatedSize = options.estimatedSize;
		this.database = options.database;
				
		this.database.transaction(this._createTable.bind(this, onSuccess, onFailure));
	} catch (e) {
		Mojo.Log.logException(e, "BookmarkStore()");
	}
}

BookmarkStore.prototype._cleanupWrite = function(){
	delete this.writeTransaction;
	delete this.writeItems;
	delete this.onWriteSuccess;
	delete this.onWriteFailure;
};

/** @private */
BookmarkStore.prototype._writeSuccess = function(){
	if (this.writeItems.length === 0) {
		var successFunc = this.onWriteSuccess;
		this._cleanupWrite();
		successFunc();
	}
	else {
		this._writeNextBookmark();
	}
};

/** @private */
BookmarkStore.prototype._writeFailure = function(e, msg){
	Mojo.Log.error("_writeFailure");
	var failFunc = this.onWriteFailure;
	this._cleanupWrite();
	failFunc(e, msg);
};

/** @private */
BookmarkStore.prototype._formatDate = function(date){
	if (!date) {
		date = new Date();
	}
	return date.getUTC().getTime();
};

/** @private */
BookmarkStore.prototype._writeNextBookmark = function(){
	try {
		if (this.writeItems.length) {
			var item = this.writeItems.pop();
			
			if (item instanceof UrlReference) {
				var utcDateCreated = this._formatDate(item.date);
				var utcLastVisited = this._formatDate(item.lastVisited);
				var parent = 0; // For when we add support for folders.
				var index = item.index || 0;
				
				var defEntry = 0;
				if (item.defaultEntry) {
					defEntry = 1;
				}
				
				var sql = "INSERT INTO 'bookmarks' (url, title, date, parent, idx, defaultEntry, iconFile32, iconFile64, thumbnailFile, visitCount, lastVisited)" +
				" VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
				this.writeTransaction.executeSql(sql, [item.url, item.title, utcDateCreated, parent, index, defEntry, item.iconFile32, item.iconFile64, item.thumbnailFile, item.visitCount, utcLastVisited], this._writeSuccess.bind(this), this._writeFailure.bind(this));
			}
			else {
				Mojo.Log.warn("Don't have a UrlReference, skipping");
				this._writeSuccess();
			}
		}
		else {
			this._writeSuccess();
		}
	} 
	catch (e) {
		this._writeFailure(e, '_writeNextBookmark');
		throw e;
	}
};

/** @private */
BookmarkStore.prototype._writeAllBookmarks = function(transaction){

	this.writeTransaction = transaction;
	this._writeNextBookmark();
};

/**
 * Write the bookmark folder to the database.
 * @param {BookmarkFolder} folder
 * @param {function} onSuccess Function to call if this operation completes successfully.
 * @param {function} onFailure Function to call if this operation fails.
 */
BookmarkStore.prototype.write = function(folder, onSuccess, onFailure){
	try {
		if (this.writeTransaction === undefined) {
			this.writeItems = $A().concat(folder.contents);
			this.onWriteSuccess = onSuccess;
			this.onWriteFailure = onFailure;
			this.database.transaction(this._writeAllBookmarks.bind(this));
		}
		else {
			onFailure('Transaction already in progress', undefined);
		}
	} 
	catch (e) {
		Mojo.Log.logException(e);
		onFailure(undefined, e);
	}
};

/**
 * Increment the sort order index of the bookmarks and then insert a new bookmark.
 *
 * @param {UrlReference} urlRef
 * @param {function} onSuccess Function to call if this operation completes successfully.
 * @param {function} onFailure Function to call if this operation fails.
 *
 * @private
 */
BookmarkStore.prototype._incrementIndex = function(urlRef, onSuccess, onFailure, transaction) {
	var success = function() {
		this._insertBookmark(urlRef, onSuccess, onFailure, transaction);
	}.bind(this);
	this._executeSql(transaction, success, onFailure, "UPDATE bookmarks SET idx = idx + 1");
};

/**
 * Add a bookmark.
 *
 * @param {UrlReference} urlRef
 * @param {function} onSuccess Function to call if this operation completes successfully.
 * @param {function} onFailure Function to call if this operation fails.
 *
 * @private
 */
BookmarkStore.prototype._insertBookmark = function(urlRef, onSuccess, onFailure, transaction){

	var utcDateCreated = this._formatDate(urlRef.date);
	var utcLastVisited = this._formatDate(urlRef.lastVisited);
	var parent = 0; // For when we add support for folders.
	var index = urlRef.index || 0;
	
	var sql = "INSERT INTO 'bookmarks' (url, title, date, parent, idx, defaultEntry, iconFile32, iconFile64, thumbnailFile, visitCount, lastVisited)" +
	" VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
	this._executeSql(transaction, onSuccess, onFailure, sql, [urlRef.url, urlRef.title, utcDateCreated, parent, index, urlRef.defaultEntry ? 1 : 0, urlRef.iconFile32, urlRef.iconFile64, urlRef.thumbnailFile, urlRef.visitCount, utcLastVisited]);
};

BookmarkStore.prototype._updateVisitCount = function(urlRef, onSuccess, onFailure, transaction){
	Mojo.assert(urlRef.id !== undefined, "bookmark not from database.");
	urlRef.lastVisited = new Date();
	urlRef.visitCount++;
	
	var sql = 'UPDATE bookmarks SET visitCount = visitCount + 1, lastVisited = ' +
	this._formatDate(urlRef.lastVisited) +
	' WHERE id = ' +
	urlRef.id;
	
	this._executeSql(transaction, onSuccess, onFailure, sql);
};

BookmarkStore.prototype.updateVisitCount = function(urlRef, onSuccess, onFailure){

	try {
		this.database.transaction(this._updateVisitCount.bind(this, urlRef, onSuccess, onFailure));
	} 
	catch (e) {
		Mojo.Log.logException(e);
		onFailure(undefined, e);
	}
};

BookmarkStore.prototype._updateThumbnail = function(urlRef, onSuccess, onFailure, transaction){
	Mojo.assert(urlRef.id !== undefined, "bookmark not from database.");
	
	var sql = "UPDATE bookmarks SET thumbnailFile = '" + urlRef.thumbnailFile +
	"' WHERE id = " +
	urlRef.id;
	
	this._executeSql(transaction, onSuccess, onFailure, sql);
};

BookmarkStore.prototype.updateThumbnail = function(urlRef, onSuccess, onFailure){

	try {
		this.database.transaction(this._updateThumbnail.bind(this, urlRef, onSuccess, onFailure));
	} 
	catch (e) {
		Mojo.Log.logException(e);
		onFailure(undefined, e);
	}
};

BookmarkStore.prototype._escapeString = function(str)
{
	return str.replace("'", "''");
};

/**
 * Update a bookmark.
 *
 * @param {UrlReference} urlRef
 * @param {function} onSuccess
 * @param {function} onFailure
 *
 * @private
 */
BookmarkStore.prototype._updateBookmark = function(urlRef, onSuccess, onFailure, transaction){

	var utcDateCreated = this._formatDate(urlRef.date);
	var utcLastVisited = this._formatDate(urlRef.lastVisited);
	
	var parent = 0; // For when we add support for folders.
	var index = urlRef.index || 0;
	
	var defEntry = 0;
	if (urlRef.defaultEntry) {
		defEntry = 1;
	}
	
	var sql = "UPDATE 'bookmarks' ";
	sql += "SET date=" + utcDateCreated + ", ";
	sql += "url='" + urlRef.url + "', ";
	sql += "title='" + this._escapeString(urlRef.title) + "', ";
	sql += "parent=" + parent + ", ";
	sql += "idx=" + index + ", ";
	sql += "defaultEntry=" + defEntry + ", ";
	sql += "iconFile32='" + urlRef.iconFile32 + "', ";
	sql += "iconFile64='" + urlRef.iconFile64 + "', ";
	sql += "thumbnailFile='" + urlRef.thumbnailFile + "', ";
	sql += "visitCount=" + urlRef.visitCount + ", ";
	sql += "lastVisited=" + utcLastVisited + " ";
	
	sql += "WHERE id=" + urlRef.id;
	this._executeSql(transaction, onSuccess, onFailure, sql);
};

/**
 * Delete <em>all</em> default bookmark.
 *
 * @param {UrlReference} id
 * @param {function} onSuccess
 * @param {function} onFailure
 *
 * @private
 */
BookmarkStore.prototype._deleteDefaultBookmarks = function(onSuccess, onFailure, transaction){
	var sql = "DELETE from 'bookmarks' WHERE defaultEntry <> 0";
	this._executeSql(transaction, onSuccess, onFailure, sql);
};

/**
 * Delete <em>all</em> default bookmark.
 *
 * @param {function} onSuccess
 * @param {function} onFailure
 */
BookmarkStore.prototype.deleteDefaultBookmarks = function(onSuccess, onFailure){
	this.database.transaction(this._deleteDefaultBookmarks.bind(this, onSuccess, onFailure));
};

/**
 * Delete a bookmark.
 *
 * @param {Number} id
 * @param {function} onSuccess
 * @param {function} onFailure
 *
 */
BookmarkStore.prototype.deleteBookmark = function(id, onSuccess, onFailure){
	if (id) {
		this.database.transaction(this._deleteBookmark.bind(this, id, onSuccess, onFailure));
	}
};

/**
 * Delete a bookmark.
 *
 * @param {Number} id
 * @param {function} onSuccess
 * @param {function} onFailure
 *
 * @private
 */
BookmarkStore.prototype._deleteBookmark = function(id, onSuccess, onFailure, transaction){
	var sql = "DELETE from 'bookmarks'";
	if (id === 'ALL') {
		Mojo.Log.info("Deleting all bookmarks");
	}
	else {
		sql += " WHERE id=" + id;
	}
	this._executeSql(transaction, onSuccess, onFailure, sql);
};

/**
 * Insert or update a bookmark. If the URL reference (bookmark) has an ID property then
 * we know it came from a database and we do an update, else we do an insert incrementing all
 * existing bookmark indexes.
 *
 * @param {UrlReference} urlRef
 * @param {function} onSuccess
 * @param {function} onFailure
 */
BookmarkStore.prototype.insertOrUpdateBookmark = function(urlRef, onSuccess, onFailure){

	try {
		if (urlRef.id === undefined) {
			this.database.transaction(this._incrementIndex.bind(this, urlRef, onSuccess, onFailure));
		}
		else {
			this.database.transaction(this._updateBookmark.bind(this, urlRef, onSuccess, onFailure));
		}
	} 
	catch (e) {
		Mojo.Log.logException(e);
		onFailure(undefined, e);
	}
};

/** @private */
BookmarkStore.prototype._extractBookmarks = function(folder, onSuccess, transaction, resultSet){
	try {
		for (var i = 0; i < resultSet.rows.length; i++) {
			var item = resultSet.rows.item(i);
			
			var date = new Date(item.date);
			date = date.addMinutes(date.getTimezoneOffset()); // Convert to local time
			var bookmark = new UrlReference(item.url, item.title, date);
			bookmark.index = item.idx;
			bookmark.defaultEntry = item.defaultEntry === undefined ? false : item.defaultEntry !== 0;
			bookmark.iconFile32 = item.iconFile32;
			bookmark.iconFile64 = item.iconFile64;
			bookmark.thumbnailFile = item.thumbnailFile;
			bookmark.visitCount = item.visitCount;
			bookmark.id = item.id; // We save the DB ID which is now we can tell that a bookmark
			// was already saved.
			if (item.lastVisited) {
				date = new Date(item.lastVisited);
				bookmark.lastVisited = date.addMinutes(date.getTimezoneOffset()); // Convert to local time
			}
			
			folder.addItem(bookmark);
		}
		onSuccess(folder);
	} 
	catch (e) {
		Mojo.Log.logException(e, '_extractBookmarks');
		throw e;
	}
};

/** @private */
BookmarkStore.prototype._readBookmarks = function(folder, filterText, onSuccess, onFailure, offset, limit, transaction){
	var sql;
	if (filterText) {
		sql = "SELECT * FROM bookmarks WHERE (url LIKE '%" + filterText +
		"%') or (title LIKE '%" +
		filterText +
		"%') ORDER BY idx ASC";
	}
	else {
		sql = "SELECT * FROM 'bookmarks' ORDER BY idx ASC";
	}
	
	// Add the limits
	if (limit !== undefined) {
		sql = sql + " LIMIT " + limit;
		if (offset !== undefined) {
			sql = sql + " OFFSET " + offset;
		}
	}

	this._executeSql(transaction, this._extractBookmarks.bind(this, folder, onSuccess), onFailure, sql);
};

/**
 * Read all bookmarks from the database.
 *
 * @param {BookmarkFolder} folder
 * @param {String} filterText
 * @param {function} onSuccess
 * @param {function} onFailure
 */
BookmarkStore.prototype.readBookmarks = function(folder, filterText, onSuccess, onFailure, offset, limit){
	
	this.database.transaction(this._readBookmarks.bind(this, folder, filterText, onSuccess, onFailure, offset, limit));
};

/** @private */
BookmarkStore.prototype._executeSql = function(transaction, onSuccess, onFailure, sqlString, valuesList){
	
	onSuccess = onSuccess || function() {};
	onFailure = onFailure || function() {};
	valuesList = valuesList || [];
	try {
		transaction.executeSql(sqlString, valuesList, onSuccess, onFailure);
	} 
	catch (e) {
		onFailure(e, '_executeSql');
		throw e;
	}
};

/** @private */
BookmarkStore.prototype._createTable = function(onSuccess, onFailure, transaction){
	var sql = "CREATE TABLE IF NOT EXISTS 'bookmarks' " +
	"('id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, " +
	"'url' TEXT NOT NULL UNIQUE ON CONFLICT REPLACE DEFAULT '', " +
	"'title' TEXT DEFAULT '', " +
	"'date' INTEGER(8) NOT NULL DEFAULT '0', " +
	"'parent' INTEGER NOT NULL DEFAULT '0', " +
	"'idx' INTEGER NOT NULL DEFAULT '0', " +
	"'defaultEntry' INTEGER NOT NULL DEFAULT '0', " +
	"'iconFile32' TEXT DEFAULT NULL, " +
	"'iconFile64' TEXT DEFAULT NULL, " +
	"'visitCount' INTEGER NOT NULL DEFAULT '0', " +
	"'thumbnailFile' TEXT DEFAULT NULL, " +
	"'lastVisited' INTEGER(8) NOT NULL DEFAULT '0', " +
	"'startIdx' INTEGER(8) NOT NULL DEFAULT '0')";
	this._executeSql(transaction, onSuccess, onFailure, sql);
};
