/// <reference path="jquery-1.7.2.js" />
/// <reference path="indexeddb.shim.js" />

var linq2indexedDB;
var enableLogging = false;

// Initializes the linq2indexeddb object.
(function () {
    "use strict";

    linq2indexedDB = function (name, configuration, enableDebugging) {
        /// <summary>Creates a new or opens an existing database for the given name</summary>
        /// <param name="name" type="String">The name of the database</param>
        /// <param name="configuration" type="Object">
        ///     [Optional] provide comment
        /// </param>
        /// <returns type="linq2indexedDB" />

        var dbConfig = {
            autoGenerateAllowed: true
        };

        if (name) {
            dbConfig.name = name;
        }

        if (configuration) {
            if (configuration.version) {
                dbConfig.version = configuration.version;
            }
            // From the moment the configuration is provided by the developper, autoGeneration isn't allowed.
            // If this would be allowed, the developper wouldn't be able to determine what to do for which version.
            if (configuration.schema) {
                var appVersion = dbConfig.version || -1;
                for (key in configuration.schema) {
                    if (typeof key === "number") {
                        appVersion = version > key ? version : key;
                    }
                }
                if (version > -1) {
                    dbConfig.autoGenerateAllowed = false;
                    dbConfig.version = appVersion;
                    dbConfig.schema = configuration.schema;
                }
            }
            if (configuration.definition) {
                dbConfig.autoGenerateAllowed = false;
                dbConfig.definition = configuration.definition;
            }
            if (configuration.onupgradeneeded) {
                dbConfig.autoGenerateAllowed = false;
                dbConfig.onupgradeneeded = configuration.onupgradeneeded;
            }
            if (configuration.oninitializeversion) {
                dbConfig.autoGenerateAllowed = false;
                dbConfig.oninitializeversion = configuration.oninitializeversion;
            }
        }

        var returnObject = {
            utilities: linq2indexedDB.prototype.utilities,
            core: linq2indexedDB.prototype.core,
            linq: linq(dbConfig),
            initialize: function () {
                linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Initialize Started");
                return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) {
                    linq2indexedDB.prototype.core.db(dbConfig.name, dbConfig.version).then(function (args) /*db*/ {
                        var db = args[0];

                        linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Close dbconnection");
                        db.close();
                        linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Initialize Succesfull");
                        pw.complete();
                    }, pw.error, function (args) /*txn, e*/ {
                        var txn = args[0];
                        var e = args[1];
                        if (e.type == "upgradeneeded") {
                            upgradeDatabase(dbConfig, e.oldVersion, e.newVersion, txn);
                        }
                    });
                });
            },
            deleteDatabase: function () {
                return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) {
                    linq2indexedDB.prototype.core.deleteDb(dbConfig.name).then(function () {
                        pw.complete();
                    }, pw.error);
                });
            }
        };

        enableLogging = enableDebugging;
        if (enableDebugging) {
            returnObject.viewer = viewer(dbConfig);
            linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.warning, "Debugging enabled: be carefull when using in production enviroment. Complex objects get written to  the log and may cause memory leaks.")

        } else {
            returnObject.viewer = null;
        }


        return returnObject;
    };

    function linq(dbConfig) {

        var queryBuilderObj = function (objectStoreName) {
            this.from = objectStoreName;
            this.where = [];
            this.select = [];
            this.sortClauses = [];
            this.get = [];
            this.insert = [];
            this.merge = [];
            this.update = [];
            this.remove = [];
            this.clear = false;
        };

        queryBuilderObj.prototype = {
            executeQuery: function () {
                executeQuery(this);
            }
        };

        function from(queryBuilder, objectStoreName) {
            queryBuilder.from = objectStoreName;
            return {
                where: function (filter) {
                    /// <summary>Filters the selected data.</summary>
                    /// <param name="filter">
                    /// The filter argument can be a string (In this case the string represents the property name you want to filter on) or a function.
                    /// (In this case the function will be used to filter the data. This callback function is called with 1 parameter: data
                    /// ,this argument holds the data that has to be validated. The return type of the function must be a boolean.)
                    ///</param>
                    return where(queryBuilder, filter, true, false);
                },
                orderBy: function (propertyName) {
                    /// <summary>Sorts the selected data ascending.</summary>
                    /// <param name="propertyName" type="String">The name of the property you want to sort on.</param>
                    return orderBy(queryBuilder, propertyName, false);
                },
                orderByDesc: function (propertyName) {
                    /// <summary>Sorts the selected data descending.</summary>
                    /// <param name="propertyName" type="String">The name of the property you want to sort on.</param>
                    return orderBy(queryBuilder, propertyName, true);
                },
                select: function (propertyNames) {
                    /// <summary>Selects the data.</summary>
                    /// <param name="propertyNames" type="Array">A list of the names of the properties you want to select.</param>
                    /// <returns type="Array">A list with the selected objects.</returns>
                    return select(queryBuilder, propertyNames);
                },
                insert: function (data, key) {
                    /// <summary>inserts data.</summary>
                    /// <param name="data" type="Object">The object you want to insert.</param>
                    /// <param name="key" type="Object">
                    ///     [Optional] The key of the data you want to insert.
                    /// </param>
                    /// <returns type="Object">The object that was inserted.</returns>
                    return insert(queryBuilder, data, key);
                },
                update: function (data, key) {
                    /// <summary>updates data.</summary>
                    /// <param name="data" type="Object">The object you want to update.</param>
                    /// <param name="key" type="Object">
                    ///     [Optional] The key of the data you want to update.
                    /// </param>
                    /// <returns type="Object">The object that was updated.</returns>
                    return update(queryBuilder, data, key);
                },
                merge: function (data, key) {
                    /// <summary>merges data.</summary>
                    /// <param name="data" type="Object">The data you want to merge.</param>
                    /// <param name="key" type="Object">
                    ///     The key of the data you want to update.
                    /// </param>
                    /// <returns type="Object">The object that was updated.</returns>
                    return merge(queryBuilder, data, key);
                },
                remove: function (key) {
                    /// <summary>Removes data from the objectstore by his key.</summary>
                    /// <param name="key" type="Object">The key of the object you want to remove.</param>
                    return remove(queryBuilder, key);
                },
                clear: function () {
                    /// <summary>Removes all data from the objectstore.</summary>
                    return clear(queryBuilder);
                },
                get: function (key) {
                    /// <summary>Gets an object by his key.</summary>
                    /// <param name="key" type="Object">The key of the object you want to retrieve.</param>
                    /// <returns type="Object">The object that has the provided key.</returns>
                    return get(queryBuilder, key);
                }
            };
        }

        function where(queryBuilder, filter, isAndClause, isOrClause, isNotClause) {
            var whereClauses = {};
            var filterMetaData;

            if (isNotClause === "undefined") {
                whereClauses.not = function () {
                    return where(queryBuilder, filter, isAndClause, isOrClause, true);
                };
            }

            if (typeof filter === "function") {
                filterMetaData = {
                    propertyName: filter,
                    isOrClause: isOrClause,
                    isAndClause: isAndClause,
                    isNotClause: (isNotClause === "undefined" ? false : isNotClause),
                    filter: linq2indexedDB.prototype.linq.createFilter("anonymous" + queryBuilder.where.length, filter, null)
                };
                return whereClause(queryBuilder, filterMetaData);
            } else if (typeof filter === "string") {
                    // Builds up the where filter methodes
                for (var filterName in linq2indexedDB.prototype.linq.filters) {
                    filterMetaData = {
                        propertyName: filter,
                        isOrClause: isOrClause,
                        isAndClause: isAndClause,
                        isNotClause: (typeof isNotClause === "undefined" ? false : isNotClause),
                        filter: linq2indexedDB.prototype.linq.filters[filterName]
                    };
                    if (typeof linq2indexedDB.prototype.linq.filters[filterName].filter !== "function") {
                        throw "Linq2IndexedDB: a filter methods needs to be provided for the filter '" + filterName + "'";
                    }
                    if (typeof linq2indexedDB.prototype.linq.filters[filterName].name === "undefined") {
                        throw "Linq2IndexedDB: a filter name needs to be provided for the filter '" + filterName + "'";
                    }

                    whereClauses[linq2indexedDB.prototype.linq.filters[filterName].name] = linq2indexedDB.prototype.linq.filters[filterName].filter(whereClause, queryBuilder, filterMetaData);
                }
            }
            return whereClauses;
        }

        function whereClause(queryBuilder, filterMetaData) {
            queryBuilder.where.push(filterMetaData);
            return {
                and: function (filter) {
                    /// <summary>Adds an extra filter.</summary>
                    /// <param name="filter">
                    /// The filter argument can be a string (In this case the string represents the property name you want to filter on) or a function.
                    /// (In this case the function will be used to filter the data. This callback function is called with 1 parameter: data
                    /// ,this argument holds the data that has to be validated. The return type of the function must be a boolean.)
                    ///</param>
                    return where(queryBuilder, filter, true, false);
                },
                or: function (filter) {
                    /// <summary>Adds an extra filter.</summary>
                    /// <param name="filter">
                    /// The filter argument can be a string (In this case the string represents the property name you want to filter on) or a function.
                    /// (In this case the function will be used to filter the data. This callback function is called with 1 parameter: data
                    /// ,this argument holds the data that has to be validated. The return type of the function must be a boolean.)
                    ///</param>
                    return where(queryBuilder, filter, false, true);
                },
                orderBy: function (propertyName) {
                    /// <summary>Sorts the selected data ascending.</summary>
                    /// <param name="propertyName" type="String">The name of the property you want to sort on.</param>
                    return orderBy(queryBuilder, propertyName, false);
                },
                orderByDesc: function (propertyName) {
                    /// <summary>Sorts the selected data descending.</summary>
                    /// <param name="propertyName" type="String">The name of the property you want to sort on.</param>
                    return orderBy(queryBuilder, propertyName, true);
                },
                select: function (propertyNames) {
                    /// <summary>Selects the data.</summary>
                    /// <param name="propertyNames" type="Array">A list of the names of the properties you want to select.</param>
                    return select(queryBuilder, propertyNames);
                },
                remove: function () {
                    return remove(queryBuilder);
                },
                merge: function (data) {
                    return merge(queryBuilder, data);
                }
            };
        }

        function orderBy(queryBuilder, propName, descending) {
            queryBuilder.sortClauses.push({ propertyName: propName, descending: descending });
            return {
                orderBy: function (propertyName) {
                    /// <summary>Sorts the selected data ascending.</summary>
                    /// <param name="propertyName" type="String">The name of the property you want to sort on.</param>
                    return orderBy(queryBuilder, propertyName, false);
                },
                orderByDesc: function (propertyName) {
                    /// <summary>Sorts the selected data descending.</summary>
                    /// <param name="propertyName" type="String">The name of the property you want to sort on.</param>
                    return orderBy(queryBuilder, propertyName, true);
                },
                select: function (propertyNames) {
                    /// <summary>Selects the data.</summary>
                    /// <param name="propertyNames" type="Array">A list of the names of the properties you want to select.</param>
                    return select(queryBuilder, propertyNames);
                }
            };
        }

        function select(queryBuilder, propertyNames) {
            if (propertyNames) {
                if (!linq2indexedDB.prototype.utilities.isArray(propertyNames)) {
                    propertyNames = [propertyNames];
                }

                for (var i = 0; i < propertyNames.length; i++) {
                    queryBuilder.select.push(propertyNames[i]);
                }
            }
            return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) {
                var returnData = [];
                executeQuery(queryBuilder, linq2indexedDB.prototype.core.transactionTypes.READ_WRITE, executeWhere).then(function () {
                    pw.complete(this, returnData);
                },pw.error, function(args) {
                    var obj = selectData(args[0].data, queryBuilder.select);
                    returnData.push(obj);
                    pw.progress(this, obj /*[obj]*/);
                });
            });
        }

        function insert(queryBuilder, data, key) {
            queryBuilder.insert.push({ data: data, key: key });
            return executeQuery(queryBuilder, linq2indexedDB.prototype.core.transactionTypes.READ_WRITE, function (qb, pw, transaction) {
                var objectStorePromis = linq2indexedDB.prototype.core.objectStore(transaction, qb.from);
                if (linq2indexedDB.prototype.utilities.isArray(qb.insert[0].data) && !qb.insert[0].key) {
                    var returnData = [];
                    for (var i = 0; i < qb.insert[0].data.length; i++) {
                        linq2indexedDB.prototype.core.insert(objectStorePromis, qb.insert[0].data[i]).then(function (args /*storedData, storedkey*/) {
                            pw.progress(this, {object: args[0], key: args[1]}/*[storedData, storedkey]*/);
                            returnData.push({ object: args[0], key: args[1] });
                            if (returnData.length == qb.insert[0].data.length) {
                                pw.complete(this, returnData);
                            }
                        }, pw.error);
                    }
                }
                else {
                    linq2indexedDB.prototype.core.insert(objectStorePromis, qb.insert[0].data, qb.insert[0].key).then(function(args /*storedData, storedkey*/) {
                        pw.complete(this, {object: args[0], key: args[1]} /*[storedData, storedkey]*/);
                    }, pw.error);
                }
            });
        }

        function update(queryBuilder, data, key) {
            queryBuilder.update.push({ data: data, key: key });
            return executeQuery(queryBuilder, linq2indexedDB.prototype.core.transactionTypes.READ_WRITE, function (qb, pw, transaction) {
                linq2indexedDB.prototype.core.update(linq2indexedDB.prototype.core.objectStore(transaction, qb.from), qb.update[0].data, qb.update[0].key).then(function (args /*storedData, storedkey*/) {
                    pw.complete(this, {object: args[0], key: args[1]} /*[storedData, storedkey]*/);
                }, pw.error);
            });
        }

        function merge(queryBuilder, data, key) {
            queryBuilder.merge.push({ data: data, key: key });
            if (key) {
                return executeQuery(queryBuilder, linq2indexedDB.prototype.core.transactionTypes.READ_WRITE, function(qb, pw, transaction) {
                    var objectStore = linq2indexedDB.prototype.core.objectStore(transaction, qb.from);
                    var obj = null;
                    linq2indexedDB.prototype.core.cursor(objectStore, IDBKeyRange.only(qb.merge[0].key)).then(function() {
                    }, pw.error, function(args /*data*/) {
                        obj = args[0].data;
                        for (var prop in qb.merge[0].data) {
                            obj[prop] = qb.merge[0].data[prop];
                        }

                        args[0].update(obj);
                        pw.complete(this, obj);
                    }, pw.error);
                });
            }
            else {
                var returnData = [];
                return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) {
                    executeQuery(queryBuilder, linq2indexedDB.prototype.core.transactionTypes.READ_WRITE, executeWhere).then(function (args) {
                        if (returnData.length > 0) {
                            pw.complete(this, returnData);
                        }
                        else {
                            executeQuery(queryBuilder, linq2indexedDB.prototype.core.transactionTypes.READ_WRITE, function (qb, promise, transaction) {
                                linq2indexedDB.prototype.core.objectStore(transaction, qb.from).then(function (objectStoreArgs) {
                                    for (var i = 0; i < args.length; i++) {
                                        var obj = args[i];
                                        for (var prop in queryBuilder.merge[0].data) {
                                            obj[prop] = queryBuilder.merge[0].data[prop];
                                        }
                                        linq2indexedDB.prototype.core.update(objectStoreArgs[1], obj).then(function (args1 /*data*/) {
                                            pw.progress(this, args1[0] /*[data]*/);
                                            returnData.push(args1[0]);
                                            if (returnData.length == args.length) {
                                                promise.complete(this, returnData);
                                            }
                                        }, promise.error);
                                    }
                                }, promise.error);
                            }).then(pw.complete, pw.error, pw.progress);
                        }
                    }, null, function (args) {
                        if (args[0].update) {
                            var obj = args[0].data;
                            for (var prop in queryBuilder.merge[0].data) {
                                obj[prop] = queryBuilder.merge[0].data[prop];
                            }

                            args[0].update(obj);
                            pw.progress(this, obj);
                            returnData.push(obj);
                        }
                    });
                });
            }
        }

        function remove(queryBuilder, key) {
            if (key) {
                queryBuilder.remove.push({ key: key });
                return executeQuery(queryBuilder, linq2indexedDB.prototype.core.transactionTypes.READ_WRITE, function (qb, pw, transaction) {
                    linq2indexedDB.prototype.core.remove(linq2indexedDB.prototype.core.objectStore(transaction, qb.from), qb.remove[0].key).then(function () {
                        pw.complete(this, queryBuilder.remove[0].key /*[queryBuilder.remove[0].key]*/);
                    }, pw.error);
                });
            }
            else {
                var cursorDelete = false;
                return linq2indexedDB.prototype.utilities.promiseWrapper(function(pw) {
                    executeQuery(queryBuilder, linq2indexedDB.prototype.core.transactionTypes.READ_WRITE, executeWhere).then(function (data) {
                        if (cursorDelete) {
                            pw.complete(this);
                        }
                        else {
                            executeQuery(queryBuilder, linq2indexedDB.prototype.core.transactionTypes.READ_WRITE, function (qb, promise, transaction) {
                                linq2indexedDB.prototype.core.objectStore(transaction, qb.from).then(function (objectStoreArgs) {
                                    var itemsDeleted = 0;
                                    for (var i = 0; i < data.length; i++) {
                                        linq2indexedDB.prototype.core.remove(objectStoreArgs[1], linq2indexedDB.prototype.utilities.getPropertyValue(data[i], objectStoreArgs[1].keyPath)).then(function(args1 /*data*/) {
                                            pw.progress(this, args1[0] /*[data]*/);
                                            if (++itemsDeleted == data.length) {
                                                promise.complete(this);
                                            }
                                        }, promise.error);
                                    }
                                }, promise.error);
                            }).then(pw.complete, pw.error, pw.progress);
                        }
                    }, null, function(args) {
                        if (args[0].remove) {
                            args[0].remove();
                            pw.progress(this);
                            cursorDelete = true;
                        }
                    });
                });
            }
        }

        function clear(queryBuilder) {
            queryBuilder.clear = true;
            return executeQuery(queryBuilder, linq2indexedDB.prototype.core.transactionTypes.READ_WRITE, function (qb, pw, transaction) {
                linq2indexedDB.prototype.core.clear(linq2indexedDB.prototype.core.objectStore(transaction, qb.from)).then(function () {
                    pw.complete(this);
                }, pw.error);
            });
        }

        function get(queryBuilder, key) {
            queryBuilder.get.push({ key: key });
            return executeQuery(queryBuilder, linq2indexedDB.prototype.core.transactionTypes.READ_ONLY, function (qb, pw, transaction) {
                linq2indexedDB.prototype.core.get(linq2indexedDB.prototype.core.objectStore(transaction, qb.from), qb.get[0].key).then(function (args /*data*/) {
                    pw.complete(this, args[0] /*[data]*/);
                }, pw.error);
            });
        }

        function executeQuery(queryBuilder, transactionType, callBack) {
            return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) {
                // Create DB connection
                linq2indexedDB.prototype.core.db(dbConfig.name, dbConfig.version).then(function (args /* [db, event] */) {
                    // Opening a transaction
                    linq2indexedDB.prototype.core.transaction(args[0], queryBuilder.from, transactionType, dbConfig.autoGenerateAllowed).then(function (transactionArgs /* [transaction] */) {
                        var txn = transactionArgs[0];
                        txn.db.close();
                        // call complete if it isn't called already
                        //pw.complete();
                    },
                    pw.error,
                    function (transactionArgs /* [transaction] */) {
                        callBack(queryBuilder, pw, transactionArgs[0]);
                    });
                }
                , pw.error
                , function (args /*txn, e*/) {
                    var txn = args[0];
                    var e = args[1];

                    // Upgrading the database to the correct version
                    if (e.type == "upgradeneeded") {
                        upgradeDatabase(dbConfig, e.oldVersion, e.newVersion, txn);
                    }
                });
            });
        }

        function executeWhere(queryBuilder, pw, transaction) {
            linq2indexedDB.prototype.core.objectStore(transaction, queryBuilder.from).then(function (objArgs) {
                try {
                    var objectStore = objArgs[1];
                    var whereClauses = queryBuilder.where || [];
                    var returnData = [];
                    var cursorPromise = determineCursor(objectStore, whereClauses);

                    cursorPromise.then(
                        function (args1 /*data*/) {
                            var data = args1[0];

                            linq2indexedDB.prototype.utilities.linq2indexedDBWorker(data, whereClauses, queryBuilder.sortClauses).then(function (d) {
                                // No need to notify again if it allready happend in the onProgress method of the cursor.
                                if (returnData.length == 0) {
                                    for (var j = 0; j < d.length; j++) {
                                        pw.progress(this, [d[j]] /*[obj]*/);
                                    }
                                }
                                pw.complete(this, d /*[returnData]*/);
                            });
                        },
                        pw.error,
                        function (args1 /*data*/) {

                            // When there are no more where clauses to fulfill and the collection doesn't need to be sorted, the data can be returned.
                            // In the other case let the complete handle it.
                            if (whereClauses.length == 0 && queryBuilder.sortClauses.length == 0) {
                                returnData.push({ data: args1[0].data, key: args1[0].key });
                                pw.progress(this, args1 /*[obj]*/);
                            }
                        }
                    );
                } catch (ex) {
                    // Handle errors like an invalid keyRange.
                    linq2indexedDB.prototype.core.abortTransaction(args[0]);
                    pw.error(this, [ex.message, ex]);
                }
            }, pw.error);
        }

        function determineCursor(objectStore, whereClauses) {
            var cursorPromise;

            // Checks if an indexeddb filter can be used
            if (whereClauses.length > 0
                && !whereClauses[0].isNotClause
                && whereClauses[0].filter.indexeddbFilter
                && (whereClauses.length == 1 || (whereClauses.length > 1 && !whereClauses[1].isOrClause))) {
                var source = objectStore;
                var indexPossible = dbConfig.autoGenerateAllowed || objectStore.indexNames.contains(whereClauses[0].propertyName + linq2indexedDB.prototype.core.indexSuffix);
                // Checks if we can use an index
                if (whereClauses[0].propertyName != objectStore.keyPath && indexPossible) {
                    source = linq2indexedDB.prototype.core.index(objectStore, whereClauses[0].propertyName, dbConfig.autoGenerateAllowed);
                }
                // Checks if we can use indexeddb filter
                if (whereClauses[0].propertyName == objectStore.keyPath
                    || indexPossible) {
                    // Gets the where clause + removes it from the collection
                    var clause = whereClauses.shift();
                    switch (clause.filter) {
                        case linq2indexedDB.prototype.linq.filters.equals:
                            cursorPromise = linq2indexedDB.prototype.core.cursor(source, IDBKeyRange.only(clause.value));
                            break;
                        case linq2indexedDB.prototype.linq.filters.between:
                            cursorPromise = linq2indexedDB.prototype.core.cursor(source, IDBKeyRange.bound(clause.minValue, clause.maxValue, clause.minValueIncluded, clause.maxValueIncluded));
                            break;
                        case linq2indexedDB.prototype.linq.filters.greaterThan:
                            cursorPromise = linq2indexedDB.prototype.core.cursor(source, IDBKeyRange.lowerBound(clause.value, clause.valueIncluded));
                            break;
                        case linq2indexedDB.prototype.linq.filters.smallerThan:
                            cursorPromise = linq2indexedDB.prototype.core.cursor(source, IDBKeyRange.upperBound(clause.value, clause.valueIncluded));
                            break;
                        default:
                            cursorPromise = linq2indexedDB.prototype.core.cursor(source);
                            break;
                    }
                } else {
                    // Get everything if the index can't be used
                    cursorPromise = linq2indexedDB.prototype.core.cursor(source);
                }
            } else {
                // Get's everything, manually filter data
                cursorPromise = linq2indexedDB.prototype.core.cursor(objectStore);
            }
            return cursorPromise;
        }

        function selectData(data, propertyNames) {
            if (propertyNames && propertyNames.length > 0) {
                if (!linq2indexedDB.prototype.utilities.isArray(propertyNames)) {
                    propertyNames = [propertyNames];
                }

                var obj = new Object();
                for (var i = 0; i < propertyNames.length; i++) {
                    linq2indexedDB.prototype.utilities.setPropertyValue(obj, propertyNames[i], linq2indexedDB.prototype.utilities.getPropertyValue(data, propertyNames[i]));
                }
                return obj;
            }
            return data;
        }

        return {
            from: function (objectStoreName) {
                return from(new queryBuilderObj(objectStoreName), objectStoreName);
            }
        };
    }

    function viewer(dbConfig) {
        var dbView = {};
        var refresh = true;

        function refreshInternal() {
            if (refresh) {
                refresh = false;
                getDbInformation(dbView, dbConfig);
            }
        }

        dbView.Configuration = {
            name: dbConfig.name,
            version: dbConfig.version,
            autoGenerateAllowed: dbConfig.autoGenerateAllowed,
            schema: dbConfig.schema,
            definition: dbConfig.definition,
            onupgradeneeded: dbConfig.onupgradeneeded,
            oninitializeversion: dbConfig.oninitializeversion
        };

        dbView.refresh = function () {
            refresh = true;
            refreshInternal();
        };

        linq2indexedDB.prototype.core.dbStructureChanged.addListener(linq2indexedDB.prototype.core.databaseEvents.databaseUpgrade, function () {
            refresh = true;
        });
        linq2indexedDB.prototype.core.dbStructureChanged.addListener(linq2indexedDB.prototype.core.databaseEvents.databaseOpened, function () {
            refreshInternal();
        });
        linq2indexedDB.prototype.core.dbStructureChanged.addListener(linq2indexedDB.prototype.core.databaseEvents.databaseRemoved, function () {
            dbView.name = null;
            dbView.version = null;
            dbView.ObjectStores = [];
        });
        linq2indexedDB.prototype.core.dbDataChanged.addListener([linq2indexedDB.prototype.core.dataEvents.dataInserted, linq2indexedDB.prototype.core.dataEvents.dataRemoved, linq2indexedDB.prototype.core.dataEvents.dataUpdated, linq2indexedDB.prototype.core.dataEvents.objectStoreCleared], function () {
            dbView.refresh();
        });

        return dbView;
    }

    function getDbInformation(dbView, dbConfig) {
        linq2indexedDB.prototype.core.db(dbConfig.name).then(function () {
            var connection = arguments[0][0];
            dbView.name = connection.name;
            dbView.version = connection.version;
            dbView.ObjectStores = [];

            linq2indexedDB.prototype.core.dbStructureChanged.addListener(linq2indexedDB.prototype.core.databaseEvents.databaseBlocked, function () {
                connection.close();
            });

            var objectStoreNames = [];
            for (var k = 0; k < connection.objectStoreNames.length; k++) {
                objectStoreNames.push(connection.objectStoreNames[k]);
            }

            if (objectStoreNames.length > 0) {
                linq2indexedDB.prototype.core.transaction(connection, objectStoreNames, linq2indexedDB.prototype.core.transactionTypes.READ_ONLY, false).then(null, null, function () {
                    var transaction = arguments[0][0];

                    for (var i = 0; i < connection.objectStoreNames.length; i++) {
                        linq2indexedDB.prototype.core.objectStore(transaction, connection.objectStoreNames[i]).then(function () {
                            var objectStore = arguments[0][1];
                            var indexes = [];
                            var objectStoreData = [];

                            for (var j = 0; j < objectStore.indexNames.length; j++) {
                                linq2indexedDB.prototype.core.index(objectStore, objectStore.indexNames[j], false).then(function () {
                                    var index = arguments[0][1];
                                    var indexData = [];

                                    linq2indexedDB.prototype.core.cursor(index).then(null, null, function () {
                                        var data = arguments[0][0];
                                        var key = arguments[0][1].primaryKey;
                                        indexData.push({ key: key, data: data });
                                    });

                                    indexes.push({
                                        name: index.name,
                                        keyPath: index.keyPath,
                                        multiEntry: index.multiEntry,
                                        data: indexData
                                    });
                                });
                            }

                            linq2indexedDB.prototype.core.cursor(objectStore).then(null, null, function () {
                                var data = arguments[0][0];
                                var key = arguments[0][1].primaryKey;
                                objectStoreData.push({ key: key, data: data });
                            });

                            dbView.ObjectStores.push({
                                name: objectStore.name,
                                keyPath: objectStore.keyPath,
                                autoIncrement: objectStore.autoIncrement,
                                indexes: indexes,
                                data: objectStoreData
                            });
                        });
                    }
                });
            }
        }, null, function (args) {
            if (args[1].type == "upgradeneeded") {
                args[0].abort();
            }
        });
    }

    function getVersionDefinition(version, definitions) {
        var result = null;
        for (var i = 0; i < definitions.length; i++) {
            if (parseInt(definitions[i].version) == parseInt(version)) {
                result = definitions[i];
            }
        }
        return result;
    }

    function initializeVersion(txn, definition) {
        try {
            if (definition.objectStores) {
                for (var i = 0; i < definition.objectStores.length; i++) {
                    var objectStoreDefinition = definition.objectStores[i];
                    if (objectStoreDefinition.remove) {
                        linq2indexedDB.prototype.core.deleteObjectStore(txn, objectStoreDefinition.name);
                    } else {
                        linq2indexedDB.prototype.core.createObjectStore(txn, objectStoreDefinition.name, objectStoreDefinition.objectStoreOptions);
                    }
                }
            }

            if (definition.indexes) {
                for (var j = 0; j < definition.indexes.length; j++) {
                    var indexDefinition = definition.indexes[j];
                    if (indexDefinition.remove) {
                        linq2indexedDB.prototype.core.deleteIndex(linq2indexedDB.prototype.core.objectStore(txn, indexDefinition.objectStoreName), indexDefinition.propertyName);
                    } else {
                        linq2indexedDB.prototype.core.createIndex(linq2indexedDB.prototype.core.objectStore(txn, indexDefinition.objectStoreName), indexDefinition.propertyName, indexDefinition.indexOptions);
                    }
                }
            }

            if (definition.defaultData) {
                for (var k = 0; k < definition.defaultData.length; k++) {
                    var defaultDataDefinition = definition.defaultData[k];
                    if (defaultDataDefinition.remove) {
                        linq2indexedDB.prototype.core.remove(linq2indexedDB.prototype.core.objectStore(txn, defaultDataDefinition.objectStoreName), defaultDataDefinition.key);
                    } else {
                        linq2indexedDB.prototype.core.insert(linq2indexedDB.prototype.core.objectStore(txn, defaultDataDefinition.objectStoreName), defaultDataDefinition.data, defaultDataDefinition.key);
                    }
                }
            }
        } catch (ex) {
            linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.exception, "initialize version exception: ", ex);
            linq2indexedDB.prototype.core.abortTransaction(txn);
        }
    }

    function upgradeDatabase(dbConfig, oldVersion, newVersion, txn) {
        if (dbConfig.onupgradeneeded) {
            dbConfig.onupgradeneeded(txn, oldVersion, newVersion);
        }
        if (dbConfig.oninitializeversion || dbConfig.schema || dbConfig.definition) {
            for (var version = oldVersion + 1; version <= newVersion; version++) {
                if (dbConfig.schema) {
                    dbConfig.schema[version](txn);
                }
                if (dbConfig.definition) {
                    var versionDefinition = getVersionDefinition(version, dbConfig.definition);
                    if (versionDefinition) {
                        initializeVersion(txn, versionDefinition);
                    }
                } else if (dbConfig.oninitializeversion) {
                    dbConfig.oninitializeversion(txn, version);
                }
            }
        }
    }

})();

