/*!
 *  jStore 2.0 - Persistent Client Side Storage
 *
 *  Copyright (c) 2010 Eric Garside (http://eric.garside.name/)
 *  Dual licensed under:
 *      MIT: http://www.opensource.org/licenses/mit-license.php
 *      GPLv3: http://www.opensource.org/licenses/gpl-3.0.html
 *
 *  ---------------------------
 *
 *  jStore Flash Storage Component
 *
 *  Copyright (c) 2006 Jeff Lerman (jeff@blip.tv)
 *  Licensed under the Creative Commons Attribution 3.0 United States License:
 *      http://creativecommons.org/licenses/by/3.0/us
 */

"use strict";

/*global Class, window, jQuery, ActiveXObject, google */

/*jslint white: true, browser: true, onevar: true, undef: true, eqeqeq: true, bitwise: true, regexp: false, strict: true, newcap: true, immed: true, maxerr: 50, indent: 4 */

(function ($, window) {
    
    //------------------------------
    //
    //  Constants
    //
    //------------------------------
    
    //------------------------------
    //  Exceptions
    //------------------------------
    
        /**
         *  An exception thrown by the StorageEngine class whenever its data accessor methods
         *  are called before the engine is ready to transact data.
         */
    var EX_UNSTABLE = 'JSTORE_ENGINE_UNSTABLE',
    
        /**
         *  An exception thrown by jStore whenever an undefined storage engine is referenced for
         *  some task by an invalid JRI (jStore Resource Identifier).
         */
        EX_UNKNOWN = 'JSTORE_UNKNOWN_ENGINE_REQUESTED',
        
        /**
         *  An exception thrown by jStore whenever a given flavor of storage is double defined.
         */
        EX_COLLISION = 'JSTORE_ENGINE_NAMESPACE_COLLISION',
        
        /**
         *  An exception thrown by jStore whenever a jri is double applied to a resource.
         */
        EX_DUPLICATE = 'JSTORE_RESOURCE_NAMESPACE_COLLISION',
        
        /**
         *  An exception thrown by jStore whenever a given flavor of storage has no defined engine.
         */
        EX_UNAVAILABLE = 'JSTORE_ENGINE_UNAVAILABLE',
        
        /**
         *  An exception thrown by jStore whenever an invalid flavor type is used.
         */
        EX_INVALID = 'JSTORE_INVALID_FLAVOR',
        
    //------------------------------
    //  Regular Expressions
    //------------------------------
    
        /**
         *  Regular expression to test property values for being JSON.
         */
        RX_JSON = (function ()
        {
            try 
            {
                return new RegExp('^("(\\\\.|[^"\\\\\\n\\r])*?"|[,:{}\\[\\]0-9.\\-+Eaeflnr-u \\n\\r\\t])+?$');
            }
            catch (e)
            {
                return (/^(true|false|null|\[.*\]|\{.*\}|".*"|\d+|\d+\.\d+)$/);
            }
        }()),
    
    //------------------------------
    //  Storage Flavors
    //------------------------------
        
        /**
         *  The storage flavor identifier for HTML5 local storage.
         */
        FLAVOR_LOCAL = 'jstore-html5-local',
        
        /**
         *  The storage flavor identifier for HTML5 database storage.
         */
        FLAVOR_SQL = 'jstore-html5-sql',
        
        /**
         *  The storage flavor identifier for Adobe Flash SharedObject storage.
         */
        FLAVOR_FLASH = 'jstore-flash',
        
        /**
         *  The storage flavor identifier for Google Gears storage.
         */
        FLAVOR_GEARS = 'jstore-google-gears',
        
        /**
         *  The storage flavor identifier for Internet Explorer storage, available to IE7 and IE6.
         */
        FLAVOR_MSIE = 'jstore-msie',
    
    //------------------------------
    //
    //  Property Declaration
    //
    //------------------------------
    
        /**
         *  The base StorageEngine class which each "storage flavor" will extend to meet the
         *  requirements for its specific implementation.
         */
        StorageEngine,
        
        /**
         *  The jStore object. Internal to this closure, jStore is referenced by "_". It is
         *  exposed to jQuery below, and made publicly accessible through jQuery.jStore
         */
        _ = {},
        
        /**
         *  The engines available to jStore for use. These are the class definitions for flavored
         *  storage engines.
         *
         *  Signature:
         *  {
         *      <storageFlavor>: <flavoredStorageEngineDefinition>,
         *
         *      ...
         *  }
         */
        definitions = {},
        
        /**
         *  Active engines instantiated by jStore, indexed by their JRI.
         *
         *  Signature:
         *  {
         *      <engineJRI>: <engineInstance>,
         *
         *      ...
         *  }
         */
        engines = {},
        
        /**
         *  If we are going to be using the flash storage engine, we want to postpone the jStore ready event until the jStore
         *  isFlashReady flag is also true. This property is set whenever flash is determined to be the storage engine.
         */
        waitForFlash = false,
        
        /**
         *  Storage for listeners, indexed by content and event type.
         *
         *  Signature:
         *  {
         *      <context>:
         *      {
         *          <eventType>: [<listener>, ...],
         *
         *          ...
         *      },
         *
         *      ...
         *  }
         */
        events = {},
        
        /**
         *  The configuration for this implementation.
         *
         *  Signature:
         *  {
         *      project: <defaultProjectName>,
         *
         *      flash: <pathToFlashBootloader>,
         *
         *      json: <pathToJSONFile>,
         *
         *      errorCallback: <listenerToNotifyOnError>
         *  }
         */
        configurations = 
        {
            project: undefined,
            
            flash: 'jStore.Flash.html',
            
            json: 'browser.json.js'
        },
        
        /**
         *  The active storage engine, being used to satisfy the get/set/remove functions on the jStore and jQuery
         *  objects.
         */
        active;
    
    //------------------------------
    //
    //  Internal Methods
    //
    //------------------------------
    
    /**
     *  Determine if the given flavor is valid.
     *
     *  @param flavor   The flavor to test.
     *
     *  @return True if the flavor is valid, false otherwise.
     */
    function validFlavor(flavor)
    {
        switch (flavor)
        {
        
        case FLAVOR_LOCAL:
        case FLAVOR_SQL:
        case FLAVOR_FLASH:
        case FLAVOR_GEARS:
        case FLAVOR_MSIE:
            return true;
        
        default:
            return false;
        
        }
    }

    /**
     *  Performs enhanced type comparison on an object. This is more reliable method
     *  of type checking a variable than a simple typeof comparison. The reason is that,
     *  typeof will reduce to the lowest common type. 
     *
     *  "typeof []" returns Object, and not Array.
     *  "typeof {}" returns Object as well.
     *
     *  typecheck( [], 'Array' )    :  returns true;
     *  typecheck( [], 'Object' )   :  returns false;
     *
     *  @param type     The variable type to check.
     *
     *  @param compare  A string representing the literal type to check.
     *
     *  @return True if the variable "type" matches the compare literal.
     */
    function typecheck(type, compare)
    {
        return !type ? false : type.constructor.toString().match(new RegExp(compare + '\\(\\)', 'i')) !== null; 
    }
    
    /**
     *  If the provided listener is a valid function, it will be triggered with the provided context
     *  and parameters.
     *
     *  @param listener     The listener being triggered.
     *  
     *  @param context      The context to provide to the listener.
     *
     *  @param parameters   The parameters to pass to the listener as arguments.
     *
     *  @return The response of the notified listener.
     */
    function notify(listener, context, parameters)
    {
        if (typecheck(listener, 'Function'))
        {
            return listener.apply(context || _, typecheck(parameters, 'Array') ? parameters : [parameters]);
        }
    }
    
    /**
     *  Load the given script.
     *
     *  @param path     The path to the file to include.
     *
     *  @param listener The listener to notify when the file finishes loading.
     */
    function loadScript(path, listener)
    {
        $.ajax(
        {
            url: path,
            complete: listener || $.noop(),
            type: 'GET',
            dataType: 'script',
            cache: false
        });  
    }
    
    /**
     *  Checks the type of the value, and returns a value safe to persist in any client-side mechanism.
     *
     *  @param value    The value which should be prepared for storage.
     *
     *  @return A value safe for storage.
     */
    function prepareForStorage(value)
    {
        if (value === undefined)
        {
            return '';
        }
        
        if (typecheck(value, 'Object') ||
            typecheck(value, 'Array') ||
            typecheck(value, 'Function'))
        {
            return JSON.stringify(value);
        }
        
        return value;
    }
    
    /**
     *  Checks the type of the value, and returns a value safe for access in any client-side mechanism.
     *
     *  @param value    The value which should be prepared for use.
     *
     *  @return A value safe for use.
     */
    function prepareForRevival(value)
    {
        return RX_JSON.test(value) ? JSON.parse(value) : value;
    }
    
    /**
     *	Normalize a key before using it, to ensure it's valid.
     *
     *  @param key  The key to normalize.
     *
     *  @return A normalized key, safe for storage.
     */
    function normalizeKey(key)
    {
        return key.replace(/^\s+|\s+$/g, "");
    }
    
    /**
     *  Define a flavored storage engine.
     *
     *  @throws EX_COLLISION, EX_INVALID
     *
     *  @param flavor       The flavor of engine being defined.
     *
     *  @param definition   An object containing the new properties and methods for the engine extension.
     *
     *  @param availability A function to invoke which must return a boolean value indicating the
     *                      availability of the storage flavor on this browser.
     */
    function define(flavor, definition, availability)
    {
        if (!validFlavor(flavor))
        {
            throw EX_INVALID;
        }
    
        if (availability[flavor] !== undefined)
        {
            throw EX_COLLISION;
        }
        
        /**
         *  The logic here has been reworked so unavailable flavors are discarded, so we don't needlessly
         *  bloat the runtime size of jStore.
         */
        if (notify(availability) === true)
        {
            _.available[flavor] = true;
            
            definition.flavor = flavor;
            
            definitions[flavor] = StorageEngine.extend(definition);
        }
        else
        {
            _.available[flavor] = false;
            
            //  Filter the invalid flavor out of the priority list.
            _.enginePriority = $.map(_.enginePriority, function (engine)
            {
                if (engine === flavor)
                {
                    return null;
                }
                else
                {
                    return engine;
                }
            });
        }
    }

    /**
     *  Make the jStore library ready.
     */
    function makeReady()
    {
        if (_.isReady)
        {
            return;
        }
        
        if ((waitForFlash && _.isFlashReady) || !waitForFlash)
        {
            _.isReady = true;
            _.trigger('jstore-ready', [engines[active]]);
        }
    }
    
    /**
     *  Create a best-fit engine.
     */
    function createBestFitEngine()
    {
        _.create(_.enginePriority[0], undefined, 'best-fit');
    }
    
    /**
     *  Get the flash version currently supported in this browser.
     *
     *  @return The flash version.
     */
    function flashVersion()
    {
        // MSIE
        try
        {
            // avoid fp6 minor version lookup issues
            // see: http://blog.deconcept.com/2006/01/11/getvariable-setvariable-crash-internet-explorer-flash-6/
            var axo = new ActiveXObject('ShockwaveFlash.ShockwaveFlash.6');
            
            try
            {
                axo.AllowScriptAccess = 'always';
            }
            catch (axo_e)
            {
                return '6,0,0';
            }
            
            return new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version').replace(/\D+/g, ',').match(/^,?(.+),?$/)[1];
        }
        
        //  Real browsers
        catch (e)
        {
            try
            {
                if (navigator.mimeTypes["application/x-shockwave-flash"].enabledPlugin)
                {
                    return (navigator.plugins["Shockwave Flash 2.0"] || navigator.plugins["Shockwave Flash"]).description.replace(/\D+/g, ",").match(/^,?(.+),?$/)[1];
                }
            }
            catch (flash_e)
            {}
        }
        
        return '0,0,0';
    }

    /**
     *  Flash Detection functions copied from the jQuery Flash Plugin
     * 
     *  Copyright (c) 2006 Luke Lutman (http://jquery.lukelutman.com/plugins/flash)
     * 
     *  Dual licensed under the MIT and GPL licenses.
     *      http://www.opensource.org/licenses/mit-license.php
     *      http://www.opensource.org/licenses/gpl-license.php 
     *
     *  @param version  The version to compare to.
     *
     *  @return True if the version is greater than or equal to the required version, false otherwise.
     */
    function hasFlashVersion(version)
    {
        var playerVersion = flashVersion().match(/\d+/g),
            requiredVersion = version.match(/\d+/g),
            index = 0,
            player, 
            required;
            
        for (; index < 3; index++)
        {
            player = parseInt(playerVersion[index], 10);
            required = parseInt(requiredVersion[index], 10);
        
            //  Player version is less than what is required.
            if (player < required)
            {
                return false;
            }
            
            //  Player version is greater than what is required.
            else if (player > required)
            {
                return true;
            }
        }
        
        //  Player and required version match exactly.
        return true;
    }

    //------------------------------
    //
    //  Plugin Definition
    //
    //------------------------------
    
    //------------------------------
    //  Error Declaration
    //------------------------------
    
    //------------------------------
    //  Plugin Creation
    //------------------------------
    
    /**
     *  The jStore object. Manages a collection of StorageEngines for particular "storage flavors", or the types
     *  of storage solutions available to each browser.
     *
     *  2.0 Version Notes:
     *
     *      - The user is now responsible for third-party script includes, with the exception of flash.
     *
     *      - jStore has been given sole responsibility for testing engine availability.
     *
     *      - For the sake of naming conventions, all property names now start with a lowercase, and are camel-cased.
     *
     *  The following properties have been changed since the 1.2.x release:
     *
     *      - EngineOrder:      For the sake of naming conventions, renamed to enginePriority.
     *
     *  The following properties and methods have been removed since the 1.2.x release:
     *
     *      - Availability:     jStore's engines would add their availability tests to this object, so jStore could test 
     *                          them. With the changes to how availability testing works, this property has been removed.
     *                          A new property, "available" on jStore contains a set of available engines.
     *
     *      - Engines:          Formerly contained the definitions of storage engines. This property has been removed, and
     *                          storage of these definitions has been moved internal to the closure.
     *     
     *      - Instances:        Formerly contained instantiated storage engines. This property has been removed, and storage
     *                          of instantiated engines has been moved internal to the closure.
     *
     *      - CurrentEngine:    Formerly contained the active storage engine being used for transacting data through the jStore
     *                          and/or jQuery objects. This property has been removed, and storage of the current engine has
     *                          been moved internal to the closure. A new method, "activeEngine" has been added to jQuery to
     *                          get and set the active engine to use.
     *
     *      - defaults:         Formerly used to set the implementation options for jStore. This property has been removed and
     *                          replaced with a new configuration metho on the jStore object.
     *
     *      - delegate:         The delegate class has been removed in favor of a much simpler bind/trigger accessor system, which
     *                          is accessible contextually through storage engines, or generically through jStore.
     *      
     *      + fail:             This registration class bound events on the delegate for jstore-fail events. Instead, use:
     *                              jStore.bind('jstore-failure', listener);
     *
     *      + flashReady:       This registration class bound events on the delegate for flash-ready events. The jstore-ready method
     *                          now accounts for waiting for flash readyness, if and only if the flash engine is being used. Simply
     *                          call to jStore.ready().
     *
     *      + load:             Replaced with the init() method, which performs the same basic functions as the old load() method. Also,
     *                          the init function is now domready safe, meaning it wraps itself in a domready listener, so the end user
     *                          doesn't have to.
     *
     *      + FindEngine:       Removed entirely. The functionality provided by this method now implicitly occurs with the new define()
     *                          system implemented for engine flavors.
     *
     *      + setCurrentEngine: Replaced by activeEngine(). Set the current active engine by passing in the JRI.
     *
     *      + safeStore:        Replaced by a method internal to this closure, "prepareForStorage".
     *
     *      + safeResurrect:    Replaced by a method internal to this closure, "prepareForRevival".
     *
     *      + use:              Replaced by "create".
     */
    $.extend(_, {
    
        //------------------------------
        //  Properties
        //------------------------------
    
        /**
         *  The priority order in which engines should be tested for use. The lower their index in the array, the higher
         *  their priority for use.
         *
         *  Be weary when reconfiguring the priority order of engines! jStore will use the first available engine it finds
         *  based on its priority when autoloading.
         *
         *  This array is filtered out as engines are defined, with invalid engines being removed.
         *
         *  Signature:
         *  [FLAVOR_<storageFlavor>, ...]
         */
        enginePriority: [FLAVOR_LOCAL, FLAVOR_SQL, FLAVOR_FLASH, FLAVOR_MSIE],
        
        /**
         *  A collection of the availability states of engines, indexed by their flavor.
         *
         *  Signature:
         *  {
         *      <storageFlavor>: true|false,
         *
         *      ...
         *  }
         */
        available: {},
        
        /**
         *  Flag to determine if the jStore library is ready. jStore becomes ready once the dom is ready and all necessary 
         *  startup procedures required by jStore to function properly are completed.
         */
        isReady: false,
        
        /**
         *  With the flash storage engine, we have to jump through a couple of hoops before the flash engine is ready to work.
         *  This flag tracks whether or not the flash storage is available.
         */
        isFlashReady: false,
        
        /**
         *  The available engine flavors.
         */
        flavors:
        {
            local: FLAVOR_LOCAL,
            
            sql: FLAVOR_SQL,
            
            flash: FLAVOR_FLASH,
            
            gears: FLAVOR_GEARS,
            
            msie: FLAVOR_MSIE
        },
        
        //------------------------------
        //  Constructor
        //------------------------------
        
        /**
         *  Constructor.
         *
         *  @throws EX_INVALID
         *
         *  @param project          The name of the jStore project. Used to generate a JRI for the engine we create.
         *
         *  @param configuration    Optionally, an object containing configuration options for this implementation.
         *
         *  @param flavor           Optionally, the flavor of storage to use. If not provided, jStore will pick the
         *                          best flavor, based on the current browser.
         *
         *  @return jStore
         */
        init: function (project, configuration, flavor)
        {   
            //  Extend our plugin configurations
            $.extend(configurations, {project: project}, configuration);
            
            $(function ()
            {
                //  If JSON parsing isn't defined in this browser, include it.
                if (window.JSON === undefined)
                {
                    loadScript(configurations.json);
                }
            
                //  If we have an explicit flavor to use, use it.
                if (flavor !== undefined)
                {
                    _.create(flavor, project, 'default');
                }
                
                //  Otherwise, attempt to create a best-fit engine.
                else
                {
                    createBestFitEngine();
                }
            });
        
            return _;
        },
    
        //------------------------------
        //  Methods
        //------------------------------
    
        /**
         *  Create an instance of a flavored engine.
         *
         *  @throws EX_INVALID, EX_UNAVAILABLE, EX_DUPLICATE
         *
         *  @param flavor       The flavor to create the engine with.
         *
         *  @param project      The project identifier for this instance.
         *
         *  @param identifier   Some arbitrary identifier for this project instance of the engine.
         *
         *  @return The created instance.
         */
        create: function (flavor, project, identifier)
        {
            project = project || configurations.project || location.hostname.replace(/\./g, '-') || 'unknown';
            
            if (!validFlavor(flavor))
            {
                throw EX_INVALID;
            }
            
            if (definitions[flavor] === undefined)
            {
                throw EX_UNAVAILABLE;            
            }
            
            var jri = (identifier !== undefined ? identifier + '.' : '') + project + '.' + flavor,
                engine;

            if (engines[jri] !== undefined)
            {
                throw EX_DUPLICATE;
            }
            
            //  Create our engine instance.
            engine = engines[jri] = new definitions[flavor](project, jri);
            
            //  Set up a listener for our jstore-engine-ready event.
            engine.ready(function ()
            {
                _.trigger('jstore-engine-ready', [engine]);
            });
            
            if (flavor === FLAVOR_FLASH && !_.isFlashReady)
            {
                if (active === undefined)
                {
                    waitForFlash = true;
                }
                
                //  Define a window-accessible function for flash to call via ExternalInterface
                window.jstore_ready = function ()
                {
                    _.isFlashReady = true;
                    _.trigger('flash-ready');
                    
                    if (active === undefined)
                    {
                        makeReady();
                    }
                    
                    //  Remove the callback from the window scope, as it is no longer necessary
                    window.flash_ready = undefined;
                };
                
                window.jstore_error = function (message)
                {
                    _.trigger('jstore-error', ['JSTORE_FLASH_EXCEPTION', null, message]);
                };
                
                $('<iframe style="height:1px;width:1px;position:absolute;left:0;top:0;margin-left:-100px;" id="jStoreFlashFrame" src="' + 
                    configurations.flash + '"></iframe>').appendTo('body');   
            }
            else if (active === undefined)
            {
                active = jri;
                makeReady();
            }
            
            return engine;
        },
        
        /**
         *  Fetch an engine by it's JRI.
         *
         *  @param jri  The JRI of the engine to retrieve.
         *
         *  @return The requested engine.
         */
        engine: function (jri)
        {
            return engines[jri];
        },
    
        /**
         *  Returns the active storage engine being used. If a value is passed, sets that engine as the active engine.
         *
         *  @throws EX_UNKNOWN
         *
         *  @param jri  Optionally, the JRI of the engine to make active, if it should be changed.
         *
         *  @return The active storage engine.
         */
        activeEngine: function (jri)
        {
            if (jri !== undefined)
            {
                if (engines[jri] === undefined)
                {
                    throw EX_UNKNOWN;
                }
                else
                {
                    active = jri;
                }
            }
            
            return engines[active];
        },
        
        /**
         *  Bind an event listener.
         *
         *  @param event    The event to bind a listener on.
         *
         *  @param listener The listener to notify when the event occurs.
         *
         *  @param context  The context of the binding. A string representing the engine flavor
         *                  binding the event, or undefined to indicate it's a jStore event.
         *
         *  @return jStore
         */
        bind: function (event, listener, context)
        {
            context = context || 'jstore';
            
            if (events[context] === undefined)
            {
                events[context] = {};
            }
            
            if (events[context][event] === undefined)
            {
                events[context][event] = [listener];
            }
            else
            {
                events[context][event].push(listener);
            }
            
            return _;
        },
        
        /**
         *  Trigger an event, notifying any bound listeners.
         *
         *  @param event        The event to trigger.
         *
         *  @param parameters   Any additional parameters to pass to the listeners being notified.
         *
         *  @param context      The context of the binding. A string representing the engine flavor
         *                      binding the event, or undefined to indicate it's a jStore event.
         *
         *  @return jStore
         */
        trigger: function (event, parameters, context)
        {
            context = context || 'jstore';
            
            if (events[context] !== undefined)
            {
                if (events[context][event] !== undefined)
                {
                    $.each(events[context][event], function ()
                    {
                        notify(this, _, parameters);
                    });
                }
            }
            
            return _;
        },
        
        /**
         *	Bind a listener to be notified when jStore causes a non-fatal exception.
         *
         *  @param listener The listener to notify when a failure occurs.
         */
        error: function (listener)
        {
            _.bind('jstore-error', listener);
        },
        
        /**
         *  Bind a listener to be notified when jStore is ready.
         *
         *  @param listener The listener to notify when jStore is ready.
         *
         *  @return jStore
         */
        ready: function (listener)
        {
            if (_.isReady)
            {
                notify(listener);
            }
            else
            {
                _.bind('jstore-ready', listener);
            }
            
            return _;
        },
        
        /**
         *  Bind a listener to be notified when jStore and the default engine are ready.
         *
         *  @param listener The listener to notify when jStore and it's default engine are ready.
         *
         *  @return jStore
         */
        engineReady: function (listener)
        {
            if (_.isReady)
            {
                notify(listener);
            }
            else
            {
                _.bind('jstore-engine-ready', listener);
            }
            
            return _;
        },
        
        /**
         *  A combined getter/setter for the active engine.
         *
         *  @param key      The key of the property to get, or set.
         *
         *  @param value    If a valid value is provided, sets the engine.
         *
         *  @return The requested property value.
         */
        store: function (key, value)
        {   
            return value === undefined ? _.get(key) : _.set(key, value);
        },
        
        /**
         *  Remove a property from the active engine.
         *
         *  @param key  The key of the property to remove.
         *
         *  @return The value of the property before removal.
         */
        remove: function (key)
        {
            return _.activeEngine().remove(key);
        },
        
        /**
         *  Get a property from the active engine.
         *
         *  @param key  The key of the property to get.
         *
         *  @return The value of the property.
         */
        get: function (key)
        {
            return _.activeEngine().get(key);
        },
        
        /**
         *  Set a property on the active engine.
         *
         *  @param key      The key of the property to set.
         *
         *  @param value    The value to set the property to.
         *
         *  @return The new value of the property.
         */
        set: function (key, value)
        {
            return _.activeEngine().set(key, value);
        }
    
    });
    
    //------------------------------
    //  Core Extension
    //------------------------------
    
    //------------------------------
    //
    //  Class Definition
    //
    //------------------------------
    
    /**
     *  The StorageEngine class is the unified API through which jStore accesses and manipulates
     *  the various storage flavors available.
     *
     *  2.0 Version Notes:
     *
     *      - All third-party loading is now the responsibility of the developer.
     *
     *      - The delegate class has been removed entirely. Engines have been given "bind" and "trigger" methods
     *          to interact directly with the delegate like-replacement that has been added to jStore.
     *
     *      - Engine availability has been moved out of the engines themselves, and elevated to a jStore
     *          responsibility.
     *
     *  The following methods have changed since the 1.2.x release:
     *
     *      - get:          When "get"ting a non-stored property, the get function will now return "undefined"
     *                      instead of "null". "null" can now be used as a valid property value.
     *
     *      - rem:          Renamed to "remove". I always felt dirty about "rem" being vaguely explicit.
     *
     *  The following properties have been removed since the 1.2.x release:
     *
     *      - autoload:     Part of the third-party loading logic.
     *
     *      - hasIncluded:  Part of the third-party loading logic.
     *  
     *      - includes:     Part of the third-party loading logic.
     *
     *      - isAvailable:  Part of the availability logic elevated to jStore.
     *
     *  @throws EX_UNSTABLE
     */
    StorageEngine = Class.extend({
    
        //------------------------------
        //  Properties
        //------------------------------
    
        /**
         *  The project which owns this storage engine.
         */
        project: undefined,
        
        /**
         *  The JRI (jStore Resource Identifier) acts as a uuid for this specific instance
         *  of the storage engine.
         */
        jri: undefined,
        
        /**
         *  The flavor of this engine.
         */
        flavor: undefined,
        
        /**
         *  The actual database object which data is transacted through.
         */
        database: undefined,
        
        /**
         *  A StorageEngine should always respond to fetch requests synchronously. However, some 
         *  of the storage flavors require callback-based asynchronous access. To get around this, 
         *  we simlpy require all engines to function off a primary data cache, to allow for 
         *  synchronous access across all implementations.
         *
         *  Signature:
         *  {
         *      <propertyKey>: <propertyValue>,
         *
         *      ...
         *  }
         */
        data: undefined,
        
        /**
         *  A number of storage engines enforce a size limit as to what they will persist for a given site.
         *  This limit is not monitored or computed by jStore currently, and this property will merely give
         *  a static indication of the total size alloted to the engine, as defined by the storage flavor.
         */
        limit: undefined,
        
        /**
         *  Each storage flavor has a different process to go through before it's "ready" to transact data. This
         *  property stores the state of the engine's readyness, and uses it to notify listeners whenever jStore
         *  is ready to function.
         */
        isReady: undefined,

        //------------------------------
        //  Constructor
        //------------------------------
        
        /**
         *  Constructor.
         *
         *  @param project  The project which instantiated this engine.
         *
         *  @param jri      The uuid assigned to this instance by jStore.
         */
        init: function (project, jri)
        {
            this.project = project;
            this.jri = jri;
            this.data = {};
            this.isReady = false;
            this.updateCache();
        },
        
        //------------------------------
        //  Methods
        //------------------------------
        
        /**
         *  Update the cache.
         */
        updateCache: function ()
        {
            this.isReady = true;
            this.trigger('engine-ready', [this]);
        },
        
        /**
         *  Bind a listener to an event dispatched by this engine.
         *
         *  @param event    The event to bind on.
         *
         *  @param listener The listener to notify when the event occurs.
         */
        bind: function (event, listener)
        {
            _.bind(event, listener, this.jri);
        },
        
        /**
         *  Trigger an event, notifying all bound listeners.
         *
         *  @param event        The event to trigger.
         *
         *  @param parameters   An optional Array of parameters to pass to the listeners.
         */
        trigger: function (event, parameters)
        {
            _.trigger(event, parameters, this.jri);
        },
        
        /**
         *  Bind a listener to the StorageEngine's ready event.
         *
         *  @param listener The listener to notify whenever this engine is ready to transact data.
         */
        ready: function (listener)
        {
            if (this.isReady)
            {
                notify(listener, this);
            }
            else
            {
                this.bind('engine-ready', listener);
            }
        },
        
        /**
         *  Get a property from the StorageEngine.
         *
         *  @param key  The property key of the data to retrieve.
         *
         *  @return The property value, or "undefined" if the property isn't stored.
         */
        get: function (key)
        {
            this.__interruptAccess();
            
            return this.data[key];
        },
        
        /**
         *  Sets a property in the StorageEngine.
         *
         *  @param key      The key of the property.
         *
         *  @param value    The value of the property.
         *
         *  @return The new value of the property.
         */
        set: function (key, value)
        {
            this.__interruptAccess();
            
            key = normalizeKey(key);
            
            try
            {
                this.__set(key, value);
            }
            catch (e)
            {
                _.trigger('jstore-error', ['JSTORE_STORAGE_FAILURE', this.jri, e]);
            }
            
            this.data[key] = value;
            
            return value;
        },
        
        /**
         *  Removes a property from the StorageEngine.
         *
         *  @param key  The property key of the data to remove.
         *
         *  @return The value of the property, before it was removed.
         */
        remove: function (key)
        {
            this.__interruptAccess();
            
            key = normalizeKey(key);
            
            try
            {
                this.__remove(key);
            }
            catch (e)
            {
                _.trigger('jstore-error', ['JSTORE_REMOVE_FAILURE', this.jri, e]);
            }
            
            var buffer = this.data[key];
            
            this.data[key] = undefined;
            
            return buffer;
        },
        
        //------------------------------
        //  Internal Methods
        //------------------------------
        
        /**
         *  Ensures the engine is in a stable state for transacting data.
         *
         *  @throws EX_UNSTABLE
         */
        __interruptAccess: function ()
        {
            if (!this.isReady)
            {
                throw EX_UNSTABLE;
            }
        },
        
        /**
         *  Sets a property in the StorageEngine. This method should be overloaded to provide actual
         *  storage flavor integration.
         *
         *  @param key      The key of the property.
         *
         *  @param value    The value of the property.
         *
         *  @return The new value of the property.
         */
        __set: function (key, value)
        {
            return;
        },
        
        /**
         *  Removes a property from the StorageEngine. This method should be overloaded to provide actual
         *  storage flavor integration.
         *
         *  @param key  The property key of the data to remove.
         *
         *  @return The value of the property, before it was removed.
         */
        __remove: function (key)
        {
            return;
        }
        
    });
        
    //------------------------------
    //
    //  jQuery Hooks
    //
    //------------------------------
    
    $.extend($.fn, {
    
        //------------------------------
        //  Methods
        //------------------------------
    
        /**
         *  A combined getter/setter for the active engine.
         *
         *  @param key      The key of the property to get, or set.
         *
         *  @param value    If a valid value is provided, sets the engine.
         *
         *  @return jQuery
         */
        store: function (key, value)
        {   
            if (value === undefined)
            {
                _.get(key);
            }
            else
            {
                _.set(key, value);
            }
            
            return this;
        },
        
        /**
         *  Remove a property from the active engine.
         *
         *  @param key  The key of the property to remove.
         *
         *  @return jQuery
         */
        removeStore: function (key)
        {
            _.activeEngine().remove(key);
            
            return this;
        },
        
        /**
         *  Get a property from the active engine.
         *
         *  @param key  The key of the property to get.
         *
         *  @return The value of the property.
         */
        getStore: function (key)
        {
            return _.activeEngine().get(key);
        },
        
        /**
         *  Set a property on the active engine.
         *
         *  @param key      The key of the property to set.
         *
         *  @param value    The value to set the property to.
         *
         *  @return jQuery
         */
        setStore: function (key, value)
        {
            _.activeEngine().set(key, value);
            
            return this;
        }
        
    });
    
    //------------------------------
    //
    //  Event Bindings
    //
    //------------------------------
    
    //------------------------------
    //
    //  Startup Code
    //
    //------------------------------
    
    //------------------------------
    //  Expose jStore through jQuery
    //------------------------------
    
    window.jStore = $.jStore = _;
    
    //------------------------------
    //
    //  Engine Definitions
    //
    //------------------------------

    //------------------------------
    //  Local
    //------------------------------
    
    define(FLAVOR_LOCAL, 
    {
        //------------------------------
        //  Properties
        //------------------------------
        
        limit: parseInt(5e5, 16),
        
        //------------------------------
        //  Constructor
        //------------------------------
    
        init: function (project, name)
        {   
            this.database = window.globalStorage === undefined ? window.localStorage : window.globalStorage[location.hostname];
            
            this._super(project, name);
        },
        
        //------------------------------
        //  Methods
        //------------------------------
        
        updateCache: function ()
        {
            var key, value;
        
            for (key in this.database)
            {
                var has_key = false;
                if (this.database.hasOwnProperty) {
                    if (this.database.hasOwnProperty(key)) {
                        has_key = true;
                    }
                } else { // IE 8
                    if (this.database.getItem(key) !== null) {
                        has_key = true;
                    }
                }
                        
                if (has_key) {
                    value = this.database.getItem(key);
            
                    //  Gecko's getItem returns {value: 'the value'}, WebKit returns 'the value'
                    this.data[key] = prepareForRevival(value && value.value ? value.value : value);
                }
            }
            
            this._super();
        },
        
        //------------------------------
        //  Internal methods
        //------------------------------
        
        __set: function (key, value)
        {
            this.database.setItem(key, prepareForStorage(value));
        },
        
        __remove: function (key)
        {
            this.database.removeItem(key);
        }
    },
    
    function ()
    {
        return window.localStorage !== undefined || window.globalStorage !== undefined;
    });
    
    //------------------------------
    //  SQL
    //------------------------------
    
    define(FLAVOR_SQL, 
    {
        //------------------------------
        //  Properties
        //------------------------------
        
        limit: parseInt(32e3, 16),
        
        //------------------------------
        //  Constructor
        //------------------------------
    
        init: function (project, name)
        {   
            this.database = window.openDatabase('jstore-' + project, '1.0', project, this.limit);

            if (!this.database)
            {
                throw 'JSTORE_SQL_NO_DB';
            }
            
            this.database.transaction(function (database)
            {
                database.executeSql('CREATE TABLE IF NOT EXISTS jstore (k TEXT UNIQUE NOT NULL PRIMARY KEY, v TEXT NOT NULL)');
            });
            
            this._super(project, name);
        },
        
        //------------------------------
        //  Methods
        //------------------------------
        
        updateCache: function ()
        {
            var self = this,
                _super = this._super;
        
            this.database.transaction(function (database)
            {
                database.executeSql('SELECT k,v FROM jstore', [], function (database, result)
                {
                    var rows = result.rows,
                        index = 0,
                        row;
                        
                    for (; index < rows.length; ++index)
                    {
                        row = rows.item(index);
                        self.data[row.k] = prepareForRevival(row.v);
                    }
                    
                    _super.apply(self);
                });
            });
        },
        
        //------------------------------
        //  Internal methods
        //------------------------------
        
        __set: function (key, value)
        {
            this.database.transaction(function (database)
            {
                database.executeSql('INSERT OR REPLACE INTO jstore(k, v) VALUES (?, ?)', [key, prepareForStorage(value)]);
            });
        },
        
        __remove: function (key)
        {
            this.database.transaction(function (database)
            {
                database.executeSql('DELETE FROM jstore WHERE k = ?', [key]);
            });
        }
    }, 
    
    function ()
    {
        return window.openDatabase !== undefined;
    });
    
    //------------------------------
    //  Flash
    //------------------------------
    
    define(FLAVOR_FLASH, 
    {
        //------------------------------
        //  Properties
        //------------------------------
        
        limit: -1,
        
        //------------------------------
        //  Constructor
        //------------------------------
    
        init: function (project, name)
        {   
            var self = this;
        
            _.bind('flash-ready', function ()
            {
                self.__flashReadyListener();
            });
            
            this._super(project, name);
        },
        
        //------------------------------
        //  Methods
        //------------------------------
        
        updateCache: function (enable)
        {
            /**
             *  The default call to updateCache passes no variable, so we can short circuit the
             *  ready state until we explictly call this after flash ready.
             */
            if (enable === true)
            {
                var key, 
                    dataset = this.database.jstore_get_all();
            
                for (key in dataset)
                {
                    if (dataset.hasOwnProperty(key))
                    {   
                        this.data[key] = prepareForRevival(this.database.jstore_get(key));
                    }
                }
            
                this._super();
            }
        },
        
        //------------------------------
        //  Internal methods
        //------------------------------
        
        __set: function (key, value)
        {
            if (!this.database.jstore_set(key, prepareForStorage(value)))
            {
                _.trigger('jstore-error', ['JSTORE_STORAGE_FAILURE', this.jri, 'Flash Exception']);
            }
        },
        
        __remove: function (key)
        {
            this.database.jstore_remove(key);
        },
        
        /**
         *  Triggered whenever flash is ready.
         */
        __flashReadyListener: function ()
        {
            var iFrame = $('#jStoreFlashFrame')[0],
                frameDocument;
            
            //  MSIE
            if (iFrame.Document !== undefined && typecheck(iFrame.Document.jStoreFlash.jstore_get, 'Function'))
            {
                this.database = iFrame.Document.jStoreFlash;
            }
            
            //  Real Browsers
            else if (iFrame.contentWindow && iFrame.contentWindow.document)
            {
                frameDocument = $(iFrame.contentWindow.document);
                
                //  Webkit
                if (typecheck($('object', frameDocument)[0].jstore_get, 'Function'))
                {
                    this.database = $('object', frameDocument)[0];
                }
                
                //  Gecko
                else if (typecheck($('embed', frameDocument)[0].jstore_get, 'Function'))
                {
                    this.database = $('embed', frameDocument)[0];
                }
            }
            
            if (this.database === undefined)
            {
                throw 'JSTORE_FLASH_REFERENCE_ISSUE';
            }
            else
            {
                this.updateCache(true);
            }
        }
    }, 
    
    function ()
    {
        return hasFlashVersion('9.0.0');
    });
    
    //------------------------------
    //  Gears
    //------------------------------
    
    define(FLAVOR_GEARS, 
    {
        //------------------------------
        //  Properties
        //------------------------------
        
        limit: -1,
        
        //------------------------------
        //  Constructor
        //------------------------------
    
        init: function (project, name)
        {   
            this.database = google.gears.factory.create('beta.database');
            this.database.open('jstore-' + project);
            this.database.execute('CREATE TABLE IF NOT EXISTS jstore (k TEXT UNIQUE NOT NULL PRIMARY KEY, v TEXT NOT NULL)');
            
            this._super(project, name);
        },
        
        //------------------------------
        //  Methods
        //------------------------------
        
        updateCache: function ()
        {
            var result = this.database.execute('SELECT k,v FROM jstore');
            
            while (result.isValidRow())
            {
                this.data[result.field(0)] = prepareForRevival(result.field(1));
                result.next();
            }
            
            result.close();
            
            this._super();
        },
        
        //------------------------------
        //  Internal methods
        //------------------------------
        
        __set: function (key, value)
        {
            this.database.execute('BEGIN');
            this.database.execute('INSERT OR REPLACE INTO jstore(k, v) VALUES (?, ?)', [key, prepareForStorage(value)]);
            this.database.execute('COMMIT');
        },
        
        __remove: function (key)
        {
            this.database.execute('BEGIN');
            this.database.execute('DELETE FROM jstore WHERE k = ?', [key]);
            this.database.execute('COMMIT');
        }
    }, 
    
    function ()
    {
        return window.google !== undefined && window.google.gears !== undefined;
    });
    
    //------------------------------
    //  MSIE
    //------------------------------
        
    define(FLAVOR_MSIE, 
    {
        //------------------------------
        //  Properties
        //------------------------------
        
        limit: parseInt(1e4, 16),
        
        //------------------------------
        //  Constructor
        //------------------------------
    
        init: function (project, name)
        {   
            this.database = $('<div style="display:none;behavior:url(\'#default#userData\')" id="jstore-' + project + '"></div>')
                .appendTo(document.body).get(0);
            
            this._super(project, name);
        },
        
        //------------------------------
        //  Methods
        //------------------------------
        
        updateCache: function ()
        {
            this.database.load(this.project);
            
            var node = document.getElementById('jstore-' + this.project),
                xmlDoc = node.XMLDocument,
                root,
                index = 0;
                
            if (xmlDoc && xmlDoc.documentElement && xmlDoc.documentElement.attributes)
            {
                root = xmlDoc.documentElement;
                
                for (; index < root.attributes.length; ++index)
                {
                    this.data[root.attributes.item(index).nodeName] = prepareForRevival(root.attributes.item(index).nodeValue);
                }  
            }
            
            this._super();
        },
        
        //------------------------------
        //  Internal methods
        //------------------------------
        
        __set: function (key, value)
        {
            this.database.setAttribute(key, prepareForStorage(value));
            this.database.save(this.project);
        },
        
        __remove: function (key)
        {
            this.database.removeAttribute(key);
            this.database.save(this.project);
        }
    }, 
    
    function ()
    {
        return window.ActiveXObject !== undefined;
    });
    
}(jQuery, window));
