import { Plugin } from '@ckeditor/ckeditor5-core';
import ImageMapCommand from './imagemapcommand';
import ResizeImageMapCommand from './resizeimagemapcommand';
import ImageMapDialogCommand from './imagemapdialogcommand';
import { IMAGEMAP_ATTRIBUTES, IMAGEMAP_ELEMENTS, drawMap, IMAGEMAP_COMMANDS, HTML_ATTRIBUTES, HTML_ELEMENTS, findMapNodes } from './utils';
import ImageLoadObserver from '@ckeditor/ckeditor5-image/src/image/imageloadobserver';
import { downcastImageWidthAttribute, downcastImageHeightAttribute, downcastImageSavedSrcAttribute, downcastImageMapAttribute,
    upcastImageSavedSrcAttribute, upcastMapElement, dowcastMapNameAttribute, dowcastMapWidthAttribute, dowcastMapHeightAttribute,
    dowcastMapAreaAttribute, upcastImageMapAttribute, upcastImageHeightAttribute, upcastImageWidthAttribute, downcastMapElement,
    downcastImagePluginAttribute
} from './converters';
import { isWidget } from  '@ckeditor/ckeditor5-widget/src/utils';
import { trimStart, debounced } from '../common/qmsCKEditorUtil';

 export default class ImageMapEditing extends Plugin {
    static get requires() {
		return ['ImageResize', 'Delete'];
	}

     static get pluginName() {
         return 'ImageMapEditing';
    }

     init() {
        const editor = this.editor;
        const model = editor.model;
        const schema = model.schema;
        const conversion = editor.conversion;
        const editingView = editor.editing.view;

        editingView.addObserver(ImageLoadObserver);

        schema.extend(IMAGEMAP_ELEMENTS.IMAGE, {
            allowAttributes: [
                IMAGEMAP_ATTRIBUTES.IMAGE_USE_MAP,
                IMAGEMAP_ATTRIBUTES.IMAGE_WIDTH,
                IMAGEMAP_ATTRIBUTES.IMAGE_HEIGHT,
                IMAGEMAP_ATTRIBUTES.IMAGE_SAVED_SRC
            ]
        });

        schema.register(IMAGEMAP_ELEMENTS.MAP, {
            isObject: true,
            allowWhere: '$block',
            allowAttributes: [
                IMAGEMAP_ATTRIBUTES.MAP_NAME,
                IMAGEMAP_ATTRIBUTES.MAP_AREAS,
                IMAGEMAP_ATTRIBUTES.MAP_WIDTH,
                IMAGEMAP_ATTRIBUTES.MAP_HEIGHT
            ]
       });

        conversion.for('dataDowncast').add(downcastImageWidthAttribute());
        conversion.for('dataDowncast').add(downcastImageHeightAttribute());
        conversion.for('dataDowncast').add(downcastImageSavedSrcAttribute(true));
        conversion.for('editingDowncast').add(downcastImageSavedSrcAttribute(false));

        conversion.for('downcast').add(downcastImagePluginAttribute());
        conversion.for('downcast').add(downcastImageMapAttribute());
        conversion.for('downcast').add(downcastMapElement());

        conversion.for('downcast')
        .add(dowcastMapNameAttribute())
        .add(dowcastMapWidthAttribute())
        .add(dowcastMapHeightAttribute())
        .add(dowcastMapAreaAttribute());

        conversion.for('upcast').add(upcastImageMapAttribute());
        conversion.for('upcast').add(upcastImageHeightAttribute());
        conversion.for('upcast').add(upcastImageWidthAttribute());
        conversion.for('upcast').add(upcastImageSavedSrcAttribute());
        conversion.for('upcast').add(upcastMapElement());

        editor.commands.get('resizeImage').on('execute', debounced(() => {
            editor.execute(IMAGEMAP_COMMANDS.RESIZE_IMAGEMAP);
        }, 100));

        this.listenTo(editingView.document, 'delete', ( evt, data ) => {
            this.allowDeleteMap = true;
		}, { context: [isWidget] });

        this.listenTo(editingView.document, 'imageLoaded', (evt, domEvent) => {
            onImageLoaded(domEvent);
        });

        model.document.on('change:data', (evt, batch) => {
            if(this.allowDeleteMap == true){
                this.allowDeleteMap = false;
                OnImageDeleted(model, batch);
            }
        } );

        editor.commands.add(IMAGEMAP_COMMANDS.IMAGEMAP_DIALOG, new ImageMapDialogCommand(editor));
        editor.commands.add(IMAGEMAP_COMMANDS.IMAGEMAP, new ImageMapCommand(editor));
        editor.commands.add(IMAGEMAP_COMMANDS.RESIZE_IMAGEMAP, new ResizeImageMapCommand(editor));
    }
}