// Namespace linq2indexedDB.prototype.linq
(function () {
    linq2indexedDB.prototype.linq = {
        addFilter: function (name, isValid, filterCallback) {
            if (typeof linq2indexedDB.prototype.linq.filters[name] !== 'undefined') {
                throw "linq2IndexedDB: A filter with the name '" + name + "' already exists.";
            }

            linq2indexedDB.prototype.linq.filters[name] = linq2indexedDB.prototype.linq.createFilter(name, isValid, filterCallback);
        },
        createFilter: function (name, isValid, filterCallback) {
            if (typeof name === 'undefined') {
                throw "linq2IndexedDB: No name argument provided to the addFilter method.";
            }
            if (typeof name !== 'string') {
                throw "linq2IndexedDB: The name argument provided to the addFilterObject method must be a string.";
            }
            if (typeof isValid === 'undefined') {
                throw "linq2IndexedDB: No isValid argument provided to the addFilter method.";
            }
            if (typeof isValid !== 'function') {
                throw "linq2IndexedDB: The isValid argument provided to the addFilterObject method must be a function.";
            }
            if (typeof filterCallback === 'undefined') {
                throw "linq2IndexedDB: No filterCallback argument provided to the addFilter method.";
            }
            //if (typeof filterCallback !== 'function') {
            //    throw "linq2IndexedDB: The filterCallback argument provided to the addFilterObject method must be a function.";
            //}

            return {
                name: name,
                indexeddbFilter: false,
                sortOrder: 99,
                isValid: isValid,
                filter: filterCallback
            };
        },
        filters: {
            equals: {
                name: "equals",
                indexeddbFilter: true,
                sortOrder: 0,
                isValid: function (data, filter) {
                    return linq2indexedDB.prototype.utilities.getPropertyValue(data, filter.propertyName) == filter.value;
                },
                filter: function (callback, queryBuilder, filterMetaData) {
                    /// <summary>Creates a function to retrieve values for the filter and adds the filter to the querybuilder.</summary>
                    /// <param name="callback" type="function">
                    ///     Callback method so the query expression can be builded.
                    /// </param>
                    /// <param name="queryBuilder" type="Object">
                    ///     The objects that builds up the query for the user.
                    /// </param>
                    /// <param name="filterMetaData" type="string">
                    ///     The metadata for the filter.
                    /// </param>
                    /// <returns type="function">
                    ///     returns a function to retrieve the necessary values for the filter
                    /// </returns>
                    return function (value) {
                        if (typeof (value) === "undefined") {
                            throw "linq2indexedDB: value needs to be provided to the equal clause";
                        }
                        filterMetaData.value = value;

                        return callback(queryBuilder, filterMetaData);
                    };
                }
            },
            between: {
                name: "between",
                sortOrder: 1,
                indexeddbFilter: true,
                isValid: function (data, filter) {
                    var value = linq2indexedDB.prototype.utilities.getPropertyValue(data, filter.propertyName);
                    return (value > filter.minValue || (filter.minValueIncluded && value == filter.minValue))
                        && (value < filter.maxValue || (filter.maxValueIncluded && value == filter.maxValue));
                },
                filter: function (callback, queryBuilder, filterMetaData) {
                    /// <summary>Creates a function to retrieve values for the filter and adds the filter to the querybuilder.</summary>
                    /// <param name="callback" type="function">
                    ///     Callback method so the query expression can be builded.
                    /// </param>
                    /// <param name="queryBuilder" type="Object">
                    ///     The objects that builds up the query for the user.
                    /// </param>
                    /// <param name="filterMetaData" type="string">
                    ///     The metadata for the filter.
                    /// </param>
                    /// <returns type="function">
                    ///     returns a function to retrieve the necessary values for the filter
                    /// </returns>
                    return function (minValue, maxValue, minValueIncluded, maxValueIncluded) {
                        var isMinValueIncluded = typeof (minValueIncluded) === undefined ? false : minValueIncluded;
                        var isMasValueIncluded = typeof (maxValueIncluded) === undefined ? false : maxValueIncluded;
                        if (typeof (minValue) === "undefined") {
                            throw "linq2indexedDB: minValue needs to be provided to the between clause";
                        }
                        if (typeof (maxValue) === "undefined") {
                            throw "linq2indexedDB: maxValue needs to be provided to the between clause";
                        }

                        filterMetaData.minValue = minValue;
                        filterMetaData.maxValue = maxValue;
                        filterMetaData.minValueIncluded = isMinValueIncluded;
                        filterMetaData.maxValueIncluded = isMasValueIncluded;

                        return callback(queryBuilder, filterMetaData);
                    };
                }
            },
            greaterThan: {
                name: "greaterThan",
                sortOrder: 2,
                indexeddbFilter: true,
                isValid: function (data, filter) {
                    var value = linq2indexedDB.prototype.utilities.getPropertyValue(data, filter.propertyName);
                    return value > filter.value || (filter.valueIncluded && value == filter.value);
                },
                filter: function (callback, queryBuilder, filterMetaData) {
                    /// <summary>Creates a function to retrieve values for the filter and adds the filter to the querybuilder.</summary>
                    /// <param name="callback" type="function">
                    ///     Callback method so the query expression can be builded.
                    /// </param>
                    /// <param name="queryBuilder" type="Object">
                    ///     The objects that builds up the query for the user.
                    /// </param>
                    /// <param name="filterMetaData" type="string">
                    ///     The metadata for the filter.
                    /// </param>
                    /// <returns type="function">
                    ///     returns a function to retrieve the necessary values for the filter
                    /// </returns>
                    return function (value, valueIncluded) {
                        if (typeof (value) === "undefined") {
                            throw "linq2indexedDB: value needs to be provided to the greatherThan clause";
                        }
                        var isValueIncluded = typeof (valueIncluded) === undefined ? false : valueIncluded;

                        filterMetaData.value = value;
                        filterMetaData.valueIncluded = isValueIncluded;

                        return callback(queryBuilder, filterMetaData);
                    };
                }
            },
            smallerThan: {
                name: "smallerThan",
                sortOrder: 2,
                indexeddbFilter: true,
                isValid: function (data, filter) {
                    var value = linq2indexedDB.prototype.utilities.getPropertyValue(data, filter.propertyName);
                    return value < filter.value || (filter.valueIncluded && value == filter.value);
                },
                filter: function (callback, queryBuilder, filterMetaData) {
                    /// <summary>Creates a function to retrieve values for the filter and adds the filter to the querybuilder.</summary>
                    /// <param name="callback" type="function">
                    ///     Callback method so the query expression can be builded.
                    /// </param>
                    /// <param name="queryBuilder" type="Object">
                    ///     The objects that builds up the query for the user.
                    /// </param>
                    /// <param name="filterMetaData" type="string">
                    ///     The metadata for the filter.
                    /// </param>
                    /// <returns type="function">
                    ///     returns a function to retrieve the necessary values for the filter
                    /// </returns>
                    return function (value, valueIncluded) {
                        if (typeof (value) === "undefined") {
                            throw "linq2indexedDB: value needs to be provided to the smallerThan clause";
                        }
                        var isValueIncluded = typeof (valueIncluded) === undefined ? false : valueIncluded;

                        filterMetaData.value = value;
                        filterMetaData.valueIncluded = isValueIncluded;

                        return callback(queryBuilder, filterMetaData);
                    };
                }
            },
            inArray: {
                name: "inArray",
                sortOrder: 3,
                indexeddbFilter: false,
                isValid: function (data, filter) {
                    var value = linq2indexedDB.prototype.utilities.getPropertyValue(data, filter.propertyName);
                    if (value) {
                        return filter.value.indexOf(value) >= 0;
                    }
                    else {
                        return false;
                    }
                },
                filter: function (callback, queryBuilder, filterMetaData) {
                    /// <summary>Creates a function to retrieve values for the filter and adds the filter to the querybuilder.</summary>
                    /// <param name="callback" type="function">
                    ///     Callback method so the query expression can be builded.
                    /// </param>
                    /// <param name="queryBuilder" type="Object">
                    ///     The objects that builds up the query for the user.
                    /// </param>
                    /// <param name="filterMetaData" type="string">
                    ///     The metadata for the filter.
                    /// </param>
                    /// <returns type="function">
                    ///     returns a function to retrieve the necessary values for the filter
                    /// </returns>
                    return function (array) {
                        if (typeof (array) === "undefined" || typeof array !== "Array") {
                            throw "linq2indexedDB: array needs to be provided to the inArray clause";
                        }

                        filterMetaData.value = array;

                        return callback(queryBuilder, filterMetaData);
                    };
                }
            },
            like: {
                name: "like",
                sortOrder: 4,
                indexeddbFilter: false,
                isValid: function (data, filter) {
                    var value = linq2indexedDB.prototype.utilities.getPropertyValue(data, filter.propertyName);
                    if (value) {
                        return value.indexOf(filter.value) >= 0;
                    }
                    else {
                        return false;
                    }
                },
                filter: function (callback, queryBuilder, filterMetaData) {
                    /// <summary>Creates a function to retrieve values for the filter and adds the filter to the querybuilder.</summary>
                    /// <param name="callback" type="function">
                    ///     Callback method so the query expression can be builded.
                    /// </param>
                    /// <param name="queryBuilder" type="Object">
                    ///     The objects that builds up the query for the user.
                    /// </param>
                    /// <param name="filterMetaData" type="string">
                    ///     The metadata for the filter.
                    /// </param>
                    /// <returns type="function">
                    ///     returns a function to retrieve the necessary values for the filter
                    /// </returns>
                    return function (value) {
                        if (typeof (value) === "undefined") {
                            throw "linq2indexedDB: value needs to be provided to the like clause";
                        }

                        filterMetaData.value = value;

                        return callback(queryBuilder, filterMetaData);
                    };
                }
            },
            isUndefined: {
                name: "isUndefined",
                sortOrder: 5,
                indexeddbFilter: false,
                isValid: function (data, filter) {
                    return linq2indexedDB.prototype.utilities.getPropertyValue(data, filter.propertyName) === undefined;
                },
                filter: function (callback, queryBuilder, filterMetaData) {
                    /// <summary>Creates a function to retrieve values for the filter and adds the filter to the querybuilder.</summary>
                    /// <param name="callback" type="function">
                    ///     Callback method so the query expression can be builded.
                    /// </param>
                    /// <param name="queryBuilder" type="Object">
                    ///     The objects that builds up the query for the user.
                    /// </param>
                    /// <param name="filterMetaData" type="string">
                    ///     The metadata for the filter.
                    /// </param>
                    /// <returns type="function">
                    ///     returns a function to retrieve the necessary values for the filter
                    /// </returns>
                    return function () {
                        return callback(queryBuilder, filterMetaData);
                    };
                }
            }
        }
    };
})();

