import React, { Component } from 'react';
import _ from 'lodash'
import axios from 'axios';
import PropTypes from 'prop-types'

import { uploadFile } from '../../api/icat/icatPlus.js'

// tinymce editor
import { Editor } from '@tinymce/tinymce-react'
import tinymce from 'tinymce/tinymce';
import { EditionModeConfig, ViewModeConfig } from '../../config/tinymce/tinymce.js'
import { GUI_CONFIG } from '../../config/gui.config.js';
import { getFileByEventId } from "../../api/icat/icatPlus"
import { NEW_EVENT_CONTEXT, PLAINTEXT_CONTENT_FORMAT, EDIT_EVENT_CONTEXT, HTML_CONTENT_FORMAT } from '../../constants/EventTypes.js';

/**
 * The HTML editor used to read and write the logbook's annotations.
 */

const EDITOR_ID_FOR_CREATION = 'myEditorForCreation';
const EDITOR_ID_FOR_EDITION = 'myEditorForEdition';
const EVENT_FOOTER_HEIGHT = 37;


class HTMLEditor extends Component {
    constructor(props) {
        super(props);
        this.originalText = props.text; // Stores original text provided in props, could be undefined
        this.state = {
            editorContent: this.originalText
        }

        this.imagesUploadHandler = this.imagesUploadHandler.bind(this);
        this.onEditorChange = this.onEditorChange.bind(this);
        this.storeToLocalStorage = this.storeToLocalStorage.bind(this);
        this.setImageHeightByCSSRule = this.setImageHeightByCSSRule.bind(this);
        this.onImageLoaded = this.onImageLoaded.bind(this);
    }

    render() {
        let isEditionMode = this.props.isEditionMode;

        const { editorContent } = this.state;
        const config = (isEditionMode === true) ? new EditionModeConfig() : new ViewModeConfig();
        return (
            <Editor
                id={this.props.text ? EDITOR_ID_FOR_EDITION : EDITOR_ID_FOR_CREATION}
                init={{
                    plugins: config.plugins,
                    skin_url: config.skin_url,
                    branding: config.branding,
                    readonly: config.readonly,
                    toolbar: config.toolbar,
                    menubar: config.menubar,
                    statusbar: config.statusbar,
                    images_upload_handler: this.imagesUploadHandler,
                    paste_data_images: config.paste_data_images,
                    formats: config.formats,
                    content_css: config.content_css,
                }}
                value={editorContent}
                onEditorChange={this.onEditorChange}
            />
        );
    }

    componentDidMount() {
        this.storeToLocalStorage()
        this.setEditingAreaHeight();
    }

    componentDidUpdate() {
        this.setEditingAreaHeight();
    }

    setEditingAreaHeight() {
        let panelPart = document.getElementById('refForEditorHeightCalculation');
        let editorHeader = document.getElementsByClassName("mce-top-part mce-container mce-stack-layout-item mce-first")[0];
        let iframe = this.props.text ? document.getElementById(EDITOR_ID_FOR_EDITION + '_ifr') : document.getElementById(EDITOR_ID_FOR_CREATION + '_ifr');
        iframe.style.height = (panelPart.clientHeight - EVENT_FOOTER_HEIGHT - editorHeader.clientHeight).toString() + 'px'; // this.props.maxEditorHeight;
        let iframeBody = iframe.contentWindow.document.getElementById('tinymce');
        iframeBody.style.height = (panelPart.clientHeight - 8 - 8 - EVENT_FOOTER_HEIGHT - editorHeader.clientHeight - 1).toString() + 'px'; //8 = marginTop and marginBottom of the iframe's body
    }

    /**
     * Callback function triggered when the image has been downloaded
     * @param {*} event the event
     */
    onImageLoaded(event) {
        let element = tinymce.activeEditor.selection.select(event.target);
        tinymce.activeEditor.dom.setStyles(element, { 'max-width': '100%', 'height': 'auto' });
    }

