
//<script>
/*****************************************************************************************************************************
 * @DOC     ClientSideCacheItem
 * @CLASS   CacheItem |
 *          A cache item is responsible for holding and maintaining data for a single DataType - Data is stored as a DataSet
 * @LOC     Client-Side
 * @LANG    JavaScript
 *
 * @XREF    <c DataSet> Class Definition
 * @XREF    <c EMRDataManager> Class Definition
 * @XREF    <c CacheConfiguration> Class Definition
 * @SUBINDEX CacheItem Members
 */

/* Interfaces:
 ******************************************************************************************************************************
 * @MFUNC   | CacheItem | CacheItem | <c CacheItem> contructor.
 * @PARM    DataSet | value | The value of the cache item.  This will be null if the data has not yet been retrieved from the 
 *              server.
 * @PARM    string | url | The url used to retrieve the data from the server.  This is set from the configuration when the 
 *              cache item is created.
 * @PARM    string[] | params | Parameters to be passed to the server in order to retrieve the data.
 * @PARM    string | type | The object type that will be returned (e.g. TEXT / OBJECT / XML / E4X).  This is needed so we can 
 *              request the data in the correct way.  This is set from the configuration when the cache item is created.
 * @PARM    string | transferMode | The transfer mode to be used when requesting data from the server (e.g. GET / POST).  This 
 *              is set when the cache item is created.
 * @PARM    pointer | onRefresh | Callback pointer to a function that will be called asyncronously when the cache item has been 
 *              refreshed.
 * @PARM    string | dataType | The fully qualified data type to be retrieved from the server e.g. EMR.SenderProfiles.
 * @PARM    bool | allFields | Flag that determines if the entire field list will be retrieved when the data is requested.  If 
 *              true, all fields will be returned, if false the server determined default subset of fields will be returned.
 * @PARM    int | maxRows | The number of rows of data that will be requested.  Special cases -ve = all, 0 = server-defined 
 *              default for this data type.
 * @XREF    <c CacheItem> Class Definition
 * @XREF    <c DataSet> Class Definition
 ******************************************************************************************************************************
 * @MDATA    DataSet | CacheItem | value | The value of the cache item.  This will be null if the data has not yet been 
 *              retrieved from the server.
 * @XREF    <c CacheItem> Class Definition
 * @XREF    <c DataSet> Class Definition
 ******************************************************************************************************************************
 * @MDATA    string | CacheItem | url | The url used to retrieve the data from the server.  This is set from the configuration 
 *              when the cache item is created.
 * @TODO     I think this can be removed and the cache configuration class be referenced directly based on the dataType 
 *              property.
 * @XREF    <c CacheItem> Class Definition
 ******************************************************************************************************************************
 * @MDATA    string[] | CacheItem | params | Parameters to be passed to the server in order to retrieve the data.
 * @TODO     Details of the parameters needs to be added here.  Also is this needed or are all the parameters now set through 
 *              properties?
 * @XREF    <c CacheItem> Class Definition
 ******************************************************************************************************************************
 * @MDATA    string | CacheItem | type | The object type that will be returned (e.g. TEXT / OBJECT / XML / E4X).  This is 
 *              needed so we can request the data in the correct way.  This is set from the configuration when the cache item 
 *              is created.
 * @TODO     I think this can be removed and the cache configuration class be referenced directly based on the dataType 
 *              property.
 * @XREF    <c CacheItem> Class Definition
 ******************************************************************************************************************************
 * @MDATA    string | CacheItem | transferMode | The transfer mode to be used when requesting data from the server (e.g. GET / 
 *              POST).  This is set when the cache item is created.
 * @TODO     I think this can be removed and the cache configuration class be referenced directly based on the dataType 
 *              property.
 * @XREF    <c CacheItem> Class Definition
 ******************************************************************************************************************************
 * @MDATA    pointer | CacheItem | onRefresh | Callback pointer to a function that will be called asyncronously when the cache 
 *              item has been refreshed.
 * @TODO     This should probably be called when the cache item starts to refresh and a new callback (onRefreshed) be called 
 *              when the refresh call completes.
 * @XREF    <c CacheItem> Class Definition
 ******************************************************************************************************************************
 * @MDATA    string | CacheItem | dataType | The fully qualified data type to be retrieved from the server e.g. 
 *              EMR.SenderProfiles.
 * @XREF    <c CacheItem> Class Definition
 ******************************************************************************************************************************
 * @MDATA    bool | CacheItem | allFields | Flag that determines if the entire field list will be retrieved when the data is 
 *              requested.  If true, all fields will be returned, if false the server determined default subset of fields will 
 *              be returned.
 * @XREF    <c CacheItem> Class Definition
 ******************************************************************************************************************************
 * @MDATA    int | CacheItem | maxRows | The number of rows of data that will be requested.  Special cases -ve = all, 0 = 
 *              server-defined default for this data type.
 * @XREF    <c CacheItem> Class Definition
 ******************************************************************************************************************************
 * @MDATA    bool | CacheItem | isValid | Flag to indicate if the data held by this cache item is valid.  If false this cache 
 *              item will be refreshed next time the EMRDataManager synchronises data with the server.  To force the cache item 
 *              to be refreshed either call refresh() on the cache item or syncNow() on the EMRDataManager.
 * @XREF    <c CacheItem> Class Definition
 ******************************************************************************************************************************
 * @MDATA    bool | CacheItem | isRefreshing | Flag to indicate that the cache item is being refreshed.  The new data will be 
 *              available soon.
 * @XREF    <c CacheItem> Class Definition
 ******************************************************************************************************************************
 * @MDATA    bool | CacheItem | hasErrored | Flag to indicate that there was a problem retrieving data from the server.
 * @XREF    <c CacheItem> Class Definition
 ******************************************************************************************************************************
 * @MFUNC   void | CacheItem | refresh | Public function to refresh the data of the cache item with the current contents on 
 *          the server
 * @XREF    <c CacheItem> Class Definition
 ****************************************************************************************************************************/
