function round (val, precision, mode) {
    // http://kevin.vanzonneveld.net
    // +   original by: Philip Peterson
    // +    revised by: Onno Marsman
    // +      input by: Greenseed
    // +    revised by: T.Wild
    // +      input by: meo
    // +      input by: William
    // +      bugfixed by: Brett Zamir (http://brett-zamir.me)
    // %        note 1: Great work. Ideas for improvement:
    // %        note 1:  - code more compliant with developer guidelines
    // %        note 1:  - for implementing PHP constant arguments look at
    // %        note 1:  the pathinfo() function, it offers the greatest
    // %        note 1:  flexibility & compatibility possible
    // *     example 1: round(1241757, -3);
    // *     returns 1: 1242000
    // *     example 2: round(3.6);
    // *     returns 2: 4
    // *     example 3: round(2.835, 2);
    // *     returns 3: 2.84
    // *     example 4: round(1.1749999999999, 2);
    // *     returns 4: 1.17
    // *     example 5: round(1.17499999999999, 2);
    // *     returns 5: 1.18

    /* Declare Variables
     * retVal  - Temporary holder of the value to be returned
     * V       - String representation of val

     * integer - Integer portion of val
     * decimal - decimal portion of val
     * decp    - Character index of . [decimal point] inV
     * negative- Was val a negative value?
     *
     * _round_half_oe - Rounding function for ROUND_HALF_ODD and ROUND_HALF_EVEN

     * _round_half_ud - Rounding function for ROUND_HALF_UP and ROUND_HALF_DOWN
     * _round_half    - Primary function for round half rounding modes
     */
    var retVal = 0, v = '', integer = '', decimal = '', decp = 0, negative = false;
    var _round_half_oe = function (dtR, dtLa, even) { // round to odd or even
        if (even === true) {
            if (dtLa === 50) {
                if ((dtR % 2) === 1) {
                    if (dtLa >= 5) {
                        dtR++;
                    } else {
                        dtR--;
                    }
                }
            } else if (dtLa >= 5) {
                dtR++;
            }
        } else {
            if (dtLa === 5) {
                if ((dtR % 2) === 0) {
                    if (dtLa >= 5) {
                        dtR++;
                    } else {
                        dtR--;
                    }
                }
            } else if (dtLa >= 5) {
                dtR++;
            }
        }
        return dtR;
    };
    var _round_half_ud = function (dtR, dtLa, up) { // round up or down
        if (up === true) {
            if (dtLa >= 5) {
                dtR++;
            }
        } else {
            if (dtLa > 5) {
                dtR++;
            }
        }
        return dtR;
    };
    var _round_half = function (val, decplaces, mode) {
    /*Declare variables
         *V       - string representation of Val
         *Vlen    - The length of V - used only when rounding integers

         *VlenDif - The difference between the lengths of the original V
         *              and the V after being truncated
         *decp    - Character in index of . [decimal place] in V
         *integer - Integer protion of Val
         *decimal - Decimal portion of Val
         *DigitToRound - The digit to round

         *DigitToLookAt- The digit to compare when rounding
         *
         *round - A function to do the rounding
         */
        var v = val.toString(), vlen = 0, vlenDif = 0;
        var decp = v.indexOf('.');
        var digitToRound = 0, digitToLookAt = 0;
        var integer = '', decimal = '';
        var round = null, bool = false;
        switch (mode) {
            case 'up':
                bool = true;
                // Fall-through
            case 'down':
                round = _round_half_ud;
                break;
            case 'even':
                bool = true;
                // Fall-through
            case 'odd':
                round = _round_half_oe;
                break;
        }
        if (decplaces < 0) { // Int round
            vlen = v.length;

            decplaces = vlen + decplaces;
            digitToLookAt = Number(v.charAt(decplaces));
            digitToRound  = Number(v.charAt(decplaces - 1));
            digitToRound  = round(digitToRound, digitToLookAt, bool);
            v = v.slice(0, decplaces - 1);
            vlenDif = vlen - v.length - 1;

            if (digitToRound === 10) {
                v = String(Number(v) + 1) + '0';
            } else {
                v += digitToRound;
            }

            v = Number(v) * (Math.pow(10, vlenDif));
        } else if (decplaces > 0) {
            integer = v.slice(0, decp);
            decimal = v.slice(decp + 1);
            digitToLookAt = Number(decimal.charAt(decplaces));

            digitToRound  = Number(decimal.charAt(decplaces - 1));
            digitToRound  = round(digitToRound, digitToLookAt, bool);
            decimal = decimal.slice(0, decplaces - 1);
            if (digitToRound === 10) {
                v = Number(integer + '.' + decimal) + (1 * (Math.pow(10, (0 - decimal.length))));
            } else {
                v = Number(integer + '.' + decimal + digitToRound);
            }
        } else { // 0 decimal places
            integer = v.slice(0, decp);
            decimal = v.slice(decp + 1);
            digitToLookAt = Number(decimal.charAt(decplaces));
            digitToRound = Number(integer.charAt(integer.length - 1));
            digitToRound = round(digitToRound, digitToLookAt, bool);
            decimal = '0';
            integer = integer.slice(0, integer.length - 1);
            if (digitToRound === 10) {
                v = Number((Number(integer) + 1) + decimal); // Need to add extra 0 since passing 10
            } else {
                v = Number(integer + digitToRound);
            }
        }
        return v;
    };

    // precision optional - defaults 0
    if (typeof precision === 'undefined') {
        precision = 0;
    }
    // mode optional - defaults round half up
    if (typeof mode === 'undefined') {
        mode = 'PHP_ROUND_HALF_UP';
    }

    negative = val < 0; // Remember if val is negative

    v = Math.abs(val).toString(); // Take a string representation of val
    decp = v.indexOf('.');        // And locate the decimal point
    if (decp === -1 && precision >= 0) {
        /* If there is no decimal point and the precision is greater than 0
         * there is no need to round, return val
         */
        return val;
    } else {
        if (decp === -1) {
            // There are no decimals so integer=V and decimal=0
            integer = v;
            decimal = '0';
        } else {
            // Otherwise we have to split the decimals from the integer
            integer = v.slice(0, decp);
            if (precision >= 0) {
                // If the precision is greater than 0 then split the decimals from the integer
                // We truncate the decimals to a number of places equal to the precision requested+1
                decimal = v.substr(decp + 1, precision + 1);
            } else {
                // If the precision is less than 0 ignore the decimals - set to 0
                decimal = '0';
            }
        }
        if (precision > 0 && precision >= decimal.length) {
            /*
            * If the precision requested is more decimal places than already exist
            * there is no need to round - return val
            */
            return val;
        } else if (precision < 0 && Math.abs(precision) >= integer.length){
           /*
            * If the precison is less than 0, and is greater than than the
            * number of digits in integer, return 0 - mimics PHP
            */
            return 0;
        }
        if (decimal === '0') {
            return Number(integer);
        }
        val = Number(integer + '.' + decimal); // After sanitizing, recreate val
    }

    // Call approriate function based on passed mode, fall through for integer constants
    switch (mode) {
        case 0: case 'PHP_ROUND_HALF_UP':
            retVal = _round_half(val, precision, 'up');
            break;
        case 1:  case 'PHP_ROUND_HALF_DOWN':
            retVal = _round_half(val, precision, 'down');
            break;
        case 2: case 'PHP_ROUND_HALF_EVEN':
            retVal = _round_half(val, precision, 'even');
            break;
        case 3: case 'PHP_ROUND_HALF_ODD':
            retVal = _round_half(val, precision, 'odd');
            break;
    }
    return negative ? 0 - retVal : retVal;
}

