/**
 * @author Martin Karkowski
 * @email m.karkowski@zema.de
 * @create date 2021-07-23 10:44:48
 * @modify date 2021-07-23 10:44:48
 * @desc [description]
 */

import * as go from "gojs";
import { ReactDiagram } from "gojs-react";
import * as React from "react";
import { generateGraph, ImplentedGraph } from "./graph-types/generateGraph";

interface DiagramProps {
  nodeDataArray: Array<go.ObjectData>;
  linkDataArray: Array<go.ObjectData>;
  initDiagram?: () => go.Diagram;
  diagramType?: ImplentedGraph;
  templates: Array<{
    name: string;
    template: go.Part;
  }>;
  modelData?: go.ObjectData;
  skipsDiagramUpdate: boolean;
  onDiagramEvent?: (e: go.DiagramEvent) => void;
  eventsToSubscribe?: go.DiagramEventName[];
  onModelChange?: (e: go.IncrementalData) => void;
}

export class DiagramWrapper extends React.Component<DiagramProps, never> {
  /**
   * Ref to keep a reference to the Diagram component, which provides access to the GoJS diagram via getDiagram().
   */
  private diagramRef: React.RefObject<ReactDiagram>;

  /** @internal */
  constructor(props: DiagramProps) {
    super(props);
    this.diagramRef = React.createRef();
  }

  /**
   * Get the diagram reference and add any desired diagram listeners.
   * Typically the same function will be used for each listener, with the function using a switch statement to handle the events.
   */
  public componentDidMount(): void {
    if (!this.diagramRef.current) return;

    const diagram = this.diagramRef.current.getDiagram();

    // Adapt its size
    diagram.div.style.height = "100%";
    diagram.div.style.width = "100%";

    if (diagram instanceof go.Diagram) {
      // If the Diagram is ready, we try to add the Templates.
      const templates = Array.isArray(this.props.templates)
        ? this.props.templates
        : [];

      for (const template of templates) {
        diagram.nodeTemplateMap.add(template.name, template.template);
      }

      if (typeof this.props.onDiagramEvent === "function") {
        // Now we define the events we are listening on.
        // We will forward these events to the callback
        // "onDiagramEvent"
        const subscribedEvents: go.DiagramEventName[] = Array.isArray(
          this.props.eventsToSubscribe
        )
          ? this.props.eventsToSubscribe
          : [];
        for (const eventName of subscribedEvents) {
          diagram.addDiagramListener(eventName, this.props.onDiagramEvent);
        }
      }
    }
  }

  /**
   * Get the diagram reference and remove listeners that were added during mounting.
   */
  public componentWillUnmount(): void {
    if (!this.diagramRef.current) return;
    const diagram = this.diagramRef.current.getDiagram();
    if (diagram instanceof go.Diagram) {
      diagram.removeDiagramListener(
        "ChangedSelection",
        this.props.onDiagramEvent
      );
    }
  }

  /**
   * Diagram initialization method, which is passed to the ReactDiagram component.
   * This method is responsible for making the diagram and initializing the model, any templates,
   * and maybe doing other initialization tasks like customizing tools.
   * The model's data should not be set here, as the ReactDiagram component handles that.
   */
  protected initDiagram(): go.Diagram {
    if (typeof this.props.initDiagram === "function") {
      return this.props.initDiagram();
    } else if (typeof this.props.diagramType === "string") {
      return generateGraph(this.props.diagramType);
    }

    throw Error("");
  }

  public render(): JSX.Element {
    const _this = this;
    return (
      <ReactDiagram
        ref={this.diagramRef}
        divClassName="None"
        initDiagram={() => {
          const diagram = _this.initDiagram();
          console.log("diagram", diagram);
          return diagram;
        }}
        nodeDataArray={_this.props.nodeDataArray}
        linkDataArray={_this.props.linkDataArray}
        modelData={_this.props.modelData}
        onModelChange={(e) => {
          if (typeof _this.props.onModelChange === "function") {
            _this.props.onModelChange(e);
          }
        }}
        skipsDiagramUpdate={_this.props.skipsDiagramUpdate}
      />
    );
  }
}
