;(function ($, window) {

"use strict";

// Helpers
var $window = $(window);

// Clone object
var clone = function (origin) {
    var cloned = { };
    for ( var name in origin ) {
        cloned[name] = origin[name];
    }
    return cloned;
};

// Is string ends with substring.
var endsWith = function (string, substring) {
    return string.substr(-substring.length) === substring;
};

/*
 * Add `@data-role` alias to jQuery.
 *
 * Copy from jquery.role by Sasha Koss https://github.com/kossnocorp/role
 */

var rewriteSelector = function (context, name, pos) {
    var original = context[name];
    if ( !original ) return;

    context[name] = function () {
        arguments[pos] = arguments[pos].replace(
            /@@([\w\u00c0-\uFFFF\-]+)/g, '[data-block~="$1"]');
        arguments[pos] = arguments[pos].replace(
            /@([\w\u00c0-\uFFFF\-]+)/g,  '[data-role~="$1"]');
        return original.apply(context, arguments);
    };

    $.extend(context[name], original);
};

rewriteSelector($, 'find', 0);
rewriteSelector($, 'multiFilter', 0);
rewriteSelector($.find, 'matchesSelector', 1);
rewriteSelector($.find, 'matches', 0);

/*
 * Evil namespace. Also can be used in Evil Front.
 */
if ( !window.evil ) {
    window.evil = { };
}
var evil = window.evil;

// Find selector inside base DOM node and cretae class for it.
var find = function (base, id, selector, klass) {
    var blocks = $().add( base.filter(selector) ).
                     add( base.find(selector) );

    if ( blocks.length == 0 ) return;

    var objects = [];

    blocks.each(function (_, node) {
        var block = $(node);

        var obj = clone(klass);
        obj.block = block;

        for ( var i = 0; i < evil.block.filters.length; i++ ) {
            var stop = evil.block.filters[i](obj, id);
            if ( stop === false ) return;
        }

        objects.push(obj)
    });

    return function () {
        for ( var i = 0; i < objects.length; i++ ) {
            if (objects[i].init) objects[i].init();
        }
    };
};

// If onready event was already happend.
var ready = false;

// If onload event was already happend.
var loaded = false;
$window.load(function (event) {
    loaded = event;
});

// Latest block ID
var lastBlock = 0;

/**
 * Create object for every `selector` finded in page and call their
 * `init` method.
 *
 *   evil.block '.user-page .buttons',
 *     init: ->
 *       @gallery.fotorama()
 *     delete: ->
 *       @deleteForm.submit ->
 *         $('user-status').trigger('deleted')
 *     'click on @deleleLink': (e) ->
 *       e.el.addClass('is-loading')
 *       delete()
 *     'on update': ->
 *       location.reload()
 *
 * Every `data-role="aName"` in HTML will create in object `aName` property
 * with jQuery node.
 *
 * To bind delegate listener just create `EVENT on SELECTOR` method.
 * In first argument it will receive jQuery node of `e.currentTarget`,
 * second will be event object and others will be parameters.
 *
 * To communicate between blocks, just trigget custom events. To receive
 * events from another blocks, create `on EVENT` method. Event object will
 * be on first argument here.
 *
 * Block node will be in `@block` property and you can search only inside
 * block by `@(selector)` method.
 *
 * If your block contrain only `init` method, you can use shortcut:
 *
 *   evil.block '.block', ->
 *     # init method
 */
evil.block = function (selector, klass) {
    lastBlock += 1;
    var id = lastBlock;

    if ( typeof(klass) == 'function' ) {
        klass = { init: klass };
    }

    evil.block.defined.push([id, selector, klass]);

    if ( ready ) {
        var init = find($(document), id, selector, klass);
        if ( init ) init();
    }
};

/**
 * Vitalize all current blocks inside base. You must call it on every
 * new content from AJAX.
 *
 *   'on click on @load': ->
 *     $.get '/comments', (comments) =>
 *       evil.block.vitalize $(comments).applyTo(@comments)
 */
evil.block.vitalize = function (base) {
    if ( base ) {
        base = $(base);
    } else {
        base = $(document);
    }

    var inits = [];
    for ( var i = 0; i < evil.block.defined.length; i++ ) {
        var define = evil.block.defined[i];
        inits.push( find(base, define[0], define[1], define[2]) );
    }

    for ( var i = 0; i < inits.length; i++ ) {
        if ( inits[i] ) inits[i]();
    }
};

/**
 * Evil blocks list.
 */
evil.block.defined = [];

/**
 * Filters to process block object and add some extra functions
 * to Evil Blocks. For example, allow to write listeners.
 *
 * Filter will receive block object and unique class ID.
 * If filter return `false`, block will not be created.
 */
evil.block.filters = [];

var filters = evil.block.filters;

/**
 * Don’t vitalize already vitalized block.
 *
 * For better perfomance, it should be last filter.
 */
filters.push(function (obj, id) {
    var ids = obj.block.data('evil-blocks');
    if ( !ids ) {
        ids = [];
    } else if ( ids.indexOf(id) != -1 ) {
        return false;
    }
    ids.push(id);
    obj.block.data('evil-blocks', ids);
});

/**
 * Create `this.$()` as alias for `this.block.find()`
 */
filters.push(function (obj) {
    obj.$ = function (subselector) {
        return obj.block.find(subselector);
    };
});

/**
 * Create properties for each element with `data-role`.
 */
filters.push(function (obj) {
    obj.block.find('[data-role]').each(function (_, el) {
        var roles = el.attributes['data-role'].value.split(' ');
        for ( var i = 0; i < roles.length; i++ ) {
            var role = roles[i];
            if ( !obj[role] ) obj[role] = $();
            if ( obj[role].jquery ) obj[role].push(el);
        }
    });
});

/**
 * Syntax sugar to listen block events.
 */
filters.push(function (obj) {
    for ( var name in obj ) {
        if ( name.substr(0, 3) != 'on ' ) continue;

        var events   = name.substr(3);
        var callback = obj[name];
        delete obj[name];

        (function (events, callback) {
            obj.block.on(events, function (e) {
                if ( e.currentTarget == e.target ) {
                    callback.apply(obj, arguments);
                }
            });
        })(events, callback);
    }
});

/**
 * Smart `load on window` listener, which fire immediately
 * if page was already loaded.
 */
filters.push(function (obj) {
    var name     = 'load on window';
    var callback = obj[name];

    if ( !callback ) return;
    delete obj[name];

    if ( loaded ) {
        setTimeout(function () {
            callback.call(obj, loaded);
        }, 1);
    } else {
        $window.load(function (event) {
            callback.call(obj, event);
        });
    }
});

/**
 * Syntax sugar to listen window and body events.
 */
filters.push(function (obj) {
    for ( var name in obj ) {
        var elem = false;
        if ( endsWith(name, 'on body') ) {
            elem = $('body');
        } else if ( endsWith(name, 'on window') ) {
            elem = $window;
        }

        if ( !elem ) continue;

        var event    = name.split(' on ')[0];
        var callback = obj[name];
        delete obj[name];

        (function (elem, event, callback) {
            elem.on(event, function () {
                callback.apply(obj, arguments);
            });
        })(elem, event, callback);
    }
});

/**
 * Syntax sugar to listen element events.
 */
filters.push(function (obj) {
    for ( var name in obj ) {
        var parts = name.split(' on ');
        if ( !parts[1] ) continue;

        var callback = obj[name];
        delete obj[name];

        (function (parts, callback) {
            obj.block.on(parts[0], parts[1], function (e) {
                e.el = $(this);
                callback.apply(obj, arguments);
            });
        })(parts, callback);
    }
});

/*
 * Run all blocks on load.
 */
$(document).ready(function () {
    ready = true;
    evil.block.vitalize();
});

})(jQuery, window);