import React from "react";
import { DynamicGraph } from "./graph";
import { loadGraphJson } from "../common/datasets";
import { NodeDetails } from "./components/nodedetails";
import { SpaceSelect } from "./components/spaceselect";
import "./editor.css";

import { Node } from "../common/graph/node";

import { NodeTypesEditor } from "./components/nodetypeseditor";
import { SpaceManager } from "./components/spacemanager";
import { SelectLayer } from "./components/selectlayer";
import { GraphData } from "../common/graph/graph";
import { NodeType } from "../common/graph/nodetype";
import { GraphRenderer2D } from "./renderer";
import Instructions from "./components/instructions";
import Settings from "./components/settings";

type propTypes = {
    spaceId: string;
};
type stateTypes = {
    /**
     * Graph structure holding the basic information.
     */
    graph: DynamicGraph;

    /**
     * Should labels on nodes be rendered, or none at all.
     */
    visibleLabels: boolean;

    /**
     * Should feature be enabled, that nodes get connected with a link of dragged close enough to each other?
     */
    connectOnDrag: boolean;

    /**
     * Current width of graph object. Used to specifically adjust and correct the graph size.
     */
    graphWidth: number;

    /**
     * True for each key, that is currently considered pressed. If key has not been pressed yet, it will not exist as dict-key.
     */
    keys: { [name: string]: boolean };

    /**
     * Collection of all currently selected nodes. Can also be undefined or empty.
     */
    selectedNodes: Node[];
};

/**
 * Knowledge space graph editor. Allows easy editing of the graph structure.
 */
export class Editor extends React.PureComponent<propTypes, stateTypes> {
    private graphContainer: React.RefObject<HTMLDivElement>;
    private rendererRef: React.RefObject<GraphRenderer2D>;

    constructor(props: propTypes) {
        super(props);

        // Making sure, all functions retain the proper this-bind
        this.loadGraph = this.loadGraph.bind(this);
        this.loadSpace = this.loadSpace.bind(this);
        this.forceUpdate = this.forceUpdate.bind(this);
        this.handleNodeTypeSelect = this.handleNodeTypeSelect.bind(this);
        this.handleBoxSelect = this.handleBoxSelect.bind(this);
        this.selectNodes = this.selectNodes.bind(this);

        document.addEventListener("keydown", (e) => {
            this.keyPressed(e.key);
        });
        document.addEventListener("keyup", (e) => {
            this.keyReleased(e.key);
        });

        this.graphContainer = React.createRef();

        // Set as new state
        this.state = {
            graph: undefined,
            visibleLabels: true,
            connectOnDrag: false,
            graphWidth: 1000,
            selectedNodes: [],
            keys: {},
        };
    }

    keyPressed(key: string) {
        const keys = this.state.keys;
        keys[key] = true;
        this.setState({ keys: keys });
    }

    keyReleased(key: string) {
        const keys = this.state.keys;
        keys[key] = false;
        this.setState({ keys: keys });
    }

    /**
     * Tries to load initial graph after webpage finished loading.
     */
    componentDidMount() {
        if (this.props.spaceId !== undefined) {
            // Load initial space
            this.loadSpace(this.props.spaceId);
        }
    }

    /**
     * Loads a space from the database to the editor.
     * @param spaceId Id of space to load.
     * @returns Promise with boolean value that is true, if successful.
     */
    public loadSpace(spaceId: string): any {
        return loadGraphJson(spaceId).then(this.loadGraph);
    }

    /**
     * Loads another graph based on the data supplied. Note: Naming currently suggests that this only loads a GRAPH, not a SPACE. Needs further work and implementation to see if that makes sense or not.
     * @param data Serialized graph data.
     * @returns True, if successful.
     */
    public loadGraph(data: GraphData): boolean {
        console.log("Starting to load new graph ...");
        console.log(data);

        // Create graph
        const graph = new DynamicGraph();
        graph.fromSerializedObject(data);

        // Set as new state
        console.log(graph);
        this.setState({
            graph: graph,
        });

        //graph.onChangeCallbacks.push(this.onGraphDataChange);

        // Subscribe to global events
        window.addEventListener("resize", () => this.handleResize());

        this.handleResize();

        return true;
    }