function CacheItem(value, url, params, type, transferMode, onRefresh, dataType, allFields, maxRows)
{
    var me = this;
    
    // value - the cached data (text / html fragment / xml fragment / JavaScript Object / ...)
    var m_value = value;
    var m_TTLTimer = null;
    
    this.getValue = function() {
        this.lastRequested = new Date();
        resetTTLTimer();
        return m_value;
    }
    
    // url - the url used to refresh the contents of this cache item
    this.url = url;
    
    // params - Array of parameters required to call the url to refresh the item correctly
    this.params = params == null ? '' : params;
    
    // type - the type of item that this is so that we can refresh in the right way (TEXT / OBJECT / XML / E4X)
    this.type = type;
    
    this.transferMode = transferMode;
    
    this.onRefresh = onRefresh;
    
    this.dataType = dataType;
    
    this.allFields = allFields;
    
    this.maxRows = maxRows;
    
    /* isValid - boolean flag to determine if the copy we have is considered to still be valid.  This will be set to true on
                 initial insertion into the cache (assuming we have some data), then reset to false if it is determined that 
                 the server version has changed since it was added */
    this.isValid = !(value == null || timeStamp == null);
    
    this.isRefreshing = false;
    
    this.hasErrored = false;
    
    this.expired = false;
    
    this.lastRequested = null;
    
    /* refresh - function to refresh the data of the cache item with the current contents on the server */
    this.refresh = function(keys) {
        try {
            me.isRefreshing = true;
            me.hasErrored = false;
            // if we have no URL we cannot refresh this item - just mark it as valid again and exit
            if(me.url == null) {
                Log('no URL refresh failed');
                me.hasErrored = true;
                me.isRefreshing = false;
                return;
            }
            
            var callParams = 'DataType=' + me.dataType + '&AllFields=' + me.allFields + '&MaxRows='+ me.maxRows;
            // if we already have data for this cacheItem and the data has a timeStamp, return this to the server too so it can be used for delta updates
            if(m_value != null && typeof(m_value.timeStamp) != 'undefined') {
                callParams += '&TimeStamp=' + m_value.timeStamp;
            }
            // if we have been passed a list of specific keys that need to be refreshed, return these to the server too (but only if we have data to update using the delta)
            if(keys != null && m_value != null)
                callParams += '&Keys=' + keys;
            // add any params from the data type configuration
            if (me.params != null) {
                for(var paramIndex = 0; paramIndex < me.params.length; paramIndex++) {
                    callParams += '&' + me.params[paramIndex];
                }
            }
            
            var cp = new cpaint();
            // Set the transfer mode (GET / POST)
            cp.set_transfer_mode(me.transferMode);
            // We don't want to use the backend API
            cp.set_use_cpaint_api(false);
            // The type of output we want (TEXT / OBJECT / XML / E4X)
            cp.set_response_type(me.type);
            // make the call asyncronously
            cp.set_async(true);
            // void call ( string url, string remote_method, function callback_function [, mixed args [, mixed ...]] )
            cp.call(me.url, '', refreshCallback, callParams);
            callParams = null;
            cp = null;
        }
        catch (ex) { /*25955*/ Log(ex, logVerbosity.Warning); }
    }
    
    this.dispose = function() {
        if(m_value)
            m_value.dispose();
        m_value = null;
        this.url = null;
        this.params = null;
        this.type = null;
        this.transferMode = null;
        this.onRefresh = null;
        this.dataType = null;
        this.allFields = null;
        this.maxRows = null;
        this.isValid = null;
        this.isRefreshing = null;
        this.hasErrored = null;
        me = null;
        if(m_TTLTimer) {
            window.clearTimeout(m_TTLTimer);
            m_TTLTimer = null;
        }
    }
    
    function refreshCallback(results) {
        // check if the object has been disposed since the refresh was triggered
        if(me == null) return;
        
        var blnFirstLoad = (m_value == null);
        if(results == null || results.documentElement == null) {
            me.hasErrored = true;
            m_value = null;
            Log('refreshing from url: ' + me.url + ' failed.  Server response document element was null', logVerbosity.Warning);
        }
        // check for error xml in the results
        else if(results.documentElement.nodeName == 'Error') {
            me.hasErrored = true;
            m_value = null;
            // Case 79872 - only log the error if it isn't caused by the users session expiring
            var sessExpired = getAttributeValue(results.documentElement, 'SessionExpired');
            var exceptionType = getAttributeValue(results.documentElement, 'Type');
            if (!(sessExpired == 'true' || exceptionType == 'EMR.Exceptions.EMRLogonException')) {
                Log('refreshing from url: ' + me.url + ' failed with the following error: ' + (results.documentElement.firstChild ? results.documentElement.firstChild.nodeValue : '[No Exception Message]'), logVerbosity.Warning); 
            }
        }
        else {
            var contentType = getAttributeValue(results.documentElement, 'ContentType');
            switch(contentType)
            {
                case 'Delta':
                case 'RowSpecificDelta':
                    if(m_value == null) {
                        Log('Cannot apply delta update without having a reference data set', logVerbosity.Serious);
                        return;
                    }
                    
                    var delta = new DataSet(null, results);
                    if(delta.tables.length != 2) {
                        Log('Invalid delta data set, wrong number of tables returned.', logVerbosity.Serious);
                    }
                    else {
                        // remove the records that are in the deleted items table
                        for(var i = 0; i < delta.tables.items[1].rows.length; i++) {
                            // delete
                            m_value.tables.items[0].rows.remove(delta.tables.items[1].rows.keys[i])
                        }
                        
                        // add / update the records that are in the main table
                        var rowIndex;
                        for(var i = 0; i < delta.tables.items[0].rows.length; i++) {
                            // add the row (if the key already exists in the recordset it will automatically be replaced)
                            m_value.tables.items[0].rows.add(delta.tables.items[0].rows.keys[i], delta.tables.items[0].rows.items[i].clone());
                        }
                        // update the timestamp used for synchronising with the server
                        m_value.timeStamp = delta.timeStamp;
                        // udpate the available rows
                        m_value.tables.items[0].availableRows = delta.tables.items[0].availableRows;
                    }
                    delta.dispose();

                    /* only update the isValid flag to true if we have recieved a full delta update.  
                     * If the update was row specific we don't know if there were any other updates to the data type that will require synchronisation */
                    // QUERY - is this going to work?  surely if you do an edm.GetData after a row specific delta update it will just get the full delta anyway.
                    if(contentType != 'RowSpecificDelta')
                        me.isValid = true; 
                    break;
                default:
                    // if the cache item already has a value DataSet - dispose it before we create a new one
                    if(m_value)
                        m_value.setXml(results);
                    else
                        m_value = new DataSet(null, results);
                    me.isValid = true;
                    break;
            }
            results = null;
        }
        
        me.isRefreshing = false;
        
        // raise an event to indicate we have finished refreshing (this is done using window.setTimeout to make the onRefresh method execute asychronously)
        if(me.onRefresh != null && !blnFirstLoad) {
            window.setTimeout(raiseOnRefresh, 0);
        }
    }
    
    function raiseOnRefresh() {
        try {
            if(me)
                me.onRefresh(m_value);
        }
        catch (ex) { /* Case 36403 - Temp disabled Log call to prevent nasty messages - further research still ongoing */ /*25955*/ /* Log(ex, logVerbosity.Warning); */  }
    }
    
    function TTLExpired() {
        if(me) {
            me.expired = true;
            me.isValid = false;
        }
    }
    
    function resetTTLTimer() {
        // clear the existing timer if there is one
        if(m_TTLTimer)
            window.clearTimeout(m_TTLTimer);
            
        // QUERY - is this going to work with TTL = 0 or will the item be expired and invalidated before it can be used?
        if(m_value && m_value.TTL != null && m_value.TTL >= 0)
            m_TTLTimer = window.setTimeout(TTLExpired, m_value.TTL * 60000) // 60000 = 1 min in ms
    }
    
    // If the item is not valid (i.e. the data has not been retrieved for it yet) when it is created we need to refresh it (asyncronously)
    if(!this.isValid)
        window.setTimeout(this.refresh, 0)
}

