/*
 * Learndo.Lib
 */
window.learndo = {};
learndo.lib = {};
learndo.URL = 'http://panel.learndo.com.br/';

learndo.exceptions = {
        generic: function(name, message){
        var name = name;
        var msg = '#000: '+message;

        return {
            'name' : name,
            'msg'  : msg
        };
    },
    InvalidPosition: function(message){
        var name = 'InvalidPosition';
        var msg = '#001: '+message;

        return {
            'name' : name,
            'msg'  : msg
        };
    },
    InvalidRequestReturnType: function(message){
        var name = 'InvalidRequestReturnType';
        var msg = '#002: '+message;

        return {
            'name': name,
            'msg': msg
        };
    },
    InvalidParam: function (message) {
        var name = 'InvalidParam';
        var msg = '#003: '+message;

        return {
            'name': name,
            'msg': msg
        };
    },
    RequestTimeout: function(message){
        var name = 'RequestTimeout';
        var msg = '#004: '+message;

        return {
            'name': name,
            'msg': msg
        };
    },
    InvalidFuncTypeParams: function(){
        return {
            'name': 'InvalidFuncTypeParams',
            'msg': '#005: Invalid Function Type in Params'
        };
    }
};

learndo.Hash = function() {
    var _hash = {};
    var pre = '__' + (new Date()).getTime() + '__';

    function put(key, value) {
        _hash[ pre+key ] = value;
        return this;
    }

    function get(key) {
        return _hash[ pre+key ];
    }

    function remove(key) {
        delete _hash[ pre+key ];
    }

    return {
        'put': put,
        'get': get,
        'remove': remove
    }
}

