function DNLog(containerSelector) {

    this.containerSelector = containerSelector;
    this.displayMode = 'compact';
    this.sortBy = 'group';
    this.messages = {
        byTime: [],
        byGroup: {}
    };
    this.groups = {};
    this.hidden = true;

    this.GetMostRecentMessage = function() {
        if (this.messages.byTime.length == 0) {
            return null;
        }

        return this.messages.byTime[this.messages.byTime.length - 1];
    };

    this.RegisterGroup = function(groupId, groupLabel) {
        if (this.groups[groupId] && this.groups[groupId].id && this.groups[groupId].id == groupId) {
            return;
        }

        this.groups[groupId] = {
            id: groupId,
            label: groupLabel
        };
        this.messages.byGroup[groupId] = [];
    };

    this.GetGroup = function(groupId) {
        if (this.groups[groupId] && this.groups[groupId].id && this.groups[groupId].id == groupId) {
            return this.groups[groupId];
        }
        return null;
    }

    this.AddMessage = function(group, type, title, content) {

        var message = {
            group: group,
            type: type,
            title: title,
            content: content,
            time: new Date()
        };
        if (message.type == 'error' || message.type == 'failure') {
            message.type = 'danger';
        }

        var firstMessage = this.messages.byTime.length == 0;
        var firstMessageInGroup = this.messages.byGroup[group].length == 0;

        this.messages.byTime[this.messages.byTime.length] = message;
        this.messages.byGroup[group][this.messages.byGroup[group].length] = message;

        if (this.IsCompact() || this.IsSortedByTime()) {
            this.RenderMessage(message);
        } else {
            if (this.$_getLogContainer().find('.dashnex-log--group[data-id="' + group + '"]').size() == 0) {
                this.RenderGroup(group);
            } else {
                var $group = this.$_getLogContainer().find('.dashnex-log--group[data-id="' + group + '"]').detach();
                this.$_getLogContainer().find('.dashnex-log__sorting').after($group);
            }
            this.RenderMessage(message);
        }

        if (firstMessage) {
            this.hidden = false;
            this.FadeIn();
        }
    };

    this.IsCompact = function() {
        return this.displayMode == 'compact';
    };

    this.IsFull = function() {
        return this.displayMode == 'full';
    };

    this.IsSortedByGroup = function() {
        return this.sortBy == 'group';
    };

    this.IsSortedByTime = function() {
        return this.sortBy == 'time';
    };

    this.Show = function() {
        this.$_getLogContainer().show();
    };

    this.Hide = function() {
        this.$_getLogContainer().hide();
    };

    this.FadeOut = function() {
        this.$_getLogContainer().fadeOut();
    };

    this.FadeIn = function() {
        this.$_getLogContainer().fadeIn();
    };

    this.Render = function() {
        this._resetMessageContainer();
        if (this.hidden) {
            this.Hide();
        }

        jQuery('#dnc-dn-log-sort-by-time').show();
        jQuery('#dnc-dn-log-sort-by-group').show();
        jQuery('#dnc-dn-log-show-all').show();
        jQuery('#dnc-dn-log-show-last').show();
        if (this.IsCompact()) {
            jQuery('#dnc-dn-log-sort-by-time').hide();
            jQuery('#dnc-dn-log-sort-by-group').hide();
            jQuery('#dnc-dn-log-show-last').hide();
        } else {
            jQuery('#dnc-dn-log-show-all').hide();
            if (this.IsSortedByGroup()) {
                jQuery('#dnc-dn-log-sort-by-group').hide();
            } else {
                jQuery('#dnc-dn-log-sort-by-time').hide();
            }
        }

        if (this.IsCompact() || this.IsSortedByTime()) {
            this.$_getLogContainer().append(
                jQuery('<div/>').addClass('dashnex-log--time')
            );
        }

        if (this.messages.byTime.length > 0) {
            if (this.IsCompact()) {
                this.RenderMessage(this.GetMostRecentMessage());
            } else if (this.IsSortedByTime()) {
                for (var i = 0; i < this.messages.byTime.length; i++) {
                    this.RenderMessage(this.messages.byTime[i]);
                }
            } else {
                var that = this;
                jQuery.each(this.groups, function(groupId, group) {
                    that.RenderGroup(groupId);
                    for (var i = 0; i < that.messages.byGroup[groupId].length; i++) {
                        that.RenderMessage(that.messages.byGroup[groupId][i]);
                    }
                });
            }

            this.hidden = false;
            this.FadeIn();
        }
    };

    this.RenderMessage = function(message) {
        if (this.IsCompact()) {
            this.$_getTimeContainer().html('');
            this.$_getTimeContainer().append(
                this.$RenderMessage(message)
            );
        } else if (this.IsSortedByTime()) {
            this.$_getTimeContainer().prepend(
                this.$RenderMessage(message)
            );
        } else if (this.IsSortedByGroup()) {
            this.$_getGroupContainer(message.group).find('.dashnex-log__group').after(
                this.$RenderMessage(message)
            );
        }
    };

    this.RenderGroup = function(groupId) {
        this.$_getLogContainer().find('.dashnex-log__sorting').after(
            this.$RenderGroup(groupId)
        );
    };

    this.$RenderMessage = function(message) {
        var $msg = jQuery('<div/>');
        var $time = jQuery('<div/>').addClass('dashnex-log__date').html(moment(message.time).fromNow());

        setInterval(function() {
            $time.html(moment(message.time).fromNow());
        }, 1000)

        if (this.IsCompact() || this.IsSortedByTime()) {
            $msg.addClass('dashnex-log');
            $msg.addClass('dashnex-log--' + message.type);
            var $groupLabel = jQuery('<div/>').addClass('dashnex-log__group');
            var $icon = this.$_renderIcon(message.type);
            if ($icon) {
                $groupLabel.append($icon);
            }
            var group = this.GetGroup(message.group);
            if (group && group.label) {
                $groupLabel.append(jQuery('<span/>').html(group.label));
            }
            $msg.append($groupLabel);
            $msg.append(
                jQuery('<div/>').addClass('dashnex-log__collapse').append(
                    jQuery('<div/>').addClass('dashnex-log__message').append(
                        jQuery('<div/>').addClass('message__content').append(
                            jQuery('<div/>').addClass('message__row').append(
                                jQuery('<strong/>').html(message.title + (message.title ? ': ' : ''))
                            ).append(
                                jQuery('<span/>').html(message.content)
                            )
                        )
                    )
                ).append($time)
            );
            if (!this.IsCompact()) {
                $msg.find('.message__content').before(
                    jQuery('<div/>').addClass('dashnex-log__icons').append(
                        jQuery('<i/>').addClass('fa').addClass('fa-chevron-down').addClass('dashnex-log__toggle')
                    ).append(
                        jQuery('<i/>').addClass('fa').addClass('fa-chevron-right').addClass('dashnex-log__toggle')
                    )
                );
            }
        } else {
            $msg.addClass('dashnex-log__collapse').append(
                jQuery('<div/>').addClass('dashnex-log__message').addClass('dashnex-log--' + message.type).append(
                    jQuery('<div/>').addClass('dashnex-log__icons').append(
                        jQuery('<i/>').addClass('fa').addClass('fa-chevron-down').addClass('dashnex-log__toggle')
                    ).append(
                        jQuery('<i/>').addClass('fa').addClass('fa-chevron-right').addClass('dashnex-log__toggle')
                    ).append(
                        this.$_renderIcon(message.type, true)
                    )
                ).append(
                    jQuery('<div/>').addClass('message__content').append(
                        jQuery('<div/>').addClass('message__row').append(
                            jQuery('<strong/>').html(message.title + (message.title ? ': ' : ''))
                        ).append(
                            jQuery('<span/>').html(message.content)
                        )
                    )
                ).append($time)
            );
        }

        return $msg;
    };

    this.$RenderGroup = function(groupId) {
        var group = this.GetGroup(groupId);
        if (!group) {
            return null;
        }
        var $group = jQuery('<div/>').addClass('dashnex-log--group').attr('data-id', group.id);
        $group.append(
            jQuery('<div/>').addClass('dashnex-log').append(
                jQuery('<div/>').addClass('dashnex-log__group').append(
                    jQuery('<span/>').html(group.label)
                )
            )
        );

        return $group;
    };

    this.EnableCompact = function() {};
    this.EnableFull = function() {};

    this.$_getLogContainer = function() {
        return jQuery(this.containerSelector);
    };

    this._resetMessageContainer = function() {
        this.$_getLogContainer().find('.dashnex-log--time').remove();
        this.$_getLogContainer().find('.dashnex-log--group').remove();
    };

    this.$_getTimeContainer = function() {
        return this.$_getLogContainer().find('.dashnex-log--time');
    };

    this.$_getGroupContainer = function(group) {
        return this.$_getLogContainer().find('.dashnex-log--group[data-id="' + group + '"]').find('.dashnex-log');
    };

    this.$_renderIcon = function(type, inGroup) {
        var iconClass = '';
        switch (type) {
            case 'success':
                iconClass = 'fa-check';
                break;
            case 'danger':
                iconClass = 'fa-times';
                break;
            case 'warning':
                iconClass = 'fa-warning';
                break;
            case 'info':
                iconClass = 'fa-info-circle';
                break;
        }

        if (!iconClass) {
            return null;
        }

        var $icon = jQuery('<i/>');
        $icon.addClass('fa').addClass(iconClass);
        if (inGroup === true) {
            $icon.addClass('dashnex-log__message__icon');
        }

        return $icon;
    };

    /*
            Event handlers: sorting, showing
    */

    this.$_getLogContainer().on('click', '#dnc-dn-log-sort-by-time', function() {
        SMLog.sortBy = 'time';
        SMLog.Render();
    });

    this.$_getLogContainer().on('click', '#dnc-dn-log-sort-by-group', function() {
        SMLog.sortBy = 'group';
        SMLog.Render();
    });

    this.$_getLogContainer().on('click', '#dnc-dn-log-show-last', function() {
        SMLog.displayMode = 'compact';
        SMLog.Render();
    });

    this.$_getLogContainer().on('click', '#dnc-dn-log-show-all', function() {
        SMLog.displayMode = 'full';
        SMLog.Render();
    });

    this.$_getLogContainer().on('click','.dashnex-log__toggle',function(){
        $(this).closest('.dashnex-log__collapse').toggleClass('open');
    });

};
