import * as React from 'react';
import * as go from 'gojs';
import { Diagram } from 'gojs';
import { GojsDiagram } from 'react-gojs';
import './GoJsChart.css';
import { BPMNLinkingTool } from './BPMNClasses';
import { ContextMenuItem } from '../Models/ContextMenuItem';

class GoJsChart extends React.Component<any, any> {
    private _diagram: Diagram;
    constructor(props: any) {
        super(props);
        this.createDiagram = this.createDiagram.bind(this);
    }
    
    public addNodeData(node: any) {
        this._diagram.model.addNodeData(node);
    }

    public removeNodeData(node: any) {
        this._diagram.commandHandler.deleteSelection();
    }

    AddAction(myDiagram: any, evType: any, evDim: any) {
        myDiagram.startTransaction("addBoundaryEvent");
        myDiagram.selection.each(function (node:any) {
          // skip any selected Links
          if (!(node instanceof go.Node)) return;
          if (node.data && (node.data.category === "state" || node.data.category === "subprocess")) {
            // compute the next available index number for the side
            var i = 0;
            var defaultPort = node.findPort("");
            while (node.findPort("be" + i.toString()) !== defaultPort) i++;           // now this new port name is unique within the whole Node because of the side prefix
            var name = "be" + i.toString();
            if (!node.data.boundaryEventArray) { myDiagram.model.setDataProperty(node.data, "boundaryEventArray", []); }       // initialize the Array of port data if necessary
            // create a new port data object
            var newportdata = {
              portId: name,
              eventType: evType,
              eventDimension: evDim,
              color: "white",
              alignmentIndex: i
              // if you add port data properties here, you should copy them in copyPortData above  ** BUG...  we don't do that.
            };
            // and add it to the Array of port data
            myDiagram.model.insertArrayItem(node.data.boundaryEventArray, -1, newportdata);
          }
        });
        myDiagram.commitTransaction("addBoundaryEvent");
    }
    private getActivityContextMenu($ : any){
        return $(go.Adornment, "Vertical",
            $("ContextMenuButton",
                $(go.TextBlock, "Add User action", { margin: 3 }),
                { click: (e: any, obj: any) => { this.AddAction(this._diagram , 2, 5) } }),
            $("ContextMenuButton",
                $(go.TextBlock, "Add Timer Action", { margin: 3 }),
                { click: (e: any, obj: any) => { this.AddAction(this._diagram , 3, 5) } }),
            $("ContextMenuButton",
                $(go.TextBlock, "State Handlers", { margin: 3 }),
                { click: (e: any, obj: any) => { this.AddAction(this._diagram , 4, 5) } }),
            $("ContextMenuButton",
                $(go.TextBlock, "State Permissions", { margin: 3 }),
                { click: (e: any, obj: any) => { this.AddAction(this._diagram , 7, 5) } }),
            $("ContextMenuButton",
                $(go.TextBlock, "State Rules", { margin: 3 }),
                { click: (e: any, obj: any) => { this.AddAction(this._diagram , 10, 5) } }),
            $("ContextMenuButton",
                $(go.TextBlock, "Delete State", { margin: 3 }),
                { click: (e: any, obj: any) => { this.removeNodeData.bind(this) } }),
        )
    }

    private nodeEventTypeConverter(s: any) {  // order here from BPMN 2.0 poster
        var tasks = ["NotAllowed",
            "Empty",
            "BpmnTaskMessage",
            "BpmnEventTimer",
            "BpmnEventEscalation",
            "BpmnEventConditional",
            "Arrow",
            "BpmnEventError",
            "ThinX",
            "BpmnStateCompensation",
            "Triangle",
            "Pentagon",
            "ThickCross",
            "Circle"];
        if (s < tasks.length) return tasks[s];
        return "NotAllowed"; // error
    }

    private nodeStateBESpotConverter(s: any) {
        var x = 10 + (42 / 2);
        if (s === 0) return new go.Spot(0, 1, x, 0);    // bottom left
        if (s === 1) return new go.Spot(1, 1, -x, 0);   // bottom right
        if (s === 2) return new go.Spot(1, 0, -x, 0);   // top right
        return new go.Spot(1, 0, -x - (s - 2) * 42, 0);    // top ... right-to-left-ish spread
    } 

