import React, { Component } from 'preact/compat';
import Editor from './editor/Editor';
import Highlighter from './highlighter/Highlighter';
import SelectionHandler from './selection/SelectionHandler';
import RelationsLayer from './relations/RelationsLayer';
import RelationEditor from './relations/editor/RelationEditor';

/**
 * Pulls the strings between the annotation highlight layer
 * and the editor popup.
 */
export default class TextAnnotator extends Component {

  state = {
    selectedAnnotation: null,
    selectedDOMElement: null,
    selectedRelation: null,

    applyTemplate: null,
    headless: false
  }

  /** Shorthand **/
  clearState = () => {
    this.setState({
      selectedAnnotation: null,
      selectedDOMElement: null
    });
  }

  handleEscape = (evt) => {
    if (evt.which === 27)
      this.onCancelAnnotation();
  }

  componentDidMount() {
    this.highlighter = new Highlighter(this.props.contentEl, this.props.config.formatter);

    this.selectionHandler = new SelectionHandler(this.props.contentEl, this.highlighter, this.props.config.readOnly);
    this.selectionHandler.on('select', this.handleSelect);

    this.relationsLayer = new RelationsLayer(this.props.contentEl);
    this.relationsLayer.readOnly = true; // Deactivate by default

    this.relationsLayer.on('createRelation', this.onEditRelation);
    this.relationsLayer.on('selectRelation', this.onEditRelation);
    this.relationsLayer.on('cancelDrawing', this.closeRelationsEditor);

    document.addEventListener('keydown', this.handleEscape);
  }

  componentWillUnmount() {
    document.removeEventListener('keydown', this.handleEscape);
  }

  /**************************/
  /* Annotation CRUD events */
  /**************************/

  /** Selection on the text **/
  handleSelect = evt => {
    const { selection, element } = evt;
    if (selection) {
      this.setState({
        selectedAnnotation: null,
        selectedDOMElement: null
      }, () => this.setState({
        selectedAnnotation: selection,
        selectedDOMElement: element
      }));

      if (!selection.isSelection)
        this.props.onAnnotationSelected(selection.clone());
    } else {
      this.clearState();
    }
  }

  /**
   * A convenience method that allows the external application to
   * override the autogenerated Id for an annotation.
   *
   * Usually, the override will happen almost immediately after
   * the annotation is created. But we need to be defensive and assume
   * that the override might come in with considerable delay, thus
   * the user might have made further edits already.
   *
   * A key challenge here is that there may be dependencies between
   * the original annotation and relations that were created meanwhile.
   */
  overrideAnnotationId = originalAnnotation => forcedId => {
    const { id } = originalAnnotation;

    // After the annotation update, we need to update dependencies
    // on the annotation layer, if any
    const updateDependentRelations = updatedAnnotation => {
      // Wait until the highlighter update has come into effect
      requestAnimationFrame(() => {
        this.relationsLayer.overrideTargetAnnotation(originalAnnotation, updatedAnnotation);
      })
    };

    // Force the editors to close first, otherwise their annotations will be orphaned
    if (this.state.selectedAnnotation || this.state.selectedRelation) {
      this.relationsLayer.resetDrawing();
      this.setState({
        selectedAnnotation: null,
        selectedRelation: null
      }, () => {
        const updated = this.highlighter.overrideId(id, forcedId);
        updateDependentRelations(updated);
      });
    } else {
      const updated = this.highlighter.overrideId(id, forcedId);
      updateDependentRelations(updated);
    }
  }

  /**
   * A convenience method that allows the external application to
   * override the autogenerated Id for a relation.
   *
   * This operation is less problematic than .overrideAnnotation().
   * We just need to make sure the RelationEditor is closed, so that
   * the annotation doesn't become orphaned. Otherwise, there are
   * no dependencies.
   */
  overrideRelationId = originalId => forcedId => {
    if (this.state.selectedRelation) {
      this.setState({ selectedRelation: null }, () =>
       this.relationsLayer.overrideId(originalId, forcedId));
    } else {
      this.relationsLayer.overrideId(originalId, forcedId);
    }
  }

  /** Common handler for annotation CREATE or UPDATE **/
  onCreateOrUpdateAnnotation = method => (annotation, previous) => {
    this.clearState();

    this.selectionHandler.clearSelection();
    this.highlighter.addOrUpdateAnnotation(annotation, previous);

    // Call CREATE or UPDATE handler
    if (previous)
      this.props[method](annotation.clone(), previous.clone());
    else
      this.props[method](annotation.clone(), this.overrideAnnotationId(annotation));
  }