// Namespace linq2indexedDB.prototype.utitlities
(function (isMetroApp) {
    "use strict";

    var utilities = {
        linq2indexedDBWorkerFileLocation: "/Scripts/Linq2IndexedDB.js",
        linq2indexedDBWorker: function (data, filters, sortClauses) {
            return utilities.promiseWrapper(function (pw) {
                if (!!window.Worker) {
                    var worker = new Worker(utilities.linq2indexedDBWorkerFileLocation);
                    worker.onmessage = function (event) {
                        pw.complete(this, event.data);
                        worker.terminate();
                    };
                    worker.onerror = pw.error;

                    var filtersString = JSON.stringify(filters, linq2indexedDB.prototype.utilities.serialize);

                    worker.postMessage({ data: data, filters: filtersString, sortClauses: sortClauses });
                } else {
                    // Fallback when there are no webworkers present. Beware, this runs on the UI thread and can block the UI
                    pw.complete(this, utilities.filterSort(data, filters, sortClauses));
                }
            });
        },
        isArray: function (array) {
            if (array instanceof Array) {
                return true;
            } else {
                return false;
            }
        },
        JSONComparer: function (propertyName, descending) {
            return {
                sort: function (valueX, valueY) {
                    if (descending) {
                        return ((valueX[propertyName] == valueY[propertyName]) ? 0 : ((valueX[propertyName] > valueY[propertyName]) ? -1 : 1));
                    } else {
                        return ((valueX[propertyName] == valueY[propertyName]) ? 0 : ((valueX[propertyName] > valueY[propertyName]) ? 1 : -1));
                    }
                }
            };
        },
        promiseWrapper: function (promise, arg1, arg2, arg3, arg4, arg5) {
            if (isMetroApp) {
                return new WinJS.Promise(function (completed, error, progress) {
                    promise({
                        complete: function (context, args) {
                            completed(args);
                        },
                        error: function (context, args) {
                            error(args);
                        },
                        progress: function (context, args) {
                            progress(args);
                        }
                    }, arg1, arg2, arg3, arg4, arg5);
                });
            } else if (typeof ($) === "function" && $.Deferred) {
                return $.Deferred(function (dfd) {
                    promise({
                        complete: function (context, args) {
                            dfd.resolveWith(context, [args]);
                        },
                        error: function (context, args) {
                            dfd.rejectWith(context, [args]);
                        },
                        progress: function (context, args) {
                            dfd.notifyWith(context, [args]);
                        }
                    }, arg1, arg2, arg3, arg4, arg5);
                }).promise();
            } else {
                throw "linq2indexedDB: No framework (WinJS or jQuery) that supports promises found. Please ensure jQuery or WinJS is referenced before the linq2indexedDB.js file.";
            }
        },
        log: function () {
            if ((window && typeof (window.console) === "undefined") || !enableLogging) {
                return false;
            }

            var currtime = (function currentTime() {
                var time = new Date();
                return time.getHours() + ':' + time.getMinutes() + ':' + time.getSeconds() + '.' + time.getMilliseconds();
            })();

            var args = [];
            var severity = arguments[0];

            args.push(currtime + ' Linq2IndexedDB: ');

            for (var i = 1; i < arguments.length; i++) {
                args.push(arguments[i]);
            }

            switch (severity) {
                case linq2indexedDB.prototype.utilities.severity.exception:
                    if (window.console.exception) {
                        window.console.exception.apply(console, args);
                    } else {
                        window.console.error.apply(console, args);
                    }
                    break;
                case linq2indexedDB.prototype.utilities.severity.error:
                    window.console.error.apply(console, args);
                    break;
                case linq2indexedDB.prototype.utilities.severity.warning:
                    window.console.warning.apply(console, args);
                    break;
                case linq2indexedDB.prototype.utilities.severity.information:
                    window.console.log.apply(console, args);
                    break;
                default:
                    window.console.log.apply(console, args);
            }

            return true;
        },
        logError: function (error) {
            return linq2indexedDB.prototype.utilities.log(error.severity, error.message, error.type, error.method, error.orignialError);
        },
        filterSort: function (data, filters, sortClauses) {
            var returnData = [];

            for (var i = 0; i < data.length; i++) {
                if (utilities.isDataValid(data[i].data, filters)) {
                    returnData = utilities.addToSortedArray(returnData, data[i], sortClauses);
                }
            }

            return returnData;
        },
        isDataValid: function (data, filters) {
            var isValid = true;

            for (var i = 0; i < filters.length; i++) {
                var filterValid = filters[i].filter.isValid(data, filters[i]);
                if (filters[i].isNotClause) {
                    filterValid = !filterValid;
                }
                if (filters[i].isAndClause) {
                    isValid = isValid && filterValid;
                } else if (filters[i].isOrClause) {
                    isValid = isValid || filterValid;
                }
            }
            return isValid;
        },
        addToSortedArray: function (array, data, sortClauses) {
            var newArray = [];
            if (array.length == 0 || sortClauses.length == 0) {
                newArray = array;
                newArray.push(data);
            } else {
                var valueAdded = false;
                for (var i = 0; i < array.length; i++) {
                    var valueX = array[i].data;
                    var valueY = data.data;
                    for (var j = 0; j < sortClauses.length; j++) {
                        var sortPropvalueX = linq2indexedDB.prototype.utilities.getPropertyValue(valueX, sortClauses[j].propertyName);
                        var sortPropvalueY = linq2indexedDB.prototype.utilities.getPropertyValue(valueY, sortClauses[j].propertyName);

                        if (sortPropvalueX != sortPropvalueY) {
                            if ((sortClauses[j].descending && sortPropvalueX > sortPropvalueY)
                                || (!sortClauses[j].descending && sortPropvalueX < sortPropvalueY)) {
                                newArray.push(array[i]);
                            } else {
                                if (!valueAdded) {
                                    valueAdded = true;
                                    newArray.push(data);
                                }
                                newArray.push(array[i]);
                            }
                        }
                        else if (j == (sortClauses.length - 1)) {
                            newArray.push(array[i]);
                        }
                    }
                }

                // Add at the end
                if (!valueAdded) {
                    newArray.push(data);
                }
            }
            return newArray;
        },
        serialize: function (key, value) {
            if (typeof value === 'function') {
                return value.toString();
            }
            return value;
        },
        deserialize: function (key, value) {
            if (value && typeof value === "string" && value.substr(0, 8) == "function") {
                var startBody = value.indexOf('{') + 1;
                var endBody = value.lastIndexOf('}');
                var startArgs = value.indexOf('(') + 1;
                var endArgs = value.indexOf(')');

                return new Function(value.substring(startArgs, endArgs), value.substring(startBody, endBody));
            }
            return value;
        },
        getPropertyValue: function (data, propertyName) {
            var structure = propertyName.split(".");
            var value = data;
            for (var i = 0; i < structure.length; i++) {
                if (value) {
                    value = value[structure[i]];
                }
            }
            return value;
        },
        setPropertyValue: function (data, propertyName, value) {
            var structure = propertyName.split(".");
            var obj = data;
            for (var i = 0; i < structure.length; i++) {
                if (i != (structure.length - 1)) {
                    obj[structure[i]] = {};
                    obj = obj[structure[i]];
                }
                else {
                    obj[structure[i]] = value;
                }
            }
            return obj;
        },
        severity: {
            information: 0,
            warning: 1,
            error: 2,
            exception: 3
        }
    };

    linq2indexedDB.prototype.utilities = utilities;
})(typeof Windows !== "undefined");

