(function ($) {

    "use strict";

    var App;

    App = {};

    App.pluginName = 'plusMoins';

    App.events = {
        open     : App.pluginName.toLowerCase() + 'open',
        close    : App.pluginName.toLowerCase() + 'close',
        destroy  : App.pluginName.toLowerCase() + 'destroy'
    };

    App.defaults = {
        changeUrl           : true,
        closeSelector       : '.close',
        closedClass         : 'closed',
        eventNamespace      : '.' + App.pluginName.toLowerCase(),
        onClose             : $.noop,
        onDestroy           : $.noop,
        onOpen              : $.noop,
        openMany            : false,
        openSelector        : '.open',
        openedClass         : 'opened',
        startupEvent        : App.events.close,
        toggleSelector      : '.toggle'
    };

    $.fn[App.pluginName] = function (options) {
        var settings, instance, elements;

        elements = this;

        if (App.Util.getInstance(elements)) {
            return this;
        }

        settings = $.extend({}, App.defaults, options);

        instance = new App.List(elements, settings);
        App.Util.setInstance(elements, true);
        instance.init();

        return this;
    };

    App.List = function (elements, settings) {

        this.elements = elements;
        this.settings = settings;

        this.App = App;
        this.items = [];

        this.opened = App.Util.bind(this.openedFilter, this);
        this.closed = App.Util.bind(this.closedFilter, this);

        this.addItems();
        this.registerEvents();
    };
    App.List.prototype = {

        init: function () {
            var openDefault;

            openDefault = $([]);

            this.forEachItem(function (item) {
                if (window.location.hash === item.hash) {
                    openDefault = item.element;
                }
            });

            this.elements.trigger(this.settings.startupEvent);
            openDefault.trigger(App.events.open);
        },

        registerEvents: function () {
            var destroy, ns;

            destroy = App.Util.bind(this.destroy, this);
            ns = this.settings.eventNamespace;

            this.elements.bind(App.events.destroy + ns, destroy);
            this.elements.bind(App.events.destroy + ns, this.settings.onDestroy);

        },

        forEachItem: function (callback) {
            var i;
            for (i = 0; i < this.items.length; ++i) {
                callback.call(this, this.items[i], i);
            }
        },

        openedFilter: function (index, element) {
            return $(element).hasClass(this.settings.openedClass);
        },

        closedFilter: function (index, element) {
            return $(element).hasClass(this.settings.closedClass);
        },

        addItems: function () {
            var i, item;
            for (i = 0; i < this.elements.length; ++i) {
                item = new App.Item($(this.elements[i]), this);
                this.items.push(item);
            }
        },

        closeOpened: function () {
            this.elements.filter(this.opened)
                .trigger(App.events.close);
        },

        openItem: function (event, item) {
            event.stopPropagation();

            if (!this.settings.openMany) {
                this.closeOpened();
            }

            if (item.hash && this.settings.changeUrl) {
                window.location.replace(item.hash);
            }

            item.element.addClass(this.settings.openedClass)
                .removeClass(this.settings.closedClass);

        },

        closeItem: function (event, item) {
            event.stopPropagation();
            item.element.addClass(this.settings.closedClass)
                .removeClass(this.settings.openedClass);
        },

        getEventType: function (handle, item) {
            if (handle.is(this.settings.openSelector)) {
                return App.events.open;
            }

            if (handle.is(this.settings.closeSelector)) {
                return App.events.close;
            }

            if (handle.is(this.settings.toggleSelector)) {
                if (item.element.hasClass(this.settings.openedClass)) {
                    return App.events.close;
                }
                if (item.element.hasClass(this.settings.closedClass)) {
                    return App.events.open;
                }
            }

            return App.events.open;
        },

        clickItem: function (event, item) {
            var handle, eventType, customEvent;

            handle = $(event.currentTarget);
            eventType = this.getEventType(handle, item);
            customEvent = $.Event(eventType);
            customEvent.clickEvent = event;

            item.element.trigger(customEvent);
        },

        destroy: function () {

            this.forEachItem(function (item) {
                item.destroy();
            });

            this.elements
                .removeClass(this.settings.openedClass)
                .removeClass(this.settings.closedClass)
                .unbind(this.settings.eventNamespace);

            App.Util.setInstance(this.elements, false);
        }

    };

    App.Item = function (element, list) {

        this.element = element;
        this.list = list;

        this.hash = App.Util.getHash(this.element);

        this.handleSelector = [
            this.list.settings.toggleSelector,
            this.list.settings.openSelector,
            this.list.settings.closeSelector
        ].join(',');

        this.handles = this.element.find(this.handleSelector);

        this.registerEvents();
    };
    App.Item.prototype = {

        registerEvents: function () {
            var open, close, click, settings, ns;

            open = App.Util.bind(this.open, this);
            close = App.Util.bind(this.close, this);
            click = App.Util.bind(this.click, this);

            settings = this.list.settings;
            ns = this.list.settings.eventNamespace;

            this.element.bind(App.events.open + ns, open);
            this.element.bind(App.events.open + ns, settings.onOpen);

            this.element.bind(App.events.close + ns, close);
            this.element.bind(App.events.close + ns, settings.onClose);

            this.handles.bind('click' + ns, click);
        },

        click: function (event) {
            this.list.clickItem(event, this);
        },

        open: function (event) {
            this.list.openItem(event, this);
        },

        close: function (event) {
            this.list.closeItem(event, this);
        },

        destroy: function () {
            this.element.unbind(this.list.settings.eventNamespace);
            this.handles.unbind(this.list.settings.eventNamespace);
        }

    };

    App.Util = {

        getHash: function (element) {
            var id, hash;
            id = element.attr('id');
            hash = id ? '#' + id : false;
            return hash;
        },

        bind: function (self, obj) {
            var slice = [].slice,
                args = slice.call(arguments, 2),
                Nop = function () {
                },
                bound = function () {
                    return self.apply(this instanceof Nop ? this : (obj || {}),
                        slice.call(arguments).concat(args));
                };
            Nop.prototype = self.prototype;
            bound.prototype = new Nop();
            return bound;
        },

        getInstance: function (elements) {
            var instance = false;
            elements.each(function (i, element) {
                if ($(element).data(App.pluginName)) {
                    instance = true;
                    return false;
                }
            });
            return instance;
        },

        setInstance: function (elements, instance) {
            elements.data(App.pluginName, instance);
        }

    };

}(jQuery));