var Displayer = {
    data: null,
    mode: null,
    html: [],
    isWorking: false,

    windowWidth: 0,
    windowHeight: 0,
    inRow: 0,
    rows: 0,
    size: 0,

    frame: null,
    container: null,
    about: null,
    borderhover: null,
    hoverdesc: null,
    hoverdescTitle: null,
    hoverdescDate: null,
    hoverdescCategory: null,
    overlay: null,
    workbox: null,
    workboxControls: null,
    workboxHeader: null,
    workboxDesc: null,
    workboxZoom: null,
    workboxPrev: null,
    workboxNext: null,

    init: function(data, mode) {
        Displayer.frame = $('#container');
        Displayer.container = $('#projects');
        Displayer.about = $('#about');
        Displayer.hoverdesc = $('#hoverdesc');
        Displayer.hoverdescTitle = $('#hoverdesc-title');
        Displayer.hoverdescDate = $('#hoverdesc-date');
        Displayer.hoverdescCategory = $('#hoverdesc-category');
        Displayer.overlay = $('#overlay');
        Displayer.workbox = $('#workbox');
        Displayer.workboxControls = $('#workbox-controls');
        Displayer.workboxHeader = $('#workbox-header');
        Displayer.workboxDesc = $('#workbox-desc');
        Displayer.workboxZoom = $('#workbox-zoom');
        Displayer.workboxPrev = $('#workbox-prev');
        Displayer.workboxNext = $('#workbox-next');

        Displayer.data = data;
        Displayer.mode = mode;

        Displayer.createHtml();
        Displayer.calculateDimensions();

        Displayer.isWorking = true;
        Displayer.addToPage();

        $(window).resize(function() {
            Displayer.calculateDimensions(true, false);
        });

        //przypisywanie eventów dolnego paska
        $('#bar-link-about').click(function(e) {
            e.preventDefault();

            Displayer.frame.scrollTo('#about', 500, function() {
                window.location.hash = '#about';
            });
        }).mouseenter(function() {
            Displayer.showButtonTitle('#title-about');
        });

        $('#header-about-link').click(function(e) {
            e.preventDefault();

            Displayer.frame.scrollTo('#about', 500, function() {
                window.location.hash = '#about';
            });
        });

        $('#bar-link-projects').click(function(e) {
            e.preventDefault();
            Displayer.frame.scrollTo('#projects', 500, function() {
                window.location.hash = '';
            });
        }).mouseenter(function() {
            Displayer.showButtonTitle('#title-home');
        });

        $('#bar-link-datemode').click(function(e) {
            e.preventDefault();
            Displayer.changeMode('date');
            Displayer.showOneHideOther('#type-date', '#type-category');
        }).mouseenter(function() {
            Displayer.showButtonTitle('#title-sortdate');
        });

        $('#bar-link-catmode').click(function() {
            Displayer.changeMode('category');
            Displayer.showOneHideOther('#type-category', '#type-date');
        }).mouseenter(function() {
            Displayer.showButtonTitle('#title-sortcategory');
        });

        $('#type-date a, #type-category a').click(function(e) {
            e.preventDefault();
            var $this = $(this);
            var label = $this.attr('href').substr(1);

            if (!isNaN(label)) {
                if ($this.hasClass('start')) {
                    Displayer.frame.scrollTo('#projects', 500);
                } else {
                    Displayer.frame.scrollTo('#date-' + (label == 2009 ? 2009 : (label - 1)), 500);
                }
            } else {
                Displayer.frame.scrollTo('#category-' + label, 500);
            }
        }).mouseenter(function() {
            Displayer.showButtonTitle('#title-' + $(this).attr('href').substr(1));
        });

        //eventy workboxa
        var workboxArrows = Displayer.workboxPrev.add(Displayer.workboxNext).css('opacity', 0);
        Displayer.workbox.mouseenter(function() {
            workboxArrows.stop().animate({opacity: 1}, 'fast');
        }).mouseleave(function() {
            workboxArrows.stop().animate({opacity: 0}, 'fast');
        });

        $('#workbox-close').click(function(e) {
            e.preventDefault();
            Displayer.closeWorkWindow();
        });

        //ładowanie obrazków, któe mogą się przydać
        $.preload('img/workbox-loader.gif');
    },

    changeMode: function(mode) {
        if (Displayer.isWorking || Displayer.mode == mode) {
            return false;
        }

        Displayer.mode = mode;
        Displayer.html = [];

        return $.get('getter.php', {mode: Displayer.mode}, function(data, textStatus, XMLHttpRequest) {
            Displayer.data = data;
            Displayer.createHtml();
            Displayer.calculateDimensions(false, true);

            Displayer.isWorking = true;
            Displayer.addToPage(true);
            return true;
        }, 'json');
    },

    addEvents: function() {
        Displayer.container.find('a').not('.categorybox').each(function() {
            var $this = $(this);
            var img = $this.find('img').eq(0);
            var url = img.attr('src');

            $this.unbind().mouseenter(function() {
                Displayer.setHoverDesc($this);
                $this.css('opacity', 1);
                img.attr('src', img.data('hoverimage'));
            }).mouseleave(function() {
                Displayer.clearHoverDesc();
                $this.css('opacity', 0.3);
                img.attr('src', url);
            }).click(function() {
                Displayer.openWorkWindow($this.attr('href'));
            });
        });
    },

    createHtml: function() {
        var lastDate = 0, lastCategory = '';

        for (var i = 0; i < Displayer.data.length; i++) {
            var cur = Displayer.data[i], a = $('<a />'), img = $('<img />');

            if (Displayer.mode == 'date') {
                var time = new Date(cur.time * 1000);

                if (lastDate != 0 && lastDate != time.getFullYear()) {
                    Displayer.html[this.html.length] = $('<a onclick="return false;" class="categorybox" id="date-' + (time.getFullYear() + 1) + '"><img src="dates/' + (time.getFullYear() + 1) + '.jpg" alt="" /></a>');
                }

                lastDate = time.getFullYear();
            }

            if (Displayer.mode == 'category') {
                if (lastCategory != cur.categoryLabel) {
                    Displayer.html[this.html.length] = $('<a onclick="return false;" class="categorybox" id="category-' + cur.categoryLabel + '"><img src="categories/' + cur.categoryLabel + '.jpg" alt="" /></a>');
                }

                lastCategory = cur.categoryLabel;
            }

            a.attr({
                href: '#' + cur.label,
                title: cur.name,
                rel: i
            });

            img.attr({
                src: cur.path + '/mini.jpg',
                alt: cur.name
            });

            $('<img src="' + cur.path + '/mini-hover.jpg' + '" alt="" />').preload();

            img.data('hoverimage', cur.path + '/mini-hover.jpg');

            a.append(img);

            Displayer.html[this.html.length] = a;
        }

        if (Displayer.mode == 'date') {
            Displayer.html[this.html.length] = $('<a class="categorybox" onclick="return false;" id="date-' + time.getFullYear() + '"><img src="dates/' + time.getFullYear() + '.jpg" alt="" /></a>');
        }
    },

    calculateDimensions: function(isResized, isChanged) {
        Displayer.windowWidth = Displayer.container.width();
        Displayer.inRow = Math.ceil(Displayer.windowWidth / 200);
        Displayer.rows = Math.ceil(Displayer.html.length / Displayer.inRow);
        Displayer.size = round(Displayer.windowWidth / Displayer.inRow, 2);

        $('#header').css('top', Displayer.size);
        Displayer.container.height(Displayer.rows * Displayer.size + 200);

        for (var i = 0; i < Displayer.html.length; i++) {
            if ((i+1) % Displayer.inRow == 0) {
                Displayer.html[i].width(Displayer.size - 0.1).height(Displayer.size).find('img').width(Displayer.size - 0.1).height(Displayer.size);
            } else {
                Displayer.html[i].width(Displayer.size).height(Displayer.size).find('img').width(Displayer.size).height(Displayer.size);
            }

            Displayer.html[i].css('margin-bottom', 0);

            if (i < Displayer.inRow) {
                Displayer.html[i].css('margin-bottom', 220);
            }
        }
    },

    setHoverDesc: function(element) {
        var pos = element.position(), st = Displayer.frame.scrollTop(), id = element.attr('rel'), isLast = false;

        if ((Displayer.windowWidth - Displayer.size * 2 + Displayer.size / 2) < pos.left) {
            isLast = true;
        }

        Displayer.hoverdescTitle.text(Displayer.data[id].name);
        Displayer.hoverdescDate.text(Displayer.data[id].human_time);
        Displayer.hoverdescCategory.text(Displayer.data[id].category);

        Displayer.hoverdesc.css({
            width: Displayer.size,
            height: Displayer.size,
            top: pos.top + st,
            left: isLast ? pos.left - Displayer.size : pos.left + Displayer.size
        }).show();
    },

    openWorkWindow: function(label) {
        var el = $('a[href=' + label + ']').eq(0);
        var data = Displayer.data[el.attr('rel')];

        Displayer.workboxHeader.text(data.name);
        Displayer.workboxDesc.html(data.desc);
        Displayer.workboxPrev.click(function(e) {
            e.preventDefault();

            var prevEl = el.prev();

            if (prevEl.hasClass('categorybox')) {
                prevEl = prevEl.prev();
            }

            if (prevEl.length == 0) {
                prevEl = Displayer.container.find('a:last-child');
                if (prevEl.hasClass('categorybox')) {
                    prevEl = prevEl.prev();
                }
            }

            var label = prevEl.attr('href');

            Displayer.reloadWorkWindow(label);
            window.location.hash = label;

            return true;
        });

        Displayer.workboxNext.click(function(e) {
            e.preventDefault();

            var nextEl = el.next();

            if (nextEl.hasClass('categorybox')) {
                nextEl = nextEl.next();
            }

            if (nextEl.length == 0) {
                nextEl = Displayer.container.find('a:first-child');
                if (nextEl.hasClass('categorybox')) {
                    nextEl = nextEl.next();
                }
            }

            var label = nextEl.attr('href');

            Displayer.reloadWorkWindow(label);
            window.location.hash = label;

            return true;
        });

        $('<img src="' + data.path + '/01.jpg' + '" alt="" />').load(function() {
            Displayer.workbox.css('backgroundImage', 'url(\'' + data.path + '/01.jpg' + '\')');
        });

        Displayer.workboxZoom.attr('href', data.label + '/');

        Displayer.overlay.fadeIn('fast', function() {
            Displayer.workbox.fadeIn('fast');
        });
    },

    closeWorkWindow: function() {
        Displayer.overlay.fadeOut('fast');
        Displayer.workbox.fadeOut('fast', function() {
            Displayer.workboxHeader.empty();
            Displayer.workboxDesc.empty();
            Displayer.workboxPrev.unbind();
            Displayer.workboxNext.unbind();
            Displayer.workbox.css('backgroundImage', 'url(\'img/workbox-loader.gif\')');

            window.location.hash = '';
        });
    },

    reloadWorkWindow: function(label) {
        Displayer.workbox.fadeOut('fast', function() {
            Displayer.workboxHeader.empty();
            Displayer.workboxDesc.empty();
            Displayer.workboxPrev.unbind();
            Displayer.workboxNext.unbind();
            Displayer.workbox.css('backgroundImage', 'url(\'img/workbox-loader.gif\')');

            Displayer.openWorkWindow(label);
        });
    },

    clearHoverDesc: function() {
        Displayer.hoverdesc.hide().css({
            width: 0,
            height: 0,
            top: 0,
            left: 0
        });

        Displayer.hoverdescTitle.empty();
        Displayer.hoverdescDate.empty();
        Displayer.hoverdescCategory.empty();
    },

    getMiniatureUrls: function() {
        var urls = [];

        for (var i = 0; i < Displayer.data.length; i++) {
            urls[urls.length] = Displayer.data[i].path + '/mini.jpg';
        }

        return urls;
    },

    addToPage: function(removePrevious) {
        $.preload(Displayer.getMiniatureUrls(), {
            onFinish: function() {
                if (removePrevious) {
                    Displayer.removeFromPage(function() {
                        for (var i = 0; i < Displayer.html.length; i++) {
                            Displayer.html[i].css('opacity', 0.3);
                            Displayer.container.append(Displayer.html[i]);
                            Displayer.html[i].fadeIn(2000);
                        }

                        window.setTimeout(function() {
                            Displayer.isWorking = false;
                            Displayer.addEvents();
                        }, 2000);
                    });
                } else {
                    for (var i = 0; i < Displayer.html.length; i++) {
                        Displayer.html[i].css('opacity', 0.3);
                        Displayer.container.append(Displayer.html[i]);
                        Displayer.html[i].fadeIn(2000);
                    }

                    window.setTimeout(function() {
                        Displayer.isWorking = false;
                        Displayer.addEvents();
                    }, 2000);

                    //jeśli otwarty hash odpowiada projektowi, otwieramy go
                    if (window.location.hash == '#about') {
                        $('#container').scrollTo('#about', 500);
                        return;
                    }

                    var initialProject = Displayer.container.find('a[href=' + window.location.hash + ']').eq(0);
                    if (initialProject.length != 0) {
                        Displayer.openWorkWindow(window.location.hash);
                    }
                }
            }
        });
    },

    removeFromPage: function(callback) {
        Displayer.container.empty();
        if ($.isFunction(callback)) {
            callback();
        }
    },

    showButtonTitle: function(id) {
        $(id).show().find('*').show();
    },

    showOneHideOther: function(show, hide) {
        $(hide).hide();
        $(show).show();
    }
};


$(function() {
    //hovery dla obrazków
    $('.hoverimage img').hover(function(){
        var el = $(this);
        el.attr('src', el.attr('src').replace(/\.[a-z0-9]{3,}$/i, '-hover$&'));
    }, function(){
        var el = $(this);
        el.attr('src', el.attr('src').replace('-hover', ''));
    });

    //wczytywanie obrazków dla hovera
    $('.hoverimage img').preload({
        find: /\.[a-z0-9]{3,}$/i,
        replace: '-hover$&'
    });

    $('#bar a').mouseleave(function() {
        $('#bar .center div').hide();
    });
});
