/*
 Wysiwyg-редактор

var zeditor = new zEditor($('#zeditor')[0], {
    'toolbar_buttons': ['brackets'],
    'relative_toolbar_buttons': ['brackets','bold','italic','underline','strikethrough','highlight']
}); 

*/

class zEditor {

    constructor(editor,  options){

        var defaultOptions = {
            base_class: 'zeditor',
            wrapper_base_class: 'zeditor_wr',
            wrapper_class: null,
            toolbar_buttons: null,
            relative_toolbar_buttons: ['bold','italic','underline'],
            after_init: function(){}            
        };

        this.options = Object.assign(defaultOptions, options);
        this.editor = editor;        
        this.placeholder = null;
        this.content = null;
        this.editor_wrapper = null;
        this.elements = {};

        this.editor_is_focused = false;
        this.is_selecting = false;
        this.relative_toolbar_top_offset = 14;

        this.init();
        this.events();

    }

    // =================
    // публичные методы
    // =================
    updateContent = function(html) {
       this.editor.innerHTML = html;
    };


    // =================
    // INIT
    // =================
    init(){

        // сохраняем содержимое в переменную
        //console.log(this.editor);
        this.content = this.editor.innerHTML.replace('\n','').replace(/\s+/g, ' ');
        
        // добавляем contenteditable="true"
        this.editor.setAttribute("contenteditable", true);

        // добавляем базовый css-класс
        this.editor.classList.add(this.options.base_class);

        // плейсхолдер
        if(this.editor.hasAttribute('placeholder')){
            var placeholder = this.editor.getAttribute('placeholder');
            if(placeholder!=''){
                this.placeholder = placeholder;
                this.editor.setAttribute("placeholder", '');
            }
        }
        
        // если в размертке нет обертки - добавляем
        if(!$(this.editor).parent().hasClass(this.options.wrapper_base_class)){
            $(this.editor).wrap('<div class="'+this.options.wrapper_base_class+'"></div>');
        }
        
        
        // добавляем техническую обертку
        if(!$(this.editor).parent().hasClass('zeditor_outer')){
            $(this.editor).wrap('<div class="zeditor_outer"></div>');
        } 

        this.editor_wrapper = this.editor.closest('.'+this.options.wrapper_base_class);

        // обновляем состояние плейсхолдера (после добавления editor_wrapper!)
        if(this.editorIsEmpty()){
            this.placeholderStatus('show');
        }else{
            this.placeholderStatus('hide');
        }

        // добавляем внешний класс, если нужно
        if(this.options.wrapper_class){
            this.editor_wrapper.classList.add(this.options.wrapper_class);
        }

        // кнопки
        var buttons = {
            'brackets':       '<button class="zeditor_button brackets" data-action="brackets">[ ]</button>',
            'slash':          '<button class="zeditor_button slash" data-action="slash">/</button>',
            'bold':           '<button class="zeditor_button bold" data-action="bold" data-command="bold" title="жирный">B</button>',
            'italic':         '<button class="zeditor_button italic" data-action="italic" data-command="italic" title="курсив">I</button>',
            'underline':      '<button class="zeditor_button underline" data-action="underline" data-command="underline" title="подчеркнутый">U</button>',
            'strikethrough':  '<button class="zeditor_button strikethrough" data-action="strikethrough" data-command="strikeThrough" title="зачеркнутый">S</button>',
            'highlight':      '<button class="zeditor_button highlight" data-action="highlight" title="выделить">H</button>'
        };

        // обычная панель
        if(this.options.toolbar_buttons){
            var buttons_tpl = '';
            this.options.toolbar_buttons.forEach(function(btn_name){
                buttons_tpl+= buttons[btn_name];
            });

            // добавляем обычную панель в DOM
            var toolbar = $('<div class="zeditor_toolbar">'+buttons_tpl+'</div>');
            $(this.editor_wrapper).prepend(toolbar);
        }

        // липкая панель
        if(this.options.relative_toolbar_buttons && this.options.relative_toolbar_buttons.length > 0){
            var relative_buttons_tpl = '';
            this.options.relative_toolbar_buttons.forEach(function(btn_name){
                relative_buttons_tpl+= buttons[btn_name];
            });

            // добавляем липкую панель в DOM
            var relative_toolbar = $('<div class="relative_toolbar"><div class="relative_toolbar_corner"></div>'+relative_buttons_tpl+'</div>');
            $(this.editor_wrapper).append(relative_toolbar);

        }

        this.elements = {
            toolbar: $(this.editor_wrapper).find('.zeditor_toolbar')[0],
            relative_toolbar: $(this.editor_wrapper).find('.relative_toolbar')[0]            
        };


        // калбек
        this.options.after_init(this);

    }