if (typeof window !== "undefined") {
    // UI Thread

    // Namespace linq2indexedDB.prototype.core
    (function (window, isMetroApp) {
        "use strict";

        // Region variables
        var defaultDatabaseName = "Default";
        var implementations = {
            NONE: 0,
            NATIVE: 1,
            MICROSOFT: 2,
            MOZILLA: 3,
            GOOGLE: 4,
            MICROSOFTPROTOTYPE: 5,
            SHIM: 6
        };
        var transactionTypes = {
            READ_ONLY: "readonly",
            READ_WRITE: "readwrite",
            VERSION_CHANGE: "versionchange"
        };
        var implementation = initializeIndexedDb();
        var handlers = {
            IDBRequest: function (request) {
                return deferredHandler(IDBRequestHandler, request);
            },
            IDBBlockedRequest: function (request) {
                return deferredHandler(IDBBlockedRequestHandler, request);
            },
            IDBOpenDBRequest: function (request) {
                return deferredHandler(IDBOpenDbRequestHandler, request);
            },
            IDBDatabase: function (database) {
                return deferredHandler(IDBDatabaseHandler, database);
            },
            IDBTransaction: function (txn) {
                return deferredHandler(IDBTransactionHandler, txn);
            },
            IDBCursorRequest: function (request) {
                return deferredHandler(IDBCursorRequestHandler, request);
            }
        };

        //Copyright (c) 2010 Nicholas C. Zakas. All rights reserved.
        //MIT License

        function eventTarget() {
            this._listeners = {};
        }

        eventTarget.prototype = {
            constructor: eventTarget,

            addListener: function (type, listener) {
                if (!linq2indexedDB.prototype.utilities.isArray(type)) {
                    type = [type];
                }

                for (var i = 0; i < type.length; i++) {
                    if (typeof this._listeners[type[i]] == "undefined") {
                        this._listeners[type[i]] = [];
                    }

                    this._listeners[type[i]].push(listener);
                }
            },

            fire: function (event) {
                if (typeof event == "string") {
                    event = { type: event };
                }
                if (!event.target) {
                    event.target = this;
                }

                if (!event.type) { //falsy
                    throw new Error("Event object missing 'type' property.");
                }

                if (this._listeners[event.type] instanceof Array) {
                    var listeners = this._listeners[event.type];
                    for (var i = 0, len = listeners.length; i < len; i++) {
                        listeners[i].call(this, event);
                    }
                }
            },

            removeListener: function (type, listener) {
                if (!linq2indexedDB.prototype.utilities.isArray(type)) {
                    type = [type];
                }

                for (var j = 0; j < type[j].length; j++) {
                    if (this._listeners[type[j]] instanceof Array) {
                        var listeners = this._listeners[type[j]];
                        for (var i = 0, len = listeners.length; i < len; i++) {
                            if (listeners[i] === listener) {
                                listeners.splice(i, 1);
                                break;
                            }
                        }
                    }
                }
            }
        };
        // End copyright

        var dbEvents = {
            objectStoreCreated: "Object store created",
            objectStoreRemoved: "Object store removed",
            indexCreated: "Index created",
            indexRemoved: "Index removed",
            databaseRemoved: "Database removed",
            databaseBlocked: "Database blocked",
            databaseUpgrade: "Database upgrade",
            databaseOpened: "Database opened"
        };

        var dataEvents = {
            dataInserted: "Data inserted",
            dataUpdated: "Data updated",
            dataRemoved: "Data removed",
            objectStoreCleared: "Object store cleared"
        };

        var upgradingDatabase = false;

        var internal = {
            db: function (pw, name, version) {
                var req;
                try {
                    // Initializing defaults
                    name = name ? name : defaultDatabaseName;

                    // Creating a new database conection
                    if (version) {
                        linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "db opening", name, version);
                        req = window.indexedDB.open(name, version);
                    } else {
                        linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "db opening", name);
                        req = window.indexedDB.open(name);
                    }

                    // Handle the events of the creation of the database connection
                    handlers.IDBOpenDBRequest(req).then(
                        function (args /*db, e*/) {
                            var db = args[0];
                            var e = args[1];
                            // Database connection established

                            // Handle the events on the database.
                            handlers.IDBDatabase(db).then(
                                function (/*result, event*/) {
                                    // No done present.
                                },
                                function (args1/*error, event*/) {
                                    // Database error or abort
                                    linq2indexedDB.prototype.core.closeDatabaseConnection(args1[1].target);
                                    // When an error occures the result will already be resolved. This way calling the reject won't case a thing
                                },
                                function (args1 /*result, event*/) {
                                    var event = args1[1];
                                    if (event) {
                                        // Sending a notify won't have any effect because the result is already resolved. There is nothing more to do than close the current connection.
                                        if (event.type === "versionchange") {
                                            if (event.version != event.target.db.version) {
                                                // If the version is changed and the current version is different from the requested version, the connection needs to get closed.
                                                linq2indexedDB.prototype.core.closeDatabaseConnection(event.target);
                                            }
                                        }
                                    }
                                });

                            var currentVersion = internal.getDatabaseVersion(db);
                            if (currentVersion < version || (version == -1) || currentVersion == "") {
                                // Current version deferres from the requested version, database upgrade needed
                                linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "DB Promise upgradeneeded", this, db, e, db.connectionId);
                                internal.changeDatabaseStructure(db, version || 1).then(
                                    function (args1 /*txn, event*/) {
                                        var txn = args1[0];
                                        var event = args1[1];

                                        // Fake the onupgrade event.
                                        var context = txn.db;
                                        context.transaction = txn;

                                        var upgardeEvent = {};
                                        upgardeEvent.type = "upgradeneeded";
                                        upgardeEvent.newVersion = version;
                                        upgardeEvent.oldVersion = currentVersion;
                                        upgardeEvent.originalEvent = event;

                                        linq2indexedDB.prototype.core.dbStructureChanged.fire({ type: dbEvents.databaseUpgrade, data: upgardeEvent });
                                        pw.progress(context, [txn, upgardeEvent]);

                                        handlers.IDBTransaction(txn).then(function (/*trans, args*/) {
                                            // When completed return the db + event of the original request.
                                            pw.complete(this, args);
                                        },
                                        function (args2 /*err, ev*/) {
                                            //txn error or abort
                                            pw.error(this, args2);
                                        });
                                    },
                                    function (args1 /*err, event*/) {
                                        // txn error or abort
                                        pw.error(this, args1);
                                    },
                                    function (args1 /*result, event*/) {
                                        // txn blocked
                                        linq2indexedDB.prototype.core.dbStructureChanged.fire({ type: dbEvents.databaseBlocked, data: args1 });
                                        pw.progress(this, args1);
                                    });
                            } else if (version && version < currentVersion) {
                                linq2indexedDB.prototype.core.closeDatabaseConnection(db);
                                var err = {
                                    severity: linq2indexedDB.prototype.utilities.severity.error,
                                    type: "VersionError",
                                    message: "You are trying to open the database in a lower version (" + version + ") than the current version of the database",
                                    method: "db"
                                };
                                linq2indexedDB.prototype.utilities.logError(err);
                                pw.error(this, err);
                            }
                            else {
                                // Database Connection resolved.
                                linq2indexedDB.prototype.core.dbStructureChanged.fire({ type: dbEvents.databaseOpened, data: db });
                                linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "DB Promise resolved", db, e);
                                pw.complete(this, [db, e]);
                            }
                        },
                        function (args /*error, e*/) {
                            // Database connection error or abort
                            var err = internal.wrapError(args[1], "db");

                            // Fix for firefox & chrome
                            if (args[1].target && args[1].target.errorCode == 12) {
                                err.type = "VersionError";
                            }

                            if (err.type == "VersionError") {
                                err.message = "You are trying to open the database in a lower version (" + version + ") than the current version of the database";
                            }

                            // Fix for firefox & chrome
                            if (args[1].target && args[1].target.errorCode == 8) {
                                err.type = "AbortError";
                            }

                            if (err.type == "AbortError") {
                                err.message = "The VERSION_CHANGE transaction was aborted.";
                            }
                            // For old firefox implementations
                            linq2indexedDB.prototype.utilities.logError(err);
                            pw.error(this, err);
                        },
                        function (args /*result, e*/) {
                            // Database upgrade + db blocked
                            if (args[1].type == "blocked") {
                                linq2indexedDB.prototype.core.dbStructureChanged.fire({ type: dbEvents.databaseBlocked, data: args });
                            } else if (args[1].type == "upgradeneeded") {
                                linq2indexedDB.prototype.core.dbStructureChanged.fire({ type: dbEvents.databaseUpgrade, data: args });
                            }
                            pw.progress(this, args);
                        }
                    );
                } catch (ex) {
                    var error = internal.wrapException(ex, "db");
                    if ((ex.INVALID_ACCESS_ERR && ex.code == ex.INVALID_ACCESS_ERR) || ex.name == "InvalidAccessError") {
                        error.type = "InvalidAccessError";
                        error.message = "You are trying to open a database with a negative version number.";
                    }
                    linq2indexedDB.prototype.utilities.logError(error);
                    pw.error(this, error);
                }
            },
            transaction: function (pw, db, objectStoreNames, transactionType, autoGenerateAllowed) {
                linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Transaction promise started", db, objectStoreNames, transactionType);

                // Initialize defaults
                if (!linq2indexedDB.prototype.utilities.isArray(objectStoreNames)) objectStoreNames = [objectStoreNames];
                transactionType = transactionType || linq2indexedDB.prototype.core.transactionTypes.READ_ONLY;

                var nonExistingObjectStores = [];

                try {
                    // Check for non-existing object stores
                    for (var i = 0; i < objectStoreNames.length; i++) {
                        if (!db.objectStoreNames || !db.objectStoreNames.contains(objectStoreNames[i])) {
                            nonExistingObjectStores.push(objectStoreNames[i]);
                        }
                    }

                    // When non-existing object stores are found and the autoGenerateAllowed is true.
                    // Then create these object stores
                    if (nonExistingObjectStores.length > 0 && autoGenerateAllowed) {
                        // setTimeout is necessary when multiple request to generate an index come together.
                        // This can result in a deadlock situation, there for the setTimeout
                        setTimeout(function() {
                            upgradingDatabase = true;
                            var version = internal.getDatabaseVersion(db) + 1;
                            var dbName = db.name;
                            linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Transaction database upgrade needed: ", db);
                            // Closing the current connections so it won't block the upgrade.
                            linq2indexedDB.prototype.core.closeDatabaseConnection(db);
                            // Open a new connection with the new version
                            linq2indexedDB.prototype.core.db(dbName, version).then(function (args /*dbConnection, event*/) {
                                upgradingDatabase = false;
                                // Necessary for getting it work in WIN 8, WinJS promises have troubles with nesting promises
                                var txn = args[0].transaction(objectStoreNames, transactionType);

                                // Handle transaction events
                                handlers.IDBTransaction(txn).then(function(args1 /*result, event*/) {
                                    // txn completed
                                    linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Transaction completed.", txn);
                                    pw.complete(this, args1);
                                },
                                    function(args1 /*err, event*/) {
                                        // txn error or abort
                                        linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.error, "Transaction error/abort.", args1);
                                        pw.error(this, args1);
                                    });

                                // txn created
                                linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Transaction created.", txn);
                                pw.progress(txn, [txn]);
                            },
                                function(args /*error, event*/) {
                                    // When an error occures, bubble up.
                                    linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.error, "Transaction error.", args);
                                    pw.error(this, args);
                                },
                                function(args /*txn, event*/) {
                                    var event = args[1];

                                    // When an upgradeneeded event is thrown, create the non-existing object stores
                                    if (event.type == "upgradeneeded") {
                                        for (var j = 0; j < nonExistingObjectStores.length; j++) {
                                            linq2indexedDB.prototype.core.createObjectStore(args[0], nonExistingObjectStores[j], { keyPath: "Id", autoIncrement: true });
                                        }
                                    }
                                });
                        }, upgradingDatabase ? 10 : 1);
                    } else {
                        // If no non-existing object stores are found, create the transaction.
                        var transaction = db.transaction(objectStoreNames, transactionType);

                        // Handle transaction events
                        handlers.IDBTransaction(transaction).then(function(args /*result, event*/) {
                            // txn completed
                            linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Transaction completed.", args);
                            pw.complete(this, args);
                        },
                            function (args /*err, event*/) {
                                var err = internal.wrapError(args[1], "transaction");
                                if (args[1].type == "abort") {
                                    err.type = "abort";
                                    err.severity = "abort";
                                    err.message = "Transaction was aborted";
                                }

                                // Fix for firefox & chrome
                                if (args[1].target && args[1].target.errorCode == 4) {
                                    err.type = "ConstraintError";
                                }

                                if (err.type == "ConstraintError") {
                                    err.message = "A mutation operation in the transaction failed. For more details look at the error on the instert, update, remove or clear statement.";
                                }
                                // txn error or abort
                                linq2indexedDB.prototype.utilities.logError(err);
                                pw.error(this, err);
                            });

                        // txn created
                        linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Transaction transaction created.", transaction);
                        pw.progress(transaction, [transaction]);
                    }
                }
                catch (ex) {
                    var error = internal.wrapException(ex, "transaction");
                    if ((ex.INVALID_ACCESS_ERR && ex.code == ex.INVALID_ACCESS_ERR) || ex.name == "InvalidAccessError") {
                        error.type = "InvalidAccessError";
                        error.message = "You are trying to open a transaction without providing an object store as scope.";
                    }
                    if ((ex.NOT_FOUND_ERR && ex.code == ex.NOT_FOUND_ERR) || ex.name == "NotFoundError") {
                        var objectStores = "";
                        for (var m = 0; m < nonExistingObjectStores.length; m++) {
                            if (m > 0) {
                                objectStores += ", ";
                            }
                            objectStores += nonExistingObjectStores[m];
                        }
                        error.type = "NotFoundError";
                        error.message = "You are trying to open a transaction for object stores (" + objectStores + "), that doesn't exist.";
                    }
                    if ((ex.QUOTA_ERR && ex.code == ex.QUOTA_ERR) || ex.name == "QuotaExceededError") {
                        error.type = "QuotaExceededError";
                        error.message = "The size quota of the indexedDB database is reached.";
                    }
                    if ((ex.UNKNOWN_ERR && ex.code == ex.UNKNOWN_ERR) || ex.name == "UnknownError") {
                        error.type = "UnknownError";
                        error.message = "An I/O exception occured.";
                    }
                    linq2indexedDB.prototype.utilities.logError(error);
                    pw.error(this, error);
                }
            },
            changeDatabaseStructure: function (db, version) {
                return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) {
                    linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "changeDatabaseStructure started", db, version);
                    handlers.IDBBlockedRequest(db.setVersion(version)).then(function (args /*txn, event*/) {
                        // txn created
                        pw.complete(this, args);
                    },
                        function (args /*error, event*/) {
                            // txn error or abort
                            pw.error(this, args);
                        },
                        function (args /*txn, event*/) {
                            // txn blocked
                            pw.progress(this, args);
                        });
                });
            },
            objectStore: function (pw, transaction, objectStoreName) {
                linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "objectStore started", transaction, objectStoreName);
                try {
                    var store = transaction.objectStore(objectStoreName);
                    linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "objectStore completed", transaction, store);
                    pw.complete(store, [transaction, store]);
                } catch (ex) {
                    var error = internal.wrapException(ex, "objectStore");
                    if ((ex.NOT_FOUND_ERR && ex.code == ex.NOT_FOUND_ERR) || ex.name == "NotFoundError") {
                        error.type = "NotFoundError";
                        error.message = "You are trying to open an object store (" + objectStoreName + "), that doesn't exist or isn't in side the transaction scope.";
                    }
                    if (ex.name == "TransactionInactiveError") {
                        error.type = "TransactionInactiveError";
                        error.message = "You are trying to open an object store (" + objectStoreName + ") outside a transaction.";
                    }

                    linq2indexedDB.prototype.utilities.logError(error);
                    linq2indexedDB.prototype.core.abortTransaction(transaction);
                    pw.error(this, error);
                }
            },
            createObjectStore: function (pw, transaction, objectStoreName, objectStoreOptions) {
                linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "createObjectStore started", transaction, objectStoreName, objectStoreOptions);
                try {
                    if (!transaction.db.objectStoreNames.contains(objectStoreName)) {
                        // If the object store doesn't exists, create it
                        var options = new Object();

                        if (objectStoreOptions) {
                            if (objectStoreOptions.keyPath) options.keyPath = objectStoreOptions.keyPath;
                            options.autoIncrement = objectStoreOptions.autoIncrement;
                        } else {
                            options.autoIncrement = true;
                        }

                        var store = transaction.db.createObjectStore(objectStoreName, options, options.autoIncrement);

                        linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "ObjectStore Created", transaction, store);
                        linq2indexedDB.prototype.core.dbStructureChanged.fire({ type: dbEvents.objectStoreCreated, data: store });
                        pw.complete(store, [transaction, store]);
                    } else {
                        // If the object store exists, retrieve it
                        linq2indexedDB.prototype.core.objectStore(transaction, objectStoreName).then(function (args /*trans, store*/) {
                            // store resolved
                            linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "ObjectStore Found", args[1], objectStoreName);
                            linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "createObjectStore Promise", args[0], args[1]);
                            pw.complete(store, args);
                        },
                            function (args /*error, event*/) {
                                // store error
                                pw.error(this, args);
                            });
                    }
                } catch (ex) {
                    // store exception
                    var error = internal.wrapException(ex, "createObjectStore");
                    if ((ex.INVALID_STATE_ERR && ex.code == ex.INVALID_STATE_ERR) || (ex.NOT_ALLOWED_ERR && ex.code == ex.NOT_ALLOWED_ERR) || ex.name == "InvalidStateError") {
                        error.type = "InvalidStateError";
                        error.message = "You are trying to create an object store in a readonly or readwrite transaction.";
                    }
                    if ((ex.INVALID_ACCESS_ERR && ex.code == ex.INVALID_ACCESS_ERR) || ex.name == "InvalidAccessError") {
                        error.type = "InvalidAccessError";
                        error.message = "The object store can't have autoIncrement on and an empty string or an array with an empty string as keyPath.";
                    }
                    linq2indexedDB.prototype.utilities.logError(error);
                    if (error.type != "InvalidStateError") {
                        linq2indexedDB.prototype.core.abortTransaction(transaction);
                    }
                    pw.error(this, error);
                }
            },
            deleteObjectStore: function (pw, transaction, objectStoreName) {
                linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "deleteObjectStore Promise started", transaction, objectStoreName);
                try {
                    transaction.db.deleteObjectStore(objectStoreName);
                    linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "ObjectStore Deleted", objectStoreName);
                    linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "deleteObjectStore completed", objectStoreName);
                    linq2indexedDB.prototype.core.dbStructureChanged.fire({ type: dbEvents.objectStoreRemoved, data: objectStoreName });
                    pw.complete(this, [transaction, objectStoreName]);
                } catch (ex) {
                    var error = internal.wrapException(ex, "deleteObjectStore");
                    if ((ex.NOT_FOUND_ERR && ex.code == ex.NOT_FOUND_ERR) || ex.name == "NotFoundError") {
                        error.type = "NotFoundError";
                        error.message = "You are trying to delete an object store (" + objectStoreName + "), that doesn't exist.";
                    }
                    if ((ex.INVALID_STATE_ERR && ex.code == ex.INVALID_STATE_ERR) || (ex.NOT_ALLOWED_ERR && ex.code == ex.NOT_ALLOWED_ERR) || ex.name == "InvalidStateError") {
                        error.type = "InvalidStateError";
                        error.message = "You are trying to delete an object store in a readonly or readwrite transaction.";
                    }
                    // store exception
                    linq2indexedDB.prototype.utilities.logError(error);
                    if (error.type != "InvalidStateError") {
                        linq2indexedDB.prototype.core.abortTransaction(transaction);
                    }
                    pw.error(this, error);
                }
            },
            index: function (pw, objectStore, propertyName, autoGenerateAllowed) {
                linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Index started", objectStore, propertyName, autoGenerateAllowed);
                var indexName = propertyName;
                if (propertyName.indexOf(linq2indexedDB.prototype.core.indexSuffix) == -1) {
                    indexName = indexName + linq2indexedDB.prototype.core.indexSuffix;
                }

                try {
                    if (!objectStore.indexNames.contains(indexName) && autoGenerateAllowed) {
                        // setTimeout is necessary when multiple request to generate an index come together.
                        // This can result in a deadlock situation, there for the setTimeout
                        setTimeout((function(objStore) {
                            upgradingDatabase = true;
                            // If index doesn't exists, create it if autoGenerateAllowed
                            var version = internal.getDatabaseVersion(objStore.transaction.db) + 1;
                            var dbName = objStore.transaction.db.name;
                            var transactionType = objStore.transaction.mode;
                            var objectStoreNames = [objStore.name]; //transaction.objectStoreNames;
                            var objectStoreName = objStore.name;
                            // Close the currenct database connections so it won't block
                            linq2indexedDB.prototype.core.closeDatabaseConnection(objStore);

                            // Open a new connection with the new version
                            linq2indexedDB.prototype.core.db(dbName, version).then(function (args /*dbConnection, event*/) {
                                upgradingDatabase = false;
                                // When the upgrade is completed, the index can be resolved.
                                linq2indexedDB.prototype.core.transaction(args[0], objectStoreNames, transactionType, autoGenerateAllowed).then(function(/*transaction, ev*/) {
                                    // txn completed
                                    // TODO: what to do in this case
                                },
                                    function(args1 /*error, ev*/) {
                                        // txn error or abort
                                        pw.error(this, args1);
                                    },
                                    function(args1 /*transaction*/) {
                                        // txn created
                                        linq2indexedDB.prototype.core.index(linq2indexedDB.prototype.core.objectStore(args1[0], objectStoreName), propertyName).then(function(args2 /*trans, index, store*/) {
                                            pw.complete(this, args2);
                                        }, function(args2 /*error, ev*/) {
                                            // txn error or abort
                                            pw.error(this, args2);
                                        });
                                    });
                            },
                                function(args /*error, event*/) {
                                    // When an error occures, bubble up.
                                    pw.error(this, args);
                                },
                                function(args /*trans, event*/) {
                                    var trans = args[0];
                                    var event = args[1];

                                    // When an upgradeneeded event is thrown, create the non-existing object stores
                                    if (event.type == "upgradeneeded") {
                                        linq2indexedDB.prototype.core.createIndex(linq2indexedDB.prototype.core.objectStore(trans, objectStoreName), propertyName).then(function (/*index, store, transaction*/) {
                                            // index created
                                        },
                                        function(args1 /*error, ev*/) {
                                            // When an error occures, bubble up.
                                            pw.error(this, args1);
                                        });
                                    }
                                });
                        })(objectStore), upgradingDatabase ? 10 : 1);
                     } else {
                         // If index exists, resolve it
                         var index = objectStore.index(indexName);
                         linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Index completed", objectStore.transaction, index, objectStore);
                         pw.complete(this, [objectStore.transaction, index, objectStore]);
                     }
                } catch (ex) {
                    var error = internal.wrapException(ex, "index");
                    if ((ex.NOT_FOUND_ERR && ex.code == ex.NOT_FOUND_ERR) || ex.name == "NotFoundError") {
                        error.type = "NotFoundError";
                        error.message = "You are trying to open an index (" + indexName + "), that doesn't exist.";
                    }
                    if (ex.name == "TransactionInactiveError") {
                        error.type = "TransactionInactiveError";
                        error.message = "You are trying to open an object store (" + indexName + ") outside a transaction.";
                    }
                    // index exception
                    linq2indexedDB.prototype.utilities.logError(error);
                    linq2indexedDB.prototype.core.abortTransaction(objectStore.transaction);
                    pw.error(this, error);
                }
            },
            createIndex: function (pw, objectStore, propertyName, indexOptions) {
                linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "createIndex started", objectStore, propertyName, indexOptions);
                try {
                    var indexName = propertyName;
                    if (propertyName.indexOf(linq2indexedDB.prototype.core.indexSuffix) == -1) {
                        indexName = indexName + linq2indexedDB.prototype.core.indexSuffix;
                    }

                    if (!objectStore.indexNames.contains(indexName)) {
                        var index = objectStore.createIndex(indexName, propertyName, { unique: indexOptions ? indexOptions.unique : false, multiRow: indexOptions ? indexOptions.multirow : false, multiEntry: indexOptions ? indexOptions.multirow : false });
                        linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "createIndex completed", objectStore.transaction, index, objectStore);
                        linq2indexedDB.prototype.core.dbStructureChanged.fire({ type: dbEvents.indexCreated, data: index });
                        pw.complete(this, [objectStore.transaction, index, objectStore]);
                    } else {
                        // if the index exists retrieve it
                        linq2indexedDB.prototype.core.index(objectStore, propertyName, false).then(function (args) {
                            pw.complete(this, args);
                        });
                    }
                } catch (ex) {
                    // store exception
                    var error = internal.wrapException(ex, "createIndex");
                    if ((ex.INVALID_STATE_ERR && ex.code == ex.INVALID_STATE_ERR) || (ex.NOT_ALLOWED_ERR && ex.code == ex.NOT_ALLOWED_ERR) || ex.name == "InvalidStateError") {
                        error.type = "InvalidStateError";
                        error.message = "You are trying to create an index in a readonly or readwrite transaction.";
                    }
                    if (error.type != "InvalidStateError") {
                        linq2indexedDB.prototype.core.abortTransaction(transaction);
                    }
                    linq2indexedDB.prototype.utilities.logError(error);
                    pw.error(this, error);
                }
            },
            deleteIndex: function (pw, objectStore, propertyName) {
                linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "deleteIndex started", objectStore, propertyName);
                var indexName = propertyName;
                if (propertyName.indexOf(linq2indexedDB.prototype.core.indexSuffix) == -1) {
                    indexName = indexName + linq2indexedDB.prototype.core.indexSuffix;
                }

                try {
                    objectStore.deleteIndex(indexName);
                    linq2indexedDB.prototype.core.dbStructureChanged.fire({ type: dbEvents.indexRemoved, data: indexName });
                    linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "deleteIndex completed", objectStore.transaction, propertyName, objectStore);
                    pw.complete(this, [objectStore.transaction, propertyName, objectStore]);
                } catch (ex) {
                    var error = internal.wrapException(ex, "deleteIndex");
                    if ((ex.NOT_FOUND_ERR && ex.code == ex.NOT_FOUND_ERR) || ex.name == "NotFoundError") {
                        error.type = "NotFoundError";
                        error.message = "You are trying to delete an index (" + indexName + ", propertyName: " + propertyName + " ), that doesn't exist.";
                    }
                    if ((ex.INVALID_STATE_ERR && ex.code == ex.INVALID_STATE_ERR) || (ex.NOT_ALLOWED_ERR && ex.code == ex.NOT_ALLOWED_ERR) || ex.name == "InvalidStateError") {
                        error.type = "InvalidStateError";
                        error.message = "You are trying to delete an index in a readonly or readwrite transaction.";
                    }
                    // store exception
                    linq2indexedDB.prototype.utilities.logError(error);
                    if (error.type != "InvalidStateError") {
                        linq2indexedDB.prototype.core.abortTransaction(transaction);
                    }
                    pw.error(this, error);
                }
            },
            cursor: function (pw, source, range, direction) {
                linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Cursor Promise Started", source);
                var keyRange;
                var returnData = [];
                var request;

                try {

                    keyRange = range;

                    if (!keyRange) {
                        if (implementation != implementations.GOOGLE) {
                            keyRange = IDBKeyRange.lowerBound(0);
                        } else {
                            keyRange = IDBKeyRange.lowerBound(parseFloat(0));
                        }
                    }

                    // direction can not be null when passed.
                    if (direction) {
                        request = handlers.IDBCursorRequest(source.openCursor(keyRange, direction));
                    } else if (keyRange) {
                        request = handlers.IDBCursorRequest(source.openCursor(keyRange));
                    } else {
                        request = handlers.IDBCursorRequest(source.openCursor());
                    }

                    request.then(function (args1 /*result, e*/) {
                        var e = args1[1];
                        var transaction = source.transaction || source.objectStore.transaction;

                        linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Cursor completed", returnData, transaction, e);
                        pw.complete(this, [returnData, transaction, e]);
                    },
                        function (args /*error, e*/) {
                            linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.error, "Cursor error", args);
                            pw.error(this, args);
                        },
                        function (args /*result, e*/) {
                            var result = args[0];
                            var e = args[1];

                            linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Cursor progress", result, e);
                            if (result.value) {
                                var progressObj = {
                                    data: result.value,
                                    key: result.primaryKey,
                                    skip: function(number) {
                                        linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Cursor skip", result, e);
                                        try {
                                            result.advance(number);
                                        }
                                        catch (advanceEx) {
                                            var advanceErr = internal.wrapException(advanceEx, "cursor - skip");

                                            if ((advanceEx.DATA_ERR && advanceEx.code == advanceEx.DATA_ERR) || advanceEx.name == "DataError") {
                                                advanceErr.type = "DataError";
                                                advanceErr.message = "The provided range parameter isn't a valid key or key range.";
                                            }

                                            if (advanceEx.name == "TypeError") {
                                                advanceErr.type = "TypeError";
                                                advanceErr.message = "The provided count parameter is zero or a negative number.";
                                            }

                                            if ((advanceEx.INVALID_STATE_ERR && advanceEx.code == advanceEx.INVALID_STATE_ERR) || advanceEx.name == "InvalidStateError") {
                                                advanceErr.type = "InvalidStateError";
                                                advanceErr.message = "You are trying to skip data on a removed object store.";
                                            }
                                            linq2indexedDB.prototype.utilities.logError(advanceErr);
                                            linq2indexedDB.prototype.core.abortTransaction(txn);
                                            pw.error(this, advanceErr);
                                        }
                                    },
                                    update: function(obj) {
                                        linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Cursor update", result, e);
                                        try {
                                            result.update(obj);
                                        }
                                        catch (updateEx) {
                                            var updateError = internal.wrapException(updateEx, "cursor - update");

                                            if ((updateEx.DATA_ERR  && updateEx.code == updateEx.DATA_ERR) || updateEx.name == "DataError") {
                                                updateError.type = "DataError";
                                                updateError.message = "The underlying object store uses in-line keys and the property in value at the object store's key path does not match the key in this cursor's position.";
                                            }

                                            if ((updateEx.READ_ONLY_ERR && ex.code == updateEx.READ_ONLY_ERR) || updateEx.name == "ReadOnlyError") {
                                                updateError.type = "ReadOnlyError";
                                                updateError.message = "You are trying to update data in a readonly transaction.";
                                            }

                                            if (updateEx.name == "TransactionInactiveError") {
                                                updateError.type = "TransactionInactiveError";
                                                updateError.message = "You are trying to update data on an inactieve transaction. (The transaction was already aborted or committed)";
                                            }

                                            if ((updateEx.DATA_CLONE_ERR && updateEx.code == updateEx.DATA_CLONE_ERR) || updateEx.name == "DataCloneError") {
                                                updateError.type = "DataCloneError";
                                                updateError.message = "The data you are trying to update could not be cloned. Your data probably contains a function which can not be cloned by default. Try using the serialize method to update the data.";
                                            }

                                            if ((updateEx.INVALID_STATE_ERR && updateEx.code == updateEx.INVALID_STATE_ERR) || updateEx.name == "InvalidStateError") {
                                                updateError.type = "InvalidStateError";
                                                updateError.message = "You are trying to update data on a removed object store.";
                                            }

                                            linq2indexedDB.prototype.utilities.logError(updateError);
                                            linq2indexedDB.prototype.core.abortTransaction(txn);
                                            pw.error(this, updateError);
                                        }
                                    },
                                    remove: function() {
                                        linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Cursor remove", result, e);
                                        try {
                                            result["delete"]();
                                        }
                                        catch (deleteEx) {
                                            var deleteError = internal.wrapException(deleteEx, "cursor - delete");

                                            if ((deleteEx.READ_ONLY_ERR && deleteEx.code == deleteEx.READ_ONLY_ERR) || deleteEx.name == "ReadOnlyError") {
                                                deleteError.type = "ReadOnlyError";
                                                deleteError.message = "You are trying to remove data in a readonly transaction.";
                                            }

                                            if (deleteEx.name == "TransactionInactiveError") {
                                                deleteError.type = "TransactionInactiveError";
                                                deleteError.message = "You are trying to remove data on an inactieve transaction. (The transaction was already aborted or committed)";
                                            }

                                            if ((deleteEx.INVALID_STATE_ERR && deleteEx.code == deleteEx.INVALID_STATE_ERR) || deleteEx.name == "InvalidStateError") {
                                                deleteError.type = "InvalidStateError";
                                                deleteError.message = "You are trying to remove data on a removed object store.";
                                            }

                                            linq2indexedDB.prototype.utilities.logError(deleteError);
                                            linq2indexedDB.prototype.core.abortTransaction(txn);
                                            pw.error(this, deleteError);
                                        }
                                    }
                                };

                                pw.progress(this, [progressObj, result, e]);
                                returnData.push({data: progressObj.data ,key: progressObj.key });
                            }
                            result["continue"]();
                        });
                } catch (ex) {
                    var txn = source.transaction || source.objectStore.transaction;
                    var error = internal.wrapException(ex, "cursor");

                    if ((ex.DATA_ERR && error.code == ex.DATA_ERR) || ex.name == "DataError") {
                        error.type = "DataError";
                        error.message = "The provided range parameter isn't a valid key or key range.";
                    }

                    if (ex.name == "TransactionInactiveError") {
                        error.type = "TransactionInactiveError";
                        error.message = "You are trying to retrieve data on an inactieve transaction. (The transaction was already aborted or committed)";
                    }

                    if (ex.name == "TypeError") {
                        error.type = "TypeError";
                        error.message = "The provided directory parameter is invalid";
                    }

                    if ((ex.INVALID_STATE_ERR && ex.code == ex.INVALID_STATE_ERR) || (ex.NOT_ALLOWED_ERR && ex.code == ex.NOT_ALLOWED_ERR) || ex.name == "InvalidStateError") {
                        error.type = "InvalidStateError";
                        error.message = "You are trying to insert data on a removed object store.";
                    }
                    linq2indexedDB.prototype.utilities.logError(error);
                    linq2indexedDB.prototype.core.abortTransaction(txn);
                    pw.error(this, error);
                }
                finally {
                    keyRange = null;
                }
            },
            keyCursor: function (pw, index, range, direction) {
                linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "keyCursor Started", index, range, direction);
                var returnData = [];

                try {
                    var request;
                    var keyRange = range;

                    if (!keyRange) {
                        keyRange = IDBKeyRange.lowerBound(0);
                    }

                    // direction can not be null when passed.
                    if (direction) {
                        request = handlers.IDBCursorRequest(source.openKeyCursor(keyRange, direction));
                    } else {
                        request = handlers.IDBCursorRequest(source.openKeyCursor(keyRange));
                    }

                    request.then(function (args /*result, e*/) {
                        var e = args[1];

                        linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "keyCursor completed", returnData, index.objectStore.transaction, e);
                        pw.complete(this, [returnData, index.objectStore.transaction, e]);
                    },
                        function (args /*error, e*/) {
                            linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.error, "keyCursor error", args);
                            pw.error(this, args);
                        },
                        function (args /*result, e*/) {
                            var result = args[0];
                            var e = args[1];

                            linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "keyCursor progress", result, e);
                            if (result.value) {
                                var progressObj = {
                                    data: result.value,
                                    key: result.primaryKey,
                                    skip: function (number) {
                                        linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "keyCursor skip", result, e);
                                        try {
                                            result.advance(number);
                                        }
                                        catch (advanceEx) {
                                            var advanceErr = internal.wrapException(advanceEx, "keyCursor - skip");

                                            if ((advanceEx.DATA_ERR && advanceEx.code == advanceEx.DATA_ERR) || advanceEx.name == "DataError") {
                                                advanceErr.type = "DataError";
                                                advanceErr.message = "The provided range parameter isn't a valid key or key range.";
                                            }

                                            if (advanceEx.name == "TypeError") {
                                                advanceErr.type = "TypeError";
                                                advanceErr.message = "The provided count parameter is zero or a negative number.";
                                            }

                                            if ((advanceEx.INVALID_STATE_ERR && advanceEx.code == advanceEx.INVALID_STATE_ERR) || advanceEx.name == "InvalidStateError") {
                                                advanceErr.type = "InvalidStateError";
                                                advanceErr.message = "You are trying to skip data on a removed object store.";
                                            }
                                            linq2indexedDB.prototype.utilities.logError(advanceErr);
                                            linq2indexedDB.prototype.core.abortTransaction(index.objectStore.transaction);
                                            pw.error(this, advanceErr);
                                        }
                                    },
                                    update: function (obj) {
                                        linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "keyCursor update", result, e);
                                        try {
                                            result.update(obj);
                                        }
                                        catch (updateEx) {
                                            var updateError = internal.wrapException(updateEx, "keyCursor - update");

                                            if ((updateEx.DATA_ERR && updateEx.code == updateEx.DATA_ERR) || updateEx.name == "DataError") {
                                                updateError.type = "DataError";
                                                updateError.message = "The underlying object store uses in-line keys and the property in value at the object store's key path does not match the key in this cursor's position.";
                                            }

                                            if ((updateEx.READ_ONLY_ERR && ex.code == updateEx.READ_ONLY_ERR) || updateEx.name == "ReadOnlyError") {
                                                updateError.type = "ReadOnlyError";
                                                updateError.message = "You are trying to update data in a readonly transaction.";
                                            }

                                            if (updateEx.name == "TransactionInactiveError") {
                                                updateError.type = "TransactionInactiveError";
                                                updateError.message = "You are trying to update data on an inactieve transaction. (The transaction was already aborted or committed)";
                                            }

                                            if ((updateEx.DATA_CLONE_ERR && updateEx.code == updateEx.DATA_CLONE_ERR) || updateEx.name == "DataCloneError") {
                                                updateError.type = "DataCloneError";
                                                updateError.message = "The data you are trying to update could not be cloned. Your data probably contains a function which can not be cloned by default. Try using the serialize method to update the data.";
                                            }

                                            if ((updateEx.INVALID_STATE_ERR && updateEx.code == updateEx.INVALID_STATE_ERR) || updateEx.name == "InvalidStateError") {
                                                updateError.type = "InvalidStateError";
                                                updateError.message = "You are trying to update data on a removed object store.";
                                            }

                                            linq2indexedDB.prototype.utilities.logError(updateError);
                                            linq2indexedDB.prototype.core.abortTransaction(index.objectStore.transaction);
                                            pw.error(this, updateError);
                                        }
                                    },
                                    remove: function () {
                                        linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "keyCursor remove", result, e);
                                        try {
                                            result["delete"]();
                                        }
                                        catch (deleteEx) {
                                            var deleteError = internal.wrapException(deleteEx, "keyCursor - delete");

                                            if ((deleteEx.READ_ONLY_ERR && deleteEx.code == deleteEx.READ_ONLY_ERR) || deleteEx.name == "ReadOnlyError") {
                                                deleteError.type = "ReadOnlyError";
                                                deleteError.message = "You are trying to remove data in a readonly transaction.";
                                            }

                                            if (deleteEx.name == "TransactionInactiveError") {
                                                deleteError.type = "TransactionInactiveError";
                                                deleteError.message = "You are trying to remove data on an inactieve transaction. (The transaction was already aborted or committed)";
                                            }

                                            if ((deleteEx.INVALID_STATE_ERR && deleteEx.code == deleteEx.INVALID_STATE_ERR) || deleteEx.name == "InvalidStateError") {
                                                deleteError.type = "InvalidStateError";
                                                deleteError.message = "You are trying to remove data on a removed object store.";
                                            }

                                            linq2indexedDB.prototype.utilities.logError(deleteError);
                                            linq2indexedDB.prototype.core.abortTransaction(index.objectStore.transaction);
                                            pw.error(this, deleteError);
                                        }
                                    }
                                };

                                pw.progress(this, [progressObj, result, e]);
                                returnData.push(progressObj.data);
                            }
                            result["continue"]();
                        });
                } catch (ex) {
                    var error = internal.wrapException(ex, "keyCursor");

                    if ((ex.DATA_ERR && error.code == ex.DATA_ERR) || ex.name == "DataError") {
                        error.type = "DataError";
                        error.message = "The provided range parameter isn't a valid key or key range.";
                    }

                    if (ex.name == "TransactionInactiveError") {
                        error.type = "TransactionInactiveError";
                        error.message = "You are trying to retrieve data on an inactieve transaction. (The transaction was already aborted or committed)";
                    }

                    if (ex.name == "TypeError") {
                        error.type = "TypeError";
                        error.message = "The provided directory parameter is invalid";
                    }

                    if ((ex.INVALID_STATE_ERR && ex.code == ex.INVALID_STATE_ERR) || (ex.NOT_ALLOWED_ERR && ex.code == ex.NOT_ALLOWED_ERR) || ex.name == "InvalidStateError") {
                        error.type = "InvalidStateError";
                        error.message = "You are trying to insert data on a removed object store.";
                    }
                    linq2indexedDB.prototype.utilities.logError(error);
                    linq2indexedDB.prototype.core.abortTransaction(index.objectStore.transaction);
                    pw.error(this, error);
                }
            },
            get: function (pw, source, key) {
                linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Get Started", source);

                try {
                    handlers.IDBRequest(source.get(key)).then(function (args /*result, e*/) {
                        var result = args[0];
                        var e = args[1];
                        var transaction = source.transaction || source.objectStore.transaction;

                        linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Get completed", result, transaction, e);
                        pw.complete(this, [result, transaction, e]);
                    }, function (args /*error, e*/) {
                        linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.error, "Get error", args);
                        pw.error(this, args);
                    });
                } catch (ex) {
                    var txn = source.transaction || source.objectStore.transaction;
                    var error = internal.wrapException(ex, "get");

                    if (error.code == ex.DATA_ERR || ex.name == "DataError") {
                        error.message = "The provided key isn't a valid key (must be an array, string, date or number).";
                    }

                    if (ex.name == "TransactionInactiveError") {
                        error.type = "TransactionInactiveError";
                        error.message = "You are trying to retrieve data on an inactieve transaction. (The transaction was already aborted or committed)";
                    }

                    if ((ex.INVALID_STATE_ERR && ex.code == ex.INVALID_STATE_ERR) || (ex.NOT_ALLOWED_ERR && ex.code == ex.NOT_ALLOWED_ERR) || ex.name == "InvalidStateError") {
                        error.type = "InvalidStateError";
                        error.message = "You are trying to retrieve data on a removed object store.";
                    }
                    linq2indexedDB.prototype.utilities.logError(error);
                    linq2indexedDB.prototype.core.abortTransaction(txn);
                    pw.error(this, error);
                }
            },
            count: function (pw, source, key) {
                linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Count Started", source);

                try {
                    var req;

                    if (key) {
                        req = source.count(key);
                    }
                    else {
                        req = source.count();
                    }

                    handlers.IDBRequest(req).then(function (args /*result, e*/) {
                        var result = args[0];
                        var e = args[1];
                        var transaction = source.transaction || source.objectStore.transaction;

                        linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Count completed", result, transaction, e);
                        pw.complete(this, [result, transaction, e]);
                    }, function (args /*error, e*/) {
                        linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.error, "Count error", args);
                        pw.error(this, args);
                    });
                } catch (ex) {
                    var txn = source.transaction || source.objectStore.transaction;
                    var error = internal.wrapException(ex, "count");

                    if (error.code == ex.DATA_ERR || ex.name == "DataError") {
                        error.type = "DataError";
                        error.message = "The provided key isn't a valid key or keyRange.";
                    }

                    if (ex.name == "TransactionInactiveError") {
                        error.type = "TransactionInactiveError";
                        error.message = "You are trying to count data on an inactieve transaction. (The transaction was already aborted or committed)";
                    }

                    if ((ex.INVALID_STATE_ERR && ex.code == ex.INVALID_STATE_ERR) || (ex.NOT_ALLOWED_ERR && ex.code == ex.NOT_ALLOWED_ERR) || ex.name == "InvalidStateError") {
                        error.type = "InvalidStateError";
                        error.message = "You are trying to count data on a removed object store.";
                    }
                    linq2indexedDB.prototype.utilities.logError(error);
                    linq2indexedDB.prototype.core.abortTransaction(txn);
                    pw.error(this, error);
                }
            },
            getKey: function (pw, index, key) {
                linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "GetKey Started", index, key);

                try {
                    handlers.IDBRequest(index.getKey(key)).then(function (args /*result, e*/) {
                        var result = args[0];
                        var e = args[1];

                        linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "GetKey completed", result, index.objectStore.transaction, e);
                        pw.complete(this, [result, index.objectStore.transaction, e]);
                    }, function (args /*error, e*/) {
                        linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.error, "GetKey error", args);
                        pw.error(this, args);
                    });
                } catch (ex) {
                    var error = internal.wrapException(ex, "getKey");

                    if (error.code == ex.DATA_ERR || ex.name == "DataError") {
                        error.type = "DataError";
                        error.message = "The provided key isn't a valid key or keyRange.";
                    }

                    if (ex.name == "TransactionInactiveError") {
                        error.type = "TransactionInactiveError";
                        error.message = "You are trying to getKey data on an inactieve transaction. (The transaction was already aborted or committed)";
                    }

                    if ((ex.INVALID_STATE_ERR && ex.code == ex.INVALID_STATE_ERR) || (ex.NOT_ALLOWED_ERR && ex.code == ex.NOT_ALLOWED_ERR) || ex.name == "InvalidStateError") {
                        error.type = "InvalidStateError";
                        error.message = "You are trying to getKey data on a removed object store.";
                    }
                    linq2indexedDB.prototype.utilities.logError(error);
                    linq2indexedDB.prototype.core.abortTransaction(index.objectStore.transaction);
                    pw.error(this, error);
                }
            },
            insert: function (pw, objectStore, data, key) {
                linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Insert Started", objectStore, data, key);
                try {
                    var req;

                    if (key /*&& !store.keyPath*/) {
                        req = handlers.IDBRequest(objectStore.add(data, key));
                    } else {
                        /*if (key) linq2indexedDB.prototype.utilities.log("Key can't be provided when a keyPath is defined on the object store", store, key, data);*/
                        req = handlers.IDBRequest(objectStore.add(data));
                    }

                    req.then(function (args /*result, e*/) {
                        var result = args[0];
                        var e = args[1];

                        // Add key to the object if a keypath exists
                        if (objectStore.keyPath) {
                            data[objectStore.keyPath] = result;
                        }

                        linq2indexedDB.prototype.core.dbDataChanged.fire({ type: dataEvents.dataInserted, data: data, objectStore: objectStore });
                        linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Insert completed", data, result, objectStore.transaction, e);
                        pw.complete(this, [data, result, objectStore.transaction, e]);
                    }, function (args /*error, e*/) {
                        var err = internal.wrapError(args[1], "insert");

                        // Fix for firefox & chrome
                        if (args[1].target && args[1].target.errorCode == 4) {
                            err.type = "ConstraintError";
                        }

                        if (err.type == "ConstraintError") {
                            var duplicateKey = key;
                            if (!duplicateKey && objectStore.keyPath) {
                                duplicateKey = data[objectStore.keyPath];
                            }
                            err.message = "A record for the key (" + duplicateKey + ") already exists in the database or one of the properties of the provided data has a unique index declared.";
                        }
                        linq2indexedDB.prototype.core.abortTransaction(objectStore.transaction);
                        linq2indexedDB.prototype.utilities.logError(err);
                        pw.error(this, err);
                    });
                } catch (ex) {
                    var error = internal.wrapException(ex, "insert");

                    if (error.code == ex.DATA_ERR || ex.name == "DataError") {
                        error.type = "DataError";
                        var possibleKey = key;
                        if (!possibleKey && objectStore.keyPath) {
                            possibleKey = data[objectStore.keyPath];
                        }
                        if (!possibleKey) {
                            error.message = "There is no key provided for the data you want to insert for an object store without autoIncrement.";
                        } else if (key && objectStore.keyPath) {
                            error.message = "An external key is provided while the object store expects a keyPath key.";
                        } else if (typeof possibleKey !== "string"
                            && typeof possibleKey !== "number"
                            && typeof possibleKey !== "Date"
                            && !linq2indexedDB.prototype.utilities.isArray(possibleKey)) {
                            error.message = "The provided key isn't a valid key (must be an array, string, date or number).";
                        }
                    }

                    if ((ex.READ_ONLY_ERR && ex.code == ex.READ_ONLY_ERR) || ex.name == "ReadOnlyError") {
                        error.type = "ReadOnlyError";
                        error.message = "You are trying to insert data in a readonly transaction.";
                    }

                    if (ex.name == "TransactionInactiveError") {
                        error.type = "TransactionInactiveError";
                        error.message = "You are trying to insert data on an inactieve transaction. (The transaction was already aborted or committed)";
                    }

                    if ((ex.DATA_CLONE_ERR && ex.code == ex.DATA_CLONE_ERR) || ex.name == "DataCloneError") {
                        error.type = "DataCloneError";
                        error.message = "The data you are trying to insert could not be cloned. Your data probably contains a function which can not be cloned by default. Try using the serialize method to insert the data.";
                    }

                    if ((ex.INVALID_STATE_ERR && ex.code == ex.INVALID_STATE_ERR) || (ex.NOT_ALLOWED_ERR && ex.code == ex.NOT_ALLOWED_ERR) || ex.name == "InvalidStateError") {
                        error.type = "InvalidStateError";
                        error.message = "You are trying to insert data on a removed object store.";
                    }
                    linq2indexedDB.prototype.utilities.logError(error);
                    linq2indexedDB.prototype.core.abortTransaction(objectStore.transaction);
                    pw.error(this, error);
                }
            },
            update: function (pw, objectStore, data, key) {
                linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Update Started", objectStore, data, key);

                try {
                    var req;
                    if (key /*&& !store.keyPath*/) {
                        req = handlers.IDBRequest(objectStore.put(data, key));
                    } else {
                        /*if (key) linq2indexedDB.prototype.utilities.log("Key can't be provided when a keyPath is defined on the object store", store, key, data);*/
                        req = handlers.IDBRequest(objectStore.put(data));
                    }
                    req.then(function (args /*result, e*/) {
                        var result = args[0];
                        var e = args[1];

                        if (objectStore.keyPath && data[objectStore.keyPath] === undefined) {
                            data[objectStore.keyPath] = result;
                        }

                        linq2indexedDB.prototype.core.dbDataChanged.fire({ type: dataEvents.dataUpdated, data: data, objectStore: objectStore });
                        linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Update completed", data, result, objectStore.transaction, e);
                        pw.complete(this, [data, result, objectStore.transaction, e]);
                    }, function (args /*error, e*/) {
                        var err = internal.wrapError(args[1], "update");
                        linq2indexedDB.prototype.core.abortTransaction(objectStore.transaction);
                        linq2indexedDB.prototype.utilities.logError(err);
                        pw.error(this, err);
                    });
                } catch (ex) {
                    var error = internal.wrapException(ex, "update");

                    if (error.code == ex.DATA_ERR || ex.name == "DataError") {
                        error.type = "DataError";
                        var possibleKey = key;
                        if (!possibleKey && objectStore.keyPath) {
                            possibleKey = data[objectStore.keyPath];
                        }
                        if (!possibleKey) {
                            error.message = "There is no key provided for the data you want to update for an object store without autoIncrement.";
                        } else if (key && objectStore.keyPath) {
                            error.message = "An external key is provided while the object store expects a keyPath key.";
                        } else if (typeof possibleKey !== "string"
                            && typeof possibleKey !== "number"
                            && typeof possibleKey !== "Date"
                            && !linq2indexedDB.prototype.utilities.isArray(possibleKey)) {
                            error.message = "The provided key isn't a valid key (must be an array, string, date or number).";
                        }
                    }

                    if ((ex.READ_ONLY_ERR && ex.code == ex.READ_ONLY_ERR) || ex.name == "ReadOnlyError") {
                        error.type = "ReadOnlyError";
                        error.message = "You are trying to update data in a readonly transaction.";
                    }

                    if (ex.name == "TransactionInactiveError") {
                        error.type = "TransactionInactiveError";
                        error.message = "You are trying to update data on an inactieve transaction. (The transaction was already aborted or committed)";
                    }

                    if ((ex.DATA_CLONE_ERR && ex.code == ex.DATA_CLONE_ERR) || ex.name == "DataCloneError") {
                        error.type = "DataCloneError";
                        error.message = "The data you are trying to update could not be cloned. Your data probably contains a function which can not be cloned by default. Try using the serialize method to update the data.";
                    }

                    if ((ex.INVALID_STATE_ERR && ex.code == ex.INVALID_STATE_ERR) || (ex.NOT_ALLOWED_ERR && ex.code == ex.NOT_ALLOWED_ERR) || ex.name == "InvalidStateError") {
                        error.type = "InvalidStateError";
                        error.message = "You are trying to update data on a removed object store.";
                    }

                    linq2indexedDB.prototype.utilities.logError(error);
                    linq2indexedDB.prototype.core.abortTransaction(objectStore.transaction);
                    pw.error(this, error);
                }
            },
            remove: function (pw, objectStore, key) {
                linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Remove Started", objectStore, key);

                try {
                    handlers.IDBRequest(objectStore["delete"](key)).then(function (args /*result, e*/) {
                        var result = args[0];
                        var e = args[1];

                        linq2indexedDB.prototype.core.dbDataChanged.fire({ type: dataEvents.dataRemoved, data: key, objectStore: objectStore });
                        linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Remove completed", result, objectStore.transaction, e);
                        pw.complete(this, [result, objectStore.transaction, e]);
                    },
                        function (args /*error, e*/) {
                            linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.error, "Remove error", args);
                            pw.error(this, args);
                        });
                } catch (ex) {
                    var error = internal.wrapException(ex, "delete");

                    if ((ex.READ_ONLY_ERR && ex.code == ex.READ_ONLY_ERR) || ex.name == "ReadOnlyError") {
                        error.type = "ReadOnlyError";
                        error.message = "You are trying to remove data in a readonly transaction.";
                    }

                    if (ex.name == "TransactionInactiveError") {
                        error.type = "TransactionInactiveError";
                        error.message = "You are trying to remove data on an inactieve transaction. (The transaction was already aborted or committed)";
                    }

                    if ((ex.INVALID_STATE_ERR && ex.code == ex.INVALID_STATE_ERR) || (ex.NOT_ALLOWED_ERR && ex.code == ex.NOT_ALLOWED_ERR) || ex.name == "InvalidStateError") {
                        error.type = "InvalidStateError";
                        error.message = "You are trying to remove data on a removed object store.";
                    }

                    linq2indexedDB.prototype.utilities.logError(error);
                    linq2indexedDB.prototype.core.abortTransaction(objectStore.transaction);
                    pw.error(this, error);
                }
            },
            clear: function (pw, objectStore) {
                linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Clear Started", objectStore);
                try {
                    handlers.IDBRequest(objectStore.clear()).then(function (args /*result, e*/) {
                        var result = args[0];
                        var e = args[1];

                        linq2indexedDB.prototype.core.dbDataChanged.fire({ type: dataEvents.objectStoreCleared, objectStore: objectStore });
                        linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Clear completed", result, objectStore.transaction, e);
                        pw.complete(this, [result, objectStore.transaction, e]);
                    },
                        function (args /*error, e*/) {
                            linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.error, "Clear error", args);
                            pw.error(this, args);
                        });
                } catch (ex) {
                    var error = internal.wrapException(ex, "clear");

                    if ((ex.READ_ONLY_ERR && ex.code == ex.READ_ONLY_ERR) || ex.name == "ReadOnlyError") {
                        error.type = "ReadOnlyError";
                        error.message = "You are trying to clear data in a readonly transaction.";
                    }

                    if (ex.name == "TransactionInactiveError") {
                        error.type = "TransactionInactiveError";
                        error.message = "You are trying to clear data on an inactieve transaction. (The transaction was already aborted or committed)";
                    }

                    if ((ex.INVALID_STATE_ERR && ex.code == ex.INVALID_STATE_ERR) || (ex.NOT_ALLOWED_ERR && ex.code == ex.NOT_ALLOWED_ERR) || ex.name == "InvalidStateError") {
                        error.type = "InvalidStateError";
                        error.message = "You are trying to clear data on a removed object store.";
                    }

                    linq2indexedDB.prototype.utilities.logError(error);
                    linq2indexedDB.prototype.core.abortTransaction(objectStore.transaction);
                    pw.error(this, error);
                }
            },
            deleteDb: function (pw, name) {
                try {
                    if (typeof (window.indexedDB.deleteDatabase) != "undefined") {

                        handlers.IDBBlockedRequest(window.indexedDB.deleteDatabase(name)).then(function (args /*result, e*/) {
                            var result = args[0];
                            var e = args[1];

                            linq2indexedDB.prototype.core.dbStructureChanged.fire({ type: dbEvents.databaseRemoved });
                            linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Delete Database Promise completed", result, e, name);
                            pw.complete(this, [result, e, name]);
                        }, function (args /*error, e*/) {
                            var error = args[0];
                            var e = args[1];

                            // added for FF, If a db gets deleted that doesn't exist an errorCode 6 ('NOT_ALLOWED_ERR') is given
                            if (e.currentTarget && e.currentTarget.errorCode == 6) {
                                linq2indexedDB.prototype.core.dbStructureChanged.fire({ type: dbEvents.databaseRemoved });
                                pw.complete(this, [error, e, name]);
                            } else if (implementation == implementations.SHIM
                                && e.message == "Database does not exist") {
                                linq2indexedDB.prototype.core.dbStructureChanged.fire({ type: dbEvents.databaseRemoved });
                                pw.complete(this, [error, e, name]);
                            } else {
                                linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.error, "Delete Database Promise error", error, e);
                                pw.error(this, [error, e]);
                            }
                        }, function (args /*result, e*/) {
                            if (args[0] == "blocked") {
                                linq2indexedDB.prototype.core.dbStructureChanged.fire({ type: dbEvents.databaseBlocked });
                            }
                            linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Delete Database Promise blocked", args /*result*/);
                            pw.progress(this, args /*[result, e]*/);
                        });
                    } else {
                        linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Delete Database function not found", name);
                        // Workaround for older versions of chrome and FireFox
                        // Doesn't delete the database, but clears him
                        linq2indexedDB.prototype.core.db(name, -1).then(function (args /*result, e*/) {
                            var result = args[0];
                            var e = args[1];

                            linq2indexedDB.prototype.core.dbStructureChanged.fire({ type: dbEvents.databaseRemoved });
                            pw.complete(this, [result, e, name]);
                        },
                            function (args /*error, e*/) {
                                linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.error, "Clear Promise error", args /*error, e*/);
                                pw.error(this, args /*[error, e]*/);
                            },
                            function (args /*dbConnection, event*/) {
                                var dbConnection = args[0];
                                var event = args[1];

                                // When an upgradeneeded event is thrown, create the non-existing object stores
                                if (event.type == "upgradeneeded") {
                                    for (var i = 0; i < dbConnection.objectStoreNames.length; i++) {
                                        linq2indexedDB.prototype.core.deleteObjectStore(dbConnection.txn, dbConnection.objectStoreNames[i]);
                                    }
                                    linq2indexedDB.prototype.core.closeDatabaseConnection(dbConnection);
                                }
                            });
                    }
                } catch (ex) {
                    linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.exception, "Delete Database Promise exception", ex);
                    pw.error(this, [ex.message, ex]);
                }
            },
            getDatabaseVersion: function (db)
            {
                var dbVersion = parseInt(db.version);
                if (isNaN(dbVersion) || dbVersion < 0) {
                    return 0;
                } else {
                    return dbVersion;
                }
            },
            wrapException: function (exception, method) {
                return {
                    code: exception.code,
                    severity: linq2indexedDB.prototype.utilities.severity.exception,
                    orignialError: exception,
                    method: method,
                    type: "unknown"
                };
            },
            wrapError: function (error, method) {
                return {
                    severity: linq2indexedDB.prototype.utilities.severity.error,
                    orignialError: error,
                    type: (error.target && error.target.error && error.target.error.name) ? error.target.error.name : "unknown",
                    method: method
                };
            }
        };

        linq2indexedDB.prototype.core = {
            db: function (name, version) {
                return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) {
                    internal.db(pw, name, version);
                });
            },
            transaction: function (db, objectStoreNames, transactionType, autoGenerateAllowed) {
                return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) {
                    if (db.then) {
                        db.then(function (args /*db, e*/) {
                            // Timeout necessary for letting it work on win8. If not, progress event triggers before listeners are coupled
                            if (isMetroApp) {
                                setTimeout(function () {
                                    internal.transaction(pw, args[0], objectStoreNames, transactionType, autoGenerateAllowed);
                                }, 1);
                            } else {
                                internal.transaction(pw, args[0], objectStoreNames, transactionType, autoGenerateAllowed);
                            }
                        },
                            function (args /*error, e*/) {
                                pw.error(this, args);
                            },
                            function (args /**/) {
                                pw.progress(this, args);
                            });
                    } else {
                        if (isMetroApp) {
                            // Timeout necessary for letting it work on win8. If not, progress event triggers before listeners are coupled
                            setTimeout(function() {
                                internal.transaction(pw, db, objectStoreNames, transactionType, autoGenerateAllowed);
                            }, 1);
                        } else {
                            internal.transaction(pw, db, objectStoreNames, transactionType, autoGenerateAllowed);
                        }
                    }
                });
            },
            objectStore: function (transaction, objectStoreName) {
                return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) {
                    if (transaction.then) {
                        transaction.then(function (/*txn, e*/) {
                            // transaction completed
                            // TODO: what todo in this case?
                        }, function (args /*error, e*/) {
                            pw.error(this, args);
                        }, function (args /*txn, e*/) {
                            internal.objectStore(pw, args[0], objectStoreName);
                        });
                    } else {
                        internal.objectStore(pw, transaction, objectStoreName);
                    }
                });
            },
            createObjectStore: function (transaction, objectStoreName, objectStoreOptions) {
                return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) {
                    if (transaction.then) {
                        transaction.then(function (/*txn, e*/) {
                            // txn completed
                            // TODO: what todo in this case?
                        },
                            function (args /*error, e*/) {
                                // txn error or abort
                                pw.error(this, args);
                            },
                            function (args /*txn, e*/) {
                                internal.createObjectStore(pw, args[0], objectStoreName, objectStoreOptions);
                            });
                    } else {
                        internal.createObjectStore(pw, transaction, objectStoreName, objectStoreOptions);
                    }
                });
            },
            deleteObjectStore: function (transaction, objectStoreName) {
                return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) {
                    if (transaction.then) {
                        transaction.then(function (/*txn, e*/) {
                            // txn completed
                            // TODO: what todo in this case?
                        }, function (args /*error, e*/) {
                            // txn error
                            pw.error(this, args);
                        },
                            function (args /*txn, e*/) {
                                internal.deleteObjectStore(pw, args[0], objectStoreName);
                            });
                    } else {
                        internal.deleteObjectStore(pw, transaction, objectStoreName);
                    }
                });
            },
            index: function (objectStore, propertyName, autoGenerateAllowed) {
                return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) {
                    if (objectStore.then) {
                        objectStore.then(function (args /*txn, objectStore*/) {
                            internal.index(pw, args[1], propertyName, autoGenerateAllowed);
                        }, function (args /*error, e*/) {
                            // store error
                            pw.error(this, args);
                        });
                    } else {
                        internal.index(pw, objectStore, propertyName, autoGenerateAllowed);
                    }
                });
            },
            createIndex: function (objectStore, propertyName, indexOptions) {
                return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) {
                    if (objectStore.then) {
                        objectStore.then(function (args/*txn, objectStore*/) {
                            internal.createIndex(pw, args[1], propertyName, indexOptions);
                        }, function (args /*error, e*/) {
                            // store error
                            pw.error(this, args);
                        });
                    } else {
                        internal.createIndex(pw, objectStore, propertyName, indexOptions);
                    }
                });
            },
            deleteIndex: function (objectStore, propertyName) {
                return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) {
                    if (objectStore.then) {
                        objectStore.then(function (args/*txn, objectStore*/) {
                            internal.deleteIndex(pw, args[1], propertyName);
                        }, function (args /*error, e*/) {
                            // store error
                            pw.error(this, args);
                        });
                    } else {
                        internal.deleteIndex(pw, objectStore, propertyName);
                    }
                });
            },
            cursor: function (source, range, direction) {
                return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) {
                    if (source.then) {
                        source.then(function (args /*txn, source*/) {
                            internal.cursor(pw, args[1], range, direction);
                        }, function (args /*error, e*/) {
                            // store or index error
                            pw.error(this, args);
                        });
                    } else {
                        internal.cursor(pw, source, range, direction);
                    }
                });
            },
            keyCursor: function (index, range, direction) {
                return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) {
                    if (index.then) {
                        index.then(function (args /*txn, index, store*/) {
                            internal.keyCursor(pw, args[1], range, direction);
                        }, function (args /*error, e*/) {
                            // index error
                            pw.error(this, args);
                        });
                    } else {
                        internal.keyCursor(pw, index, range, direction);
                    }
                });
            },
            get: function (source, key) {
                return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) {
                    if (source.then) {
                        source.then(function (args /*txn, source*/) {
                            internal.get(pw, args[1], key);
                        }, function (args /*error, e*/) {
                            // store or index error
                            pw.error(this, args);
                        });
                    } else {
                        internal.get(pw, source, key);
                    }
                });
            },
            count: function (source) {
                return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) {
                    if (source.then) {
                        source.then(function (args /*txn, source*/) {
                            internal.count(pw, args[1]);
                        }, function (args /*error, e*/) {
                            // store or index error
                            pw.error(this, args);
                        });
                    } else {
                        internal.count(pw, source);
                    }
                });
            },
            getKey: function (index, key) {
                return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) {
                    if (index.then) {
                        index.then(function (args /*txn, index, objectStore*/) {
                            internal.getKey(pw, args[1], key);
                        }, function (args /*error, e*/) {
                            // index error
                            pw.error(this, args);
                        });
                    } else {
                        internal.getKey(pw, index, key);
                    }
                });
            },
            insert: function (objectStore, data, key) {
                return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) {
                    if (objectStore.then) {
                        objectStore.then(function (args /*txn, store*/) {
                            internal.insert(pw, args[1], data, key);
                        }, function (args /*error, e*/) {
                            // store error
                            pw.error(this, args);
                        });
                    } else {
                        internal.insert(pw, objectStore, data, key);
                    }
                });
            },
            update: function (objectStore, data, key) {
                return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) {
                    if (objectStore.then) {
                        objectStore.then(function (args /*txn, store*/) {
                            internal.update(pw, args[1], data, key);
                        }, function (args /*error, e*/) {
                            // store error
                            pw.error(this, args);
                        });
                    } else {
                        internal.update(pw, objectStore, data, key);
                    }
                });
            },
            remove: function (objectStore, key) {
                return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) {
                    if (objectStore.then) {
                        objectStore.then(function (args /*txn, store*/) {
                            internal.remove(pw, args[1], key);
                        }, function (args /*error, e*/) {
                            // store error
                            pw.error(this, args);
                        });
                    } else {
                        internal.remove(pw, objectStore, key);
                    }
                });
            },
            clear: function (objectStore) {
                return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) {
                    if (objectStore.then) {
                        objectStore.then(function (args /*txn, store*/) {
                            internal.clear(pw, args[1]);
                        }, function (args /*error, e*/) {
                            // store error
                            pw.error(this, args);
                        });
                    } else {
                        internal.clear(pw, objectStore);
                    }
                });
            },
            deleteDb: function (name) {
                return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) {
                    internal.deleteDb(pw, name);
                });
            },
            closeDatabaseConnection: function (target) {
                var db;
                if (target instanceof IDBCursor) {
                    target = target.source;
                }

                if (target instanceof IDBDatabase) {
                    db = target;
                } else if (target instanceof IDBTransaction) {
                    db = target.db;
                } else if (target instanceof IDBObjectStore || target instanceof IDBRequest) {
                    db = target.transaction.db;
                } else if (target instanceof IDBIndex) {
                    db = target.objectStore.transaction.db;
                }

                linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Close database Connection: ", db);
                db.close();
            },
            abortTransaction: function (transaction) {
                linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Abort transaction: " + transaction);
                // Calling the abort, blocks the database in IE10
                if (implementation != implementations.MICROSOFT) {
                    transaction.abort();
                    linq2indexedDB.prototype.core.closeDatabaseConnection(transaction);
                }
            },
            transactionTypes: transactionTypes,
            dbStructureChanged: new eventTarget(),
            dbDataChanged: new eventTarget(),
            databaseEvents: dbEvents,
            dataEvents: dataEvents,
            implementation: implementation,
            implementations: implementations
        };

        if (implementation == implementations.SHIM) {
            linq2indexedDB.prototype.core.indexSuffix = "IIndex";
        } else {

            linq2indexedDB.prototype.core.indexSuffix = "-Index";
        }

        // Region Functions

        function initializeIndexedDb() {
            if (window === 'undefined') {
                return implementations.NONE;
            }

            if (window.indexedDB) {
                linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Native implementation", window.indexedDB);
                return implementations.NATIVE;
            } else {
                // Initialising the window.indexedDB Object for FireFox
                if (window.mozIndexedDB) {
                    window.indexedDB = window.mozIndexedDB;

                    if (typeof window.IDBTransaction.READ_ONLY === "number"
                        && typeof window.IDBTransaction.READ_WRITE === "number"
                        && typeof window.IDBTransaction.VERSION_CHANGE === "number") {
                        transactionTypes.READ_ONLY = window.IDBTransaction.READ_ONLY;
                        transactionTypes.READ_WRITE = window.IDBTransaction.READ_WRITE;
                        transactionTypes.VERSION_CHANGE = window.IDBTransaction.VERSION_CHANGE;
                    }

                    linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "FireFox Initialized", window.indexedDB);
                    return implementations.MOZILLA;
                }

                    // Initialising the window.indexedDB Object for Chrome
                else if (window.webkitIndexedDB) {
                    if (!window.indexedDB) window.indexedDB = window.webkitIndexedDB;
                    if (!window.IDBCursor) window.IDBCursor = window.webkitIDBCursor;
                    if (!window.IDBDatabase) window.IDBDatabase = window.webkitIDBDatabase; //if (!window.IDBDatabaseError) window.IDBDatabaseError = window.webkitIDBDatabaseError
                    if (!window.IDBDatabaseException) window.IDBDatabaseException = window.webkitIDBDatabaseException;
                    if (!window.IDBFactory) window.IDBFactory = window.webkitIDBFactory;
                    if (!window.IDBIndex) window.IDBIndex = window.webkitIDBIndex;
                    if (!window.IDBKeyRange) window.IDBKeyRange = window.webkitIDBKeyRange;
                    if (!window.IDBObjectStore) window.IDBObjectStore = window.webkitIDBObjectStore;
                    if (!window.IDBRequest) window.IDBRequest = window.webkitIDBRequest;
                    if (!window.IDBTransaction) window.IDBTransaction = window.webkitIDBTransaction;
                    if (!window.IDBOpenDBRequest) window.IDBOpenDBRequest = window.webkitIDBOpenDBRequest;
                    if (typeof window.IDBTransaction.READ_ONLY === "number"
                        && typeof window.IDBTransaction.READ_WRITE === "number"
                        && typeof window.IDBTransaction.VERSION_CHANGE === "number") {
                        transactionTypes.READ_ONLY = window.IDBTransaction.READ_ONLY;
                        transactionTypes.READ_WRITE = window.IDBTransaction.READ_WRITE;
                        transactionTypes.VERSION_CHANGE = window.IDBTransaction.VERSION_CHANGE;
                    }

                    linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Chrome Initialized", window.indexedDB);
                    return implementations.GOOGLE;
                }

                    // Initialiseing the window.indexedDB Object for IE 10 preview 3+
                else if (window.msIndexedDB) {
                    window.indexedDB = window.msIndexedDB;

                    transactionTypes.READ_ONLY = 0;
                    transactionTypes.READ_WRITE = 1;
                    transactionTypes.VERSION_CHANGE = 2;

                    linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "IE10+ Initialized", window.indexedDB);
                    return implementations.MICROSOFT;
                }

                    // Initialising the window.indexedDB Object for IE 8 & 9
                else if (navigator.appName == 'Microsoft Internet Explorer') {
                    try {
                        window.indexedDB = new ActiveXObject("SQLCE.Factory.4.0");
                        window.indexedDBSync = new ActiveXObject("SQLCE.FactorySync.4.0");
                    } catch (ex) {
                        linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Initializing IE prototype exception", ex);
                    }

                    if (window.JSON) {
                        window.indexedDB.json = window.JSON;
                        window.indexedDBSync.json = window.JSON;
                    } else {
                        var jsonObject = {
                            parse: function (txt) {
                                if (txt === "[]") return [];
                                if (txt === "{}") return {};
                                throw { message: "Unrecognized JSON to parse: " + txt };
                            }
                        };
                        window.indexedDB.json = jsonObject;
                        window.indexedDBSync.json = jsonObject;
                    }

                    // Add some interface-level constants and methods.
                    window.IDBDatabaseException = {
                        UNKNOWN_ERR: 0,
                        NON_TRANSIENT_ERR: 1,
                        NOT_FOUND_ERR: 2,
                        CONSTRAINT_ERR: 3,
                        DATA_ERR: 4,
                        NOT_ALLOWED_ERR: 5,
                        SERIAL_ERR: 11,
                        RECOVERABLE_ERR: 21,
                        TRANSIENT_ERR: 31,
                        TIMEOUT_ERR: 32,
                        DEADLOCK_ERR: 33
                    };

                    window.IDBKeyRange = {
                        SINGLE: 0,
                        LEFT_OPEN: 1,
                        RIGHT_OPEN: 2,
                        LEFT_BOUND: 4,
                        RIGHT_BOUND: 8
                    };

                    window.IDBRequest = {
                        INITIAL: 0,
                        LOADING: 1,
                        DONE: 2
                    };

                    window.IDBTransaction = {
                        READ_ONLY: 0,
                        READ_WRITE: 1,
                        VERSION_CHANGE: 2
                    };

                    transactionTypes.READ_ONLY = 0;
                    transactionTypes.READ_WRITE = 1;
                    transactionTypes.VERSION_CHANGE = 2;

                    window.IDBKeyRange.only = function (value) {
                        return window.indexedDB.range.only(value);
                    };

                    window.IDBKeyRange.leftBound = function (bound, open) {
                        return window.indexedDB.range.lowerBound(bound, open);
                    };

                    window.IDBKeyRange.rightBound = function (bound, open) {
                        return window.indexedDB.range.upperBound(bound, open);
                    };

                    window.IDBKeyRange.bound = function (left, right, openLeft, openRight) {
                        return window.indexedDB.range.bound(left, right, openLeft, openRight);
                    };

                    window.IDBKeyRange.lowerBound = function (left, openLeft) {
                        return window.IDBKeyRange.leftBound(left, openLeft);
                    };

                    return implementations.MICROSOFTPROTOTYPE;
                } else if (window.shimIndexedDB) {
                    window.indexedDB = window.shimIndexedDB;

                    return implementations.SHIM;
                } else {
                    linq2indexedDB.prototype.utilities.log(linq2indexedDB.prototype.utilities.severity.information, "Your browser doesn't support indexedDB.");
                    return implementations.NONE;
                }
            }
        };

        function deferredHandler(handler, request) {
            return linq2indexedDB.prototype.utilities.promiseWrapper(function (pw) {
                try {
                    handler(pw, request);
                } catch (e) {
                    e.type = "exception";
                    pw.error(request, [e.message, e]);
                }
                finally {
                    request = null;
                }
            });
        };

        function IDBSuccessHandler(pw, request) {
            request.onsuccess = function (e) {
                pw.complete(e.target, [e.target.result, e]);
            };
        };

        function IDBErrorHandler(pw, request) {
            request.onerror = function (e) {
                pw.error(e.target, [e.target.errorCode, e]);
            };
        };

        function IDBAbortHandler(pw, request) {
            request.onabort = function (e) {
                pw.error(e.target, [e.target.errorCode, e]);
            };
        };

        function IDBVersionChangeHandler(pw, request) {
            request.onversionchange = function (e) {
                pw.progress(e.target, [e.target.result, e]);
            };
        };

        function IDBCompleteHandler(pw, request) {
            request.oncomplete = function (e) {
                pw.complete(e.target, [e.target, e]);
            };
        };

        function IDBRequestHandler(pw, request) {
            IDBSuccessHandler(pw, request);
            IDBErrorHandler(pw, request);
        };

        function IDBCursorRequestHandler(pw, request) {
            request.onsuccess = function (e) {
                if (!e.target.result) {
                    pw.complete(e.target, [e.target.result, e]);
                } else {
                    pw.progress(e.target, [e.target.result, e]);
                }
            };
            IDBErrorHandler(pw, request);
        };

        function IDBBlockedRequestHandler(pw, request) {
            IDBRequestHandler(pw, request);
            request.onblocked = function (e) {
                pw.progress(e.target, ["blocked", e]);
            };
        };

        function IDBOpenDbRequestHandler(pw, request) {
            IDBBlockedRequestHandler(pw, request);
            request.onupgradeneeded = function (e) {
                pw.progress(e.target, [e.target.transaction, e]);
            };
        };

        function IDBDatabaseHandler(pw, database) {
            IDBAbortHandler(pw, database);
            IDBErrorHandler(pw, database);
            IDBVersionChangeHandler(pw, database);
        };

        function IDBTransactionHandler(pw, txn) {
            IDBCompleteHandler(pw, txn);
            IDBAbortHandler(pw, txn);
            IDBErrorHandler(pw, txn);
        };

    })(window, typeof Windows !== "undefined");
    window.linq2indexedDB = linq2indexedDB;

} else {
    // Web Worker Thread
    onmessage = function (event) {
        var data = event.data.data;
        var filtersString = event.data.filters || "[]";
        var sortClauses = event.data.sortClauses || [];
        var filters = JSON.parse(filtersString, linq2indexedDB.prototype.utilities.deserialize);
        var returnData = linq2indexedDB.prototype.utilities.filterSort(data, filters, sortClauses);

        postMessage(returnData);
        return;
    };
}

// Extend array for Opera
//Array.prototype.contains = function (obj) {
//    return this.indexOf(obj) > -1;
//};