    private getBoundaryEventItemTemplate($:any):go.Part{
        var EventNodeSize = 42;
        var EventNodeInnerSize = EventNodeSize - 6;
        var EventNodeSymbolSize = EventNodeInnerSize - 14;

        return $(go.Panel, "Spot",
        {
            //contextMenu: boundaryEventMenu,
            alignmentFocus: go.Spot.Center,
            fromLinkable: true, toLinkable: false, cursor: "pointer", fromSpot: go.Spot.Bottom,
            fromMaxLinks: 1, toMaxLinks: 0
        },
        new go.Binding("portId", "portId"),
        new go.Binding("alignment", "alignmentIndex", this.nodeStateBESpotConverter),
        $(go.Shape, "Circle",
            { desiredSize: new go.Size(EventNodeSize, EventNodeSize) },
            new go.Binding("strokeDashArray", "eventDimension", function (s) { return (s === 6) ? [4, 2] : null; }),
            new go.Binding("fill", "color")),
        $(go.Shape, "Circle",
            {
                alignment: go.Spot.Center,
                desiredSize: new go.Size(EventNodeInnerSize, EventNodeInnerSize), fill: null
            },
            new go.Binding("strokeDashArray", "eventDimension", function (s) { return (s === 6) ? [4, 2] : null; })
        ),
        $(go.Shape, "NotAllowed",
            {
                alignment: go.Spot.Center,
                desiredSize: new go.Size(EventNodeSymbolSize, EventNodeSymbolSize), fill: "white"
            },
            new go.Binding("figure", "eventType", this.nodeEventTypeConverter)
        )
    );
    }
    private getStateNodeTemplate($:any):go.Part{
        return $(go.Node, "Spot",
        {
            locationObjectName: "SHAPE", locationSpot: go.Spot.Center,
            resizable: true, resizeObjectName: "PANEL",
            selectionAdorned: false,  // use a Binding on the Shape.stroke to show selection
            contextMenu: this.getActivityContextMenu($),
            itemTemplate: this.getBoundaryEventItemTemplate($)
        },
        new go.Binding("itemArray", "boundaryEventArray"),
        new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
        // move a selected part into the Foreground layer, so it isn"t obscured by any non-selected parts
        new go.Binding("layerName", "isSelected", function (s) { return s ? "Foreground" : ""; }).ofObject(),
        $(go.Panel, "Auto",
            {
            name: "PANEL",
            },
            new go.Binding("desiredSize", "size", go.Size.parse).makeTwoWay(go.Size.stringify),
            $(go.Panel, "Spot",
            $(go.Shape, "RoundedRectangle",  // the outside rounded rectangle
                {
                name: "SHAPE",
                fill: $(go.Brush, "Linear", { 0: "OldLace", 1: "PapayaWhip" }), stroke: "#CDAA7D",
                parameter1: 10, // corner size
                portId: "", fromLinkable: true, toLinkable: true, cursor: "pointer",
                fromSpot: go.Spot.RightSide, toSpot: go.Spot.LeftSide
                },
                new go.Binding("fill", "color"),
            ),

            // $(go.Shape, "BpmnTaskScript",    // will be None, Script, Manual, Service, etc via converter
            //     {
            //         alignment: new go.Spot(0, 0, 5, 5), alignmentFocus: go.Spot.TopLeft,
            //         width: 22, height: 22
            //     },
            // ), // end Task Icon
            ),  // end main body rectangles spot panel
            $(go.TextBlock,  // the center text
            {
                alignment: go.Spot.Center, textAlign: "center", margin: 12,
                editable: true
            },
            new go.Binding("text").makeTwoWay())
        )  // end Auto Panel
        );  // end go.Node, which is a Spot Panel with bound itemArray

    }

    private relayoutDiagram() {
        this._diagram.layout.invalidateLayout();
        this._diagram.findTopLevelGroups().each(function (g) { if (g.category === "Pool") g.layout.invalidateLayout(); });
        this._diagram.layoutDiagram();
    }

    private getContextMenu($:any,contextMenuItems:ContextMenuItem[]){
        if(!contextMenuItems)
            return;
        
        return $(go.Adornment, "Vertical",
                contextMenuItems.map((item:ContextMenuItem)=>{
                    return $("ContextMenuButton",
                    $(go.TextBlock, item.Text, { margin: 3 }),
                    { click:item.CallBackMethod })
                }) 
            )
    }

    private createDiagram(diagramId: string): Diagram {
        let $ = go.GraphObject.make; 

        var nodeTemplateMap =  new go.Map("string", go.Node);
        nodeTemplateMap.add("state", this.getStateNodeTemplate($));

        var linkTemplateMap = new go.Map("string", go.Link);

        var groupTemplateMap = new go.Map("string", go.Group);


        this._diagram =  $(go.Diagram, diagramId, {
        nodeTemplateMap: nodeTemplateMap,
        linkTemplateMap: linkTemplateMap,
        groupTemplateMap: groupTemplateMap,
        linkingTool: new BPMNLinkingTool(), // defined in BPMNClasses.js
        "SelectionMoved": this.relayoutDiagram.bind(this),  // defined below
        "SelectionCopied": this.relayoutDiagram.bind(this),
        contextMenu:this.getContextMenu($,this.props.ContextMenuItems)
        });
        
        this._diagram.initialContentAlignment = go.Spot.Center;
        return this._diagram;
    }

    componentDidMount() {
        this.props.onRef(this)
    }

    componentWillUnmount() {
        this.props.onRef(undefined)
    }

    public render(): JSX.Element {
        return (
            <GojsDiagram
                diagramId="gojsDiagram"
                model={this.props.model}
                createDiagram={this.createDiagram}
                className="gojsDiagram"
            />
        );
    }
}

export default GoJsChart;