    /**
     * Sets a default image height using a CSS rule. This is a trick to make sure the image height 
     * is applied in the editor (for scrollabar propose) especially because the image is not necessary 
     * yet downloaded at this step. The height is changed to auto after the image is fully downloaded 
     * such that image ratio is kept. 
     */
    setImageHeightByCSSRule() {
        if (tinymce && tinymce.activeEditor) {
            let selectedNode = tinymce.activeEditor.selection.getNode();
            if (selectedNode.nodeName === 'IMG' && selectedNode.style.height !== 'auto') {
                let nxElement = selectedNode;
                nxElement.style.height = GUI_CONFIG().NEW_EVENT_MAX_HEIGHT;
                nxElement.style.width = 'auto'; // a css trick for some browsers IE8 and old iceweasel
                nxElement.onload = this.onImageLoaded;
                tinymce.activeEditor.dom.replace(nxElement, selectedNode);
            }
        }
    }

    /**
     * The function executed when the editor state changes (mouse click, key press for example )
     * @param {string} editorContent the editor content in html format
     */
    onEditorChange(editorContent) {
        this.setImageHeightByCSSRule();

        // Inform parent component that the current text equals the original text as provided in the props                
        if (this.props.onEventModified) {
            let hasText = editorContent.length !== 0 ? true : false;
            let isCurrentTextEqualsOriginal;
            if (this.originalText) {
                isCurrentTextEqualsOriginal = (_.isEqual(editorContent, this.originalText)) ? true : false;
            } else {
                isCurrentTextEqualsOriginal = false;
            }
            this.props.onEventModified({ hasText: hasText, currentTextEqualsOriginal: isCurrentTextEqualsOriginal });
        }

        this.setState({ editorContent: editorContent });
        this.storeToLocalStorage(editorContent);
    }

    /**
     * Defines what to do when the user drag an image onto the dropzone of the editor image plugin. This function must return a promise. 
     * The value of a fullfilled promise must be an array of the form { data: { link: url } } where url value is the link to the image which 
     * has just been uoloaded to the ICAT+ server.
     * @param {*} file : the image which has just been dropped on the drop zone.
     */

    imagesUploadHandler(blobInfo, success, failure) {
        let { investigationId } = this.props;
        var sessionId = this.props.user.sessionId;


        let data = new FormData();
        data.append('file', blobInfo.blob(), blobInfo.filename());
        data.append('investigationId', investigationId);
        data.append('creationDate', Date());
        data.append('type', 'attachment');
        data.append('category', 'file');
        data.append('username', this.props.user.username);

        axios({
            method: "post",
            url: uploadFile(sessionId, investigationId),
            data: data,
        })
            .then(function (value) {
                let eventId = value.data._id;
                success(getFileByEventId(sessionId, investigationId, eventId));
            }, function (error) {
                console.log("[ERROR] Retrieval of the image you have just upladed into the editor failed ! ");
                failure(error);
            });
    }

    /**
     * Store the editor content to localStorage.
     * @param {*} editorContent the editor content in HTML format
            */
    storeToLocalStorage(editorContent) {
        if (editorContent) {
            // Editor content has been modified by the user. Save the update to localStorage
            if (tinymce && tinymce.activeEditor) {
                // Save the plain text format to localstorage
                this.props.storeToLocalStorage(this.props.text ? EDIT_EVENT_CONTEXT : NEW_EVENT_CONTEXT, tinymce.activeEditor.getContent({ format: 'text' }), PLAINTEXT_CONTENT_FORMAT)
            }
            // Save the HTML format to localstorage
            this.props.storeToLocalStorage(this.props.text ? EDIT_EVENT_CONTEXT : NEW_EVENT_CONTEXT, editorContent, HTML_CONTENT_FORMAT);
        }
        else {
            // The following is executed on componentDidMount. Usefull when the event title is changed only while the editor content was not modified
            this.props.storeToLocalStorage(this.props.text ? EDIT_EVENT_CONTEXT : NEW_EVENT_CONTEXT, this.originalText, HTML_CONTENT_FORMAT);
        }
    }
}

HTMLEditor.defaultProps = {
    /** by default, the editor is not in editing mode */
    isEditionMode: false
};


HTMLEditor.propTypes = {
    /** Determines whether the editor is in editing mode or not. */
    isEditionMode: PropTypes.bool,
    /** The text provided to the editor. No text indicates that HTMLEditor is begin used for the creation of a new event. */
    text: PropTypes.string,
    /** the investigationId of the event being edited. */
    investigationId: PropTypes.string,
    /** the user who is currently logged in */
    user: PropTypes.object.isRequired,
    /** callback function called when editor content changed : from no text to text or vice versa, or  when the current text is identical to the original text provided to the editor*/
    onEventModified: PropTypes.func,
}

export default HTMLEditor;
