this.Aino = window.Aino || (function($, window, undefined) {

    var
        document = window.document,

        $win = $(window),
        $doc = $(document),

        // The script’s path (it’s always the last found)
        getPath = function() {
            var src = $('script:last').attr('src'),
                slices = src.split('/');
            if (slices.length == 1) {
                return '';
            }
            slices.pop();
            return slices.join('/') + '/';
        },

        // cache this script’s path
        PATH = getPath(),

        // IE Detection
        IE = (function( div ) {
            var undef, v = 3;
            while (
                div.innerHTML = '<!--[if gt IE '+(++v)+']><i></i><![endif]-->',
                div.getElementsByTagName('i')[0]
            );
            return v > 4 ? v : undef;
        }( document.createElement('div') ));

    // add a js class
    document.documentElement.className+=' js';

    return {

        // the debug option
        DEBUG: DEBUG,

        // bring the IE object
        IE: IE,

        support: {
            touch: ('ontouchstart' in document)
        },

        // the init
        init: function() {
            Aino.removeDottedBorders();

            Aino.support.letterspacing = (function(d) {
                var test = document.createElement('div'),
                    ret = true;
                test.style.letterSpacing = '.5px';
                d.body.appendChild(test);
                if (d.defaultView && d.defaultView.getComputedStyle) {
                    if (d.defaultView.getComputedStyle( test, null ).letterSpacing == 'normal') {
                        ret = false;
                    }
                }
                d.body.removeChild(test);
                test = null;
                return ret;
            }(document));

            if ( Aino.DEBUG ) {
                Aino.grid();
                Aino.loadCSS( PATH + 'aino.error.css');
                Aino.prototyping();
                $doc.ajaxError(function(e, xhr, settings) {
                    $('body').html( xhr.responseText );
                    Aino.raise( 'Ajax error from URL: ' + settings.url, true );
                });
            }

            // LOAD views (because now DOM IS ready...)
            this.dispatch();

            return this;
        },

        isExternal : function( href ) {

            if (!href || href.substr(0,4) !== 'http' || href.slice(0,1) == '#' || href.slice(0,1) == '/') {
                return false;
            }

            var reg = new RegExp("^http://" + location.host);

            if ( reg.test( href ) ) {
                return false;
            }

            return true;
        },

        // toggle design grid
        grid : function() {
            var grid = $("#grid").hide();
            var storage = !!('localStorage' in window);
            if (Aino.support.touch || !storage) {
                return;
            }
            if (storage) {
                grid[0].style.display = localStorage.getItem("grid") || 'none';
            }
            if(grid.length) {
                $(document).keydown(function(e) {
                    if(e.keyCode == 71 && e.ctrlKey) {
                        grid.toggle();
                        if(storage) {
                            localStorage["grid"] = grid[0].style.display || 'block';
                        }
                    }
                });
            }
        },

        // remove dotted borders on links without disturbing accessibility
        removeDottedBorders: function() {
            $('a').live('mousedown mouseup', function(e) {
                if ('hideFocus' in this) { // IE
                    this.hideFocus = e.type == 'mousedown';
                }
                this.blur();
            });
            return this;
        },

        // slice array
        array: function( obj ) {
            return Array.prototype.slice.call( obj );
        },

        // timestamp abstraction
        timestamp: function() {
            return new Date().getTime();
        },

        // error handling
        raise: function( msg, fatal ) {
            var type = fatal ? 'Fatal error' : 'Error',
                error = $('#aino-error').length ? $('#aino-error') : $('<div>').attr('id','aino-error').appendTo( document.body );
            // if debug is on, display errors and throw exception if fatal
            if ( Aino.DEBUG ) {
                error.append( $('<div>').addClass( fatal ? 'fatal' : '').html( type + ': ' + msg ) );
                if ( fatal ) {
                    throw new Error(type + ': ' + msg);
                }
            }
            return this;
        },

        // logging method
        log: function() {
            if ( Aino.DEBUG ) {
                try {
                    window.console.log.apply(this, Aino.array( arguments ) )
                } catch(e) {}
            }
            return this;
        },

        views : {},

        dispatch: function() {
            var views = this.views;
            $( ['_global'].concat( document.body.className.split(' ') ) ).each( function(i, name) {
                if ( typeof views[ name ] == 'function' ) {
                    views[ name ].call( window );
                }
            });
        },

        // utility for executing a method when another method returns true
        when : function( until, success, err, timeout) {
            timeout = timeout || 10000;
            if (typeof err == 'number') {
                timeout = err;
            }
            success = success || function(){};
            var start = Aino.timestamp(),
                elapsed,
                now,
                fn = function() {
                    now = Aino.timestamp();
                    elapsed = now - start;
                    if ( until( elapsed ) ) {
                        success.call( Aino, elapsed );
                        return false;
                    }
                    if (now >= start + timeout) {
                        if (typeof err == 'function') {
                            err.call( Aino, elapsed );
                        }
                        return false;
                    }
                    window.setTimeout(fn, 2);
                };
            window.setTimeout(fn, 2);
            return this;
        },

        // for HTML prototyping
        prototyping: function() {
            // placeholder image
            $('img[src="#"]').each(function() {
                $(this).css({
                    border: '1px solid #ddd',
                    display: 'inline-block',
                    background: 'rgba(0,0,0,.05) url(' + PATH + 'placeholder.png' + ') no-repeat 50% 50%'
                }).width( this.width-2 ).height( this.height-2 ).attr('src', PATH + 'pixel.gif');
            });
            // prevent page jump on #
            $('a[href="#"]').live('click', function(e) {
                e.preventDefault();
            });
            return this;
        },

        loadCSS : function( href, id, callback ) {
            var link,
                ready = false,
                length;

            // look for manual css
            $('link[rel=stylesheet]').each(function() {
                if ( new RegExp( href ).test( this.href ) ) {
                    link = this;
                    return false;
                }
            });
            if ( typeof id === 'function' ) {
                callback = id;
                id = undefined;
            }
            callback = callback || function() {}; // dirty

            // if already present, return
            if ( link ) {
                callback.call( link, link );
                return link;
            }

            // save the length of stylesheets to check against
            length = document.styleSheets.length;
            // add timestamp if DEBUG is true
            if ( Aino.DEBUG ) {
                href += '?' + Aino.timestamp();
            }

            // check for existing id
            if( $('#'+id).length ) {
                $('#'+id).attr('href', href);
                length--;
                ready = true;
            } else {
                link = $( '<link>' ).attr({
                    rel: 'stylesheet',
                    href: href,
                    id: id
                }).get(0);
                window.setTimeout(function() {
                    var styles = $('link[rel="stylesheet"], style');
                    if ( styles.length ) {
                        styles.get(0).parentNode.insertBefore( link, styles[0] );
                    } else {
                        $('head').append( link );
                    }
                    if ( IE ) {

                        // IE has a limit of 31 stylesheets in one document
                        if( length >= 31 ) {
                            Aino.raise( 'You have reached the browser stylesheet limit (31)', true );
                            return;
                        }
                        link.onreadystatechange = function(e) {
                            if ( !ready && (!this.readyState ||
                                this.readyState === 'loaded' || this.readyState === 'complete') ) {
                                ready = true;
                            }
                        };
                    } else {
                        // final test via ajax if not local
                        if ( !( new RegExp('file://','i').test( href ) ) ) {
                            $.ajax({
                                url: href,
                                success: function() {
                                    ready = true;
                                },
                                error: function(e) {
                                    // pass if origin is rejected
                                    if( e.isRejected() ) {
                                        ready = true;
                                    }
                                }
                            });
                        } else {
                            ready = true;
                        }
                    }
                }, 10);
            }
            if ( typeof callback === 'function' ) {
                Aino.when(function() {
                    return ready && document.styleSheets.length > length;
                }, function() {
                    window.setTimeout(function() {
                        callback.call( link, link );
                    }, 100);
                }, function() {
                    Aino.raise( 'CSS at ' + href + ' could not load' );
                }, 5000 );
            }
            return this;
        },

        getScriptPath: getPath,

        create: function( selector, type ) {
            var elem = $(document.createElement( type || 'div')),
                reg = /\#/;
            if (selector) {
                if( reg.test(selector) ) {
                    return elem.attr('id', selector.replace(reg,'') );
                }
                return elem.addClass( selector );
            }
            return elem;
        },

        preload: function(images, callback, progress) {
            var loaded = 0,
                len = images.length;

            if (!len) {
                return;
            }

            $.each(images, function(i,src) {
                var img = $(new Image);
                img.load(function() {
                    loaded++;
                    if ( typeof progress == 'function' ) {
                        progress(loaded,len);
                    }
                    if (loaded == len && typeof callback == 'function') {
                        callback.call(window);
                    }
                }).attr('src',src);
            });
        },

        elapsed: function( timestamp, locale) {

            var l = locale || {
                now : "Now",
                second : "second ago",
                seconds : "seconds ago",
                minute : "minute ago",
                minutes : "minutes ago",
                hour : "hour ago",
                hours : "hours ago",
                yesterday : "Yesterday",
                days : "days ago",
                week : "week ago",
                weeks : "weeks ago",
                month : "month ago",
                months : "months ago",
                year : "year ago",
                years : "years ago",
                forever : 'more than one year ago'
            };

            var diff = Aino.timestamp() - timestamp;

            var s = parseInt(Math.abs(diff / 1000), 10);
            var d = Math.floor(s / (3600 * 24));

            if(d === 0) {
                if(s === 0) {
                    return l["now"];
                }
                if(s === 1) {
                    return '1 ' + l["second"];
                }
                if(s < 60) {
                    return s + ' ' + l["seconds"];
                }
                if(s < 120) {
                    return '1 ' + l["minute"];
                }
                if(s < 3600) {
                    return Math.round(s / 60) + ' ' + l["minutes"];
                }
                if(s < 7200) {
                    return '1 ' + l["hour"];
                }
                if(s < 86400) {
                    return Math.round(s / 3600) + ' ' + l["hours"];
                }
            }
            if(d == 1) {
                return l["yesterday"];
            }
            if(d < 7) {
                return d + ' ' + l["days"];
            }
            if (d < 14) {
                return '1 ' + l['week'];
            }
            if(d < 30) {
                return Math.floor( d/7 ) + ' ' + l['weeks'];
            }
            if (d < 60) {
                return '1 ' + l['month'];
            }
            if (d < 365) {
                return Math.floor( d/30 ) + l['months'];
            }
            if (d < 730) {
                return '1 ' + l['year'];
            }
            else {
                return l['forever'];
            }
        },

        // parse anything into a number
        parseValue: function( val ) {
            if (typeof val === 'number') {
                return val;
            } else if (typeof val === 'string') {
                var arr = val.match(/\-?\d|\./g);
                return arr && arr.constructor === Array ? arr.join('')*1 : 0;
            } else {
                return 0;
            }
        },

        hashParams: function(sub) {

            sub = sub || 2;

            var params = {};
            var e,
                a = /\+/g,  // Regex for replacing addition symbol with a space
                r = /([^&;=]+)=?([^&;]*)/g,
                d = function (s) { return decodeURIComponent(s.replace(a, " ")); },
                q = window.location.hash.substring(sub);

            while (e = r.exec(q)) {
                params[d(e[1])] = d(e[2]);
            }
            return params;
        },

        // CSS3 animations
        animate : (function() {

            // detect transition
            var transition = (function( style ) {
                var props = 'transition WebkitTransition MozTransition OTransition'.split(' '),
                    i;

                for ( i = 0; props[i]; i++ ) {
                    if ( typeof style[ props[ i ] ] !== 'undefined' ) {
                        return props[ i ];
                    }
                }
                return false;
            }(( document.body || document.documentElement).style ));

            // map transitionend event
            var endEvent = {
                MozTransition: 'transitionend',
                OTransition: 'oTransitionEnd',
                WebkitTransition: 'webkitTransitionEnd',
                transition: 'transitionend'
            }[ transition ];

            // map bezier easing conversions
            var easings = {
                _default: [0.25, 0, 0.25, 1],
                ease: [0.25, 0, 0.25, 1],
                linear: [0.25, 0.25, 0.75, 0.75],
				'ease-in': [0.42, 0, 1, 1],
				'ease-out': [0, 0, 0.58, 1],
				'ease-in-out': [0.42, 0, 0.58, 1]
            };

            // function for setting transition css for all browsers
            var setStyle = function( elem, value ) {
                var css = {};
                $.each( 'webkit moz ms o'.split(' '), function() {
                    css[ '-' + this + '-transition' ] = value;
                });
                elem.css( css );
            };

            // the actual animation method
            return function( elem, to, options ) {

                // extend defaults
                options = $.extend({
                    duration: 400,
                    complete: function(){},
                    stop: false
                }, options);

                // cache jQuery instance
                elem = $( elem );

                if ( !options.duration ) {
                    elem.css( to );
                    options.complete.call( elem[0] );
                    return;
                }

                // fallback to jQuery’s animate if transition is not supported
                if ( !transition ) {
                    elem.animate(to, options);
                    return;
                }

                // stop
                if ( options.stop ) {
                    // clear the animation
                    elem.unbind( endEvent );
                    setStyle(elem, 'none');
                }

                // see if there is a change
                var match = true
                $.each( to, function( key, val ) {
                    if ( Aino.parseValue( elem.css(key) ) != Aino.parseValue( val ) ) {
                        match = false;
                    }
                });
                if (match) {
                    window.setTimeout( function() {
                        options.complete.call( elem[0] );
                    }, options.duration);
                    return;
                }

                // the css strings to be applied
                var strings = [];

                // the easing bezier
                var easing = options.easing in easings ? easings[ options.easing ] : easings._default;

                // add a tiny timeout so that the browsers catches any css changes before animating
                window.setTimeout(function() {

                    // attach the end event
                    elem.one(endEvent, (function( elem ) {
                        return function() {

                            // clear the animation
                            setStyle(elem, 'none');

                            // run the complete method
                            options.complete.call(elem[0]);
                        };
                    }( elem )));

                    // push the animation props
                    $.each(to, function( p, val ) {
                        strings.push(p + ' ' + options.duration + 'ms' + ' cubic-bezier('  + easing.join(',') + ')');
                    });

                    // set the animation styles
                    setStyle( elem, strings.join(',') );

                    // animate
                    elem.css( to );

                },1 );
            };
        }())
    };
}(jQuery, this));

