Object.extend(
    document.viewport,
    {
        getPageSize : function()
        {
        var xScroll, yScroll, windowWidth, windowHeight, pageHeight, pageWidth;

            if (window.innerHeight && window.scrollMaxY) {
                xScroll = document.body.scrollWidth;
                yScroll = window.innerHeight + window.scrollMaxY;
            }
            else if (document.body.scrollHeight > document.body.offsetHeight) { //all but Explorer Mac
                xScroll = document.body.scrollWidth;
                yScroll = document.body.scrollHeight;
            }
            else { // Explorer Mac...would also work in Explorer 6 Strict, Mozilla and Safari
                xScroll = document.body.offsetWidth;
                yScroll = document.body.offsetHeight;
            }

            if (self.innerHeight) { // all except Explorer
                windowWidth = self.innerWidth;
                windowHeight = self.innerHeight;
            }
            else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode
                windowWidth = document.documentElement.clientWidth;
                windowHeight = document.documentElement.clientHeight;
            }
            else if (document.body) { // other Explorers
                windowWidth = document.body.clientWidth;
                windowHeight = document.body.clientHeight;
            }

            // for small pages with total w/h less then w/h of the viewport
            pageHeight = (yScroll < windowHeight) ? windowHeight : yScroll;
            pageWidth  = (xScroll < windowWidth)  ? windowWidth  : xScroll;

            return [
                pageWidth, pageHeight, windowWidth, windowHeight
            ];
        }
    }
);

