/* IE7 doesn't have an Array.indexOf function. Not sure about IE8, but we can't expect much from MS... */
if ( typeof Array.indexOf != 'function' ) {
    Array.prototype.indexOf = function(value) {
        var j = this.length;
        for ( var i = 0; i < j; i++ ) {
            if ( this[i] == value ) {
                return i;
            }
        }
        return -1;
    }
}

var multiModal = {
    /* Keeps track of what we've opened */
    openedWindows: [],

    /* If we're inside a modal, this is set to the document that opened us. */
    caller: null,

    /* Functions used to generate the contents of the window
     * Each of these will be passed three parameters:
     *  - url is the same as multiModal.open(url)
     *  - options is the options hash with all values filled in
     *  - container is the element to append the content to.
     */
    contentHandlers: {
        // iframe is used for HTML modals. Since it's a common use, it's built in.
        iframe: function(url, options, container, caller) {
            var iframe = document.createElement('iframe');

            set_styles(iframe, {
                border:     'none', // Ignored by IE
                position:   'absolute',
                width:      container.style.width,
                height:     container.style.height
            });

            // Add the esc handler to the frame after the frame loads
            _addEvent(iframe, 'load', function(e){
                _addEvent(iframe.contentWindow.document, 'keydown', multiModal._keypressCloseWindow);
                if ( iframe.contentWindow.multiModal ) {
                    iframe.contentWindow.multiModal.caller = caller;
                }
            });

            // If done before inserting the element, this works fine.
            // If it's done afterwards, you have to mess with the iframe's document object.
            iframe.src = url;

            return iframe;
        }
    },

    /* Opens the specified url using given method */
    open: function(url, options, caller) {
        if ( window.self != window.top ) {
            // If the URL is relative to the caller, we need to fix it in case the top level is a different path.
            return window.top.multiModal.open(_makeFullURL(url), options, document);
        }
        else {
            caller = (caller || document);
        }

        // Initialise options struct if not defined
        options = (options || {});

        // Name for the window. If not given, it'll generate one from the URL.
        options.window_name = (options.window_name || 'multiModal_window_' + make_id(url) );

        // Don't try to open the same window twice
        if ( this.openedWindows.indexOf(options.window_name) != -1 ) {
            return false;
        }

        // MIME content type, used to choose the best display method if that's not specified
        options.type = (options.type || options.content_type || 'text/html');

        // Method used to display url. The default content type is HTML, so the default for this is "iframe".
        if ( ! options.method ) {
            switch ( options.type ) {
                case 'image/jpeg':
                case 'image/png':
                case 'image/gif':
                    options.method = 'image';
                break;

                case 'text/html':
                    options.method = 'iframe';
                break;

                default:
                    options.method = 'object';
            }
        }

        // Width and height of window (px)
        options.width = (options.width || 600);
        options.height = (options.height || 450);

        var container = this._createWindow(options.window_name, options.width, options.height);

        // Do nothing if a window with this name is already open
        // We expect one child element (the close window button)
        if ( container.childNodes.length > 1 ) {
            //console.debug('open() did nothing because window already open: ', options);
            return options.window_name;
        }

        if ( typeof this.contentHandlers[options.method] == 'function' ) {
            container.appendChild( this.contentHandlers[options.method](url, options, container, caller) );
        }
        else {
            throw "Can't do '" + options.method + "' (did you forget to load the content handler plugin?)";
        }

        this.openedWindows.push(options.window_name);
        this._updateTopWindow();

        return options.window_name;
    },

    /* Close a window by name (the name is returned from open()) */
    close: function(window_name) {
        if ( window.self != window.top ) {
            //console.debug('close() called from iframe.');
            return window.top.multiModal.close(window_name);
        }

        var div = document.getElementById(window_name);
        if ( ! div ) {
            return false;
        }

        div.parentNode.removeChild(div);

        // If this is the last window, just delete the whole overlay
        if ( this.openedWindows.length == 1 ) {
            this._removeOverlay();
            return true;
        }

        // Remove window from open windows list
        this.openedWindows.splice(this.openedWindows.indexOf(window_name), 1);

        this._updateTopWindow();

        return true;
    },

    /* Closes topmost window */
    closeTop: function() {
        if ( window.self != window.top ) {
            return window.top.multiModal.closeTop();
        }

        return this.close(this.openedWindows[this.openedWindows.length-1]);
    },

    /* Attaches onclick event handler to all links matching a given class name */
    attachLinks: function(class_name) {
        var links = _getElementsByClassName(class_name);

        for ( var i = links.length - 1; i >= 0; i-- ) {
            _addEvent(links[i], 'click', multiModal._linkClickHandler);
        }
    },

    /* Attaches onclick event handler to an <a> element */
    _linkClickHandler: function(evt) {
        var element;

        // Standard
        if ( typeof evt.preventDefault == 'function' ) {
            evt.preventDefault();
            element = this;
        }
        // MSIE
        else if ( evt.srcElement ) {
            evt.returnValue = false;
            element = evt.srcElement;

            // srcelement will be whatever was clicked on, not the containing link.
            // For some screwed up reason, IE will set img.href = img.src so we can't just test for href existing
            while ( 'a' != element.nodeName.toLowerCase() ) {
                element = element.parentNode;
            }
        }
        else {
            throw "Can't figure out how to handle click";
        }

        // The .open function will fill in the blanks on this
        var options = _getElementDataset(element);
        multiModal.open(element.href, options);
    },

    /* Makes sure the background and close button are in the right place */
    _updateTopWindow: function() {
        var overlay = document.getElementById('multiModalOverlay');
        var background_layer = document.getElementById('multiModalBackground');
        var top_window = document.getElementById(this.openedWindows[this.openedWindows.length-1]);

        // Reposition the background layer directly before the last modal, so it covers any other open modal windows
        if ( overlay.childNodes[overlay.childNodes.length-2] != background_layer ) {
            var bg_layer = overlay.removeChild(background_layer);
            overlay.insertBefore(background_layer, top_window);
        }

        this._showCloseButton();
    },

    /* Creates a modal window, or returns it if already exists */
    _createWindow: function(window_name, width, height) {
        var div = document.getElementById(window_name);
        if ( div ) {
            return div;
        }

        div = document.createElement('div');
        div.id = window_name;
        div.className = 'multiModal';

        // try at least not to break *completely* in IE6
        var divPosition = 'fixed';
        if ( div.attachEvent && navigator.userAgent.indexOf('MSIE 6.') !== -1 ) {
            divPosition = 'absolute';
        }


        set_styles(div, {
            position:   divPosition,
            width:      width + 'px',
            height:     height + 'px',
            left:       '50%',
            top:        '50%',
            marginLeft: (-width/2) + 'px',
            marginTop:  (-height/2) + 'px'
        });

        var overlay = this._showOverlay();
        overlay.appendChild(div);

        return div;
    },

    /* Creates a "close window" button, or repositions it onto toplevel window */
    _showCloseButton: function() {
        var top_window = document.getElementById(this.openedWindows[this.openedWindows.length-1]);

        // Close button is a child element of this, so if it's not there do nothing.
        if ( ! top_window ) {
            return;
        }

        var old_close_button = document.getElementById('closeModal');
        var close_button;

        if ( old_close_button ) {
            close_button = old_close_button.parentNode.removeChild(old_close_button);
        }
        else {
            close_button = document.createElement('button');
            close_button.id = 'closeModal';
            close_button.title = 'Click to close this box (shortcut: shift+alt+x).';
            close_button.accessKey = 'x';

            // Standard
            if ( typeof close_button.textContent != 'undefined' ) {
                // This is technically the right symbol, but windows has no decent unicode fonts.
                //close_button.textContent = '\u2327';
                close_button.textContent = 'X';
            }
            // MSIE
            else if ( typeof close_button.innerText != 'undefined' ) {
                close_button.innerText = 'X';
            }

            _addEvent(close_button, 'click', multiModal._clickCloseWindow);
        }

        // Make sure it's inserted first. We don't strictly need it first, but it keeps things consistent.
        if ( top_window.firstChild ) {
            top_window.insertBefore(close_button, top_window.firstChild);
        }
        else {
            top_window.appendChild(close_button);
        }
    },

    /* Get a handle to full-page overlay div, creating it if it doesn't exist */
    _showOverlay: function() {
        var existing_div = document.getElementById('multiModalOverlay');
        if ( existing_div ) {
            return existing_div;
        }

        // This div contains all open windows
        var overlay_div = document.createElement('div');
        overlay_div.id = 'multiModalOverlay';
        document.getElementsByTagName('body')[0].appendChild(overlay_div);

        // We create a separate div to create a fade-out background effect, also to close windows when clicked on
        // This is inserted directly under the topmost window, to prevent other windows being clicked on.
        var background_div = document.createElement('div');
        background_div.id = 'multiModalBackground';
        _addEvent(background_div, 'click', multiModal._clickCloseWindow);

        // Add Esc key handler
        _addEvent(document, 'keydown', multiModal._keypressCloseWindow);

        overlay_div.appendChild(background_div);

        return overlay_div;
    },

    /* Remove overlay div */
    _removeOverlay: function() {
        var o = document.getElementById('multiModalOverlay');
        o.parentNode.removeChild(o);
        this.openedWindows = [];
    },

    /* Close a window if overlay is clicked on */
    _clickCloseWindow: function(evt) {
        // Only trigger if the background div is clicked on directly
        if ( evt.target == evt.currentTarget ) {
            // Note: this is called from an event handler, so we can't use "this"
            multiModal.closeTop();
        }
    },

    /* Close a window if Esc is pressed */
    _keypressCloseWindow: function(evt) {
        // ASCII value
        if ( evt.keyCode == 27 ) {
            // See _clickCloseWindow function
            multiModal.closeTop();
        }
    }

};

