/*global window:false, setTimeout:true, console:true */

(function(context) {
    'use strict';

    var win = context, doc = win.document;

    var global_instance_name = 'cbinstance';

    /*!
     * contentloaded.js
     *
     * Author: Diego Perini (diego.perini at gmail.com)
     * Summary: cross-browser wrapper for DOMContentLoaded
     * Updated: 20101020
     * License: MIT
     * Version: 1.2
     *
     * URL:
     * http://javascript.nwbox.com/ContentLoaded/
     * http://javascript.nwbox.com/ContentLoaded/MIT-LICENSE
     *
     */
    // @win window reference
    // @fn function reference
    function contentLoaded(win, fn) {
        var done = false, top = true,
        doc = win.document, root = doc.documentElement,

        add = doc.addEventListener ? 'addEventListener' : 'attachEvent',
        rem = doc.addEventListener ? 'removeEventListener' : 'detachEvent',
        pre = doc.addEventListener ? '' : 'on',

        init = function(e) {
            if (e.type == 'readystatechange' && doc.readyState != 'complete') return;
            (e.type == 'load' ? win : doc)[rem](pre + e.type, init, false);
            if (!done && (done = true)) fn.call(win, e.type || e);
        },

        poll = function() {
            try { root.doScroll('left'); } catch(e) { setTimeout(poll, 50); return; }
            init('poll');
        };

        if (doc.readyState == 'complete') fn.call(win, 'lazy');
        else {
            if (doc.createEventObject && root.doScroll) {
                try { top = !win.frameElement; } catch(e) { }
                if (top) poll();
            }
            doc[add](pre + 'DOMContentLoaded', init, false);
            doc[add](pre + 'readystatechange', init, false);
            win[add](pre + 'load', init, false);
        }
    }

    var Cookies = {
        get: function (key) {
            return decodeURIComponent(doc.cookie.replace(new RegExp('(?:(?:^|.*;)\\s*' + encodeURIComponent(key).replace(/[\-\.\+\*]/g, '\\$&') + '\\s*\\=\\s*([^;]*).*$)|^.*$'), '$1')) || null;
        },
        set: function (key, val, end, path, domain, secure) {
            if (!key || /^(?:expires|max\-age|path|domain|secure)$/i.test(key)) {
                return false;
            }
            var expires = '';
            if (end) {
                switch (end.constructor) {
                    case Number:
                        expires = end === Infinity ? '; expires=Fri, 31 Dec 9999 23:59:59 GMT' : '; max-age=' + end;
                        break;
                    case String:
                        expires = '; expires=' + end;
                    break;
                    case Date:
                        expires = '; expires=' + end.toUTCString();
                    break;
                }
            }
            doc.cookie = encodeURIComponent(key) + '=' + encodeURIComponent(val) + expires + (domain ? '; domain=' + domain : '') + (path ? '; path=' + path : '') + (secure ? '; secure' : '');
            return true;
        },
        has: function (key) {
            return (new RegExp('(?:^|;\\s*)' + encodeURIComponent(key).replace(/[\-\.\+\*]/g, '\\$&') + '\\s*\\=')).test(doc.cookie);
        },
        remove: function (key, path, domain) {
            if (!key || !this.has(key)) { return false; }
            doc.cookie = encodeURIComponent(key) + '=; expires=Thu, 01 Jan 1970 00:00:00 GMT' + ( domain ? '; domain=' + domain : '') + ( path ? '; path=' + path : '');
            return true;
        }
    };

    var Utils = {

        // merge objects and whatnot
        merge: function(){
            var obj = {},
                i = 0,
                al = arguments.length,
                key;
            if (0 === al) {
                return obj;
            }
            for (; i < al; i++) {
                for (key in arguments[i]) {
                    if (Object.prototype.hasOwnProperty.call(arguments[i], key)) {
                        obj[key] = arguments[i][key];
                    }
                }
            }
            return obj;
        },

        str2bool: function(str) {
            str = '' + str;
            switch (str.toLowerCase()) {
                case 'false':
                case 'no':
                case '0':
                case '':
                    return false;
                default:
                    return true;
                }
        },

        fade_in: function(el) {
            if (el.style.opacity < 1) {
                el.style.opacity = (parseFloat(el.style.opacity) + 0.05).toFixed(2);
                win.setTimeout(function(){
                    Utils.fade_in(el);
                }, 50);
            }
        },

        get_data_attribs: function(script) {
            var data = {};
            if (Object.prototype.hasOwnProperty.call(script, 'dataset')) {
                data = script.dataset;
            } else {
                var attribs = script.attributes;
                var key;
                for (key in attribs) {
                    if (Object.prototype.hasOwnProperty.call(attribs, key)) {
                        var attr = attribs[key];
                        if (/^data-/.test(attr.name)) {
                            var camelized = Utils.camelize(attr.name.substr(5));
                            data[camelized] = attr.value;
                        }
                    }
                }
            }
            return data;
        },

        /**
        * "Standardizes" the options keys by converting and removing
        * any potential "dashed-property-name" into "dashedPropertyName".
        * In case both are present, the dashedPropertyName wins.
        */
        normalize_keys: function(options_object) {
            var camelized = {};
            for (var key in options_object) {
                if (Object.prototype.hasOwnProperty.call(options_object, key)) {
                    var camelized_key = Utils.camelize(key);
                    // TODO: could this break for "falsy" values within options_object?
                    // avoiding "dashed-property-name" overriding a potentially existing "dashedPropertyName"
                    camelized[camelized_key] = options_object[camelized_key] ? options_object[camelized_key] : options_object[key];
                }
            }
            return camelized;
        },

        camelize: function(str) {
            var separator = '-',
                match = str.indexOf(separator);
            while (match != -1) {
                var last = (match === (str.length - 1)),
                    next = last ? '' : str[match + 1],
                    upnext = next.toUpperCase(),
                    sep_substr =  last ? separator : separator + next;
                str = str.replace(sep_substr, upnext);
                match = str.indexOf(separator);
            }
            return str;
        },

        find_script_by_id: function(id) {
            var scripts = doc.getElementsByTagName('div');
            for (var i = 0, l = scripts.length; i < l; i++) {
                if (id === scripts[i].id) {
                    return scripts[i];
                }
            }
            return null;
        }

    };

    var script_el_invoker = Utils.find_script_by_id('cookiebanner');

    var Cookiebanner = context.Cookiebanner = function(opts) {
        this.init(opts);
    };

    Cookiebanner.prototype = {

        // for testing stuff from the outside mostly
        cookiejar: Cookies,

        init: function(opts) {

            this.inserted = false;
            this.closed = false;
            this.test_mode = false; // TODO: implement

            var default_text = 'We use cookies to enhance your experience. ' +
                'By continuing to visit this site you agree to our use of cookies.';
            var default_link = 'Learn more';

            this.default_options = {
                // autorun: true,
                cookie: 'cookiebanner-accepted',
                closeText: '&#10006;',
                cookiePath: '/',
                debug: false,
                expires: Infinity,
                zindex: 255,
                mask: false,
                maskOpacity: 0.5,
                maskBackground: '#000',
                height: 'auto',
                minHeight: '21px',
                bg: '#000',
                fg: '#ddd',
                link: '#aaa',
                position: 'bottom',
                message: default_text,
                linkmsg: default_link,
                moreinfo: 'http://aboutcookies.org',
                effect: null,
                fontSize: '14px',
                fontFamily: 'arial, sans-serif',
                instance: global_instance_name,
                textAlign: 'center'
            };

            this.options = this.default_options;
            this.script_el = script_el_invoker;

            if (this.script_el) {
                var data_options = Utils.get_data_attribs(this.script_el);
                this.options = Utils.merge(this.options, data_options);
            }

            // allowing opts passed to the ctor to override everything
            if (opts) {
                // mimics the "data-option-name" HTML attribute becoming
                // this.options.optionName
                opts = Utils.normalize_keys(opts);
                this.options = Utils.merge(this.options, opts);
            }

            // allows customizing the global instance name via options too
            global_instance_name = this.options.instance;

            // TODO: parse/validate other options that can benefit
            this.options.zindex = parseInt(this.options.zindex, 10);
            this.options.mask = Utils.str2bool(this.options.mask);

            // check for a possible global callback specified as a string
            if ('string' === typeof this.options.expires) {
                if ('function' === typeof context[this.options.expires]) {
                    this.options.expires = context[this.options.expires];
                }
            }

            // check if expires is a callback
            if ('function' === typeof this.options.expires) {
                // TODO: this might not always be as simple as this
                this.options.expires = this.options.expires();
            }

            // Proceed with our plans only if we're invoked via a <script> element
            // that has the required id attribute.
            // For manually created instances one must call run() explicitly.
            if (this.script_el) {
                this.run();
            }
        },

        log: function(){
            if ('undefined' !== typeof console) {
                console.log.apply(console, arguments);
            }
        },

        run: function() {
            if (!this.agreed()) {
                var self = this;
                contentLoaded(win, function(){
                    self.insert();
                });
            }
        },

        build_viewport_mask: function() {
            var mask = null;
            if (true === this.options.mask) {
                var mask_opacity = this.options.maskOpacity;
                var bg = this.options.maskBackground;
                var mask_markup = '<div id="cookiebanner-mask" style="' +
                    'position:fixed;top:0;left:0;width:100%;height:100%;' +
                    'background:' + bg + ';zoom:1;filter:alpha(opacity=' +
                    (mask_opacity * 100) +');opacity:' + mask_opacity +';' +
                    'z-index:' + this.options.zindex +';"></div>';
                var el = doc.createElement('div');
                el.innerHTML = mask_markup;
                mask = el.firstChild;
            }
            return mask;
        },

        agree: function() {
            this.cookiejar.set(this.options.cookie, 1, this.options.expires, this.options.cookiePath);
            return true;
        },

        agreed: function(){
            return this.cookiejar.has(this.options.cookie);
        },

        close: function() {
            if (this.inserted) {
                if (!this.closed) {
                    if (this.element) {
                        doc.body.removeChild(this.element);
                    }
                    if (this.element_mask) {
                        doc.body.removeChild(this.element_mask);
                    }
                    this.closed = true;
                }
            }/* else {
                throw new Error("Not inserted but closing?!");
            }*/
            return this.closed;
        },

        agree_and_close:function() {
            this.agree();
            return this.close();
        },

        // close and remove every trace of ourselves completely
        cleanup: function() {
            this.close();
            return this.unload();
        },

        unload: function() {
            if (this.script_el) {
                this.script_el.parentNode.removeChild(this.script_el);
            }
            context[global_instance_name] = undefined;
            // delete context[global_instance_name];
            return true;
        },

        insert: function() {
            this.element_mask = this.build_viewport_mask();

            var zidx = this.options.zindex;

            if (this.element_mask) {
                // bump notice element's zindex so it's above the mask
                zidx += 1;
            }

            var el = doc.createElement('div');
            el.className = 'cookiebanner';
            el.style.position = 'fixed';
            el.style.left = 0;
            el.style.right = 0;
            el.style.height = this.options.height;
            el.style.minHeight = this.options.minHeight;
            el.style.zIndex = zidx;
            el.style.background = this.options.bg;
            el.style.color = this.options.fg;
            el.style.lineHeight = el.style.minHeight;

            el.style.padding = '5px 16px';

            el.style.fontFamily = this.options.fontFamily;
            el.style.fontSize = this.options.fontSize;
            el.style.textAlign = this.options.textAlign;

            if ('top' === this.options.position) {
                el.style.top = 0;
            } else {
                el.style.bottom = 0;
            }

            el.innerHTML = '<div class="cookiebanner-close" style="float:right;padding-left:5px;">' +
                this.options.closeText + '</div>' +
                '<span>' + this.options.message + ' <a>' + this.options.linkmsg + '</a></span>';

            this.element = el;

            var el_a = el.getElementsByTagName('a')[0];
            el_a.href = this.options.moreinfo;
            el_a.target = '_blank';
            el_a.style.textDecoration = 'none';
            el_a.style.color = this.options.link;

            var el_x = el.getElementsByTagName('div')[0];
            el_x.style.cursor = 'pointer';

            function on(el, ev, fn) {
                var add = el.addEventListener ? 'addEventListener' : 'attachEvent',
                    pre = el.addEventListener ? '' : 'on';
                el[add](pre + ev, fn, false);
            }

            var self = this;
            on(el_x, 'click', function(){
                self.agree_and_close();
            });

            if (this.element_mask) {
                on(this.element_mask, 'click', function(){
                    self.agree_and_close();
                });
                doc.body.appendChild(this.element_mask);
            }

            doc.body.appendChild(this.element);
            this.inserted = true;

            if ('fade' === this.options.effect) {
                this.element.style.opacity = 0;
                Utils.fade_in(this.element);
            } else {
                this.element.style.opacity = 1;
            }
        }

    };

    if (script_el_invoker) {
        if (!context[global_instance_name]) {
            context[global_instance_name] = new Cookiebanner();
        }
    }

})(window);