var EXPORTED_SYMBOLS = ['BaseDAO'];

const Cu = Components.utils;
const Cc = Components.classes;
const Ci = Components.interfaces;

if (!Function.prototype.bind) {
    Function.prototype.bind = function (context) {
        var __method = this, args = Array.prototype.slice.call(arguments, 1);
        return function() {
            return __method.apply(context, args.concat(Array.prototype.slice.call(arguments, 0)));
        }
    };
}

Cu.import("resource://gre/modules/XPCOMUtils.jsm");

XPCOMUtils.defineLazyGetter(this, "utils", function() {
    let obj = { };
    Cu["import"]("resource://livemargins/utils.jsm", obj);
    return obj.utils;
});

XPCOMUtils.defineLazyGetter(this, "storageService", function() {
    return Cc['@mozilla.org/storage/service;1'].getService(Ci.mozIStorageService);
});

XPCOMUtils.defineLazyGetter(this, "logger", function() {
    return utils.logger('LM4.BaseDAO');
});

function BaseDAO(file) {
    this.initialize(file);
}

BaseDAO.prototype = {
    initialize: function(file) {
        this.connection = null;
        this.file = file;
        this.openDatabase();
    },
    
    openDatabase: function() {
        try {
            this.connection = storageService.openDatabase(this.file);
        } catch (e) {
            Components.utils.reportError("AppCenter database appears to have been corrupted." + 
                                e);
        }
    },
    
    executeAync: function(sqls, onError, onCompletion) {
        if (!sqls || sqls.length == 0) {
            return;
        }
        
        var stmts = [];
        var self = this;
        sqls.forEach(function(aSQL) {
            try {
                stmts.push(self.connection.createAsyncStatement(aSQL));
            } catch (e) {
                logger.error(e);
            }
        });
        
        try {
            this.connection.executeAsync(stmts, stmts.length, {
                handleError: function(aError) {
                    if (typeof onError == 'function') {
                        onError(aError);
                    }
                },
                handleCompletion: function(aReason) {
                    if (typeof onCompletion == 'function') {
                        onCompletion(aReason);
                    }
                }
            });
        } catch (e) {
            logger.error(e);
        }
        
        stmts.forEach(function(aStmt) {
            try {
                aStmt.finalize();
            } catch (e) {
                logger.error(e);
            }
        });
    },
    
    executeSQL: function(sql, argObj) {
        var statement = null;
        var result = false;
        
        try {
            statement = this.connection.createStatement(sql);
            if (argObj) {
                for (var key in argObj) {
                    var value = argObj[key];
                    if (value) {
                        // If a string is wrapped as an object
                        // an unkown error will occur
                        value = typeof value == 'object' ? value.toString() : value
                    }
                    statement.params[key] = value;
                }
            }
            result = statement.execute();
        } catch (e) {
            logger.error(e);
        } finally {
            if (null != statement) {
                try {
                    statement.finalize();
                } catch (e) { }
            }
        }
        
        return result;    
    },
    
    /**
     * A wrapper for sql query
     * @param sql
     *         SQL statement which contains parameter style: ":param1, :param2".
     * @param argObj
     *         params for SQL statement which is an object. The attribute name of argObj is same as param name in the SQL statement.
     *         e.g. {param1: value1, param2: value2}
     * @return
     *         An array of object.
     *         e.g. [{id: 1, name: 'abc'}, {id: 2, name: 'def'}]
     */
    query: function(sql, argObj) {
        var statement = null;
        var results = [];
        try {
            statement = this.connection.createStatement(sql);
            if (argObj) {
                for (var key in argObj) {
                    // Only works in Gecko 1.9.1
                    var value = argObj[key];
                    if (value) {
                        // If a string is wrapped as an object
                        // an unkown error will occur
                        value = typeof value == 'object' ? value.toString() : value
                    }
                    statement.params[key] = value;
                }
            }
            
            // Do not use statement.columnCount in for loop
            // which fetches the value again and again.
            var columnCount = statement.columnCount;
            var columns = [];
            
            for (var i = 0; i < columnCount; i++) {
                columns.push([statement.getColumnName(i), null]);
            }
            
            var firstRow = true;
            while (statement.executeStep()) {
                data = {};
                for (var i = 0; i < columnCount; i++) {
                    // 0: null, 1: int, 2: float, 3: string, 4: blob
                    if (firstRow) {
                        columns[i][1] = statement.getTypeOfIndex(i);
                    }
                    var type = columns[i][1];
                    
                    var cell = null;
                    switch (type) {
                        case statement.VALUE_TYPE_NULL:
                            break;
                        case statement.VALUE_TYPE_INTEGER:
                            // use getInt64 instead of getInt32 which causes long integer value is not correct.
                            cell = statement.getInt64(i);
                            break;
                        case statement.VALUE_TYPE_FLOAT:
                            cell = statement.getDouble(i);
                            break;
                        case statement.VALUE_TYPE_TEXT:
                            cell = statement.getString(i);
                            break;
                        case statement.VALUE_TYPE_BLOB:
                            throw new Error('NOT IMPLEMENTED.');
                            break;
                    }
                    
                    data[columns[i][0]] = cell;
                }
                results.push(data);
            }
        } finally {
            if (statement) {
                try {
                    statement.finalize();
                } catch (e) { }
            }
        }
        
        return results;
    }
};