/**
 * Track changes to form (with class vm-change-tracking) and prevent
 * browsing away if changed
 * 
 * @author Visual Media
 */

(function($) {

    var forms = [];

    /**
     * Generated serialized version of form
     * 
     * @param {DOMElement} frm Form to serialize
     * 
     * @returns {String} Serialized form
     */
    var serialize = function(frm) {

        // Indicate we are serializing
        frm.triggerHandler('beforeserialize');
        var data = frm.serializeArray();
        frm.triggerHandler('afterserialize');
        
        return JSON.stringify(data);
    };

    var looseCompareStrings = function(a, b)
    {
        if (a.match(/<\/*[a-z][^>]+?>/gi) || b.match(/<\/*[a-z][^>]+?>/gi)) {
            return (a.replace(/[\n\r]/g, '') == b.replace(/[\n\r]/g, ''));
        } else {
            return (a == b);
        }
    };

    /**
     * Compare two serialized form arrays and determine the
     * real differences
     *
     * @param {String} currTxt  Curr
     * @param {String} startTxt Start
     * 
     * @returns {Array}
     */
    var cmpDiffSerialized = function(currTxt, startTxt)
    {
        var curr = JSON.parse(currTxt),
            start = JSON.parse(startTxt),
            diff = {},
            found;



        for(var x = 0, y = curr.length; x < y; x++) {
            found = false;
            for(var i = 0, j = start.length; i < j; i++) {
                if (curr[x].name == start[i].name) {
                    if (!looseCompareStrings(curr[x].value, start[i].value) && diff[curr[x].name] === undefined) {
                        diff[curr[x].name] = [ curr[x].value, start[i].value ];
                    }
                    found = true;
                    break;
                }
            }
            if (!found) {
                if (diff[curr[x].name] === undefined) {
                    diff[curr[x].name] = [ curr[x].value, null ];
                }
            }
        }

        for(var x = 0, y = start.length; x < y; x++) {
            found = false;
            for(var i = 0, j = curr.length; i < j; i++) {
                if (start[x].name == curr[i].name) {
                    if (!looseCompareStrings(start[x].value, curr[i].value) && diff[start[x].name] === undefined) {
                        diff[start[x].name] = [ start[x].value, curr[i].value ];
                    }
                    found = true;
                    break;
                }
            }
            if (!found) {
                if (diff[start[x].name] === undefined) {
                    diff[start[x].name] = [ start[x].value, null ];
                }
            }
        }

        return diff;
    };



    var changeTrackingActions = {        
        
        /**
         * Initialize changeTracking 
         * 
         * @param {DOMElement} element Form element
         * 
         * @returns {DOMElement}
         */
        init : function(element) {
            
            changeTrackingActions.resetState(element);
            
            // Give form some time to do its work, and if someone leaves 
            // within 1 sec, its problably not a problem that information 
            // is lost
            setTimeout(function() {
                // Set submit time when submitting, this prevents change tracking
                // to activate on form submit                
                element.on('submit', function() {
                    var now = new Date();
                    var ms = now.getTime();
                    element.data('vm-change-submit-time', ms);
                });
                                
            }, 1000);
            
            // Add element to list to check
            forms.push(element);
            
            return element;
        },
        
        /**
         * Reset state so that changes are only recorder after this point
         *  
         * @param {DOMElement} element DOMElement
         * 
         * @returns {DOMElement}
         */
        resetState : function(element) {
            // Save starting state to form element
            var start = serialize(element);            
            element.data('vm-change-start', start);
            return element;
        },
        
        
        /**
         * Mark form as changed
         *  
         * @param {DOMElement} element DOMElement
         * 
         * @returns {DOMElement}
         */
        markChanged : function(element) {
            
            // Fill start data with random data, this will trigger
            // data changed
            element.data('vm-change-start', '{ "hello" : "world" }');
            element.data('vm-change-submit-time', 0);
            
        },
        
        /**
         * Check if form contents is changed
         * 
         * @param {DOMElement} element Form element
         * 
         * @returns {Boolean} Is changed
         */
        isChanged : function(element) {
            
            // Check if start is already filled (happens after 1 sec)
            // If not there is no way to know.. assume nothing changed
            var start = element.data('vm-change-start');
            if (start === undefined) {
                return false;
            }
            
            // Get current data
            var curr = serialize(element);
            
            // Check if changed simple
            if (curr != start) {

                // Now determine which changes are reason to signal the
                // user, data with the same value but disabled is not
                // considered changed
                var diff = cmpDiffSerialized(curr, start),
                    same = true,
                    cmpEl;

                for (var k in diff) {
                    if(!diff.hasOwnProperty(k)) { continue; }
                    cmpEl = element.find('*[name="' + k + '"]');
                    if (cmpEl.length == 0 || cmpEl.prop('disabled') == false) {
                        same = false;
                        break;
                    }
                }

                if (!same) {
                    return true;
                }
            }
            
            return false;
            
        }
        
        
    };
    
    /**
     * ChangeTracking Plugin
     * 
     * @param {String} Action
     * 
     * @return {Object}
     */
    $.fn.changeTracking = function(action) {

        if ((JSON === undefined) || (JSON.stringify === undefined)) {
            throw "changeTracking needs JSON stringify.";
        }
        
        if (!this.is('form') || this.length > 1) {
            throw "changeTracking expects to be called from a single FORM element";
        }

        if (action === undefined) {
            action = 'init';
        }
        
        if (changeTrackingActions[action] === undefined) {
            throw "action " + action + " is not valid for changeTracking";
        }
        
        // Execute action
        return changeTrackingActions[action](this);
        
        
    };
    

    $(document).ready(function() {        
        
        // Attach change tracking to 
        $('form.vm-change-tracking').each(function() {
            var frm = $(this);
            frm.changeTracking();                 
        });
        
        $(document).on('leaveWithChanges', function(e) {
            return "Er zijn niet opgeslagen wijzigingen, weet u zeker dat u deze pagina wilt verlaten?";
        });
        
        // beforeunload is not working with jQuery event handlers, therefore
        // we add it directly with window.addEventListner        
        window.addEventListener("beforeunload", function(e) {
            
            var changed = [];
            
            // Go trough tracked forms
            $.each(forms, function() {                
                
                // If the user just hitted the submit button ignore the fact
                // that the form has changed, we are submitting
                var ms = new Date().getTime();
                var submit_time = this.data('vm-change-submit-time') + 0;
                if ((ms - submit_time) < 1000) {
                    return;
                }

                if (this.changeTracking('isChanged')) {
                    changed.push(this);
                }      
                
            });
            
            var ev = $.Event((changed.length > 0) ? 'leaveWithChanges' : 'leaveWithoutChanged');
            ev.changed = changed;
            $(document).trigger(ev);
            
            // Check result, if it's undefined (no handler called) or false
            // then show no warning
            if ((ev.result === undefined) || (ev.result === false)) {
                return;
            }
            
            // Most crossbrowser way to show message
            // https://developer.mozilla.org/en-US/docs/Web/Reference/Events/beforeunload
            var confirmationMessage = ev.result;
            (e || window.event).returnValue = confirmationMessage;     //Gecko + IE
            return confirmationMessage;

        });
        
    });


})(jQuery);