/*****************************************************************************************************************************
 * @DOC     ClientSideEMRDataManager
 * @Class   EMRDataManager |
 *          (ACE Data Manager) The data manager is a single point of contact for all pages to retrieve data.
 * @LOC     Client-Side
 * @LANG    JavaScript
 *
 * @XREF    <c CacheItem> Class Definition
 * @XREF    <c DataSet> Class Definition
 * @SUBINDEX EMRDataManager Members
 */
 
/* Interfaces:
 ******************************************************************************************************************************
 * @MFUNC    | EMRDataManager | EMRDataManager | <c EMRDataManager> constructor.
 * @XREF    <c EMRDataManager> Class Definition
 ******************************************************************************************************************************
 * @MFUNC    CacheItem | EMRDataManager | getData | Public function to retrieve data from the cache.  This will result in the cache 
 *              being checked for a valid copy of the data requested, requuesting it from the server when necessary before the 
 *              callback function is called.  The cache item will be returned immediately.
 * @PARM    string | type | The data type to be retrieved e.g EMR.SenderProfiles.  Data types are defined in the 
 *              CacheConfiguration class.
 * @PARM    int | maxRows | The number of rows to be returned - special cases -ve = all? 0=server-defined default for this data 
 *              type
 * @PARM    bool | allFields | true = all fields, false=server-defined default set.
 * @PARM    pointer | callback |  JavaScript callback routine that you want to be invoked when the data is ready. 
 *              TODO: params to callback fn?
 * @XREF    <c EMRDataManager> Class Definition
 * @XREF    <c DataSet> Class Definition
 ******************************************************************************************************************************
 * @MFUNC    CachItem | EMRDataManager | getCacheItem | Public function to retrieve an item from the client side cache.  This 
 *              will result in an instance of the necessary cache item being created (where it doesn't already exist in the cache) 
 *              and returned.
 * @PARM    string | type | The data type to be retrieved e.g EMR.SenderProfiles  Data types are defined in the 
 *              CacheConfiguration class.
 * @PARM    int | maxRows | The number of rows to be returned - special cases -ve = all? 0=server-defined default for this data 
 *              type
 * @PARM    bool | allFields | true = all fields, false=server-defined default set.
 * @XREF    <c EMRDataManager> Class Definition
 * @XREF    <c CacheItem> Class Definition
 ******************************************************************************************************************************
 * @MFUNC    void | EMRDataManager | invalidateItem | Public fcuntion to invalidate a cache item.  This will invalidate the 
 *              cache item for a given data type if it exists in the cache, causing it to be synchronised with the server.  If no 
 *              cache item for the given data type exists, no action is taken.
 * @PARM    string | type | The data type to be invalidated e.g. EMR.SenderProfiles.   Data types are defined in the 
 *              CacheConfiguration class.
 * @XREF    <c EMRDataManager> Class Definition
 ******************************************************************************************************************************
 * @MFUNC    void | EMRDataManager | clearCache | Public function to empty the client side cache of all items.  This can be used 
 *              to remove all data when, for example, a new user logs onto the system.
 * @XREF    <c EMRDataManager> Class Definition
 ******************************************************************************************************************************
 * @MFUNC    void | EMRDataManager | startSync | Public function to start the sycronisation timer.  Each time the synchronisation 
 *              timer fires, sync() will be called.  This is also called when the timer is first started.
 * @XREF    <c EMRDataManager> Class Definition
 ******************************************************************************************************************************
 * @MFUNC    void | EMRDataManager | stopSync | Public function to stop the sycronisation timer.  All cache items will be left in 
 *              their current state, regardless of changes made to the data on the server.
 * @XREF    <c EMRDataManager> Class Definition
 ******************************************************************************************************************************
 * @MFUNC    void | EMRDataManager | syncNow | Public function to force the synchronisation.  This can be used to refresh cache 
 *              items that have been invlidataed using the invalidateCacheItem() method.
 * @XREF    <c EMRDataManager> Class Definition
 ******************************************************************************************************************************
 * @MFUNC    void | EMRDataManager | registerActivity | Public function to inform the server that the client is still using the 
 *              UI.  This is to prevent the session timing-out when the user is still working.  As the UI moves more and more 
 *              into an AJAX client with pages being cached the time between server interactions will increase.
 * @PARM    Activity | activityCode | Code to describe the type of action taken in the UI.  Activity codes are defined in the 
 *              CacheConfiguration class.
 * @XREF    <c EMRDataManager> Class Definition
 ******************************************************************************************************************************
*/

