/*! UIkit 2.27.2 | www.getuikit.com | © 2014 YOOtheme | MIT License */ (function(addon) {

var component;

if (window.UIkit) {
    component = addon(UIkit);
}

if (typeof define == 'function' && define.amd) {
    define('uikit-htmleditor', ['uikit'], function(){
        return component || addon(UIkit);
    });
}

})(function(UI) {

"use strict";

var editors = [];

UI.component('htmleditor', {

    defaults: {
        iframe       : false,
        mode         : 'split',
        markdown     : false,
        autocomplete : true,
        enablescripts: false,
        height       : 500,
        maxsplitsize : 1000,
        codemirror   : { mode: 'htmlmixed', lineWrapping: true, dragDrop: false, autoCloseTags: true, matchTags: true, autoCloseBrackets: true, matchBrackets: true, indentUnit: 4, indentWithTabs: false, tabSize: 4, hintOptions: {completionSingle:false} },
        toolbar      : [ 'bold', 'italic', 'strike', 'link', 'image', 'blockquote', 'listUl', 'listOl' ],
        lblPreview   : 'Preview',
        lblCodeview  : 'HTML',
        lblMarkedview: 'Markdown'
    },

    boot: function() {

        // init code
        UI.ready(function(context) {

            UI.$('textarea[data-uk-htmleditor]', context).each(function() {

                var editor = UI.$(this);

                if (!editor.data('htmleditor')) {
                    UI.htmleditor(editor, UI.Utils.options(editor.attr('data-uk-htmleditor')));
                }
            });
        });
    },

    init: function() {

        var $this = this, tpl = UI.components.htmleditor.template;

        this.CodeMirror = this.options.CodeMirror || CodeMirror;
        this.buttons    = {};

        tpl = tpl.replace(/\{:lblPreview}/g, this.options.lblPreview);
        tpl = tpl.replace(/\{:lblCodeview}/g, this.options.lblCodeview);

        this.htmleditor = UI.$(tpl);
        this.content    = this.htmleditor.find('.uk-htmleditor-content');
        this.toolbar    = this.htmleditor.find('.uk-htmleditor-toolbar');
        this.preview    = this.htmleditor.find('.uk-htmleditor-preview').children().eq(0);
        this.code       = this.htmleditor.find('.uk-htmleditor-code');

        this.element.before(this.htmleditor).appendTo(this.code);
        this.editor = this.CodeMirror.fromTextArea(this.element[0], this.options.codemirror);
        this.editor.htmleditor = this;
        this.editor.on('change', UI.Utils.debounce(function() { $this.render(); }, 150));
        this.editor.on('change', function() {
            $this.editor.save();
            $this.element.trigger('input');
        });
        this.code.find('.CodeMirror').css('height', this.options.height);

        // iframe mode?
        if (this.options.iframe) {

            this.iframe = UI.$('<iframe class="uk-htmleditor-iframe" frameborder="0" scrolling="auto" height="100" width="100%"></iframe>');
            this.preview.append(this.iframe);

            // must open and close document object to start using it!
            this.iframe[0].contentWindow.document.open();
            this.iframe[0].contentWindow.document.close();

            this.preview.container = UI.$(this.iframe[0].contentWindow.document).find('body');

            // append custom stylesheet
            if (typeof(this.options.iframe) === 'string') {
                this.preview.container.parent().append('<link rel="stylesheet" href="'+this.options.iframe+'">');
            }

        } else {
            this.preview.container = this.preview;
        }

        UI.$win.on('resize load', UI.Utils.debounce(function() { $this.fit(); }, 200));

        var previewContainer = this.iframe ? this.preview.container:$this.preview.parent(),
            codeContent      = this.code.find('.CodeMirror-sizer'),
            codeScroll       = this.code.find('.CodeMirror-scroll').on('scroll', UI.Utils.debounce(function() {

                if ($this.htmleditor.attr('data-mode') == 'tab') return;

                // calc position
                var codeHeight      = codeContent.height() - codeScroll.height(),
                    previewHeight   = previewContainer[0].scrollHeight - ($this.iframe ? $this.iframe.height() : previewContainer.height()),
                    ratio           = previewHeight / codeHeight,
                    previewPosition = codeScroll.scrollTop() * ratio;

                // apply new scroll
                previewContainer.scrollTop(previewPosition);

            }, 10));

        this.htmleditor.on('click', '.uk-htmleditor-button-code, .uk-htmleditor-button-preview', function(e) {

            e.preventDefault();

            if ($this.htmleditor.attr('data-mode') == 'tab') {

                $this.htmleditor.find('.uk-htmleditor-button-code, .uk-htmleditor-button-preview').removeClass('uk-active').filter(this).addClass('uk-active');

                $this.activetab = UI.$(this).hasClass('uk-htmleditor-button-code') ? 'code' : 'preview';
                $this.htmleditor.attr('data-active-tab', $this.activetab);
                $this.editor.refresh();
            }
        });

        // toolbar actions
        this.htmleditor.on('click', 'a[data-htmleditor-button]', function() {

            if (!$this.code.is(':visible')) return;

            $this.trigger('action.' + UI.$(this).data('htmleditor-button'), [$this.editor]);
        });

        this.preview.parent().css('height', this.code.height());

        // autocomplete
        if (this.options.autocomplete && this.CodeMirror.showHint && this.CodeMirror.hint && this.CodeMirror.hint.html) {

            this.editor.on('inputRead', UI.Utils.debounce(function() {
                var doc = $this.editor.getDoc(), POS = doc.getCursor(), mode = $this.CodeMirror.innerMode($this.editor.getMode(), $this.editor.getTokenAt(POS).state).mode.name;

                if (mode == 'xml') { //html depends on xml

                    var cur = $this.editor.getCursor(), token = $this.editor.getTokenAt(cur);

                    if (token.string.charAt(0) == '<' || token.type == 'attribute') {
                        $this.CodeMirror.showHint($this.editor, $this.CodeMirror.hint.html, { completeSingle: false });
                    }
                }
            }, 100));
        }

        this.debouncedRedraw = UI.Utils.debounce(function () { $this.redraw(); }, 5);

        this.on('init.uk.component', function() {
            $this.debouncedRedraw();
        });

        this.element.attr('data-uk-check-display', 1).on('display.uk.check', function(e) {
            if (this.htmleditor.is(":visible")) this.fit();
        }.bind(this));

        editors.push(this);
    },

    addButton: function(name, button) {
        this.buttons[name] = button;
    },

    addButtons: function(buttons) {
        UI.$.extend(this.buttons, buttons);
    },

    replaceInPreview: function(regexp, callback) {

        var editor = this.editor, results = [], value = editor.getValue(), offset = -1, index = 0;

        this.currentvalue = this.currentvalue.replace(regexp, function() {

            offset = value.indexOf(arguments[0], ++offset);

            var match  = {
                matches: arguments,
                from   : translateOffset(offset),
                to     : translateOffset(offset + arguments[0].length),
                replace: function(value) {
                    editor.replaceRange(value, match.from, match.to);
                },
                inRange: function(cursor) {

                    if (cursor.line === match.from.line && cursor.line === match.to.line) {
                        return cursor.ch >= match.from.ch && cursor.ch < match.to.ch;
                    }

                    return  (cursor.line === match.from.line && cursor.ch   >= match.from.ch) ||
                            (cursor.line >   match.from.line && cursor.line <  match.to.line) ||
                            (cursor.line === match.to.line   && cursor.ch   <  match.to.ch);
                }
            };

            var result = typeof(callback) === 'string' ? callback : callback(match, index);

            if (!result && result !== '') {
                return arguments[0];
            }

            index++;

            results.push(match);
            return result;
        });

        function translateOffset(offset) {
            var result = editor.getValue().substring(0, offset).split('\n');
            return { line: result.length - 1, ch: result[result.length - 1].length }
        }

        return results;
    },

    _buildtoolbar: function() {

        if (!(this.options.toolbar && this.options.toolbar.length)) return;

        var $this = this, bar = [];

        this.toolbar.empty();

        this.options.toolbar.forEach(function(button) {
            if (!$this.buttons[button]) return;

            var title = $this.buttons[button].title ? $this.buttons[button].title : button;

            bar.push('<li><a data-htmleditor-button="'+button+'" title="'+title+'" data-uk-tooltip>'+$this.buttons[button].label+'</a></li>');
        });

        this.toolbar.html(bar.join('\n'));
    },

    fit: function() {

        var mode = this.options.mode;

        if (mode == 'split' && this.htmleditor.width() < this.options.maxsplitsize) {
            mode = 'tab';
        }

        if (mode == 'tab') {
            if (!this.activetab) {
                this.activetab = 'code';
                this.htmleditor.attr('data-active-tab', this.activetab);
            }

            this.htmleditor.find('.uk-htmleditor-button-code, .uk-htmleditor-button-preview').removeClass('uk-active')
                .filter(this.activetab == 'code' ? '.uk-htmleditor-button-code' : '.uk-htmleditor-button-preview')
                .addClass('uk-active');
        }

        this.editor.refresh();
        this.preview.parent().css('height', this.code.height());

        this.htmleditor.attr('data-mode', mode);
    },

    redraw: function() {
        this._buildtoolbar();
        this.render();
        this.fit();
    },

    getMode: function() {
        return this.editor.getOption('mode');
    },

    getCursorMode: function() {
        var param = { mode: 'html'};
        this.trigger('cursorMode', [param]);
        return param.mode;
    },

    render: function() {

        this.currentvalue = this.editor.getValue();

        if (!this.options.enablescripts) {
            this.currentvalue = this.currentvalue.replace(/<(script|style)\b[^<]*(?:(?!<\/(script|style)>)<[^<]*)*<\/(script|style)>/img, '');
        }

        // empty code
        if (!this.currentvalue) {

            this.element.val('');
            this.preview.container.html('');

            return;
        }

        this.trigger('render', [this]);
        this.trigger('renderLate', [this]);

        this.preview.container.html(this.currentvalue);
    },

    addShortcut: function(name, callback) {
        var map = {};
        if (!UI.$.isArray(name)) {
            name = [name];
        }

        name.forEach(function(key) {
            map[key] = callback;
        });

        this.editor.addKeyMap(map);

        return map;
    },

    addShortcutAction: function(action, shortcuts) {
        var editor = this;
        this.addShortcut(shortcuts, function() {
            editor.element.trigger('action.' + action, [editor.editor]);
        });
    },

    replaceSelection: function(replace) {

        var text = this.editor.getSelection();

        if (!text.length) {

            var cur     = this.editor.getCursor(),
                curLine = this.editor.getLine(cur.line),
                start   = cur.ch,
                end     = start;

            while (end < curLine.length && /[\w$]+/.test(curLine.charAt(end))) ++end;
            while (start && /[\w$]+/.test(curLine.charAt(start - 1))) --start;

            var curWord = start != end && curLine.slice(start, end);

            if (curWord) {
                this.editor.setSelection({ line: cur.line, ch: start}, { line: cur.line, ch: end });
                text = curWord;
            }
        }

        var html = replace.replace('$1', text);

        this.editor.replaceSelection(html, 'end');
        this.editor.focus();
    },

    replaceLine: function(replace) {
        var pos  = this.editor.getDoc().getCursor(),
            text = this.editor.getLine(pos.line),
            html = replace.replace('$1', text);

        this.editor.replaceRange(html , { line: pos.line, ch: 0 }, { line: pos.line, ch: text.length });
        this.editor.setCursor({ line: pos.line, ch: html.length });
        this.editor.focus();
    },

    save: function() {
        this.editor.save();
    }
});

UI.components.htmleditor.template = [
    '<div class="uk-htmleditor uk-clearfix" data-mode="split">',
        '<div class="uk-htmleditor-navbar">',
            '<ul class="uk-htmleditor-navbar-nav uk-htmleditor-toolbar"></ul>',
            '<div class="uk-htmleditor-navbar-flip">',
                '<ul class="uk-htmleditor-navbar-nav">',
                    '<li class="uk-htmleditor-button-code"><a>{:lblCodeview}</a></li>',
                    '<li class="uk-htmleditor-button-preview"><a>{:lblPreview}</a></li>',
                    '<li><a data-htmleditor-button="fullscreen"><i class="uk-icon-expand"></i></a></li>',
                '</ul>',
            '</div>',
        '</div>',
        '<div class="uk-htmleditor-content">',
            '<div class="uk-htmleditor-code"></div>',
            '<div class="uk-htmleditor-preview"><div></div></div>',
        '</div>',
    '</div>'
].join('');

UI.plugin('htmleditor', 'base', {

    init: function(editor) {

        editor.addButtons({

            fullscreen: {
                title  : 'Fullscreen',
                label  : '<i class="uk-icon-expand"></i>'
            },
            bold : {
                title  : 'Bold',
                label  : '<i class="uk-icon-bold"></i>'
            },
            italic : {
                title  : 'Italic',
                label  : '<i class="uk-icon-italic"></i>'
            },
            strike : {
                title  : 'Strikethrough',
                label  : '<i class="uk-icon-strikethrough"></i>'
            },
            blockquote : {
                title  : 'Blockquote',
                label  : '<i class="uk-icon-quote-right"></i>'
            },
            link : {
                title  : 'Link',
                label  : '<i class="uk-icon-link"></i>'
            },
            image : {
                title  : 'Image',
                label  : '<i class="uk-icon-picture-o"></i>'
            },
            listUl : {
                title  : 'Unordered List',
                label  : '<i class="uk-icon-list-ul"></i>'
            },
            listOl : {
                title  : 'Ordered List',
                label  : '<i class="uk-icon-list-ol"></i>'
            }

        });

        addAction('bold', '<strong>$1</strong>');
        addAction('italic', '<em>$1</em>');
        addAction('strike', '<del>$1</del>');
        addAction('blockquote', '<blockquote><p>$1</p></blockquote>', 'replaceLine');
        addAction('link', '<a href="http://">$1</a>');
        addAction('image', '<img src="http://" alt="$1">');

        var listfn = function(tag) {
            if (editor.getCursorMode() == 'html') {

                tag = tag || 'ul';

                var cm        = editor.editor,
                    doc       = cm.getDoc(),
                    pos       = doc.getCursor(true),
                    posend    = doc.getCursor(false),
                    im        = CodeMirror.innerMode(cm.getMode(), cm.getTokenAt(cm.getCursor()).state),
                    inList    = im && im.state && im.state.context && ['ul','ol'].indexOf(im.state.context.tagName) != -1;

                for (var i=pos.line; i<(posend.line+1);i++) {
                    cm.replaceRange('<li>'+cm.getLine(i)+'</li>', { line: i, ch: 0 }, { line: i, ch: cm.getLine(i).length });
                }

                if (!inList) {
                    cm.replaceRange('<'+tag+'>'+"\n"+cm.getLine(pos.line), { line: pos.line, ch: 0 }, { line: pos.line, ch: cm.getLine(pos.line).length });
                    cm.replaceRange(cm.getLine((posend.line+1))+"\n"+'</'+tag+'>', { line: (posend.line+1), ch: 0 }, { line: (posend.line+1), ch: cm.getLine((posend.line+1)).length });
                    cm.setCursor({ line: posend.line+1, ch: cm.getLine(posend.line+1).length });
                } else {
                    cm.setCursor({ line: posend.line, ch: cm.getLine(posend.line).length });
                }

                cm.focus();
            }
        };

        editor.on('action.listUl', function() {
            listfn('ul');
        });

        editor.on('action.listOl', function() {
            listfn('ol');
        });

        editor.htmleditor.on('click', 'a[data-htmleditor-button="fullscreen"]', function() {

            editor.htmleditor.toggleClass('uk-htmleditor-fullscreen');

            var wrap = editor.editor.getWrapperElement();

            if (editor.htmleditor.hasClass('uk-htmleditor-fullscreen')) {

                var fixedParent = false, parents = editor.htmleditor.parents().each(function(){
                    if (UI.$(this).css('position')=='fixed' && !UI.$(this).is('html')) {
                        fixedParent = UI.$(this);
                    }
                });

                editor.htmleditor.data('fixedParents', false);

                if (fixedParent) {

                    var transformed = [];

                    fixedParent = fixedParent.parent().find(parents).each(function(){

                        if (UI.$(this).css('transform') != 'none') {
                            transformed.push(UI.$(this).data('transform-reset', {
                                'transform': this.style.transform,
                                '-webkit-transform': this.style.webkitTransform,
                                '-webkit-transition':this.style.webkitTransition,
                                'transition':this.style.transition
                            }).css({
                                'transform': 'none',
                                '-webkit-transform': 'none',
                                '-webkit-transition':'none',
                                'transition':'none'
                            }));
                        }
                    });

                    editor.htmleditor.data('fixedParents', transformed);
                }

                editor.editor.state.fullScreenRestore = {scrollTop: window.pageYOffset, scrollLeft: window.pageXOffset, width: wrap.style.width, height: wrap.style.height};
                wrap.style.width  = '';
                wrap.style.height = editor.content.height()+'px';
                document.documentElement.style.overflow = 'hidden';

            } else {

                document.documentElement.style.overflow = '';
                var info = editor.editor.state.fullScreenRestore;
                wrap.style.width = info.width; wrap.style.height = info.height;
                window.scrollTo(info.scrollLeft, info.scrollTop);

                if (editor.htmleditor.data('fixedParents')) {
                    editor.htmleditor.data('fixedParents').forEach(function(parent){
                        parent.css(parent.data('transform-reset'));
                    });
                }
            }

            setTimeout(function() {
                editor.fit();
                UI.$win.trigger('resize');
            }, 50);
        });

        editor.addShortcut(['Ctrl-S', 'Cmd-S'], function() { editor.element.trigger('htmleditor-save', [editor]); });
        editor.addShortcutAction('bold', ['Ctrl-B', 'Cmd-B']);

        function addAction(name, replace, mode) {
            editor.on('action.'+name, function() {
                if (editor.getCursorMode() == 'html') {
                    editor[mode == 'replaceLine' ? 'replaceLine' : 'replaceSelection'](replace);
                }
            });
        }
    }
});

UI.plugin('htmleditor', 'markdown', {

    init: function(editor) {

        var parser = editor.options.mdparser || window.marked || null;

        if (!parser) return;

        if (editor.options.markdown) {
            enableMarkdown();
        }

        addAction('bold', '**$1**');
        addAction('italic', '*$1*');
        addAction('strike', '~~$1~~');
        addAction('blockquote', '> $1', 'replaceLine');
        addAction('link', '[$1](http://)');
        addAction('image', '![$1](http://)');

        editor.on('action.listUl', function() {

            if (editor.getCursorMode() == 'markdown') {

                var cm      = editor.editor,
                    pos     = cm.getDoc().getCursor(true),
                    posend  = cm.getDoc().getCursor(false);

                for (var i=pos.line; i<(posend.line+1);i++) {
                    cm.replaceRange('* '+cm.getLine(i), { line: i, ch: 0 }, { line: i, ch: cm.getLine(i).length });
                }

                cm.setCursor({ line: posend.line, ch: cm.getLine(posend.line).length });
                cm.focus();
            }
        });

        editor.on('action.listOl', function() {

            if (editor.getCursorMode() == 'markdown') {

                var cm      = editor.editor,
                    pos     = cm.getDoc().getCursor(true),
                    posend  = cm.getDoc().getCursor(false),
                    prefix  = 1;

                if (pos.line > 0) {
                    var prevline = cm.getLine(pos.line-1), matches;

                    if(matches = prevline.match(/^(\d+)\./)) {
                        prefix = Number(matches[1])+1;
                    }
                }

                for (var i=pos.line; i<(posend.line+1);i++) {
                    cm.replaceRange(prefix+'. '+cm.getLine(i), { line: i, ch: 0 }, { line: i, ch: cm.getLine(i).length });
                    prefix++;
                }

                cm.setCursor({ line: posend.line, ch: cm.getLine(posend.line).length });
                cm.focus();
            }
        });

        editor.on('renderLate', function() {
            if (editor.editor.options.mode == 'gfm') {
                editor.currentvalue = parser(editor.currentvalue);
            }
        });

        editor.on('cursorMode', function(e, param) {
            if (editor.editor.options.mode == 'gfm') {
                var pos = editor.editor.getDoc().getCursor();
                if (!editor.editor.getTokenAt(pos).state.base.htmlState) {
                    param.mode = 'markdown';
                }
            }
        });

        UI.$.extend(editor, {

            enableMarkdown: function() {
                enableMarkdown();
                this.render();
            },
            disableMarkdown: function() {
                this.editor.setOption('mode', 'htmlmixed');
                this.htmleditor.find('.uk-htmleditor-button-code a').html(this.options.lblCodeview);
                this.render();
            }

        });

        // switch markdown mode on event
        editor.on({
            enableMarkdown  : function() { editor.enableMarkdown(); },
            disableMarkdown : function() { editor.disableMarkdown(); }
        });

        function enableMarkdown() {
            editor.editor.setOption('mode', 'gfm');
            editor.htmleditor.find('.uk-htmleditor-button-code a').html(editor.options.lblMarkedview);
        }

        function addAction(name, replace, mode) {
            editor.on('action.'+name, function() {
                if (editor.getCursorMode() == 'markdown') {
                    editor[mode == 'replaceLine' ? 'replaceLine' : 'replaceSelection'](replace);
                }
            });
        }
    }
});

return UI.htmleditor;

});