learndo.lib.dom = (function() {
    var hostMethodRegExp = new RegExp('^function|object$', 'i');

    function updateContent(content, element) {
        while(element.firstChild) {
            element.removeChild(element.firstChild);
        }
        return learndo.lib.dom.append('bottom', content, element);
    }

    function removeNode(node) {
        if(node) {
            var p = node.parentNode;
            if(p) {
                p.removeChild(node);
            }
        }
        return node;
    }

    function append(position, content, destination) {
        if( typeof(content) == 'string' ){
            if( destination.firstChild ) {
                content = document.createTextNode(content);
            }
            else{
                destination.innerHTML = content;
                return destination;
            }
        }
        else if( content == undefined || content == null ) {
            return null;
        }

        if(position == 'bottom'){
            return destination.appendChild(content);
        }
        else if(position == 'top') {
            return destination.insertBefore(content, destination.firstChild);
        }
        else {
            throw new learndo.exceptions.InvalidPosition('`' + position + '` is not a valid insertion placement');
        }
    }

    function isHostMethod(object, method) {
        var t = typeof object[method];
        return !!((hostMethodRegExp.test(t) && object[method]) || t == 'unknown');
    }

    function isHostObjectProperty(object, property){
        var t = typeof object[property];
        return !!(hostMethodRegExp.test(t) && object[property]);
    }

    function createElement(type, properties) {
        var element = document.createElement(type);
        this.setProperties(element, properties);
        return element;
    }

    function setProperties(element, properties) {
        for (var property in properties){
            if (property == 'style'){
                learndo.lib.style.setStyles(element, properties[property]);
            } else {
                element[property] = properties[property];
            }
        }
        return element;
    }

    function isElement(objeto) {
        return !!(objeto && objeto.nodeType == 1);
    }

    function addClassName(element, className) {
        var classes = element.className.split(' ');

        if( learndo.lib.container.isEmpty(classes) ||
            learndo.lib.container.isEmpty(learndo.lib.container.arrayGrep(classes, new RegExp(className))) ) {
            element.className = element.className +
                                (element.className ? ' ' : '') +
                                className;
        }
    }

    function removeClassName(element, className) {
        var classes = element.className;
        element.className = classes.replace(new RegExp('(^|\s)'+className+'(\s|$)'), '$1$2');
    }

    function hasClassName(element, className)
    {
        var classes = element.className.split(' ');
        for(var l = classes.length; --l >= 0;)
        {
            if(classes[l] == className)
            {
                return true;
            }
        }
        return false;
    }

    function isStyleSheetEmbeded(sheetName){
        var container = learndo.lib.container;
        if(isHostObjectProperty(document, 'styleSheets')){
            var ssHrefs = container.extract('/href', document.styleSheets);
            for(var i = 0, len = ssHrefs.length; i < len; ++i){
                if(ssHrefs[i] && typeof ssHrefs[i] == 'string' && ssHrefs[i].indexOf(sheetName) != -1){
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * getChildrenByProperty searches for childNodes that matches the given property
     * @param HTMLElement parentNode
     * @param string propName The property that will be queried on the children nodes
     * @param string propValue The value that will have to match
     * @param bool breakOnFirst If true, break on the first match and return that element
     * @param bool recursive If true, acts recursively
     * @return HTMLElement|null the child element that matches the search
     */
    function getChildrenByProperty(parentNode, propName, propValue, breakOnFirst, recursive)
    {
        var children = parentNode.childNodes;
        var results = breakOnFirst ? null : [];
        var strict = (typeof propValue == "string") ? false : true;

        if(!strict)
        {
            var propReg = new RegExp('(^|\\s+)'+propValue+'(\\s+|$)');
        }

        for(var i = 0, len = children.length; i < len; ++i)
        {
            if(children[i].nodeType == 1)
            {
                if((strict && (propValue === children[i][propName])) || (!strict && propReg.test(children[i][propName])))
                {
                    if(breakOnFirst)
                    {
                        results = children[i];
                        break;
                    }
                    else
                    {
                        results.push(children[i]);
                    }
                }

            }
            if(recursive)
            {
                results = results.concat(getChildrenByProperty(children[i], propName, propValue, breakOnFirst, recursive));
            }
        }
        parentNode = children = null;
        return results;
    }

    function getDescendantsByClassName(ancestorNode, className, childrenOnly)
    {
        var results = [];
        if(!className) return results;
        var elems = getChildElements(obj);
        var classes = className.match(/(^|\s)\S+(\s|$)/ig);
        var rexp;
        var j;
        for(var i=0; i<elems.length; i++)
        {
            for(j=0; j<classes.length; j++)
            {
                classes[j] = classes[j].replace(/\s?(\S+)\s?/, "$1");
                rexp = new RegExp("(^|\\s)"+classes[j]+"(\\s|$)");
                if(!rexp.test(elems[i].className)) break;   //one of the classes was not found
            }
            if(j == classes.length) results.push(elems[i]); //if elems[i].className includes all the classes, add elems[i]
            if(!childrenOnly)
            {
                results = results.concat(getDescendantsByClassName(elems[i], className));
            }
        }
        return results;
    }

    return {
        'updateContent'         : updateContent,
        'removeNode'            : removeNode,
        'append'                : append,
        'createElement'         : createElement,
        'isHostMethod'          : isHostMethod,
        'isHostObjectProperty'  : isHostObjectProperty,
        'setProperties'         : setProperties,
        'isElement'             : isElement,
        'addClassName'          : addClassName,
        'removeClassName'          : removeClassName,
        'isStyleSheetEmbeded'   : isStyleSheetEmbeded,
        'getChildrenByProperty'          : getChildrenByProperty,
        'hasClassName'          : hasClassName
    }
})();

learndo.lib.event = (function() {
    var keys = {
        'backspace' : 8,
        'tab' : 9,
        'return': 13,
        'esc': 27,
        'left': 37,
        'up': 38,
        'right': 39,
        'dowm': 40,
        'delete': 46,
        'home': 36,
        'end': 35,
        'pageup':  33,
        'pagedown': 34,
        'insert': 45
    };

    var listeners = [];
    var timerID = false;
    var IS_READYSTATE_SUPPORTED = typeof document.readyState == 'string';
    var bodyEndTag = new RegExp('<\/body>', 'i');
    var dom = learndo.lib.dom;

    function _fireListeners(){
        window.clearTimeout(timerID);
        timerID = false;
        var len = listeners.length;

        for(var i = 0; i < len;len--){
            listeners[i]();
        }
        listeners = [];
    }

    function _DOMLoadedCallback(){
        if(
            (timerID !== false) &&
            (IS_READYSTATE_SUPPORTED && dom.isHostObjectProperty(document, 'body') && document.readyState == 'complete') ||
            (dom.isHostObjectProperty(document, 'documentElement') && bodyEndTag.test(document.documentElement.innerHTML))
        )
        {
            _fireListeners();
        }
    }

    function DOMLoaded(func) {
        var len = listeners.length;
        if(typeof func == 'function'){
            listeners[len] = func;
            if(len == 0){
                addEvent('DOMContentLoaded', window, _fireListeners);
                timerID = window.setInterval(_DOMLoadedCallback, 10);
            }
        }
    }

    function addEvent(type, element, func, options){
        options = options || {};
        var dom = learndo.lib.dom;
        var cbFun = function(evt){
            if(!evt){
                evt = window.event;
            }
            if(typeof(evt.target) == 'undefined'){
                evt.target = evt.srcElement;
            }
            if(options.stopPropagation === true){
                evt.cancelBubble = true;
                if(dom.isHostMethod(evt, 'stopPropagation')){
                    evt.stopPropagation();
                }
            }
            if(options.preventDefault === true){
                evt.returnValue = false;
                if(dom.isHostMethod(evt, 'preventDefault')){
                    evt.preventDefault();
                }
            }
            return func(evt);
        };
        if (typeof func == 'function'){
            if (dom.isHostMethod(element, 'addEventListener')){
                element.addEventListener(type, cbFun, false);
            } else if (dom.isHostMethod(element, 'attachEvent')){
                element.attachEvent('on'+type, cbFun);
            } else {
                var event = 'on'+type;
                var old = (element[event]) ? element[event]: function () {};
                element[event] = function (evt) {old(evt); cbFun(evt)};
            }
        }
    }

    function removeEvent(type, element, func){
        if (typeof func == 'function'){
            if (element.removeEventListener){
                element.removeEventListener(type, func, false);
            }else if (element.detachEvent){
                element.detachEvent('on'+type, func);
            }else {
                element['on'+type] = null;
            }
        } else {
            throw new learndo.exceptions.InvalidFuncTypeParams();
        }
    }
    function keyPress(evt, key){
        if (!evt) evt = window.event;
        var code;
        if (evt.keyCode) code = evt.keyCode;
	else if (evt.which) code = evt.which;

        if (keys[key]){
            key = keys[key];
        }else if (key.match(/[a-zA-Z]/)){
            key = key.charCodeAt(0);
        }
        return (code == key);
    }
    return {
        'addEvent': addEvent,
        'removeEvent': removeEvent,
        'keyPress': keyPress,
        'DOMLoaded': DOMLoaded
    }
})();

learndo.Engine = function(scope) {
    /** Construtor
    *  Cria o Hash das ajudas, o layer de fechamento e instancia os objetos de ajuda
    */
    var _self = {};
    function init(scope){
        var elementsWithLearndo = {};
        _self.foundIds = [];
        _self.activeHelper = null;
        _self.elements = learndo.lib.selector.getLearndoElements(scope);
        for( var len = _self.elements.length, i = 0; i < len; ++i ){
            if( learndo.lib.dom.isElement(_self.elements[i])){
                var element = _self.elements[i];
                var found = learndo.lib.container.arrayGrep(element.className.split(' '), /^learndo-/);
                var foundSize = found.length;
                if(foundSize > 0){
                    /*
                    *  Sobre o found[found.size() - 1], estou pegando sempre o
                    *  último learn-do encontrado numa class=''
                    */
                    var idFound = found[foundSize - 1].split('-')[1];
                    if(!elementsWithLearndo[idFound]){
                        elementsWithLearndo[idFound] = [];
                    }
                    elementsWithLearndo[idFound].push(element);
                    _self.foundIds.push(idFound);
                }
                element = null;
            }
        }
        if (!learndo.lib.container.isEmpty(elementsWithLearndo)){
            _self.elements = elementsWithLearndo;
            requestConfig(_self.foundIds);
            learndo.lib.event.addEvent('keyup', document, removeBalloon);
        }
    }

    /**
    * Depois que é verificado os id's presentes no documento, é feita uma
    * requisição para retornar os valores iniciais e instanciar a Ajuda.
                                                                        */
    function requestConfig(foundIds) {
            var url =
                learndo.URL + 'balloons/get_initial_config/' +
                parseIds(foundIds);

            learndo.lib.request.get(url, 'json', createHelpers);
    }

    function parseIds(ids){
        return ids.join('-');
    }

    function createHelpers(request){
        var helper, cfg;
        _self.helpers = [];
        if (checkConfigs(request.configs)){
            embedCss();
            for(var configIndex = 0; configIndex < _self.foundIds.length; configIndex++){
                var idLearndo = _self.foundIds[configIndex];
                var callerElements = _self.elements[idLearndo];
                var arrSize = callerElements.length;
                for(var i = 0; i < arrSize; i++) {
                    if(!request.errors[idLearndo]){
                        cfg = mergeWithDefaultConfig(request.configs[idLearndo]);
                        helper = new learndo.Helper();
                        helper.init(
                            callerElements[i],
                            cfg
                        );
                        helper.setEngine(_self);
                        _self.helpers.push(helper);
                    }
                }
            }
        }
    }

    function mergeWithDefaultConfig(config){
        if(typeof(_self.defaultConfig) == "undefined"){
            _self.defaultConfig = learndo.lib.container.mergeCloneRecursive(config, {});
            return _self.defaultConfig;
        }
        var dest = learndo.lib.container.cloneObject(_self.defaultConfig);
        return learndo.lib.container.mergeCloneRecursive(dest, config);
    }

    function checkConfigs(configs){
        return !learndo.lib.container.isEmpty(configs);
    }

    function removeBalloon(evt) {
        if(learndo.lib.event.keyPress(evt, 'esc')){
            var balloon = _self.activeHelper;
            if (balloon){
                balloon.dismissBalloon(evt);
            }
        }
    }

    function embedCss(css){
        var dom = learndo.lib.dom;
        if(typeof(css) != "string"){
            css = learndo.URL+'css/embed-learn-do.css';
        }
        if (!dom.isStyleSheetEmbeded(css)){
            css += '?rand='+Math.random();
            if(dom.isHostMethod(document, 'createStyleSheet')) {
                document.createStyleSheet(css);
            }
            else {
                var link = dom.createElement('LINK',{
                    rel: 'stylesheet',
                    href: css
                });
                dom.append('bottom', link, learndo.lib.selector.getHead());
            }
        }
    }

    return {
        'init': init
    }
}

learndo.Helper = function(element, configInit) {
    var self;
    function init (element, configInit){
        self = this;
        self.config = {
            caller: element,
            balloon: {
                margin: configInit.balloon.margin,
                size: configInit.balloon.size,
                element: null,
                content: null
            },
            id: configInit.id,
            levels: {
                list: configInit.levels,
                current: 0
            },
            anchor: {
                element: null,
                image: configInit.anchor.image,
                loading: configInit.anchor.loading,
                size: configInit.anchor.size,
                margin: configInit.anchor.margin,
                position: configInit.anchor.position
            },
            active: false,
            cache: new learndo.Hash(),
            residenceTime: null,
            templates: {
                structure:
                    "<img class='c' src='"+learndo.URL+"img/close.png'>"+
                    "<div class='i'></div><div class='m'></div>"+
                    "<div class='f'></div>"+
                    "<div class='e'><span class='p'>powered by <span>learn.do</span></span></div>",
                more:
                    "<img src='"+learndo.URL+"img/mais.png' alt='Saiba Mais'><span>Quer saber mais?</span>",
                loading:
                    "<img src='"+learndo.URL+"img/loading.gif'> Carregando...",
                feedback:
                    "<h6>Esta ajuda foi importante?</h6>"+
                    "<button id='learn-do-fly' class='km btn'>Sim</button>"+
                    "<button id='learn-do-fln' class='km btn'>N&atilde;o</button>"+
                    "<img src='"+learndo.URL+"img/feedback-loading.gif>"
            }
        };
        createAndInsertAnchor();
    }

    /** insertAnchor
    *  Cria o elemento IMG, adiciona na árvore DOM e adiciona o evento
    */
    function createAndInsertAnchor(){
        var image = learndo.lib.dom.createElement('IMG', {
            src: self.config.anchor.image,
            border: 0,
            alt: 'learn-do Helper',
            title: 'Clique para visualizar a ajuda do learn.do desse item',
            style: {
                cursor: 'pointer',
                verticalAlign: 'middle',
                padding: self.config.anchor.margin+'px'
            }
        });

        learndo.lib.event.addEvent('click', image,
            function(evt){
                var active = self.config.engine.activeHelper;
                if (active) {
                    active.dismissBalloon();
                }
                else {
                    openBalloon();
                }
                active = null;
            },
            {'preventDefault': true}
        );

        self.config.anchor.element = image;
        insertAnchor();
    }

    /**
    *  Encapsulamento para o método de inserção
    */
    function insertAnchor(){
        var options;
        switch(self.config.anchor.position.placement){
            case 'before':
                options = 'top';
                break;
            case 'after':
                options = 'bottom';
                break;
            case 'absolute':
                options = 'bottom';
                learndo.lib.style.setStyles(
                    self.config.anchor.element,
                    {
                        'position': 'relative',
                        'top': self.config.anchor.position.top+'px',
                        'left': self.config.anchor.position.left+'px'
                    }
                );
                break;
        }

        learndo.lib.dom.append(options,self.config.anchor.element,self.config.caller);
    }

    /** abreJanela
    *  Método de abertuda de janela
    */
    function openBalloon(){
        if (self.config.balloon.element == null || !self.config.active){
            createBalloonElement();

            requestContent();

            self.config.active = true;
            self.config.engine.activeHelper = self;
            self.config.residenceTime = new Date();
        }
    }

    function createBalloonElement(){
        if(!self.config.balloon.element){
            var divLearnDo = learndo.lib.dom.createElement('DIV',
                {
                    'id': 'learn-do',
                    'style': {
                        'position': 'absolute'
                    }
                }
            );
            self.config.balloon.element = learndo.lib.dom.updateContent(self.config.templates.structure, divLearnDo);
            self.config.balloon.contentElement = learndo.lib.dom.getChildrenByProperty(self.config.balloon.element, 'className', 'i', true);
            self.config.balloon.moreElement = learndo.lib.dom.getChildrenByProperty(self.config.balloon.element, 'className', 'm', true);
            self.config.balloon.feedbackElement = learndo.lib.dom.getChildrenByProperty(self.config.balloon.element, 'className', 'f', true);
            createFeedbackTemplate();
            if (self.config.levels.list.length <= 1){
                learndo.lib.dom.removeNode(self.config.balloon.moreElement);
            }
            self.config.balloon.closeElement = learndo.lib.dom.getChildrenByProperty(self.config.balloon.element, 'className', 'c', true);
            learndo.lib.event.addEvent('click', self.config.balloon.closeElement, dismissBalloon);
        }
    }

    /** posicionaJanela
    *  Posiciona o balao de ajuda na tela
    *  IMPORTANTE: Posicionamento referencial ao DOCUMENTO e não mais ao icone ajuda
    */
    function positionBalloon(){
        var p = learndo.lib.position,
            imgOffsets = p.getElementOffsets(self.config.anchor.element),
            scrolledDimensions = p.getScrolledDimensions();

        var helperOffsets = {
            left: imgOffsets.left + self.config.anchor.size.width + (2 * self.config.anchor.margin),
            top: imgOffsets.top
        };

        var viewport = learndo.lib.position.getWinDimensions();

        var maxTotalDimensions = {
            width: helperOffsets.left + self.config.balloon.size.width + (2 * self.config.balloon.margin),
            height: helperOffsets.top + p.getElementDimensions(self.config.balloon.element).height + self.config.balloon.margin
        };
        if (maxTotalDimensions.width > (viewport.width + scrolledDimensions.left)){
            helperOffsets = {
                left: helperOffsets.left  - (maxTotalDimensions.width - (viewport.width + scrolledDimensions.left)) ,
                top: helperOffsets.top + self.config.anchor.size.height + (2 * self.config.anchor.margin)
            };
            maxTotalDimensions.height += self.config.anchor.size.height + (2 * self.config.anchor.margin);
            var limit =  self.config.balloon.margin + scrolledDimensions.left;
            if(helperOffsets.left <  limit){
                helperOffsets.left = limit;
            }
        }

        if (maxTotalDimensions.height > (viewport.height + scrolledDimensions.top)){
            helperOffsets.top = helperOffsets.top - (maxTotalDimensions.height - (viewport.height + scrolledDimensions.top));
            var limit =  self.config.balloon.margin + scrolledDimensions.top;
            if(helperOffsets.top < limit){
                helperOffsets.top = limit;
            }
        }
        learndo.lib.style.setStyles(self.config.balloon.element, {
            left: helperOffsets.left+'px',
            top: helperOffsets.top+'px'
        });
    }

    /** carregaInfo
    *  Método assíncrono para adicionar informações
    *   Retorna o conteudo que deve ser adicionado ou false
    */
    function requestContent() {
        var levelId = (!!self.config.levels.list[self.config.levels.current])
            ? self.config.levels.list[self.config.levels.current]
            : '';
        var level = self.config.levels.current + 1;
        var cached = self.config.cache.get(level);

        if(cached) {
            handleResponse(cached);
        }
        else{
            var url =
                learndo.URL + 'balloons/get/' +
                self.config.id + '/' + levelId;
            learndo.lib.request.get(url, 'json', handleResponse);
            if (!learndo.lib.selector.isLearndoOpen()){
                var height = learndo.lib.position.getElementDimensions(self.config.balloon.contentElement).height;

                var div = learndo.lib.dom.createElement('DIV');
                learndo.lib.dom.updateContent(self.config.templates.loading, div);
                learndo.lib.dom.addClassName(div, 'l');
                learndo.lib.style.setStyles(div, {
                    lineHeight: height+'px',
                    textAlign : 'center'
                });

                learndo.lib.dom.updateContent(div, self.config.balloon.contentElement);
            }
            self.config.anchor.element.src = self.config.anchor.loading;
        }
    }

    function handleResponse(requestResponse){
        self.config.balloon.content = requestResponse;

        if(!self.config.cache.get(self.config.balloon.content.level)){
            self.config.cache.put(self.config.balloon.content.level, self.config.balloon.content);
        }

        self.config.anchor.element.src = self.config.anchor.image;

        if (!self.config.balloon.content.error) {
            assembleAndInsertBalloon();
            resizeBalloon();
            positionBalloon();
        }
    }

    function resizeBalloon(){
    var position = learndo.lib.position;
    var maxHeight =
            position.getScrolledDimensions().top +
            position.getWinDimensions().height;
    var balloonHeight = position.getElementDimensions(self.config.balloon.element).height;
    var elementMaxHeight =
            position.getElementOffsets(self.config.anchor.element).top +
            balloonHeight;
    var diff = maxHeight - (elementMaxHeight + self.config.balloon.margin);
        if (diff < 0){
            var finalContentHeight;
            if ((balloonHeight+diff) < self.config.balloon.size.height){
                finalContentHeight =  position.getElementDimensions(self.config.balloon.contentElement).height - ((balloonHeight) - self.config.balloon.size.height);
                var paddingBalloon = position.getPaddingElm(self.config.balloon.element);
                var newBalloonHeight = self.config.balloon.size.height - paddingBalloon.top - paddingBalloon.bottom;
                learndo.lib.style.setStyles(
                    self.config.balloon.element,
                    {
                        height: newBalloonHeight+ 'px'
                    }
                );
            }else{
                finalContentHeight = position.getElementDimensions(self.config.balloon.contentElement).height + diff;

            }
            finalContentHeight+='px';
            learndo.lib.style.setStyles(self.config.balloon.contentElement,
                {
                    height: finalContentHeight
                }
            );
        }
    }

    /**
    * Método de interir links de Saiba Mais
    * TODO: Não faz nenhuma verificação da existência ou não do Saiba Mais
    */
    function assembleAndInsertBalloon(){
        learndo.lib.dom.updateContent(self.config.balloon.content.body, self.config.balloon.contentElement);

        learndo.lib.style.setStyles(self.config.balloon.element,{
            width: self.config.balloon.size.width+'px',
            height: 'auto'
        });

        learndo.lib.dom.append('bottom',self.config.balloon.element, document.body);

        learndo.lib.style.setStyles(self.config.balloon.contentElement,{
            height: 'auto'
        });
        if (self.config.levels.list.length > 1){
            if(typeof(self.config.levels.list[self.config.levels.current+1]) != "undefined"){
                var buttonMore = learndo.lib.dom.createElement('BUTTON',{
                    className: 'km btn'
                });
        var elm = learndo.lib.dom.updateContent(self.config.templates.more, buttonMore);
        learndo.lib.event.addEvent('click', elm, nextLevel);
                learndo.lib.dom.append(
                    'bottom',
                    elm,
                    self.config.balloon.moreElement
                );
            }
            if(typeof(self.config.levels.list[self.config.levels.current-1])!= "undefined"){
                var backButton = learndo.lib.dom.createElement('BUTTON',{
                    className: 'v btn'
                });
        var elm = learndo.lib.dom.updateContent('Voltar', backButton);
        learndo.lib.event.addEvent('click', elm, previousLevel);
                learndo.lib.dom.append(
                    'bottom',
                    elm,
                    self.config.balloon.moreElement
                );
            }
        }
    }

    function nextLevel(evt){
        self.config.levels.current++;
        learndo.lib.dom.updateContent('',self.config.balloon.moreElement);
        requestContent();
    }

    function previousLevel(evt){
        self.config.levels.current--;
        learndo.lib.dom.updateContent('',self.config.balloon.moreElement);
        requestContent();
    }

    /** fechaJanela
    *  Remove a janela da arvore DOM, e seta como inativa
    */
    function dismissBalloon(evt){
        sendResidenceTime();
        if(self.reqScript){
            learndo.lib.dom.removeNode(self.reqScript);
            self.reqScript = false;
        }
        if(learndo.requestResponse){
            delete(learndo.requestResponse);
        }
        self.config.active = false;
        self.config.engine.activeHelper = null;
        learndo.lib.dom.updateContent('',self.config.balloon.moreElement);
        learndo.lib.dom.removeNode(self.config.balloon.element);
    }

    function setEngine(engine) {
        self.config.engine = engine;
    }

    function createFeedbackTemplate(){
        learndo.lib.dom.updateContent(self.config.templates.feedback, self.config.balloon.feedbackElement);

        learndo.lib.event.addEvent(
            'click',
            learndo.lib.dom.getChildrenByProperty(self.config.balloon.feedbackElement, 'id', 'learn-do-fly', true),
            function (){
                sendFeedback('1');
            }
        );

        learndo.lib.event.addEvent(
            'click',
            learndo.lib.dom.getChildrenByProperty(self.config.balloon.feedbackElement, 'id', 'learn-do-fln', true),
            function (){
                sendFeedback('0');
            }
        );
    }

    function sendFeedback(value){
        learndo.lib.dom.addClassName(self.config.balloon.feedbackElement, 'loading');
        var url = learndo.URL + 'feedbacks/send/'+self.config.id+'/'+value;
        learndo.lib.request.get(url, 'json', feedbackResponse);
    }

    function feedbackResponse(response){
        learndo.lib.dom.removeClassName(self.config.balloon.feedbackElement, 'loading');
        var msg;
        if(response && response.learndo && response.learndo.feedback) {
            msg = '<h6>Obrigado por enviar sua opini\u00e3o</h6>';
        }
        else{
            msg = '<h6>Infelizmente n\u00e3o foi poss\u00edvel contabilizar sua opini\u00e3o. Por favor, tente novamente mais tarde</h6>';
        }
        learndo.lib.dom.updateContent(msg, self.config.balloon.feedbackElement);
    }

    function sendResidenceTime(){
        self.config.residenceTime =  new Date() - self.config.residenceTime;

        var url = learndo.URL + '/residence_times/send/'+self.config.id+'/'+self.config.residenceTime;
        learndo.lib.request.get(url, 'javascript');
    }

    return {
        'init': init,
        'setEngine': setEngine,
        'dismissBalloon': dismissBalloon
    }
}

learndo.lib.request = (function() {
    var json = false,
        intervalTicket,
        delay = 10,
        iterations = 0,
        timeout = 30 /*segundos*/,
        maxIterations = timeout * (1000/delay) /* iteracoes por segundo */,
        excep = learndo.exceptions,
        callbackFn;

    function get(url, returnType, callback) {
        var dom = learndo.lib.dom;
        var remoteReturn;

        if (!url) {
            throw excep.InvalidParam('Url must not be empty.');
            return false;
        }

        switch (returnType) {
            case 'javascript':
                remoteReturn = dom.createElement('SCRIPT', {
                    type: 'text/'+returnType,
                    src: url
                });
                break;
            case 'css':
                remoteReturn = dom.createElement('LINK', {
                    type: 'text/'+returnType,
                    href: url,
                    rel: 'stylesheet'
                });
                break;
            case 'json':
                remoteReturn = dom.createElement('SCRIPT', {
                    type: 'text/javascript',
                    src: url
                });
                callbackFn = callback;
                intervalTicket = window.setInterval(requestHandler, delay);
                break;
            default:
                remoteReturn = false;
                throw excep.InvalidParam('Invalid Return Type for the Request. Choose either javascript, css or json.');
                return false;
        }

        if (remoteReturn) {
            dom.append('bottom', remoteReturn, learndo.lib.selector.getHead());
        }
    }

    function requestHandler(){
        if(++iterations > maxIterations){
            window.clearInterval(intervalTicket);
            throw excep.RequestTimeout('Request timed out after ' + timeout + ' seconds.');
        }
        if(json !== false){
            window.clearInterval(intervalTicket);
            iterations = 0;
            if(typeof callbackFn == 'function'){
                callbackFn(json);
            }
            json = false;
        }
    }

    function setResponse(response){
        json = response;
    }

    return {
        'get': get,
        'setResponse': setResponse
    }
})();

learndo.lib.container = (function() {

    function arrayGrep(strings, pattern) {
        var matches = [];

        for (var i = 0, len = strings.length; i < len; ++i){
            if(pattern.test(strings[i])) {
                matches.push(strings[i]);
            }
        }

        return matches;
    }

    function extract(path, iterable){
        if((typeof iterable.length != 'number') || (path.indexOf('/') !== 0)){
            return false;
        }
        if(path == '/'){
            return iterable;
        }
        var filtered = [];
        var props = path.slice(1).split('/');
        for(var i = 0, len = iterable.length, tmpVal; i < len; ++i){
            tmpVal = getPropertyRecursively(props.slice(), iterable[i]);
            if(typeof tmpVal != 'undefined'){
                filtered.push(tmpVal);
            }
        }
        return filtered;
    }

    function getPropertyRecursively(props, obj){
        if(props.length === 1){
            return obj[props];
        }

        var prop = props.shift();
        if(typeof obj[prop] != 'undefined'){
            return getPropertyRecursively(props, obj[prop]);
        }
    }

    function isEmpty(objeto) {
        if(typeof objeto.length != 'undefined') {
            return objeto.length == 0;
        }

        for(var i in objeto) {
            if(objeto.hasOwnProperty(i)) {
                return false;
            }
        }

        return true;
    }

    function cloneObject(object){
        var destination = {};
        for(var property in object){
            destination[property] = object[property];
        }
        return destination;
    }

    function getClass(objeto) {
        return Object.prototype.toString.call(objeto).match(/^\[object\s(.*)\]$/)[1]
    }

    function isArray(objeto){
        return ( getClass(objeto) === 'Array' )
    }

    function mergeCloneRecursive(destination, source){
        for (var property in source){
            if(source.hasOwnProperty(property)){
                if( typeof(source[property]) != "object" || isArray(source[property]) ){
                    destination[property] = source[property];
                }
                else{
                    destination[property] = mergeCloneRecursive(cloneObject(destination[property]), source[property]);
                }
            }
        }
        return destination;
    }

    return {
        'arrayGrep'             : arrayGrep,
        'isEmpty'               : isEmpty,
        'cloneObject'           : cloneObject,
        'mergeCloneRecursive'   : mergeCloneRecursive,
        'getClass'              : getClass,
        'isArray'               : isArray,
        'extract'               : extract,
        'getPropertyRecursively': getPropertyRecursively
        /*'Hash'     : Hash*/
    }

})();

learndo.lib.position = (function() {

    function getWinDimensions(win){
        var IS_BODY_ACTING_ROOT,
            IS_DOCUMENT_ELEMENT_HEIGHT_OFF;

        win = win || window;
        IS_BODY_ACTING_ROOT = win.document.documentElement && win.document.documentElement.clientHeight < 9;

        if(typeof win.document.clientWidth == "number") {
            getWinDimensions = function(){
                var dim = [win.document.clientWidth, win.document.clientHeight];
                dim['width'] = dim[0];
                dim['height'] = dim[1];
                return dim;
            };
        }
        else if(IS_BODY_ACTING_ROOT) {
            getWinDimensions = function(){
                var dim = [win.document.body.clientWidth, win.document.body.clientHeight];
                dim['width'] = dim[0];
                dim['height'] = dim[1];
                return dim;
            }
        } else {
            getWinDimensions = function(){
                var dim = [win.document.documentElement.clientWidth, win.document.documentElement.clientHeight];
                dim['width'] = dim[0];
                dim['height'] = dim[1];
                return dim;
            }
        }
        return getWinDimensions();
    }


    /* getElementDimensions: get the element's width and height including padding and scrollbars
     * @param HTMLElement elm the element in question
     * @return Array [width, height] or null if it cannot find out the values
     */
    function getElementDimensions(elm){
        var dom = learndo.lib.dom,
            w = 0, h = 0;

        if(elm.offsetWidth && elm.offsetHeight){
            w = elm.offsetWidth;
            h = elm.offsetHeight;
        }
        else {
            var padding = getPaddingElm(elm);
            var innerDim = getInnerDimensionElm(elm);
            w = innerDim.width + padding.left + padding.right;
            h = innerDim.height + padding.top + padding.bottom;
        }
        var dim = [w, h];
        dim['width'] = dim[0];
        dim['height'] = dim[1];
        return dim;
    }
    function getInnerDimensionElm(elm){
        var dim = [0, 0],
            dom = learndo.lib.dom;
        if(dom.isHostObjectProperty(elm, 'currentStyle')){
            dim = [
                parseInt(elm.currentStyle.width, 10),
                parseInt(elm.currentStyle.height, 10)
            ];
        }
        else if(dom.isHostObjectProperty(document, 'defaultView') &&
                dom.isHostMethod(document.defaultView, 'getComputedStyle')){
            var s = document.defaultView.getComputedStyle(elm, null);
            dim = [
                parseInt(s.width ,10),
                parseInt(s.height, 10)
            ];
        }
        dim['width'] = dim[0];
        dim['height'] = dim[1];
        return dim;
    }
    function getPaddingElm(elm){
        var padding,
            dom = learndo.lib.dom;
        if(dom.isHostObjectProperty(elm, 'currentStyle')){
            padding = [
                parseInt(elm.currentStyle.paddingTop, 10),
                parseInt(elm.currentStyle.paddingRight, 10),
                parseInt(elm.currentStyle.paddingBottom, 10),
                parseInt(elm.currentStyle.paddingLeft, 10)
            ];
        }
        else if(dom.isHostObjectProperty(document, 'defaultView') &&
                dom.isHostMethod(document.defaultView, 'getComputedStyle')){
            var s = document.defaultView.getComputedStyle(elm, null);
            padding = [
                parseInt(s.paddingTop, 10),
                parseInt(s.paddingRight, 10),
                parseInt(s.paddingBottom, 10),
                parseInt(s.paddingLeft, 10)
            ];
        }
        padding['top'] = padding[0];
        padding['right'] = padding[1];
        padding['bottom'] = padding[2];
        padding['left'] = padding[3];
        return padding;
    }
    function getElementOffsets(elm){
        var offLeft =
            offTop = 0;
        if(elm.offsetParent){
            do{
                offLeft += elm.offsetLeft;
                offTop += elm.offsetTop;
            }while(!!(elm = elm.offsetParent));
        }
        var off = [offLeft, offTop];
        off['left'] = off[0];
        off['top'] = off[1];
        return off;
    }

    function getScrolledDimensions(elm){
        var dom = learndo.lib.dom, dim,
            IS_BODY_ACTING_ROOT = dom.isHostObjectProperty(document, 'documentElement') && document.documentElement.clientHeight === 0;

        if(!dom.isElement(elm)){
            var body = document.body;
            elm = (IS_BODY_ACTING_ROOT) ? body : document.documentElement;
            dim = [elm.scrollLeft || body.scrollLeft, elm.scrollTop || body.scrollTop];
            body = null;
        }
        if(!dim){
            dim = [elm.scrollLeft || 0, elm.scrollTop || 0];
        }
        dim['left'] = dim[0];
        dim['top'] = dim[1];
        return dim;
    }

    return {
        'getWinDimensions': getWinDimensions,
        'getElementOffsets': getElementOffsets,
        'getElementDimensions': getElementDimensions,
        'getScrolledDimensions': getScrolledDimensions,
        'getPaddingElm': getPaddingElm,
        'getInnerDimensionElm': getInnerDimensionElm
    };
})();

learndo.lib.selector = (function() {
    var dom = learndo.lib.dom;
    function getHead(){
        var head = document.getElementsByTagName('head');
        return (head && head[0]) ? head[0] : null;
    }
    function getLearndoElements(scope){
        scope =  scope || document.body;
        return dom.getChildrenByProperty(scope, 'className', 'learndo-[\\d\\w]+', false, true);
    }
    function isLearndoOpen(){
        return (document.getElementById('learn-do') == null) ? false : true;
    }
    return {
        'getHead': getHead,
        'getLearndoElements':getLearndoElements,
        'isLearndoOpen': isLearndoOpen
    };
})();

learndo.lib.style = (function() {

    function setStyles(element, styles) {
        for (var style in styles) {
            element.style[style] = styles[style];
        }
        return element;
    }

    return {
        'setStyles': setStyles
    }

})();