  onDeleteAnnotation = annotation => {
    // Delete connections
    this.relationsLayer.destroyConnectionsFor(annotation);

    this.clearState();
    this.selectionHandler.clearSelection();
    this.highlighter.removeAnnotation(annotation);

    this.props.onAnnotationDeleted(annotation);
  }

  /** Cancel button on annotation editor **/
  onCancelAnnotation = () => {
    this.clearState();
    this.selectionHandler.clearSelection();
  }

  /************************/
  /* Relation CRUD events */
  /************************/

  // Shorthand
  closeRelationsEditor = () => {
    this.setState({ selectedRelation: null });
    this.relationsLayer.resetDrawing();
  }

  /**
   * Selection on the relations layer: open an existing
   * or newly created connection for editing.
   */
  onEditRelation = relation => {
    this.setState({
      selectedRelation: relation
    });
  }

  /** 'Ok' on the relation editor popup **/
  onCreateOrUpdateRelation = (relation, previous) => {
    this.relationsLayer.addOrUpdateRelation(relation, previous);
    this.closeRelationsEditor();

    // This method will always receive a 'previous' connection -
    // if the previous is just an empty connection, fire 'create',
    // otherwise, fire 'update'
    const isNew = previous.annotation.bodies.length === 0;

    if (isNew)
      this.props.onAnnotationCreated(relation.annotation.clone(), this.overrideRelationId(relation.annotation.id));
    else
      this.props.onAnnotationUpdated(relation.annotation.clone(), previous.annotation.clone());
  }

  /** 'Delete' on the relation editor popup **/
  onDeleteRelation = relation => {
    this.relationsLayer.removeRelation(relation);
    this.closeRelationsEditor();
    this.props.onAnnotationDeleted(relation.annotation);
  }

  /****************/
  /* External API */
  /****************/

  addAnnotation = annotation => {
    this.highlighter.addOrUpdateAnnotation(annotation.clone());
  }

  removeAnnotation = annotation => {
    this.highlighter.removeAnnotation(annotation);

    // If the editor is currently open on this annotation, close it
    const { selectedAnnotation } = this.state;
    if (selectedAnnotation && annotation.isEqual(selectedAnnotation))
      this.clearState();
  }

  setAnnotations = annotations => {
    const clones = annotations.map(a => a.clone());
    this.highlighter.init(clones).then(() =>
      this.relationsLayer.init(clones));
  }

  getAnnotations = () => {
    const annotations = this.highlighter.getAllAnnotations();
    const relations = this.relationsLayer.getAllRelations();
    return annotations.concat(relations).map(a => a.clone());
  }

  setMode = mode => {
    if (mode === 'RELATIONS') {
      this.clearState();

      this.selectionHandler.enabled = false;

      this.relationsLayer.readOnly = false;
      this.relationsLayer.startDrawing();
    } else {
      this.setState({ selectedRelation: null });

      this.selectionHandler.enabled = true;

      this.relationsLayer.readOnly = true;
      this.relationsLayer.stopDrawing();
    }
  }

  applyTemplate = (bodies, headless) =>
    this.setState({ applyTemplate: bodies, headless })

  render() {
    return (
      <>
        { this.state.selectedAnnotation &&
          <Editor
            wrapperEl={this.props.wrapperEl}
            annotation={this.state.selectedAnnotation}
            selectedElement={this.state.selectedDOMElement}
            config={this.props.config}
            env={this.props.env}
            applyTemplate={this.state.applyTemplate}
            onAnnotationCreated={this.onCreateOrUpdateAnnotation('onAnnotationCreated')}
            onAnnotationUpdated={this.onCreateOrUpdateAnnotation('onAnnotationUpdated')}
            onAnnotationDeleted={this.onDeleteAnnotation}
            onCancel={this.onCancelAnnotation} />
        }

        { this.state.selectedRelation &&
          <RelationEditor
            relation={this.state.selectedRelation}
            onRelationCreated={this.onCreateOrUpdateRelation}
            onRelationUpdated={this.onCreateOrUpdateRelation}
            onRelationDeleted={this.onDeleteRelation}
            onCancel={this.closeRelationsEditor}
            vocabulary={this.props.relationVocabulary}
          />
        }
      </>
    );
  }

}