    // =================
    // EVENTS
    // =================
    events(){

        var _this = this;

        
        // keyup
        this.editor.addEventListener('keyup', function(e){

            // если нет выделения - скрываем тулбар
            if(!this.selectionContainsContent()){
                this.hideToolbar();
            }                

            // показываем плейсхолдер (если редактор пустой)
            if(this.editorIsEmpty()){
                this.placeholderStatus('show');
            }else{
                this.placeholderStatus('hide');
            }

        }.bind(this));

        
        // blur
        this.editor.addEventListener('blur', function(e){       

            // показываем плейсхолдер (если редактор пустой)
            if(this.editorIsEmpty()){this.placeholderStatus('show');}

        }.bind(this));

            
        // focus
        this.editor.addEventListener('focus', function(e){      
            //console.log('{focus}');      
            this.editor_is_focused = true;         
            this.showToolbar();

            // скрываем плейсхолдер (если редактор пустой)
            if(this.editorIsEmpty()){this.placeholderStatus('hide');}

        }.bind(this));


        // dblclick
        this.editor.addEventListener('dblclick', function(e){
            
            //console.log('{dblclick}');

            // получаем текущее выделение
            var selection = _this.getSelection();
            var range = _this.getRange();

            
            var selection_start = {
                'nodeName': range.startContainer.parentNode.nodeName,               
                'nodeType': range.startContainer.nodeType,
                'startOffset': range.startOffset,
                'textContent': range.startContainer.textContent
            };
            
            var selection_end = {
                'nodeName': range.endContainer.parentNode.nodeName,                
                'nodeType': range.endContainer.nodeType,
                'endOffset': range.endOffset,
                'textContent': range.endContainer.textContent
            };

           //console.log(selection_start, selection_end);
            //console.log(selection_end);            
            var selection_html = range.commonAncestorContainer.parentNode.outerHTML;
            var selection_length = range.toString().length;

            //console.log(selection_html);
            //console.log('selection_length', selection_length);
            //console.log('['+range.toString()+']');

            // выделение начинается и заканчивается в одной ноде
            if(range.startContainer.parentNode === range.endContainer.parentNode){

                //console.log('выделение начинается и заканчивается в одной ноде!');

                if (range.endOffset > 0 && (range.endContainer.nodeType===3) && (range.endContainer.textContent != '')){

                    // обрезаем пробел справа
                   // while ( /[\s\S]+\s$/.test(range.endContainer.textContent.substr(0,range.endOffset)) ) { //have to use instead of range.toString() for IE11+ as it auto-trims whitespaces there and in selection.toString()
                    while ( /[\s\S]+\s$/.test(range.toString()) ) { //have to use instead of range.toString() for IE11+ as it auto-trims whitespaces there and in selection.toString()
                        range.setEnd(range.endContainer, range.endOffset - 1);
                    }
                }

            // начало и конец выделения в разных нодах
            }else{

                //console.log('начало и конец выделения в разных нодах');

                // Firefox при выделении может заканчивать выделение в нулевой позиции следующей ноды (тега)
                // пример: [слово <b>]следующее</b>
                if(range.endOffset==0){
                    
                    //console.log('выделение закончилось в нулевой позиции следующей ноды?');

                    // длина выделения
                    var selection_length = range.toString().length;

                    //console.log('уменьшаем длину на 1 символ справа');

                    // уменьшаем длину на 1 символ справа
                    range.setEnd(range.startContainer, range.startOffset + selection_length - 1);

                // пример: [<b>следующее</b> ]слово
                }else{

                    //console.log('уменьшаем длину на 1 символ справа');
                    range.setEnd(range.endContainer, range.endOffset - 1);

                    //var range = _this.getRange();
                    //console.log('['+range.toString()+']');


                }
            }

            // обновляем состояния кнопок тулбаров
            this.toolbarButtonStateUpdate();

        }.bind(this));


        // клик по любому месту документа
        document.addEventListener('click', function(e){

            //console.log('{document.click}');

            // редактор был в фокусе
            if(this.editor_is_focused){

                //console.dir(e.target);

                //console.log('closest', e.target.closest(this.options.base_class));
                //console.log('contains', this.editor.contains(e.target));

                // клик вне редактора и тулбара
                //if (!this.editor.contains(e.target) && !this.elements.relative_toolbar.contains(e.target)) {
                //if (!this.elements.relative_toolbar.contains(e.target)) {
                
                // родитель редактора не содержит кликнутый элемент
                if (!this.editor_wrapper.contains(e.target)) {

                    //console.log('клик вне редактора и тулбара');

                    this.editor_is_focused = false;

                    //console.log('очищаем содержимое от всех тегов, кроме разрешенных');

                    // очищаем содержимое от всех тегов, кроме разрешенных
                    var editor_content = this.editor.innerHTML;                    
                    this.editor.innerHTML = html_strip_tags(editor_content, '<br><b><strong><i><u><s><strike><mark>');

                    // скрываем панельку
                    //this.hideToolbar();
                }

            }

        }.bind(this));


        // selectstart
        document.addEventListener("selectstart", function(e){
            
            //console.log('{selectstart}');            

            // скрываем тулбар
            this.hideToolbar();

        }.bind(this));


        // если "отжат" шифт - вероятно обновлено было выделение..
        document.addEventListener('keyup', function(e) {     
            var key = e.keyCode || e.which;     
            if (key == 16) {
                this.showToolbar();
            }
        }.bind(this));


        // selectionchange
        document.addEventListener("selectionchange", function(){

            if(!this.selectionContainsContent()){return;}

            //console.log('{selectionchange}');

            this.is_selecting = true;

            //console.log('is_selecting', this.is_selecting);

            //this.showToolbar();

        }.bind(this));



        // touchstart
        document.addEventListener("touchstart", function(){
            //console.log('{touchstart}');
        }.bind(this));
        
        // touchend
        document.addEventListener("touchend", function(){
            //console.log('{touchend}');
        }.bind(this));

        document.addEventListener("pointerup", function(){
            
            //console.log('{pointerup}');
            //console.log('is_selecting', this.is_selecting);

            if(this.is_selecting){
                //console.log('показываем панельку');
                this.showToolbar();
                this.is_selecting = false;
            }

          

        }.bind(this));


        // ресайз окна - обновляем положение тулбара
        $(window).resize(function(){
            _this.relativeToolbarPositionUpdate();
        });


        // скрол окна - обновляем положение тулбара
        $(window).scroll(function(){        
            _this.relativeToolbarPositionUpdate();
        });


        // запрещаем drop мышкой текста в поле редактора        
        $(this.editor).on('drop', function(e){
            e.preventDefault();
        });

        /*
        document.addEventListener('paste', function (evt) {
            
            evt.preventDefault();

            var clipdata = evt.clipboardData || window.clipboardData;
            var text = clipdata.getData('text/plain');
            var html = clipdata.getData('text/html');
            console.log('text', text);
            console.log('html', html);
            html = html_strip_tags(html, '<br><b><strong><i><u><s><strike><mark>');
            console.log(html);

            document.execCommand('insertHtml', false, html);

        });
        */


        // paste - очистка текста от всех тегов
        $(this.editor).on('paste', function(e){              
            
            e.preventDefault();

            var clipdata = (e.originalEvent || e).clipboardData;
            var text = clipdata.getData('text/plain').trim();
            var html = clipdata.getData('text/html').trim();
            
            if(html==''){
                            
                text = text.replace(/&nbsp;/g, ' ');
                
                // хак: при копировании из некоторых pdf-учебников вместо пробелов вставлялся этот символ (�).
                // решил его аккуратно заменять на пробел. Возможно это будет иметь неожиданный сайд-эффект.
                text = text.replace(/�/g, ' ');

                document.execCommand('insertText', false, text);

            }else{

                // 1. при вставке нужно сохранить абзацы
                html = html.replace(/<\/p>/g,'<br><br>');
                // 2. заменяем спецсимвол на обычный пробел
                //html = html.replace(/&nbsp;/g, ' ');
                html = html.replace(/[&]nbsp[;]/gi," ");
                // 3. вырезаем все теги, кроме..
                html = html_strip_tags(html, '<br><b><strong><i><u><s><strike><mark>');
                
                document.execCommand('insertHtml', false, html);

            }            
            
        });
  

        // клик по кнопке '/'
        $(this.editor_wrapper).on('click', '.zeditor_button.slash', function(e){
            
            e.preventDefault();
            
            var sel, range, insert_symbol = '/';
            if (window.getSelection) {
                sel = window.getSelection();
                console.log('sel', sel);
                if (sel.getRangeAt && sel.rangeCount) {
                    range = sel.getRangeAt(0);
                    range.deleteContents();
                    range.insertNode(document.createTextNode(insert_symbol));
                }
            } else if (document.selection && document.selection.createRange) {
                document.selection.createRange().text = insert_symbol;
            }
            

        });


        // при нажатии enter ставим <br> вместо "<div><br></div>"
        //$(document).on("keypress", function(e){
        $('.zeditor').on("keypress", function(e){
            if (e.which == 13) {
                if (window.getSelection) {
                    var selection = window.getSelection(),
                        range = selection.getRangeAt(0),
                        br = document.createElement("br");
                    range.deleteContents();
                    range.insertNode(br);
                    range.setStartAfter(br);
                    range.setEndAfter(br);
                    range.collapse(false);
                    selection.removeAllRanges();
                    selection.addRange(range);
                    return false;
                }
            }
        });        

        // клик по кнопке панели инструментов
        $(this.editor_wrapper).on('click', '.zeditor_button', function(e){

            e.preventDefault();

            if(!_this.selectionContainsContent()){return;}

            //console.log('клик по кнопке панели инструментов');

            var action = $(this).data('action');
            var command = $(this).data('command');

            // стандартные кнопки
            if(command!==undefined){

                //console.log('стандартные кнопки');

                document.execCommand(command, false, null);
                $(_this.editor_wrapper).find('.zeditor_button[data-command='+command+']').toggleClass('is_active');

            // highlight
            }else if(action=='highlight'){

                // получаем текущее выделение
                var selection = _this.getSelection();
                var range = _this.getRange();

                var selection_html = range.commonAncestorContainer.parentNode.outerHTML;
        
                var mark_btn_is_active = false;

                // выбран текст содержащий "<mark>слово</mark>"
                if(/^<mark>.*<\/mark>$/.test(selection_html)){mark_btn_is_active = true;}
                
                // выбрана нода MARK
                if(range.commonAncestorContainer.parentNode.nodeName=='MARK'){mark_btn_is_active = true;}

                // выделенный текст уже содержит <mark> - отменяем
                if(mark_btn_is_active){

                    var container = range.startContainer.parentNode;
                    $(container).contents().unwrap();

                    $(_this.editor_wrapper).find('.zeditor_button.highlight').removeClass('is_active');

                // оборачиваем в тег <mark>
                }else{

                    var selection_text = selection.toString();
                    document.execCommand("insertHTML", false, "<mark>"+selection_text+"</mark>");

                    $(_this.editor_wrapper).find('.zeditor_button.highlight').addClass('is_active');
                }

            // brackets
            }else if(action=='brackets'){
                
                // получаем текущее выделение
                var selection = _this.getSelection();
                var selectedText = selection.toString();
                var range = _this.getRange();

                // выделение уже с кавычками - убираем
                if(/\[.*\]/.test(range)){

                    var text = document.createTextNode(selectedText.slice(1,-1));
                    $(_this.editor_wrapper).find('.zeditor_button.brackets').removeClass('is_active');

                }else{

                    var text = document.createTextNode('['+selectedText+']');
                    $(_this.editor_wrapper).find('.zeditor_button.brackets').addClass('is_active');

                }

                range.deleteContents();
                range.insertNode(text);

            }

        });

    }