function EMRDataManager() {
//    var me = this;
    var syncEnabled = false;
    var syncInterval = 60;
    var cache = new Dictionary();
    var syncTimer = null;
    var sessionExpireyWarningTimeout = 5;   // warn the user when the session only has x minutes remaining
    var  previousSessionExpireyTime = new Date();
    var expireyMessageShown = false;
    
    var dataRequests = new Object();

    this.getData = function(type, maxRows, allFields, callback) {
        try {
            // register the activity
            this.registerActivity('DataRequest');
            
            var cacheItem = this.getCacheItem(type, maxRows, allFields)
            if(cacheItem) {
                var newRequest = new DataRequest(cacheItem, callback, getDataCallback);
                dataRequests[newRequest.requestId] = newRequest;
                newRequest.retrieve();
                newRequest = null;
            }
            return cacheItem;
        }
        catch (ex) { /*25955*/ Log(ex, logVerbosity.Warning); }
    }
    
    this.getCacheItem = function(type, maxRows, allFields) {
        try {
            // check the data type exists
            if(ACEConfig[type] == null) {
                /*25955*/ Log('Unable to create cache item.  "' + type + '" is not a valid data type.', logVerbosity.Serious);
                return null;
            }
            
            var cachedItem = cache.items[cache.indexes[type]];
            
            // check for the existance of the item in the cache
            if(cachedItem != null && cachedItem.allFields >= allFields && (cachedItem.maxRows == -1 || (maxRows != -1 && cachedItem.maxRows >= maxRows))) {
                return cachedItem;
            }
            // the item is not in the cache - we need to add it
            else {
                // Case 79872 - if we are not synchronising data don't bother getting data it probably means the session has expired.
                if(syncTimer == null)
                    return null;
                // if the type has not been configured, return null
                if(ACEConfig[type].url == null)
                    return null;
                
                // create the new cache item
                cachedItem = new CacheItem(null, ACEConfig[type].url, null, ACEConfig[type].type, ACEConfig[type].transferMode, null, type, allFields, maxRows);
                // if this data type is cached not just retrieved through the framework, add it to the in-memory cache
                if(ACEConfig[type].isCached)
                    cachedItem = cache.add(type, cachedItem);
                // return the item
                return cachedItem;
            }
        }
        catch (ex) { /*25955*/ Log(ex, logVerbosity.Warning); }
    }
    
    this.invalidateItem = function(type) {
        try {
            var cachedItem = cache.items[cache.indexes[type]];
            
            // check for the existance of the item in the cache
            if(cachedItem != null) {
                if(cachedItem.isValid) {
                    cachedItem.isValid = false;
                }
            }
        }
        catch (ex) { /*25955*/ Log(ex, logVerbosity.Warning); }
    }
    
    function clearCache() {
        try {
            for(var i = 0; i < cache.length; i++)
                cache.items[i].dispose();
            cache = new Dictionary();
        }
        catch (ex) { /*25955*/ Log(ex, logVerbosity.Warning); }
    }
    this.clearCache = clearCache;
    
    function startSync() {
        stopSync();
        syncEnabled = true;
        syncTimer = window.setTimeout(sync, 0);
    }
    this.startSync = startSync;
    
    function stopSync() {
        // Clear the current timer if one has been set
        if(syncTimer != null)
            window.clearTimeout(syncTimer);
        
        syncEnabled = false;
    }
    this.stopSync = stopSync;
    
    this.syncNow = function() {
        sync();
    }
    
    this.registerActivity = function(activityCode) {
        return true; // MJA 26Jun08 case 60761 - This removes about 1 second from the first loadtime of the action/properties dialog on my local dev system. (was doing 6x calls, taking between 100ms and 250 ms each)
//        try {
//            // send the request
//            var cp = new cpaint();
//            // We don't want to use the backend API
//            cp.set_use_cpaint_api(false);
//            // Set the transfer mode (GET / POST)
//            cp.set_transfer_mode('POST');
//            // The type of output we want (TEXT / OBJECT / XML / E4X)
//            cp.set_response_type('XML');
//            // make the call asyncronously
//            cp.set_async(true);
//            // void call ( string url, string remote_method, function callback_function [, mixed args [, mixed ...]] )
//            cp.call('/ClientCacheInterface.asmx/RegisterActivity', '', null, 'ActivityCode=' + ACEConfig.activities[activityCode]);
//            cp = null;
//        }
//        catch (ex) { /*25955*/ Log(ex, logVerbosity.SuperVerbose); }
    }
    
    function getDataCallback(request) {
        delete dataRequests[request.requestId];
        request.dispose();
    }
    
    function sync() {
        // if the monitor has been disabled since we last set the timer, abandon this call
        if(!syncEnabled) return;

        // check the current status of each item in the cache
        checkTimeStamps();

        // schedule the next sync process (only if still enabled)
        if(syncEnabled)
            syncTimer = window.setTimeout(sync, syncInterval * 1000);
    }
    
    function refreshInvalidItems() {
        try {
            // update the invalid items in the cache
            var thisItem = null;
            for(var i = 0; i < cache.length; i++)
            {
                thisItem = cache.items[i];
                
                // if the item is not currently valid, is not in the process of refreshing itself and hasn't expired, schedule the refresh call.
                if((!thisItem.isValid) && (!thisItem.isRefreshing) && (!thisItem.expired))
                    window.setTimeout(thisItem.refresh, 0);
                
                thisItem = null;
            }
        }
        catch (ex) { /*25955*/ Log(ex, logVerbosity.Warning); }
    }
    
    function checkTimeStamps() {
        try {
            // build the list of datatypes we are interested in
            var dataTypes = 'DataTypes=';
            var joinChar = '';
            for(var cacheItemIndex = 0; cacheItemIndex < cache.length; cacheItemIndex++) {
                // no point checking data types we already know are invalid
                if(cache.items[cacheItemIndex].isValid) {
                    dataTypes += joinChar + cache.keys[cacheItemIndex];
                    joinChar = ',';
                }
            }
            joinChar = null;
            
            // send the request
            var cp = new cpaint();
            // We don't want to use the backend API
            cp.set_use_cpaint_api(false);
            // Set the transfer mode (GET / POST)
            cp.set_transfer_mode('POST');
            // The type of output we want (TEXT / OBJECT / XML / E4X)
            cp.set_response_type('XML');
            // make the call asyncronously
            cp.set_async(true);
            // void call ( string url, string remote_method, function callback_function [, mixed args [, mixed ...]] )
            cp.call('/ClientCacheInterface.asmx/GetSyncTimes', '', updateValidFlags, dataTypes);
            dataTypes = null;
            cp = null;
        }
        catch (ex) { /*25955*/ Log(ex, logVerbosity.Warning); }
    }
    
    function updateValidFlags(results) {
        try {
            if(results.documentElement != null) {
                // check for error xml in the results
                if(results.documentElement.nodeName == 'Error') {
                    //
                    var sessExpired = getAttributeValue(results.documentElement, 'SessionExpired');
                    var exceptionType = getAttributeValue(results.documentElement, 'Type');
                    if (sessExpired == 'true' || exceptionType == 'EMR.Exceptions.EMRLogonException') {
                        // Case 79872 - clear the cache and stop syncronising when the session expires.
                        clearCache();
                        stopSync();
                        showLogonDialog();
                        return;
                    }
                }
                else {
                    try {
                        var nodeList = results.documentElement.childNodes
                        var cacheItemIndex = null;
//                        var timeStamp = new Date();
                        for(var nodeIndex = 0; nodeIndex < nodeList.length; nodeIndex++) {
                            cacheItemIndex = cache.indexes[nodeList[nodeIndex].nodeName];
                            if (cacheItemIndex != null && cache.items[cacheItemIndex].isValid) {
                                try {
                                    if(Date.parse(nodeList[nodeIndex].firstChild.nodeValue) > Date.parse(cache.items[cacheItemIndex].getValue().timeStamp)) {
                                        cache.items[cacheItemIndex].isValid = false;
                                    }
                                }
                                catch (ex) { /*25955*/ Log(ex, logVerbosity.SuperVerbose); }
                            }
                        }
                    }
                    catch (ex) { /*25955*/ Log(ex, logVerbosity.SuperVerbose); }
                }
            }
            
            refreshInvalidItems();
            
            checkSessionExpireyTime(results);
        }
        catch (ex) { /*25955*/ Log(ex, logVerbosity.Warning); }
    }
    
    function checkSessionExpireyTime(data) {
        try {
            if(data.documentElement != null) {
                // check for error xml in the results
                if(data.documentElement.nodeName != 'Error') {
                    var serverTime = new Date();
                    var sessionExpireyTime = new Date();
                    
                    var attributeList = data.documentElement.attributes;
                    for(var attributeIndex = 0; attributeIndex < attributeList.length; attributeIndex++) {
                        switch(attributeList[attributeIndex].nodeName)
                        {
                            case "ServerTime":
                                serverTime.setTime(Date.parse(attributeList[attributeIndex].nodeValue));
                                break;
                            case "SessionExpireyTime":
                                if(attributeList[attributeIndex].nodeValue == '')
                                    sessionExpireyTime = null;
                                else
                                    sessionExpireyTime.setTime(Date.parse(attributeList[attributeIndex].nodeValue));
                                break;
                        }
                    }
                    attributeList = null;
                    
                    // if we have no expirey time, return as we cannot compare them
                    if(sessionExpireyTime == null)
                        return;
                    
                    // if the session expirey time has changed since last time, reset the expireyMessageShown flag and store the expirey time for next time
                    if(sessionExpireyTime - previousSessionExpireyTime != 0) {
                        expireyMessageShown = false;
                        previousSessionExpireyTime = sessionExpireyTime;
                    }
                        
                    // if the session is going to expire soon (i.e. within the next [sessionExpireyWarningTimeout] minutes - show a message to the user
                    if (!expireyMessageShown) {
                        var difference = (sessionExpireyTime - serverTime) / 60000;  // 60000 = number of miliseconds in a minute
                        if(difference < sessionExpireyWarningTimeout) {
                            //alert('Your session has less than ' + sessionExpireyWarningTimeout + ' minutes remaining');
                            alert('Your session has less than '+ sessionExpireyWarningTimeout +' minutes remaining');
                            expireyMessageShown = true;
                        }
                        difference = null;
                    }
                }
            }
        }
        catch (ex) { /*25955*/ Log(ex, logVerbosity.SuperVerbose); }
    }
    
    // Case 79872 - don't kick off the sync till we are logged on.  This is now done by eramdin/adminframe.asp
    // kick off the sycnronisation process
//    this.startSync();
}

