/**
 * PageData Class
 *
 * This is the core class to make a site "ajaxified".
 * The class is created when the window.onload event is fired, this "onload"
 * script is generated by the server-side Pager
 */
PageData = new Class({
    /**
     * Constructor
     *
     * @param string, the base url, corresponds with the php constant BASE_URL
     */
    initialize: function(base_url) {
        this.base_url = base_url;
        this.listener_arr = [];

        // create the loading class
        this.loading = new Loading();

        // create the lightbox manager, this object will handle all lightbox related actions
        this.lightbox_manager = new LightboxManager(this);

        this.createLinks();
    },

    /**
     * @return void
     */
    createLinks: function() {
        // get all child anchors
        var anchor_arr = $(document.body).getElements('a');

        for (var i = 0; i < anchor_arr.length; i++) {
            // get the href property of the anchor, remove the base_url (if provided)
            var href = anchor_arr[i].get('href');

            if (! href) {
                continue;
            }

            href = href.replace(this.base_url, '');
            var rel = anchor_arr[i].get('rel');

            // the regular expressions a href has to match to open in a new window/tab
            var file_re       = /\.[a-z0-9]{2,4}$/i;
            var http_re       = /^https?\:\/\//i;

            // the regular expressions a href has NOT to match to open in a new window/tab
            var mailto_re     = /^mailto\:/;
            var javascript_re = /^javascript\:/;

            // image file
            var image_reg_exp = /\.(gif|jpg)$/i;

            if (image_reg_exp.test(href)) {
                this.createLightboxLink(anchor_arr[i], href);
            } else if (rel == 'rss' || ! (mailto_re.test(href) || javascript_re.test(href) || ! (file_re.test(href) || http_re.test(href)))) {
                // external link
                this.createExternalLink(anchor_arr[i], href);
            }
        }
    },

    /**
     * Create a link a lightbox with an image
     *
     * @param Element, an anchor with a href to another domain
     * @return void
     */
    createLightboxLink: function(anchor, href) {
        var thisObject = this;
        anchor.set('href', 'javascript:void(null);');

        // add onclick event
        anchor.addEvent('click', function(e) {
            // stop the default event
            new Event(e).stop();

            var image = new Image();
            image.onload = function() {
                thisObject.renderLightbox(1, '<a class="lightbox-close" rel="close" href="javascript:void(null);"></a><img src="'+ href +'" title="'+ anchor.get('title') +'" alt="'+ anchor.get('title') +'" width="'+ image.width +'" height="'+ image.height +'" />', image.width, image.height);
            };
            image.src = href;
        });
    },

    /**
     * Create a link to a location on an other server/domain
     *
     * @param Element, an anchor with a href to another domain
     * @return void
     */
    createExternalLink: function(anchor, href) {
        anchor.set('href', 'javascript:void(null);');

        // add onclick event
        anchor.addEvent('click', function(e) {
            // stop the default event
            new Event(e).stop();

            window.open(href);
        });
    },

    /**
     * Render a lightbox
     *
     * @param string, the id of the lightbox
     * @param string, the content of the lightbox
     * @param integer, the width of the lightbox
     * @param integer or '*', the height of the lightbox
     * @param Array
     * @return void
     */
    renderLightbox: function(id, data, width, height, scripts) {
        this.lightbox_manager.open(id, data, width, height, scripts);
    },

    /**
     * Register a listener, a listener is an object that is created by the handleScripts method
     *
     * @param Object
     * @param string, the name of the event
     */
    registerListener: function(object, event) {
        // check if the object event combination is already registered
        if (this.listener_arr.contains({o: object, e: event})) {
            return;
        }

        this.listener_arr.push({o: object, e: event});
    },

    /**
     * Un-register a listener
     * @param Object
     * @param string, the name of the event
     */
    unregisterListener: function(object, event) {
        this.listener_arr.remove({o: object, e: event});
    },

    /**
     * Trigger all isteners that listen to an event
     *
     * @param string, the name of the event
     */
    handleListeners: function(event) {
        for (var i = 0; i < this.listener_arr.length; i++) {
            if (this.listener_arr[i].e == event) {
                this.listener_arr[i].o.listen('PageData', event);
            }
        }
    },

    /**
     * Create the objects that belong to the rendered content
     *
     * @param Array
     * @return void
     */
    handleScripts: function(scripts) {
        // guard: check if the argument is an array
        if (! scripts || ! scripts.length) {
            return;
        }

        for (var i = 0; i < scripts.length; i++) {
            eval(scripts[i]);
        }
    }
});

/**
 * LightboxManager Class
 * All visible lightboxes are stored in the stack array
 */