var ProtoBox = Class.create();
ProtoBox.prototype = {
    initialize : function(options)
    {
        this.options = Object.extend(
            {
                hide_flash   : false,
                loading_img  : 'protobox/images/loading.gif',
                close_img    : 'protobox/images/closelabel.png',
                rel          : 'lightbox',
                my_id        : 'lightbox',
                protect      : true,

                force_update : false,

                max_width    : 0,
                max_height   : 0,

                ring         : true,
                drag         : true,

                overlay      : null,
                overlay_id   : 'overlay',
                ovl_opacity  : 0.8,
                ovl_speed    : 0.2,

                animate      : true,
                speed        : 8
            },
            options || {}
        );

        this.listening_kd = false;
        this._keydown     = this._onKeyDown.bindAsEventListener(this);

        this.body    = $$('body')[0];
        this.overlay = $(this.options.overlay);
        if (null == this.overlay) {
            this.overlay = $(document.createElement('div'));
            this.overlay.setAttribute('id', this.options.overlay_id);
            this.overlay.style.display = 'none';
            this.body.appendChild(this.overlay);
        }

        this.overlay.observe('click', this._closeSelf.bindAsEventListener(this));

        if (false == this.options.animate) {
            this.resize_duration = 0;
        }
        else {
            if (this.options.speed > 10) {
                this.options.speed = 10;
            }

            if (this.options.speed < 1) {
                this.options.speed = 1;
            }

            this.resize_duration = 0.1 * (11 - this.options.speed);
        }

        this.act_rel = '';
        this.active  = -1;
        this.images  = {};
        this._buildLayout();
        this.updateImageList();
    },
    _buildLayout : function()
    {
        this.myself = $(Builder.node('div', { id : this.options.my_id }, [
            Builder.node('div', { 'class' : 'imageContainer' }, [
                Builder.node('img', { alt : '',  src : '' }),
                Builder.node('div', { 'class' : ((true == this.options.protect) ? 'protector' : '') }),
                Builder.node('a', { href : 'javascript:void(0)', 'class' : 'prevLink' }),
                Builder.node('a', { href : 'javascript:void(0)', 'class' : 'nextLink' }),
                Builder.node('div', { 'class' : 'loading' }, [
                    Builder.node('img', { alt : 'Loading...',  src : this.options.loading_img })
                ]),
            ]),
            Builder.node('div', { 'class' : 'imageDataContainer' }, [
                Builder.node('div', { 'class' : 'imageDetails' }, [
                    Builder.node('div', { 'class' : 'caption' }),
                    Builder.node('div', { 'class' : 'numberDisplay' })
                ]),
                Builder.node('img', { 'class' : 'bottomNavClose', alt : 'Close', src : this.options.close_img })
            ])
        ]));

        if (window.external && ('undefined' == typeof window.XMLHttpRequest) && document.all && -1 == navigator.userAgent.toLowerCase().indexOf('opera')) {
            var iframe = document.createElement('iframe');
            iframe.className = 'ie_iframe';
            iframe.frameBorder = 0;
            this.myself.appendChild(iframe);
        }

        this.myself.setStyle({ display : 'none' });
        if (false == this.options.drag) {
            this.myself.observe('click', this._closeSelf.bindAsEventListener(this));
        }

        this.body.appendChild(this.myself);

        this.imageContainer     = this.myself.down();
        this.imageDataContainer = this.imageContainer.next();
        this.the_image          = this.imageContainer.down();
        this.prevLink           = this.the_image.next().next();
        this.nextLink           = this.prevLink.next();
        this.loading            = this.nextLink.next();
        this.loadingLink        = this.loading.down();
        var imageDetails        = this.imageDataContainer.down();
        this.bottomNavClose     = imageDetails.next();
        this.caption            = imageDetails.down();
        this.numberDisplay      = this.caption.next();

        if (this.options.animate) {
            this.imageContainer.setStyle( { width : '250px', height : '250px' } );
            this.imageDataContainer.setStyle( { width : '250px' } );
        }
        else {
            this.imageContainer.setStyle( { width : '40px', height : '40px' } );
            this.imageDataContainer.setStyle( { width : '40px' } );
        }

        if (this.options.drag) {
            new Draggable(this.myself, { handle : this.imageContainer, onEnd : this._fixOverlay.bind(this) });
            this.imageContainer.style.cursor = 'move';
        }

        this.prevLink.observe('click', this._prev.bindAsEventListener(this));
        this.nextLink.observe('click', this._next.bindAsEventListener(this));

        [this.loadingLink, this.bottomNavClose].invoke('observe', 'click', this._closeSelf.bindAsEventListener(this));
    },
    _fixOverlay : function()
    {
        var ow = document.documentElement.offsetWidth;
        var oh = document.documentElement.offsetHeight;
        var sw = document.documentElement.scrollWidth;
        var sh = document.documentElement.scrollHeight;

        if (ow > sw) {
            sw = ow;
        }

        if (oh > sh) {
            sh = oh;
        }

        this.overlay.setStyle({ width : sw + 'px', height : sh + 'px' });
    },
    _closeSelf : function(e)
    {
        e.stop();
        this._end();
    },
    _startShow : function(e)
    {
        e.stop();
        if (true == this.options.force_update) {
            this.updateImageList();
        }

        this._start(e.findElement('a'));
    },
    updateImageList : function()
    {
        this.images  = [];
        this.active  = -1;
        this.act_rel = null;

        $$('a[rel^=' + this.options.rel + ']', 'area[rel^=' + this.options.rel + ']').each(
            function(node)
            {
                var rel   = node.getAttribute('rel') || this.options.rel;
                var href  = node.getAttribute('href') || '';
                var title = node.getAttribute('title') || '';

                if (-1 != rel.indexOf('[')) {
                    if ('undefined' == typeof this.images[rel]) {
                        this.images[rel] = [];
                    }

                    this.images[rel].push({ 'href' : href, 'title' : title });
                }

                if (!node.protoboxed) {
                    node.observe('click', this._startShow.bindAsEventListener(this));
                    node.protoboxed = true;
                }
            }.bind(this)
        );
    },
    _start : function(element)
    {
        this._hideFlash();

        var dims    = document.viewport.getDimensions();
        var scrolls = document.viewport.getScrollOffsets();

        new Effect.Appear(
            this.overlay,
            {
                duration : this.options.ovl_speed,
                from     : 0,
                to       : this.options.ovl_opacity
            }
        );

        var rel   = element.getAttribute('rel');
        this.href = element.getAttribute('href');
        var title = element.getAttribute('title');
        var idx   = -1;

        if (-1 != rel.indexOf('[')) {
            if ('undefined' == typeof this.images[rel]) {
                this.images[rel] = [{ 'href' : href, 'title' : title }];
            }
            else {
                var a = this.images[rel];
                var l = a.length;
                for (var i=0; i<l; ++i) {
                    if (a[i].href == this.href) {
                        idx = i;
                        break;
                    }
                }

                if (-1 == idx) {
                    idx = l;
                    a.push(this.href);
                }
            }
        }
        else {
            rel = title;
        }

        var top  = scrolls.top + dims.height / 15;
        var left = scrolls.left;

        this.myself.setStyle({ 'left' : left + 'px', 'top' : top + 'px' })
        this.myself.show();

        this._changeImage(rel, idx);
    },
    _end : function()
    {
        this._disableKeyboardNav();
        this.myself.hide();
        new Effect.Fade(this.overlay, { duration: this.options.ovl_speed });
        this._showFlash();
    },
    _changeImage : function(rel, idx)
    {
        this.act_rel = rel;
        this.active  = idx;
        if (this.options.animate) {
            this.loading.show();
        }

        [this.the_image, this.prevLink, this.nextLink, this.imageDataContainer, this.numberDisplay].invoke('hide');

        imgPreloader = new Image();

        imgPreloader.onload = function()
        {
            imgPreloader.onload = null;

            if (window.external && ('undefined' == typeof window.XMLHttpRequest) && document.all && -1 == navigator.userAgent.toLowerCase().indexOf('opera')) {
                $(this.the_image).onload = function()
                {
                    $(this.the_image).onload = null;
                    var w = imgPreloader.width;
                    var h = imgPreloader.height;
                    var r;

                    if (0 != this.options.max_width) {
                        r  = this.options.max_width/w;
                        w  = this.options.max_width;
                        h *= r;
                    }

                    if (0 != this.options.max_height) {
                        r  = this.options.max_height/h;
                        h  = this.options.max_height;
                        w *= r;
                    }

                    $(this.the_image).setStyle({ width : w + 'px', height : h + 'px' });
                    this._resizeImageContainer(w, h);
                }.bindAsEventListener(this);

                $(this.the_image).setAttribute('src', imgPreloader.src);
            }
            else {
                $(this.the_image).setAttribute('src', imgPreloader.src);

                var w = imgPreloader.width;
                var h = imgPreloader.height;
                var r;

                if (0 != this.options.max_width) {
                    r  = this.options.max_width/w;
                    w  = this.options.max_width;
                    h *= r;
                }

                if (0 != this.options.max_height) {
                    r  = this.options.max_height/h;
                    h  = this.options.max_height;
                    w *= r;
                }

                $(this.the_image).setStyle({ width : w + 'px', height : h + 'px' });
                this._resizeImageContainer(w, h);
            }
        }.bindAsEventListener(this);

        imgPreloader.setAttribute('src', ((this.active < 0) ? this.href : this.images[rel][idx].href));
    },
    _resizeImageContainer : function(w, h)
    {
        var width_current  = this.imageContainer.getWidth();
        var height_current = this.imageContainer.getHeight();

        var width_new  = w;
        var height_new = h;

        var x_scale = width_new / width_current * 100;
        var y_scale = height_new / height_current * 100;

        var w_diff = width_current - width_new;
        var h_diff = height_current - height_new;

        if (0 != h_diff) {
            new Effect.Scale(
                this.imageContainer,
                y_scale,
                {
                    scaleX   : false,
                    duration : this.resize_duration,
                    queue    : 'front'
                }
            );
        }

        if (0 != w_diff) {
            new Effect.Scale(
                this.imageContainer,
                x_scale,
                {
                    scaleY   : false,
                    duration : this.resize_duration,
                    delay    : this.resize_duration
                }
            );

            this.imageDataContainer.setStyle( { width : width_new + 'px' } );
        }

        if (0 == h_diff && 0 == w_diff) {
            this._pause(
                (-1 != navigator.appVersion.indexOf("MSIE")) ? 250 : 100
            );
        }

        this.prevLink.style.height = h + 'px';
        this.nextLink.style.height = h + 'px';
        this.imageDataContainer.style.width = w + 'px';
        this._showImage();
    },
    _showImage: function()
    {
        if (this.active >= 0) {
            if (this.images[this.act_rel].length - 1 > this.active) {
                var p_next = new Image();
                p_next.src = this.images[this.act_rel][this.active + 1].href;
            }

            if (this.active > 0) {
                var p_prev = new Image();
                p_prev.src = this.images[this.act_rel][this.active - 1].href;
            }
        }

        this.loading.hide();
        new Effect.Appear(
            this.the_image,
            {
                duration    : this.resize_duration,
                queue       : 'end',
                afterFinish : this._updateDetails.bind(this)
            }
        );
    },
    _updateDetails : function()
    {
        if (this.active >= 0) {
            var e = this.images[this.act_rel][this.active];
            if ('' != e.title){
                this.caption.update(e.title);
                this.caption.show();
            }

            if (this.images[this.act_rel].length && this.images[this.act_rel].length > 1) {
                this.numberDisplay.update("" + (this.active + 1) + " - " + this.images[this.act_rel].length);
                this.numberDisplay.show();
            }
        }
        else {
            if ('' != this.act_rel){
                this.caption.update(this.act_rel);
                this.caption.show();
            }
        }

        new Effect.Parallel(
            [
                new Effect.SlideDown(this.imageDataContainer, { sync: true, duration: this.resize_duration, from: 0.0, to: 1.0 }),
                new Effect.Appear(this.imageDataContainer, { sync: true, duration: this.resize_duration })
            ],
            {
                duration    : this.resize_duration,
                afterFinish : this._updateNav.bind(this)
            }
        );
    },
    _prev : function(e)
    {
        Event.stop(e);
        this.__prev();
    },
    __prev : function()
    {
        if (this.active > 0) {
            this._changeImage(this.act_rel, this.active - 1);
        }
        else if (true == this.options.ring && 0 == this.active) {
            this._changeImage(this.act_rel, this.images[this.act_rel].length - 1);
        }
    },
    _next : function(e)
    {
        Event.stop(e);
        this.__next();
    },
    __next : function()
    {
        if (this.active >= 0) {
            if (this.active != this.images[this.act_rel].length - 1) {
                this._changeImage(this.act_rel, this.active + 1);
            }
            else if (true == this.options.ring) {
                this._changeImage(this.act_rel, 0);
            }
        }
    },
    _updateNav: function()
    {
        if (this.active >= 0) {
            if (this.active > 0 || true == this.options.ring) {
                this.prevLink.show();
            }

            if (this.active != this.images[this.act_rel].length - 1 || true == this.options.ring) {
                this.nextLink.show();
            }
        }

        this._fixOverlay();
        this._enableKeyboardNav();
    },
    _enableKeyboardNav : function()
    {
        if (false == this.listening_kd) {
            this.listening_kd = true;
            Event.observe(document, 'keydown', this._keydown);
        }
    },
    _disableKeyboardNav : function()
    {
        if (true == this.listening_kd) {
            this.listening_kd = false;
            Event.stopObserving(document, 'keydown', this._keydown);
        }
    },
    _onKeyDown : function(e)
    {
        var keycode = e.keyCode;
        var key     = String.fromCharCode(keycode).toLowerCase();

        if ('x' == key || 'c' == key || 'o' == key || Event.KEY_ESC == keycode) {
            this._end();
        }
        else if ('p' == key || Event.KEY_LEFT == keycode) {
            this._disableKeyboardNav();
            this.__prev();
        }
        else if ('n' == key || Event.KEY_RIGHT == keycode) {
            this._disableKeyboardNav();
            this.__next();
        }
    },
    _hideFlash : function()
    {
        if (true == this.options.hide_flash) {
            this.all_flash = $$("object", "embed").select(
                function(node)
                {
                    if ('visible' == node.getStyle('visibility')) {
                        node.style.visibility = 'hidden';
                        return true;
                    }

                    return false;
                }
            ) || [];
        }
    },
    _showFlash : function()
    {
        if (true == this.options.hide_flash) {
            this.all_flash.invoke('setStyle', { visibility : 'visible' });
            this.all_flash = [];
        }
    },
    _pause : function(ms)
    {
        var date    = new Date();
        var curDate = null;
        do {
            curDate = new Date();
        } while (curDate - date < ms);
    }
};

var myLightbox;

function initLightbox(options)
{
    myLightbox = new ProtoBox(options);
}