function OnImageDeleted(model, batch){
    if( batch.type == 'transparent'){
        return;
    }
    const differ = model.document.differ;
    // if no difference
    if (differ.isEmpty) { return; }
    const changes = differ.getChanges({ includeChangesInGraveyard: true });
    if (changes.length === 0) { return; }

    let hasNoImageRemoved = true;
    // check any image remove or not
    for (let i = 0; i < changes.length; i++){
        const change = changes[i]
        // if image remove exists
        if (change && change.type === 'remove' && change.name === IMAGEMAP_ELEMENTS.IMAGE) {
            hasNoImageRemoved = false
            break
        }
    }

    // if not image remove stop execution
    if (hasNoImageRemoved) { return; }
    // get removed nodes
    const removedNodes = changes.filter(change => (change.type === 'insert' && change.name === IMAGEMAP_ELEMENTS.IMAGE))

    // removed image nodes
    const removedMapNames = []
    removedNodes.forEach(node => {
        let nodeAfter = node.position.nodeAfter;
        if(nodeAfter && nodeAfter.hasAttribute(IMAGEMAP_ATTRIBUTES.IMAGE_USE_MAP)){
            let mapname = nodeAfter.getAttribute(IMAGEMAP_ATTRIBUTES.IMAGE_USE_MAP) || '';
            removedMapNames.push(trimStart(mapname, '#'));
        }
    });

    if(removedMapNames && removedMapNames.length > 0){
        const range = model.createRangeIn(model.document.getRoot());
        const existingMapElements = findMapNodes(removedMapNames, range);

        if(existingMapElements && existingMapElements.length > 0){
            model.enqueueChange(batch, writer => {
                existingMapElements.forEach(element => {
                    writer.remove(element);
                });
            } );
        }
    }
}

function onImageLoaded(domEvent){
    //Must be attached only to images loaded by the `ImageInsert`, `ImageUpload` or `LinkImage` plugins.
    if (!domEvent.target.matches('figure.image.ck-widget > img, figure.image.ck-widget > a > img')) {
        return;
    }
    if(domEvent.target.hasAttribute(HTML_ATTRIBUTES.USE_MAP)
        && domEvent.target.getAttribute(HTML_ATTRIBUTES.USE_MAP)
        && !domEvent.target.hasAttribute(HTML_ATTRIBUTES.SAVED_SRC)){

        const originalSrc =  domEvent.target.getAttribute(HTML_ATTRIBUTES.SRC);
        const mapName =  trimStart(domEvent.target.getAttribute(HTML_ATTRIBUTES.USE_MAP), '#');
        const selector = `${HTML_ELEMENTS.MAP}[${HTML_ATTRIBUTES.NAME}='${mapName}']`;
        const map = document.querySelector(selector);
        if(map && map.childNodes){
            const areas = [];
            map.childNodes.forEach((child) =>{
                if(child.nodeName && child.nodeName.toLowerCase() === HTML_ELEMENTS.AREA){
                    let area = {};
                    area[HTML_ATTRIBUTES.COORDS] = child.getAttribute(HTML_ATTRIBUTES.COORDS);
                    area[HTML_ATTRIBUTES.SHAPE] = child.getAttribute(HTML_ATTRIBUTES.SHAPE) || '';
                    areas.push(area);
                }
            });
            if(areas.length > 0){
                const width =  map.getAttribute(HTML_ATTRIBUTES.WIDTH) || domEvent.target.width;
                const height =  map.getAttribute(HTML_ATTRIBUTES.HEIGHT) || domEvent.target.height;
                const target = domEvent.target;
                drawMap(originalSrc, width, height, areas, (oldsrc, newsrc) => {
                    if(target){
                        target.setAttribute(HTML_ATTRIBUTES.SRC, newsrc);
                        target.setAttribute(HTML_ATTRIBUTES.SAVED_SRC, oldsrc);
                    }

                    if(map){
                        map.setAttribute(HTML_ATTRIBUTES.WIDTH, width);
                        map.setAttribute(HTML_ATTRIBUTES.HEIGHT, height);
                    }
                })
            }
        }
    }
}