    // =================
    // FUNC
    // =================


    // плейсхолдер
    placeholderStatus(action){

        // плейсхолдер не задан
        if(!this.placeholder){return;}

        // добавляем
        if(action=='show'){

            // ...если такого элемента в DOMe еще нет
            if(this.editor_wrapper.contains(document.querySelector('.zeditor_placeholder'))){return;}
                
            var elem = document.createElement('div');
            elem.setAttribute('class', 'zeditor_placeholder');
            elem.innerHTML = this.placeholder;

            this.editor_wrapper.querySelector('.zeditor_outer').appendChild(elem);

        // удаляем
        }else{

            if(this.editor_wrapper.contains(document.querySelector('.zeditor_placeholder'))){
                this.editor_wrapper.querySelector('.zeditor_placeholder').remove();          
            }

        }

    }


    // показываем тулбар
    showToolbar(){

        //console.log('showToolbar()');

        // сейчас ничего не выделено - выходим
        if(!this.selectionContainsContent()){
            this.hideToolbar();
            return;
        } 

        // выделен текст именно в редакторе?
        var selection = window.getSelection();
        if(!this.editor_wrapper.contains(selection.anchorNode)){
            return;
        }

        
        if(!this.toolbarIsDisplayed()){
            //console.log('показываем панельку');
            $(this.elements.relative_toolbar).addClass('is_active');      
        }

        // обновляем позицию
        this.relativeToolbarPositionUpdate();  

        // обновляем состояния кнопок тулбаров
        this.toolbarButtonStateUpdate();        

    }