/****************************************************************************/
// Utility functions

/* IE-compatible wrapper around addEventListener */
function _addEvent(element, event_name, callback) {
    // Standards-compliant
    if ( typeof element.addEventListener == 'function' ) {
        return element.addEventListener(event_name, callback, false);
    }
    // IE
    else if ( element.attachEvent ) {
        return element.attachEvent('on'+event_name, callback);
    }

    throw 'Unable to add event listener';
}

/* "flatten" a string into an identifier we can use in a HTML ID */
function make_id(string) {
    return string.replace(/[^a-z0-9_-]+/g, '_').replace(/^[^a-z_]/, '_');
}

/* Set multiple CSS styles on an element by using a property->value hash */
function set_styles(element, styles) {
    for ( var i in styles ) {
        element.style[i] = styles[i];
    }
}

/* Similar to the standard getElementsByClassName function */
function _getElementsByClassName(classname, node, tagname) {
    // Default parameter values
    node = (node || document.body);
    tagname = (tagname || '*');

    // Use browser-native function if possible
    if ( typeof node.getElementsByClassName == 'function' && tagname == '*' ) {
        return node.getElementsByClassName(classname);
    }

    var found = [];
    var re = new RegExp('\\b' + classname + '\\b');
    var elements = node.getElementsByTagName(tagname);

    for ( var i = 0, j = elements.length; i < j; i++ ) {
        if ( re.test(elements[i].className) ) {
            found.push(elements[i]);
        }
    }

    return found;
}

/* Implements the HTML5 algorithm for reading custom data attributes
 * http://www.whatwg.org/specs/web-apps/current-work/multipage/dom.html#embedding-custom-non-visible-data*/
function _getElementDataset(element) {
    if ( element.dataset ) {
        return element.dataset;
    }

    var dataset = {};
    var data_regex = /^data-(.*)/;

    for ( var i = element.attributes.length-1; i >= 0; i-- ) {
        var attribute = element.attributes[i].nodeName;
        var matches = data_regex.exec(attribute);

        if ( matches ) {
            dataset[matches[1]] = element.getAttribute(attribute);
        }
    }

    return dataset;
}

/* Makes an URL relative to the current page into an absolute url */
function _makeFullURL(url) {
    var re_abs_url = new RegExp('^(https?:)?/');

    // Already absolute
    if ( re_abs_url.test(url) ) {
        return url;
    }
    // Remove everything after the last "/"
    return window.location.toString().replace(/[^\/]+$/, '') + url;
}