LightboxManager = new Class({
    /**
     * Constructor
     *
     * @param PageData
     */
    initialize: function(page_data) {
        this.page_data = page_data;

        // an array of visible lightbox objects
        this.stack_arr = [];

        // an array of objects {id: lightbox-id, lightbox: lightbox-object}
        this.lightbox_arr = [];

        // the depth at which the lightboxes are stacked
        this.depth = 800;

        // create a ghost container, the ghost container determines the height of given html so 
        // the height of a lighbox can be calculated
        this.ghost_container = new GhostContainer();

        var thisObject = this;
        // if the user presses "ESC" hide the active lightbox
        document.addEvent('keydown', function(e) {
            var event = new Event(e);

            if (event.key == 'esc') {
                thisObject.removeFocus();
            }
        });
    },

    /**
     * Open a lightbox
     *
     * @param string, the id of the lightbox
     * @param string, the content of the lightbox
     * @param integer, the width of the lightbox
     * @param integer or '*', the height of the lightbox
     * @param Array
     * @return void
     */
    open: function(id, data, width, height, scripts) {
        // check if the lightbox is already generated and in the cache
        var lightbox = this.find(id);

        // create the lightbox if it is not found in the cache
        if (! lightbox) {
            lightbox = this.create(id);
        } else {
            lightbox.setDepth(this.depth++);

            // if the lightbox was already in the stack array the remove it
            if (this.stack_arr.contains(lightbox) && this.stack_arr[this.stack_arr.length - 1] != lightbox) {
                this.stack_arr.remove(lightbox);
            }
        }

        // add the lightbox to the stack array
        this.stack_arr.push(lightbox);

        // hide all selectboxes for ie6
        this.toggleSelects('hidden');

        // let the ghostcontainer render the content to determine the height
        this.ghost_container.render(data, width, height, scripts, lightbox)
    },

    /**
     * Lightbox factory, Create a lightbox object
     *
     * @param string, the id of the lightbox
     * @return Lightbox
     */
    create: function(id) {
        var lightbox = new Lightbox(this, this.page_data, this.depth++, id);

        // add the lightbox to the cache
        this.lightbox_arr.push({id: id, lightbox: lightbox});

        return lightbox;
    },

    /**
     * Try to find a lightbox in the cache
     *
     * @param string, the id of the lightbox
     * @return Lightbox or false
     */
    find: function(id) {
        for (var i = 0; i < this.lightbox_arr.length; i++) {
            if (this.lightbox_arr[i].id == id) {
                return this.lightbox_arr[i].lightbox;
            }
        }

        return false;
    },

    /**
     * Removes the focus of the currently active lightbox
     *
     * @return void
     */
    removeFocus: function() {
        if (! this.stack_arr.length) {
            return;
        }

        this.stack_arr[this.stack_arr.length - 1].hide();
        this.stack_arr.erase(this.stack_arr[this.stack_arr.length - 1]);

        if (! this.stack_arr.length) {
            this.toggleSelects('visible');
        }
    },

    /**
     * Remove the focus of all lightboxes
     *
     * @return void
     */
    killFocus: function() {
        for (var i = 0; i < this.stack_arr.length; i++) {
            this.stack_arr[i].hide();
        }

        this.stack_arr = [];
        this.toggleSelects('visible');
    },

    /**
     * Hide/show selectboxes for ie6
     *
     * @param string, hidden or visibile
     * @return void
     */
    toggleSelects: function(visibility) {
        if (! window.ie6) {
            return;
        }

        // array of all selectboxes
        var select_arr = $(document.body).getElements('select');

        for (var i = 0; i < select_arr.length; i++) {
            select_arr[i].setStyle('visibility', visibility);
        }
    }
});

/**
 * GhostContainer Class
 * This class is used to preload the html content of a lightbox, the height of the content will be calculated
 * when the content is preloaded
 */
GhostContainer = new Class({
    /**
     * Constructor
     */
    initialize: function() {
        this.container = new Element('div', {
            'class': 'lightbox-tmp'
        });

        this.container.inject(document.body);

        this.content = new Element('div', {
            'class': 'lightbox-tmp-content'
        });

        this.content.inject(this.container);
    },

    /**
     * @param string
     * @param integer
     * @param integer or '*'
     * @param Array
     * @param Lightbox
     * @return void
     */
    render: function(data, width, height, scripts , lightbox) {
        this.data = data;
        this.width = width;
        this.height = height;
        this.scripts = scripts;
        this.lightbox = lightbox;
        this.image_arr = [];
        this.image_events = 0;

        // the height is already defined, show the lightbox
        if (this.height != '*') {
            this.lightbox.show(this.data, this.width, this.height, this.scripts);
        } else {
            this.content.setStyles({
                width: this.width,
                left: (-2 * this.width)
            })

            this.content.setHTML(this.data);

            var thisObject = this;
            this.image_arr = this.content.getElements('img');

            if (this.image_arr.length) {
                for (var i = 0; i < this.image_arr.length; i++) {
                    new Asset.image(this.image_arr[i].src, {
                        onload: function() {
                            thisObject.onImageEvent();
                        },
                        onerror: function() {
                            thisObject.onImageEvent();
                        },
                        onabort: function() {
                            thisObject.onImageEvent();
                        }
                    });
                }
            } else {
                this.onContentLoaded();
            }
        }
    },

    /**
     * This method is called by the load/error/abort event of an inline image ot he content for a lightbox
     *
     * @return void
     */
    onImageEvent: function() {
        this.image_events++;

        if (this.image_events == this.image_arr.length) {
            this.onContentLoaded();
        }
    },

    /**
     * The content is loaded and the height can be measured
     *
     * @return void
     */
    onContentLoaded: function() {
        var coordinates = this.content.getCoordinates();

        this.height = coordinates.height;
        this.lightbox.show(this.data, this.width, this.height, this.scripts);
    }
});

