
/*!
 * @preserve
 * Screentime.js | v0.2.0
 * Copyright (c) 2016 Rob Flaherty (@robflaherty)
 * Licensed under the MIT and GPL licenses.
 */

/* Universal module definition */

(function(factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD
        define(['jquery'], factory);
    } else if (typeof module === 'object' && module.exports) {
        // CommonJS
        module.exports = factory(require('jquery'));
    } else {
        // Browser globals
        factory(jQuery);
    }
}(function($) {

    /* Screentime */

    var defaults = {
        fields: [],
        percentOnScreen: '50%',
        reportInterval: 10,
        googleAnalytics: false,
        callback: function() {}
    };

    $.screentime = function(options) {
        options = $.extend({}, defaults, options);

        // Convert perecent string to number
        options.percentOnScreen = parseInt(options.percentOnScreen.replace('%', ''), 10);

        var counter = {};
        var cache = {};
        var log = {};
        var looker = null;
        var started = false;
        var universalGA, classicGA;

        if (!options.fields.length) {
            return;
        }

        if (options.googleAnalytics) {

            if (typeof ga === "function") {
                universalGA = true;
            }

            if (typeof _gaq !== "undefined" && typeof _gaq.push === "function") {
                classicGA = true;
            }

        }

        /*
         * Utilities
         */

        /*!
         * visibly - v0.6 Aug 2011 - Page Visibility API Polyfill
         * http://github.com/addyosmani
         * Copyright (c) 2011 Addy Osmani
         * Dual licensed under the MIT and GPL licenses.
         *
         * Methods supported:
         * visibly.onVisible(callback)
         * visibly.onHidden(callback)
         * visibly.hidden()
         * visibly.visibilityState()
         * visibly.visibilitychange(callback(state));
         */

        (function() {
            window.visibly = {
                q: document,
                p: undefined,
                prefixes: ["webkit", "ms", "o", "moz", "khtml"],
                props: ["VisibilityState", "visibilitychange", "Hidden"],
                m: ["focus", "blur"],
                visibleCallbacks: [],
                hiddenCallbacks: [],
                genericCallbacks: [],
                _callbacks: [],
                cachedPrefix: "",
                fn: null,
                onVisible: function(i) {
                    if (typeof i == "function") {
                        this.visibleCallbacks.push(i)
                    }
                },
                onHidden: function(i) {
                    if (typeof i == "function") {
                        this.hiddenCallbacks.push(i)
                    }
                },
                getPrefix: function() {
                    if (!this.cachedPrefix) {
                        for (var i = 0; b = this.prefixes[i++];) {
                            if (b + this.props[2] in this.q) {
                                this.cachedPrefix = b;
                                return this.cachedPrefix
                            }
                        }
                    }
                },
                visibilityState: function() {
                    return this._getProp(0)
                },
                hidden: function() {
                    return this._getProp(2)
                },
                visibilitychange: function(i) {
                    if (typeof i == "function") {
                        this.genericCallbacks.push(i)
                    }
                    var t = this.genericCallbacks.length;
                    if (t) {
                        if (this.cachedPrefix) {
                            while (t--) {
                                this.genericCallbacks[t].call(this, this.visibilityState())
                            }
                        } else {
                            while (t--) {
                                this.genericCallbacks[t].call(this, arguments[0])
                            }
                        }
                    }
                },
                isSupported: function(i) {
                    return this.cachedPrefix + this.props[2] in this.q
                },
                _getProp: function(i) {
                    return this.q[this.cachedPrefix + this.props[i]]
                },
                _execute: function(i) {
                    if (i) {
                        this._callbacks = i == 1 ? this.visibleCallbacks : this.hiddenCallbacks;
                        var t = this._callbacks.length;
                        while (t--) {
                            this._callbacks[t]()
                        }
                    }
                },
                _visible: function() {
                    window.visibly._execute(1);
                    window.visibly.visibilitychange.call(window.visibly, "visible")
                },
                _hidden: function() {
                    window.visibly._execute(2);
                    window.visibly.visibilitychange.call(window.visibly, "hidden")
                },
                _nativeSwitch: function() {
                    this[this._getProp(2) ? "_hidden" : "_visible"]()
                },
                _listen: function() {
                    try {
                        if (!this.isSupported()) {
                            if (this.q.addEventListener) {
                                window.addEventListener(this.m[0], this._visible, 1);
                                window.addEventListener(this.m[1], this._hidden, 1)
                            } else {
                                if (this.q.attachEvent) {
                                    this.q.attachEvent("onfocusin", this._visible);
                                    this.q.attachEvent("onfocusout", this._hidden)
                                }
                            }
                        } else {
                            this.q.addEventListener(this.cachedPrefix + this.props[1], function() {
                                window.visibly._nativeSwitch.apply(window.visibly, arguments)
                            }, 1)
                        }
                    } catch (i) {}
                },
                init: function() {
                    this.getPrefix();
                    this._listen()
                }
            };
            this.visibly.init()
        })();

        function random() {
            return Math.round(Math.random() * 2147483647);
        }

        /*
         * Constructors
         */

        function Field(elem) {
            this.selector = elem.selector;
            $elem = this.$elem = $(elem.selector);
            this.name = elem.name;

            this.top = $elem.offset().top;
            this.height = $elem.height();
            this.bottom = this.top + this.height;
            this.width = $elem.width();
        }

        function Viewport() {
            var $window = $(window);

            this.top = $window.scrollTop();
            this.height = $window.height();
            this.bottom = this.top + this.height;
            this.width = $window.width();
        }

        /*
         * Do Stuff
         */

        function sendGAEvent(field, time) {

            if (universalGA) {
                ga('send', 'event', 'Screentime', 'Time on Screen', field, parseInt(time, 10), {
                    'nonInteraction': true
                });
            }

            if (classicGA) {
                _gaq.push(['_trackEvent', 'Screentime', 'Time on Screen', field, parseInt(time, 10), true]);
            }

        }

        function onScreen(viewport, field) {
            var cond, buffered, partialView;

            // Field entirely within viewport
            if ((field.bottom <= viewport.bottom) && (field.top >= viewport.top)) {
                return true;
            }

            // Field bigger than viewport
            if (field.height > viewport.height) {

                cond = (viewport.bottom - field.top) > (viewport.height / 2) && (field.bottom - viewport.top) > (viewport.height / 2);

                if (cond) {
                    return true;
                }

            }

            // Partially in view
            buffered = (field.height * (options.percentOnScreen / 100));
            partialView = ((viewport.bottom - buffered) >= field.top && (field.bottom - buffered) > viewport.top);

            return partialView;

        }

        function checkViewport() {
            var viewport = new Viewport();

            $.each(cache, function(key, val) {
                if (onScreen(viewport, val)) {
                    log[key] += 1;
                    counter[key] += 1;
                }
            });

        }

        function report() {

            var data = {};

            $.each(counter, function(key, val) {
                if (val > 0) {
                    data[key] = val;
                    counter[key] = 0;

                    if (options.googleAnalytics) {
                        sendGAEvent(key, val);
                    }

                }
            });

            if (!$.isEmptyObject(data)) {
                options.callback.call(this, data, log);
            }

        }

        function startTimers() {

            if (!started) {
                checkViewport();
                started = true;
            }

            looker = setInterval(function() {
                checkViewport();
            }, 1000);

            reporter = setInterval(function() {
                report();
            }, options.reportInterval * 1000);

        }

        function stopTimers() {
            clearInterval(looker);
            clearInterval(reporter);
        }

        $.screentime.reset = function() {
            stopTimers();

            $.each(cache, function(key, val) {
                log[key] = 0;
                counter[key] = 0;
            });

            startTimers();
        }

        function init() {

            $.each(options.fields, function(index, elem) {
                if ($(elem.selector).length) {
                    var field = new Field(elem);
                    cache[field.name] = field;
                    counter[field.name] = 0;
                    log[field.name] = 0;
                }
            });

            startTimers();

            visibly.onHidden(function() {
                stopTimers();
            });

            visibly.onVisible(function() {
                stopTimers();
                startTimers();
            });

        }

        init();

    };

})); 