/*****************************************************************************************************************************
 * DataRequest class
 * The DataRequest object is used to manage the asychronus requests to retrieve data from the server.
 ****************************************************************************************************************************/
function DataRequest(cacheItem, callback, internalCallback) {
    var me = this;
    var triedOnce = false;
    
    this.requestId = Math.random();
    this.cacheItem = cacheItem;
    
    // check that the callback function exists before we start
    if(typeof(callback) != 'function')
        Log('Callback function specified for request for data from ACE does not exist.', logVerbosity.Serious);
    this.callback = callback;
    this.internalCallback = internalCallback;
    
    this.retrieve = function() {
        try {
            // check we have a cache item to work with
            if(cacheItem == null) {
                Log('Cannot process DataRequest.retrieve with a null cache item', logVerbosity.Serious);
                return;
            }
            
            // check that the cache item has not errored during retrieval
            if(me.cacheItem.hasErrored) {
                if(triedOnce) {
                    // if we have already tried once to get the data, give up
                    // Case 79872 - Don't log here.  We will have already logged in the cache items refresh function if there was a problem getting the data
                    /*25955*/ // Log('Error while retrieving data from the server for data type \'' + me.cacheItem.dataType + '\' from URL \'' + me.cacheItem.url + '\'', logVerbosity.Fatal);
                }
                else {
                    // otherwise refresh the cacheItem it may work this time!
                    me.cacheItem.refresh();
                    triedOnce = true;
                    window.setTimeout(me.retrieve, 50);
                }
                return;
            }
            
            // the cacheItem is not in an error state, check that it is valid
            if(me.cacheItem.isValid || me.cacheItem.hasErrored) {
                try { me.callback(me.cacheItem.getValue()); } catch(ex) { /*25955*/ /* Log(ex, logVerbosity.SuperVerbose); */ /* removed as caused an error to be displayed if the user navigated away from the page requesting the data before it had loaded. */ }
                try { me.internalCallback(me); } catch (ex) { /*25955*/ Log(ex, logVerbosity.SuperVerbose); }
            }
            else {
                // if the items is invalid but is not being refreshed, trigger the refresh now
                if(!me.cacheItem.isRefreshing && triedOnce) {
                    me.cacheItem.refresh();
                }
                window.setTimeout(me.retrieve, 50);
            }
            triedOnce = true;
        }
        catch (ex) { /*25955*/ Log(ex, logVerbosity.Warning); }
    }
    
    this.dispose = function() {
        this.requestId = null;
        this.cacheItem = null;
        this.callback = null;
        this.internalCallback = null;
        me = null;
    }
}