/**
 * Lightbox Class
 */
Lightbox = new Class({
    /**
     * @param PageData
     * @param LightboxManager
     * @param integer
     * @param string
     */
    initialize: function(lightbox_manager, page_data, depth, id) {
        this.page_data = page_data;
        this.lightbox_manager = lightbox_manager;
        this.id = id;

        this.lightbox_min_width = 40;
        this.lightbox_min_height = 40;

        var thisObject = this;
        this.container = $(document.body).getElement('div.lightbox');

        this.overlay = $(document.body).getElement('div.lightbox-overlay');
        this.overlay.setStyles({
            opacity: 0,
            width: Window.getScrollWidth().toInt(),
            height: Window.getScrollHeight().toInt()
        });
        this.overlay.addEvent('click', function() {
            // do totally nothing
            thisObject.lightbox_manager.removeFocus();
        });
        this.canvas = $(document.body).getElement('div.lightbox-canvas');
        this.content = $(document.body).getElement('div.lightbox-content');

        // get, if available, the animation object
        try {
            this.animation = new LightboxAnimation(this);
        } catch (e) {
            this.animation = null;
        }
    },

    /**
     * @param string
     * @param integer
     * @param integer
     * @param Array
     * @return void
     */
    show: function(data, width, height, scripts) {
        width += 26;
        height += 26;

        if (this.animation != null) {
            return this.animation.show(data, width, height, scripts);
        }

        // if no animation is present just show the lightbox
        this.overlay.setStyles({
            width: Window.getScrollWidth().toInt(),
            height: Window.getScrollHeight().toInt(),
            opacity: 0.8
        });
        this.canvas.setStyles({
            width: width,
            height: height,
            left: ((Window.getWidth().toInt() - width) / 2) + Window.getScrollLeft(),
            top: ((Window.getHeight().toInt() - height) / 2) + Window.getScrollTop()
        });

        this.setContent(data);
        this.handleScripts(scripts);
        this.container.setStyle('display', 'block');
    },

    /**
     * @param Array
     * @return void
     */
    handleScripts: function(scripts) {
        this.page_data.handleScripts(scripts);
    },

    /**
     * Hide the lightbox
     *
     * @return void
     */
    hide: function() {
        if (this.animation != null) {
            return this.animation.hide();
        }

        // if no animation is present just hide the lightbox
        this.removeContent();
        this.container.setStyle('display', 'none');
    },

    /**
     * Set the content of the lightbox
     *
     * @param string
     * @return void
     */
    setContent: function(data) {
        this.content.set('html', data);
//        this.page_data.ajaxify(this.content);

        var thisObject = this;
        var anchor_arr = $(document.body).getElements('a[rel=close]', this.lightbox);
        for (var i = 0; i < anchor_arr.length; i++) {
            anchor_arr[i].addEvent('click', function(e) {
                new Event(e).stop();

                thisObject.lightbox_manager.removeFocus();
            });
        }

        this.content.setStyle('display', 'block');
    },

    /**
     * Remove the content of the lightbox
     *
     * @return void
     */
    removeContent: function() {
        this.content.empty();
        this.content.setStyle('display', 'none');
    },

    /**
     * @return void
     */
    setDepth: function(depth) {
        this.container.setStyle('z-index', depth);
    }
});

/**
 * Loading Class
 * This class is only used to prevent that users can click on an anchor or a button while there is a request going on
 * This makes the page_data NOT asynchronous, thus SJAX?
 */
Loading = new Class({
    /**
     * Constructor
     */
    initialize: function() {
        // create a div Element that fills the whole browser
        this.container = new Element('div', {
            'styles': {
                'width': Window.getScrollWidth().toInt(),
                'height': Window.getScrollHeight().toInt()
            },
            'events': {
                'click': function() {
                    // do totally nothing
                }
            },
            'id': 'loading',
            'class': 'loading'
        });

/*/        this.container.setText('Loading ...');/**/
        this.container.inject(document.body);
    },

    /**
     * Show the container
     *
     * @return void
     */
    show: function() {
        this.container.setStyle('display', 'block');
    },

    /**
     * Hide the container
     *
     * @return void
     */
    hide: function() {
        this.container.setStyle('display', 'none');
    }
});