    /**
     * Processes resize window event. Focusses on resizing the graph accordingly.
     */
    private handleResize() {
        const newGraphWidth = this.graphContainer.current.clientWidth;
        this.setState({
            graphWidth: newGraphWidth,
        });
    }

    // /**
    //  * Makes sure to always offer a valid format of the selected nodes. Is either undefined or contains at least one valid node. An empty array is never returned.
    //  */
    // private get selectedNodes(): Node[] {
    //     // TODO: Here are a lot of things that should not be possible by design
    //
    //     // Remove undefines
    //     let selectedNodes = this.state.selectedNodes.filter(
    //         (n: Node) => n !== undefined
    //     );
    //
    //     // Remove duplicates
    //     selectedNodes = [...new Set(selectedNodes)];
    //
    //     if (selectedNodes.length > 0) {
    //         return selectedNodes;
    //     }
    //
    //     return undefined;
    // }

    handleBoxSelect(selectedNodes: Node[]) {
        if (selectedNodes !== undefined && selectedNodes.length <= 0) {
            return;
        }

        this.selectNodes(selectedNodes.concat(this.state.selectedNodes));
    }

    /**
     * Selects multiple nodes, or clears selection if given undefined or empty array.
     * @param nodes Multiple nodes to mark as selected.
     */
    public selectNodes(nodes: Node[]) {
        this.setState({
            selectedNodes: nodes,
        });
    }

    private handleNodeTypeSelect(type: NodeType) {
        const nodesWithType = this.state.graph.nodes.filter((n: Node) =>
            n.type.equals(type)
        );
        this.selectNodes(nodesWithType);
    }

    render(): React.ReactNode {
        return (
            <div id="ks-editor">
                <h1>Interface</h1>
                <SpaceSelect onLoadSpace={this.loadSpace} />
                <SpaceManager />
                <div id="content">
                    {this.state.graph && (
                        <div
                            id="force-graph-renderer"
                            ref={this.graphContainer}
                        >
                            <SelectLayer
                                allNodes={
                                    this.state.graph
                                        ? this.state.graph.nodes
                                        : []
                                }
                                screen2GraphCoords={
                                    this.rendererRef
                                        ? this.rendererRef.current
                                              .screen2GraphCoords
                                        : undefined
                                }
                                isEnabled={this.state.keys["Shift"]}
                                onBoxSelect={this.handleBoxSelect}
                            >
                                <GraphRenderer2D
                                    ref={this.rendererRef}
                                    graph={this.state.graph}
                                    width={this.state.graphWidth}
                                    onNodeSelectionChanged={this.selectNodes}
                                    selectedNodes={this.state.selectedNodes}
                                />
                            </SelectLayer>
                        </div>
                    )}
                    {this.state.graph && (
                        <div id="sidepanel">
                            {/*<HistoryNavigator*/}
                            {/*    spaceId="space"*/}
                            {/*    history={this.state.graph.history}*/}
                            {/*    onChange={this.onGraphDataChange}*/}
                            {/*/>*/}
                            <hr />
                            <NodeDetails
                                selectedNodes={this.state.selectedNodes}
                                allTypes={
                                    this.state.graph
                                        ? this.state.graph.objectGroups
                                        : []
                                }
                                onChange={this.forceUpdate}
                            />
                            <hr />
                            <h3>Node types</h3>
                            <NodeTypesEditor
                                onChange={this.forceUpdate}
                                graph={this.state.graph}
                                onSelectAll={this.handleNodeTypeSelect}
                            />
                            <hr />
                            <Settings
                                labelVisibility={this.state.visibleLabels}
                                onLabelVisibilityChange={(visible) =>
                                    this.setState({ visibleLabels: visible })
                                }
                                connectOnDrag={this.state.connectOnDrag}
                                onConnectOnDragChange={(connectOnDrag) =>
                                    this.setState({
                                        connectOnDrag: connectOnDrag,
                                    })
                                }
                            />
                            <hr />

                            <Instructions
                                connectOnDragEnabled={this.state.connectOnDrag}
                            />
                        </div>
                    )}
                </div>
            </div>
        );
    }
}