/*****************************************************************************************************************************
 * ClientSideCacheConfiguration
 ****************************************************************************************************************************/
var ACEConfig = {'EMR.BoothProperties':{'url':'/ClientCacheInterface.asmx/GetData','type':'XML','transferMode':'POST','isCached':true},'EMR.CategoryCodes':{'url':'/ClientCacheInterface.asmx/GetData','type':'XML','transferMode':'POST','isCached':true},'EMR.CampaignGroups':{'url':'/ClientCacheInterface.asmx/GetData','type':'XML','transferMode':'POST','isCached':true},'EMR.DataLayoutNames':{'url':'/ClientCacheInterface.asmx/GetData','type':'XML','transferMode':'POST','isCached':true},'EMR.Emails':{'url':'/ClientCacheInterface.asmx/GetData','type':'XML','transferMode':'POST','isCached':true},'EMR.EmailFolders':{'url':'/ClientCacheInterface.asmx/GetData','type':'XML','transferMode':'POST','isCached':true},'EMR.Emailings':{'url':'/ClientCacheInterface.asmx/GetData','type':'XML','transferMode':'POST','isCached':true},'EMR.Fields':{'url':'/ClientCacheInterface.asmx/GetData','type':'XML','transferMode':'POST','isCached':true},'EMR.FieldSelections':{'url':'/ClientCacheInterface.asmx/GetData','type':'XML','transferMode':'POST','isCached':true},'EMR.Folders':{'url':'/ClientCacheInterface.asmx/GetData','type':'XML','transferMode':'POST','isCached':true},'EMR.Forms':{'url':'/ClientCacheInterface.asmx/GetData','type':'XML','transferMode':'POST','isCached':true},'EMR.ImagePaths':{'url':'/ClientCacheInterface.asmx/GetData','type':'XML','transferMode':'POST','isCached':true},'EMR.Lists':{'url':'/ClientCacheInterface.asmx/GetData','type':'XML','transferMode':'POST','isCached':true},'EMR.MTAPools':{'url':'/ClientCacheInterface.asmx/GetData','type':'XML','transferMode':'POST','isCached':true},'EMR.Partitions':{'url':'/ClientCacheInterface.asmx/GetData','type':'XML','transferMode':'POST','isCached':true},'EMR.Permissions':{'url':'/ClientCacheInterface.asmx/GetData','type':'XML','transferMode':'POST','isCached':true},'EMR.Queries':{'url':'/ClientCacheInterface.asmx/GetData','type':'XML','transferMode':'POST','isCached':true},'EMR.QueryNames':{'url':'/ClientCacheInterface.asmx/GetData','type':'XML','transferMode':'POST','isCached':true},'EMR.RepeatEmailings':{'url':'/ClientCacheInterface.asmx/GetData','type':'XML','transferMode':'POST','isCached':true},'EMR.SenderProfiles':{'url':'/ClientCacheInterface.asmx/GetData','type':'XML','transferMode':'POST','isCached':true},'EMR.Stationery':{'url':'/ClientCacheInterface.asmx/GetData','type':'XML','transferMode':'POST','isCached':true},'EMR.SystemMessages':{'url':'/ClientCacheInterface.asmx/GetData','type':'XML','transferMode':'POST','isCached':true},'EMR.Texts':{'url':'/ClientCacheInterface.asmx/GetData','type':'XML','transferMode':'POST','isCached':true},'EMR.UserAccounts':{'url':'/ClientCacheInterface.asmx/GetData','type':'XML','transferMode':'POST','isCached':true},'EMR.ViewNames':{'url':'/ClientCacheInterface.asmx/GetData','type':'XML','transferMode':'POST','isCached':true},'EMR.Actions':{'url':'/ClientCacheInterface.asmx/GetData','type':'XML','transferMode':'POST','isCached':true},'EMR.Campaigns':{'url':'/ClientCacheInterface.asmx/GetData','type':'XML','transferMode':'POST','isCached':true},'EMR.MTAPoolProfiles':{'url':'/ClientCacheInterface.asmx/GetData','type':'XML','transferMode':'POST','isCached':true},'EMR.ActionIcons':{'url':'/ClientCacheInterface.asmx/GetData','type':'XML','transferMode':'POST','isCached':true},'activities':{'ClientAction':0,'LockSession':1,'DataRequest':2}};






