$(function () {
    var last;
    var sync;
    var listeners = [];

    // poll checks for changes to the url hash
    function poll(force) {
        var hash = location.hash;
        if (!force && hash == last) return;

        sync = {};
        last = hash;

        $.each(listeners, function (i, f) {
            f.call(this, hash);
        });
    };

    // init hash polling
    setInterval(function () { poll(false); }, 25);

    // $.mvc transitions between mvc states
    jQuery.mvc = function (controller, action) {
        var args = $.makeArray(arguments).slice(2);
        var hash = '#/' + controller + '/' + action;

        if (args.length) {
            args = $.map(args, function (a) { return escape(a); });

            hash += ':' + args.join(',');
        }

        window.location = hash;

        // poll immediately, forcing a refresh
        poll(true);
    };

    // $(selector).mvc sets up an mvc container
    jQuery.fn.mvc = function (config) {
        config = $.extend({}, config);

        var $this = $(this);

        var current = null;
        var elements = {};
        var instances = {};

        var listener = function (hash) {
            var regex = new RegExp('^#/([^/]+)(/|$)([^/:]+)?(:|$)(.*)$');
            var match = regex.exec(hash);

            if (!match) return;

            var controller = match[1];
            var action = match[3];
            var args = match[5];

            action = action || 'index';

            if (instances[controller] || $.isFunction(config[controller])) {
                // lazy-load controllers
                if (!instances[controller]) {
                    // create an element
                    var element = $('<div/>').addClass('mvc').css('position', 'relative').appendTo($this);

                    // locate the constructor
                    var constructor = config[controller];

                    // instantiate the controller
                    var instance = new constructor(element);

                    // store the controller
                    instances[controller] = instance;

                    // store the element
                    elements[controller] = element;
                }

                // get the controller instance
                var instance = instances[controller];

                // try to find a controller action
                var fn = instance[action];

                if ($.isFunction(fn)) {
                    var x = sync;
                    var element = elements[controller];

                    // show the element for this controller and hide others
                    element
						.css('left', '')
						.css('position', 'relative')
						.css('visibility', 'visible')
						.siblings('div.mvc')
							.css('left', -9999)
							.css('position', 'absolute')
							.css('visibility', 'hidden');

                    // create a view function
                    var view = function (html) {
                        if (sync == x) {
                            element.html(html);
                            return true;
                        }

                        return false;
                    };

                    // split args into an array
                    args = (args || '').split(',');

                    // unescape args
                    args = $.map(args, function (a) { return unescape(a); });

                    // pass view first
                    args.unshift(view);

                    // call action
                    fn.apply(instance, args);
                }
            }
        };

        listeners.push(listener);

        if (last)
            listener(location.hash);        
    };
});