    // скрываем тулбар
    hideToolbar(){
        //console.log('hideToolbar()');
        if(this.toolbarIsDisplayed()){
            $(this.elements.relative_toolbar).removeClass('is_active');
            $(this.editor_wrapper).find('.zeditor_button').removeClass('is_active');
        }
    }

    // содержит ли выделение хоть что-нибудь?
    selectionContainsContent(){
        var selection = window.getSelection();
        if (selection.toString().trim() !== '') {return true;}
        return false;
    }

    // виден ли сейчас тулбар?
    toolbarIsDisplayed(){
        return $(this.elements.relative_toolbar).hasClass('is_active');
    }


    // пустой ли рдактор?
    editorIsEmpty(){
        //return this.editor.textContent.trim()=='';

        //console.log("{"+this.editor.textContent+"}");

        if(this.editor.textContent.trim()==''){
            //console.log('editor пуст');
            return true;
        }else{
            //console.log('editor НЕ пустой');
            return false;
        }

    }

    // высчитываем координаты
    relativeToolbarPositionUpdate() {

        // сейчас ничего не выделено - выходим
        if(!this.selectionContainsContent()){return;}

        // получаем "position" контейнера 
        var editor_wrapper_position = $(this.editor_wrapper).css('position');
        
        // ширина тулбара
        var relative_toolbar_width = $(this.elements.relative_toolbar).outerWidth(true);

        // выделение
        var range_native = window.getSelection().getRangeAt(0);

        // координаты выделенного текста (от окна)
        var selection_boundary = range_native.getBoundingClientRect();
        var selection_width = selection_boundary.width;
        var selection_height = selection_boundary.height;

        // контейнер relative - поэтому нужно считать координаты от него, а не от window
        if(editor_wrapper_position=='relative' || editor_wrapper_position=='absolute'){

            // размеры и положение контейнера
            var wrapper_boundary = this.editor_wrapper.getBoundingClientRect();
            
            // правый край родителя редактора (= офсет слева + ширина родителя)
            var wrapper_boundary_right = wrapper_boundary.left + wrapper_boundary.width;

            var top = selection_boundary.top - wrapper_boundary.top + selection_height + this.relative_toolbar_top_offset;
            var left = selection_boundary.left - wrapper_boundary.left + selection_width/2 - relative_toolbar_width/2;

        // контейнер статический - считаем координаты от window
        }else{

            var top = selection_boundary.top + selection_height + this.relative_toolbar_top_offset;
            var left = selection_boundary.left + selection_width/2 - relative_toolbar_width/2;
        }

        // по-умолчанию, уголок позиционируем по центру тулбара
        var corner_left = '50%';

        // правый край тулбара
        var relative_toolbar_right = left + wrapper_boundary.left + relative_toolbar_width;

        // тулбар выходит за границы СЛЕВА
        if(left < 0){

            // прижимаем его к левому краю редактора
            left = 0;

            // уголок позиционируем по центру выделенного
            corner_left = (selection_boundary.left - wrapper_boundary.left) + selection_width / 2;
            corner_left += 'px';           

        // тулбар выходит за границы СПРАВА
        }else if(relative_toolbar_right > wrapper_boundary_right){

            left = wrapper_boundary_right - wrapper_boundary.left - relative_toolbar_width;

            // уголок позиционируем по центру выделенного
            corner_left =  (selection_boundary.left - wrapper_boundary.left - left) + selection_width / 2;
            corner_left += 'px';   

        }

        // положение тулбара
        $(this.elements.relative_toolbar).css('top', top+'px').css('left', left+'px'); 

        // положение "уголка"
        $(this.elements.relative_toolbar).find('.relative_toolbar_corner').css('left', corner_left);

    }

