(function (window, undefined) {

var LightBoxerSettings = {
        throbberSrc: 'throbber.gif',
        dimmerColor: 'rgba(51, 51, 51, 0.95)',
        imageMargin: 10,
        imagePadding: 4,
        zOrder: {
            dimmer  : 100,
            throbber: 110,
            image   : 120,
            controls: 130
        },
        autoRun: false,
        requireRelAttribute: true
    },
    LightBoxer = function () {
        if ('object' == typeof arguments && arguments.length == 1) {
            for (var key in arguments[0]) {
                if ('undefined' != typeof LightBoxerSettings[key]) {
                    LightBoxerSettings[key] = arguments[0][key];
                }
            }
        }

        return new LightBoxer.fn.init();
    },
    LightBoxerDimmer = function (LB) {
        this.init(LB);
    },
    LightBoxerImageContainer = function (LB) {
        this.init(LB);
    },
    LightBoxerControls = function (LB) {
        this.init(LB);
    },
    LightBoxerThrobber = function (LB) {
        this.init(LB);
    },
    document = window.document;

LightBoxer.fn = LightBoxer.prototype = {
    init: function () {
        this.rootNode = document.createElement('div');
        this.rootNode.id = 'LightBoxer';

        LightBoxerUtils.applyStyles(this.rootNode, {
            cursor: 'pointer',
            height: '100%',
            left: '0',
            position: 'fixed',
            top: '0',
            width: '100%'
        });

        this.dimmer         = new LightBoxerDimmer(this);
        this.imagecontainer = new LightBoxerImageContainer(this);
        this.throbber       = new LightBoxerThrobber(this);
        this.controls       = new LightBoxerControls(this);
        this.nodes          = [];
        this.nodecursor     = -1;

        return this;
    },

    close: function () {
        this.dimmer.hide();
        this.imagecontainer.hide();
        this.throbber.hide();
        this.controls.hide();

        document.body.removeChild(this.rootNode);
    },

    displayNext: function () {
        this.nodecursor++;
        this.displayImage(this.nodes[this.nodecursor].href, this.extractTitle(this.nodes[this.nodecursor]));
    },

    displayPrevious: function () {
        this.nodecursor--;
        this.displayImage(this.nodes[this.nodecursor].href, this.extractTitle(this.nodes[this.nodecursor]));
    },

    hasNext: function () {
        return this.nodes.length > 1 && this.nodecursor != -1 && this.nodecursor < this.nodes.length - 1;
    },

    hasPrevious: function () {
        return this.nodes.length > 1 && this.nodecursor != -1 && this.nodecursor > 0;
    },

    displayImage: function (url, title) {
        // Find position
        this.nodecursor = -1;
        for (var i = 0; i < this.nodes.length; i++) {
            if (this.nodes[i].href == url) {
                this.nodecursor = i;
                break;
            }
        }

        // Show image
        this.throbber.show();
        this.dimmer.show();
        this.controls.show();

        this.imagecontainer.loadImage(url, LightBoxerUtils.delegate(this, this.imageLoadedHandler));
        this.displayTitle(title);

        document.body.appendChild(this.rootNode);
    },

    displayTitle: function (title) {
        this.controls.addTitle(title);
    },

    imageLoadedHandler: function (e) {
        this.throbber.hide();
        this.imagecontainer.scaleAndCenter();
        this.imagecontainer.show();
    },

    extractTitle: function (node) {
        if (node.title && node.title.length > 0) {
            return node.title;
        } else {
            var imgs = node.getElementsByTagName('img');

            if (imgs.length == 1 && imgs[0].title && imgs[0].alt.title > 0) {
                return imgs[0].title;
            } else if (imgs.length == 1 && imgs[0].alt && imgs[0].alt.length > 0) {
                return imgs[0].alt;
            }
        }

        return null;
    },

    addLBNode: function (node) {
        this.nodes.push(node);
    },

    autoSetup: function () {
        var LB = this;
        var links = document.getElementsByTagName('a');
        var click = function (e) {
                        e.preventDefault();

                        var imageUrl = this.href;
                        var imageTitle = LB.extractTitle(this);

                        LB.displayImage(imageUrl, imageTitle);
                    };

        for (var i = 0; i < links.length; i++) {
            var node = links[i];

            if (! LightBoxerSettings.requireRelAttribute || node.rel == 'lightbox') {
                switch (node.href.substr(-3)) {
                    case 'jpg':
                    case 'peg':
                    case 'gif':
                    case 'png':
                        break;

                    default:
                        continue;
                }

                this.addLBNode(node);

                LightBoxerUtils.bindEvent(node, 'click', click);
            }
        }
    }
};

LightBoxer.fn.init.prototype = LightBoxer.prototype;

LightBoxerControls.prototype = {
    node: null,
    LB: null,
    buttons: {
        close: null,
        next: null,
        previous: null
    },
    keyboard: {
        handler: null,
        left: 37,
        right: 39,
        escape: 27
    },
    resizeBound: null,

    init: function (LB) {
        this.LB = LB;
        this.node = this.createNode();
        this.resizeBound = LightBoxerUtils.delegate(this, this.resizeHandler);

        var listener = function (event) {
            event = event || window.event;

            switch (event.keyCode) {
                case this.keyboard.left:
                    this.previousHandler();
                    break;

                case this.keyboard.right:
                    this.nextHandler();
                    break;

                case this.keyboard.escape:
                    this.LB.close();
                    break;
            }

        };

        this.keyboard.handler = LightBoxerUtils.delegate(this, listener);
    },

    show: function () {
        if (! this.node.parentNode) {
            this.LB.rootNode.appendChild(this.node);
            this.resizeHandler();

            this.buttons.previous.disabled = ! this.LB.hasPrevious();
            this.buttons.next.disabled = ! this.LB.hasNext();

            LightBoxerUtils.bindEvent(window, 'keydown', this.keyboard.handler);
            LightBoxerUtils.bindEvent(window, 'resize', this.resizeBound);
        }
    },

    hide: function () {
        if (this.node.parentNode) {
            this.LB.rootNode.removeChild(this.node);
            this.removeTitle();

            LightBoxerUtils.unbindEvent(window, 'keydown', this.keyboard.handler);
            LightBoxerUtils.unbindEvent(window, 'resize', this.resizeBound);
        }
    },

    addTitle: function (value) {
        this.removeTitle();

        if (value) {
            var title = document.createElement('p');
            title.innerHTML = value || 'Untitled';

            LightBoxerUtils.applyStyles(title, {
                cssFloat: 'left',
                styleFloat: 'left',
                display: 'inline',
                margin: '0 0 0 15px'
            });

            this.node.appendChild(title);
        }
    },

    removeTitle: function () {
        var titles = this.node.getElementsByTagName('p');

        if (titles.length > 0) {
            for (var i = 0; i < titles.length; i++) {
                titles[i].parentNode.removeChild(titles[i]);
            }
        }
    },

    nextHandler: function () {
        if (this.LB.hasNext()) {
            this.LB.displayNext.apply(this.LB);
            this.buttonStateHandler();
        }
    },

    previousHandler: function () {
        if (this.LB.hasPrevious()) {
            this.LB.displayPrevious.apply(this.LB);
            this.buttonStateHandler();
        }
    },

    buttonStateHandler: function () {
        this.buttons.next.disabled = ! this.LB.hasNext();
        this.buttons.previous.disabled = ! this.LB.hasPrevious();
    },

    resizeHandler: function () {
        LightBoxerUtils.applyStyles(this.node, {
            width: (LightBoxerUtils.getViewportDimensions().width - 8) + 'px'
        });
    },

    createNode: function () {
        try {
            return LightBoxerUtils.getElementOrFail('LightBoxerControls');
        } catch (e) {

        }

        this.buttons.close = document.createElement('button');
        this.buttons.close.innerHTML = 'Close';

        this.buttons.next = document.createElement('button');
        this.buttons.next.innerHTML = 'Next';

        this.buttons.previous = document.createElement('button');
        this.buttons.previous.innerHTML = 'Previous';

        LightBoxerUtils.applyStyles(this.buttons.close, {
            backgroundColor: '#f00',
            color: '#fff',
            marginLeft: '30px'
        });

        LightBoxerUtils.bindEvent(this.buttons.close, 'click', LightBoxerUtils.delegate(this.LB, this.LB.close));
        LightBoxerUtils.bindEvent(this.buttons.next, 'click', LightBoxerUtils.delegate(this, this.nextHandler));
        LightBoxerUtils.bindEvent(this.buttons.previous, 'click', LightBoxerUtils.delegate(this, this.previousHandler));

        var buttonContainer = document.createElement('div');
        buttonContainer.appendChild(this.buttons.previous);
        buttonContainer.appendChild(this.buttons.next);
        buttonContainer.appendChild(this.buttons.close);

        var controls = document.createElement('div');
        controls.id = 'LightBoxerControls';
        controls.appendChild(buttonContainer);

        LightBoxerUtils.applyStyles(buttonContainer, {
            cssFloat: 'right',
            styleFloat: 'right',
            display: 'inline',
            marginRight: '15px'
        });
        LightBoxerUtils.applyStyles(controls, {
            backgroundColor: '#fff',
            borderBottom: '2px solid #888',
            color: '#333',
            cursor: 'default',
            left: '0',
            padding: '0',
            position: 'absolute',
            right: '0',
            top: '0',
            zIndex: LightBoxerSettings.zOrder.controls
        });

        return controls;
    }
};

LightBoxerDimmer.prototype = {
    node: null,
    LB: null,
    init: function (LB) {
        this.LB = LB;
        this.node = this.createNode();
        LightBoxerUtils.bindEvent(this.node, 'click', LightBoxerUtils.delegate(LB, LB.close));
    },

    show: function () {
        if (! this.node.parentNode) {
            this.LB.rootNode.appendChild(this.node);
        }
    },

    hide: function () {
        if (this.node.parentNode) {
            this.LB.rootNode.removeChild(this.node);
        }
    },

    createNode: function () {
        try {
            return LightBoxerUtils.getElementOrFail('LightBoxerDimmer');
        } catch (e) {

        }

        var dimmer = document.createElement('div');
        dimmer.id = 'LightBoxerDimmer';
        LightBoxerUtils.applyStyles(dimmer, {
            backgroundColor: LightBoxerSettings.dimmerColor,
            bottom: '0',
            left: '0',
            position: 'absolute',
            right: '0',
            top: '0',
            zIndex: LightBoxerSettings.zOrder.dimmer
        });

        return dimmer;
    }
};

LightBoxerImageContainer.prototype = {
    node: null,
    LB: null,
    init: function (LB) {
        this.LB = LB;
        this.node = this.createNode();
        LightBoxerUtils.bindEvent(window, 'resize', LightBoxerUtils.delegate(this, this.scaleAndCenter));
        LightBoxerUtils.bindEvent(this.node, 'click', LightBoxerUtils.delegate(LB, LB.close));
    },

    show: function () {
        if (! this.node.parentNode) {
            this.LB.rootNode.appendChild(this.node);
        }
    },

    hide: function () {
        if (this.node.parentNode) {
            this.LB.rootNode.removeChild(this.node);
        }
    },

    loadImage: function (url, callback) {
        var img = document.createElement('img');
        img.src = url;

        LightBoxerUtils.bindEvent(img, 'load', function () {
                this.originalDimensions = {
                    height: this.height,
                    width: this.width
                };
                callback.call();
            }
        );

        this.node.innerHTML = '';
        this.node.appendChild(img);
        this.hide();
    },

    scaleAndCenter: function () {
        var img = this.node.getElementsByTagName('img');
        var dims = LightBoxerUtils.getViewportDimensions();
        var yAdj = 0;

        // Reduce the controls height from the viewport height
        if (! isNaN(this.LB.controls.node.clientHeight)) {
            yAdj = this.LB.controls.node.clientHeight + 2;
        }

        if (img && img.length > 0) {
            img = img[0];

            var ratio = 1;
            var imageDims = {
                width : img.originalDimensions.width,
                height: img.originalDimensions.height
            };

            if (imageDims.width > dims.width || imageDims.height > dims.height) {
                ratio =
                    (Math.min(dims.width, dims.height) -
                        LightBoxerSettings.imagePadding * 2 -
                        LightBoxerSettings.imageMargin  * 2 -
                        yAdj) /
                        Math.max(imageDims.height, imageDims.width);

                ratio = ratio < 1 ? ratio : 1;
            }

            img.width  = Math.floor(imageDims.width  * ratio);
            img.height = Math.floor(imageDims.height * ratio);

            var xPos = Math.floor((dims.width - img.width) / 2);
            var yPos = yAdj + Math.floor((
                (dims.height - yAdj / 2) - (
                    img.height +
                    LightBoxerSettings.imagePadding * 2 +
                    LightBoxerSettings.imageMargin * 2
                )) / 2);

            LightBoxerUtils.applyStyles(img, {
                background: '#fff',
                padding: LightBoxerSettings.imagePadding + 'px',
                position: 'absolute',
                left: xPos + 'px',
                top: yPos + 'px'
            });
        }
    },

    createNode: function () {
        try {
            return LightBoxerUtils.getElementOrFail('LightBoxerImageContainer');
        } catch (e) {

        }

        var imagecontainer = document.createElement('div');
        imagecontainer.id = 'LightBoxerImageContainer';
        LightBoxerUtils.applyStyles(imagecontainer, {
            height: '100%',
            left: '0',
            position: 'absolute',
            top: '0',
            width: '100%',
            zIndex: LightBoxerSettings.zOrder.image
        });

        return imagecontainer;
    }
};

LightBoxerThrobber.prototype = {
    node: null,
    LB: null,
    init: function (LB) {
        this.LB = LB;
        this.node = this.createNode();
    },

    show: function () {
        if (! this.node.parentNode) {
            this.LB.rootNode.appendChild(this.node);
            this.center();
        }
    },

    hide: function () {
        if (this.node.parentNode) {
            this.LB.rootNode.removeChild(this.node);
        }
    },

    center: function () {
        var img    = this.node.getElementsByTagName('img')[0];
        var vpDims = LightBoxerUtils.getViewportDimensions();
        var myDims = {width: img.width, height: img.height};

        LightBoxerUtils.applyStyles(this.node, {
            height: myDims.height + 'px',
            left: ((vpDims.width - myDims.width) / 2) + 'px',
            top: ((vpDims.height - myDims.height) / 2) + 'px',
            width: myDims.width + 'px'
        });
    },

    createNode: function () {
        try {
            return LightBoxerUtils.getElementOrFail('LightBoxerThrobber');
        } catch (e) {

        }

        var throbber = document.createElement('img');
        throbber.src = LightBoxerSettings.throbberSrc;

        var container = document.createElement('div');
        container.id = 'LightBoxerThrobber';
        container.appendChild(throbber);

        LightBoxerUtils.applyStyles(container, {
            backgroundColor: '#fff',
            border: '1px solid #888',
            position: 'absolute',
            zIndex: LightBoxerSettings.zOrder.throbber
        });

        LightBoxerUtils.bindEvent(window, 'resize', LightBoxerUtils.delegate(this, this.center));

        return container;
    }
};

var LightBoxerUtils = {
    bindEvent: function (target, event, callback) {
        var handler = this.delegate(target, callback);

        if (target.addEventListener) {
            target.addEventListener(event, handler, false);
        } else if (target.attachEvent) {
            target.attachEvent('on' + event, handler);
        }
    },

    unbindEvent: function (target, event, callback) {
        if (target.removeEventListener) {
            target.removeEventListener(event, callback, false);
        } else if (target.detachEvent) {
            target.detachEvent('on' + event, callback);
        }
    },

    delegate: function (obj, method) {
        return function () {
            method.apply(obj, arguments);
        };
    },

    getElementOrFail: function (id) {
        var el = document.getElementById(id);

        if (el) {
            return el;
        }

        throw new Error('Couldn\'t find element');
    },

    applyStyles: function (element, styles) {
        for (var i in styles) {
            var style = styles[i];

            element.style[i] = styles[i];
        }
    },

    getViewportDimensions: function () {
        var __width;
        var __height;

        if (window.innerHeight) {
            // Provided by most browsers, but importantly, not Internet Explorer
            __height = window.innerHeight;
            __width  = window.innerWidth;
        } else if (document.documentElement.clientHeight) {
            // Provided by most DOM browsers, including Internet Explorer
            __height = document.documentElement.clientHeight;
            __width  = document.documentElement.clientWidth;
        } else if (document.body.clientHeight) {
            // Provided by many browsers, including Internet Explorer.
            __height  = document.body.clientHeight;
            __width = document.body.clientWidth;
        }

        return {
            width: __width,
            height: __height
        };
    }
};

window.LightBoxer = LightBoxer;

if (LightBoxerSettings.autoRun) {
    LightBoxerUtils.bindEvent(window, 'load', function () { LightBoxer().autoSetup(); });
}
})(window);