/*****************************************************************************************************************************
 * Global instance of the data manager class
 ****************************************************************************************************************************/
if(EMRDataManager == null)
    /*25955*/ Log('Unable to create EMR Data Manager Object', logVerbosity.Fatal);
else
    var edm = new EMRDataManager();



/*****************************************************************************************************************************
 * Client side session - this is not a client side represnetation of the server side session, but rathher a global allocation 
 *   of memory that can be used to store information that will be useful thoughout the lifetime of the session.
 *   The client side session is implemented as a Dictionary and is cleared on logoff
 ****************************************************************************************************************************/
 function Session() {
    // store for the client side values
    // QUERY - does this need to be a Dictionary instead of just an Object?
    var sess = new Object();
    
    this.getValue = function(key, isClientSideOnly) {
        values = getValues([key], isClientSideOnly);
        if(values[key] != null)
            return values[key];
        
        return null;
    }
    
    this.getValues = function (keys, isClientSideOnly) {
        return getValues(keys, isClientSideOnly);
    }
    
    function getValues(keys, isClientSideOnly) {
        if(isClientSideOnly == null) isClientSideOnly = true;  // By default all session variables are treated as client side only
        
        // only go to the server for values if the variables are not client side only
        if(!isClientSideOnly) {
            var keysToRequest = '';
            var joinChar = '';
            // build a list of the ones we need to request from the server
            for(var i = 0; i < keys.length; i++) {
                if(sess[keys[i]] == null) {
                    keysToRequest += joinChar + keys[i];
                    joinChar = ',';
                }
            }
            
            if(keysToRequest != '') {
                // send the request
                var cp = new cpaint();
                // We don't want to use the backend API
                cp.set_use_cpaint_api(false);
                // Set the transfer mode (GET / POST)
                cp.set_transfer_mode('POST');
                // The type of output we want (TEXT / OBJECT / XML / E4X)
                cp.set_response_type('XML');
                // make the call asyncronously
                cp.set_async(false);
                // void call ( string url, string remote_method, function callback_function [, mixed args [, mixed ...]] )
                cp.call('/ClientCacheInterface.asmx/GetSessionValues', '', getValuesCallback, 'Keys=' + keysToRequest);
                dataTypes = null;
                cp = null;
            }
        }

        var values = new Object();
        // build the list of values to be returned
        for(var i = 0; i < keys.length; i++) {
            if(sess[keys[i]] != null)
                values[keys[i]] = sess[keys[i]];
        }
        
        return values;
    }
    
    function getValuesCallback(data) {
        // read the values from the data returned and add them to the client side session
        var doc = data.documentElement;
        if(doc.nodeName == 'Session') {
            for(var i = 0; i < doc.childNodes.length; i++) {
                if(doc.childNodes[i].nodeName == 'variable')
                    sess[getAttributeValue(doc.childNodes[i], 'name')] = getAttributeValue(doc.childNodes[i], 'value');
            }
        }
    }
    
    this.setValue = function (key, value, isClientSideOnly) {
        values = setValues([key], [value], isClientSideOnly);
        if(values[key] != null)
            return values[key];
        
        return null;
    }
    
    this.setValues = function (keys, values, isClientSideOnly) {
        return setValues(keys, values, isClientSideOnly);
    }
    
    function setValues(keys, values, isClientSideOnly) {
        if(isClientSideOnly == null) isClientSideOnly = true;  // By default all session variables are treated as client side only
        if(keys.length != values.length) return;
        
        // only go to the server if the variables are not client side only
        if(!isClientSideOnly) {
            var callbackData = '';
            var joinChar = '';
            // build a list of the ones we need to update on the server
            for(var i = 0; i < keys.length; i++) {
                callbackData += joinChar + keys[i] + '=' + values[i];
                joinChar = '&';
            }
            
            if(callbackData != '') {
                // send the request
                var cp = new cpaint();
                // We don't want to use the backend API
                cp.set_use_cpaint_api(false);
                // Set the transfer mode (GET / POST)
                cp.set_transfer_mode('POST');
                // The type of output we want (TEXT / OBJECT / XML / E4X)
                cp.set_response_type('XML');
                // make the call asyncronously
                cp.set_async(false);
                // void call ( string url, string remote_method, function callback_function [, mixed args [, mixed ...]] )
                cp.call('/ClientCacheInterface.asmx/SetSessionValues', '', setValuesCallback, callbackData);
                dataTypes = null;
                cp = null;
            }
        }
        else {
            // update the values in the client side session
            // non client side only values will be updated using the results of the callback from the server rather than here
            for(var i = 0; i < keys.length; i++) {
                sess[keys[i]] = values[i];
            }
        }
        
        // return the values that are in the local session store (i.e. the values that have been stored if the set was successfull)
        return getValues(keys, true);
    }
    
    function setValuesCallback(data) {
        // read the values from the data returned and add them to the client side session
        var doc = data.documentElement;
        if(doc.nodeName == 'Session') {
            for(var i = 0; i < doc.childNodes.length; i++) {
                if(doc.childNodes[i].nodeName == 'variable')
                    sess[getAttributeValue(doc.childNodes[i], 'name')] = getAttributeValue(doc.childNodes[i], 'value');
            }
        }
    }
    
    this.clearLocal = function() {
        sess = new Object();
    }
 }
 
 var session = new Session();