    // обновление состояния кнопок тулбаров после выделения
    toolbarButtonStateUpdate() {

        if(!this.selectionContainsContent()){return;}

        //console.log('toolbarButtonStateUpdate()');

        var _this = this;

        // 1. перебираем стандартные кнопки у которых есть data-command (bold, italic, strike...)
        $(this.elements.relative_toolbar).find('button').each(function(){

            var command = $(this).data('command');           

            // у кнопки указано стандартное действие
            if(command!==undefined){                

                // в выделении присутствует проверяемый тег
                if(document.queryCommandState(command)){
                    //console.log('command', command, 'присутствует проверяемый тег');
                    $(_this.editor_wrapper).find('.zeditor_button[data-command='+command+']').addClass('is_active');
                }else{
                    $(_this.editor_wrapper).find('.zeditor_button[data-command='+command+']').removeClass('is_active');
                }
            }
        });

        // получаем текущее выделение
        var selection = this.getSelection();
        var range = this.getRange();

        var selection_html = range.commonAncestorContainer.parentNode.outerHTML;
        
        // 2. вручную проверяем кнопку "highlight" на наличие тега <mark>        
        var mark_btn_is_active = false;

        // выбран текст содержащий "<mark>слово</mark>"
        if(/^<mark>.*<\/mark>$/.test(selection_html)){mark_btn_is_active = true;}

        // выбрана нода MARK
        if(range.commonAncestorContainer.parentNode.nodeName=='MARK'){mark_btn_is_active = true;}

        if(mark_btn_is_active){
            $(this.editor_wrapper).find('.zeditor_button.highlight').addClass('is_active');
        }else{
            $(this.editor_wrapper).find('.zeditor_button.highlight').removeClass('is_active');
        }

        // 3. вручную проверяем кнопку "brackets" на наличие вокруг выделнного текста квадратных скобок [selection]

        // если выделили ноду целиком - ниче проверяем
        if(range.commonAncestorContainer.nodeValue && range.startOffset > 0){

            var symbol_before_selection = range.commonAncestorContainer.nodeValue.substring(range.startOffset-1, range.startOffset);
            var symbol_after_selection = range.commonAncestorContainer.nodeValue.substring(range.endOffset, range.endOffset+1);

            // 'по краям выделения скобочки - расширяем выделение на эти скобочки
            if(symbol_before_selection=='[' && symbol_after_selection==']'){
                
                range.setStart(range.startContainer, range.startOffset - 1);
                range.setEnd(range.endContainer, range.endOffset + 1);      
                
                // заменяем старое выделение новым      
                selection.removeAllRanges();
                selection.addRange(range);
            }

            // проверяем на скобочки
            if(/\[.*\]/.test(range.toString())){            
                $(this.editor_wrapper).find('.zeditor_button.brackets').addClass('is_active');
            }else{
                $(this.editor_wrapper).find('.zeditor_button.brackets').removeClass('is_active');
            }

        }

    }


    // получаем Selection
    getSelection(){
        return window.getSelection();
        //return rangy.getSelection();
    }

    // получаем Range
    getRange(){
        return window.getSelection().getRangeAt(0);
        //return rangy.getSelection().getRangeAt(0);
    }
   
}