/*****************************************************************************************************************************
 * System messages.
 * Provision has been made to return system messages to the user.  These messages could relate the the system as a whole, the 
 *   booth the user is currently logged into or the user themselves.  The messages will be syncronised like any other cache item
 *   so when a new message is retrieved from the server, an alert can be displayed to the user.
 *
 * TODO - This needs to be implemented when more development time is available
 ****************************************************************************************************************************/
/*
var SystemMessagesCacheItem = edm.getCacheItem('SystemMessages', true, true);
function SystemMessagesCacheItem_Changed() {
    var data = SystemMessagesCacheItem.getValue;
    
    if(data.tables.items[0].rows.length > 0)
        alert('');
        
    data = null;
}
SystemMessagesCacheItem.onRefresh = SystemMessagesCacheItem_Changed;
*/





/*****************************************************************************************************************************
 * Utility function to retrieve the value of an attribute based on it's name
 ****************************************************************************************************************************/
function getAttributeValue(node, attributeName) {
    for(var attributeIndex = 0; attributeIndex < node.attributes.length; attributeIndex++) {
        if (node.attributes[attributeIndex].nodeName == attributeName) {
            return node.attributes[attributeIndex].nodeValue;
        }
    }
}

/*****************************************************************************************************************************
 * Utitlity function to show a logon dialog, this is intended to replace the session expired page, so when a user is logged off,
 *   they are  prompted to re-enter thier logon details so the system can authenticate them without interupting anything they 
 *   were previously working on
 *
 * TODO - This needs to be implemented when more development time is available
 ****************************************************************************************************************************/
function showLogonDialog() {
    return;
    
    if(!showLogonDialog.logonDialogVisible) {
        showLogonDialog.logonDialogVisible = true;
        edm.stopSync();
        var height = 250;
        var width = 400;
        var top = (window.height / 2) - (height / 2);
        var left = (window.width / 2) - (width / 2);
        EMR.Window.open('/eradmin/logonDialog.asp',null,'dialogHeight:' + height + ', dialogWidth:' + width + ', dialogTop:' + top + ', dialogLeft:' + left + 'center:no',
            function(ret)
            {
                edm.startSync();
                showLogonDialog.logonDialogVisible = false;
            }
        );
    }
}

/*****************************************************************************************************************************
 * Utility function to clone a JavaScript object
 ****************************************************************************************************************************/
function clone(myObj) {
	if(typeof(myObj) != 'object') return myObj;
	if(myObj == null) return myObj;

	var myNewObj = new Object();

	for(var i in myObj)
		myNewObj[i] = clone(myObj[i]);

	return myNewObj;
}
//</script>