import * as React from 'react';
import { Button, ListGroup } from 'react-bootstrap';
import { v4 as generateID } from 'uuid';
import { deepClone, rgetattr, rsetattr } from '../../../lib/helpers/objectMethods';
import { ILayout } from '../layout/interfaces/ILayout';
import { ITab } from '../layout/interfaces/ITab';
import Layout, { ILayoutProps } from '../layout/layout';
// Import Addons:
import { makeMeMultiSelect } from './addons/selectionbox.extension';
import { generateDefaultSelection } from './defaults/default.elements';
import { generateGraphOptions } from './defaults/default.graph-options';
import { defaultHotkeys } from './defaults/default.hotkeys';
import { defaultToolbar } from './defaults/default.toolbar';
// Graph related imports:
import { UndoRedoGraph } from './graph';
import { getSubElements } from './helpers/data.handlers';
import { IBaseEdgeOptions } from './interfaces/IBaseEdgeOptions';
import { IBaseNodeOptions } from './interfaces/IBaseNodeOptions';
import { IEdgeTypeDefinition } from './interfaces/IEdgeTypeDefinition';
import { IUndoRedoGraph } from './interfaces/IGraph';
import { IGraphCallbackData } from './interfaces/IGraphCallbackData';
import { IGraphTemplate } from './interfaces/IGraphTemplate';
import { DEFAULT_NETWORK, INetwork } from './interfaces/INetwork';
import { IVisjsOptions } from './interfaces/IVisjsOptions';

export interface GraphicalEditorComponentProps<N, E> extends Partial<ILayoutProps<IGraphTemplate<N,E>, IGraphCallbackData<N,E>>> {
    graphOptions?: IVisjsOptions;
    network?: INetwork<N, E>;
    addEdgeCallback?: (edgeData: E, callback: (edgeData: E) => void) => void;
    editOnSelect?: boolean;
    editOnChange?: boolean;
    parseFunctions?: boolean;
    enableContextMenu?: boolean;
    enableEditing?: boolean;
    hidePanelOnDeselect?: boolean;
    hideToolbar?: boolean;
    hideRightPanel?: boolean;
    allowSelfConnection?: boolean;
    allowDoubleConnections?: boolean;
    edgeTypes?: IEdgeTypeDefinition<N, E>[];
    showEdgeSelction?: boolean
}

export interface GraphicalEditorComponentState {
    update: null
}

class GraphicalEditorComponent<N, E> extends React.Component<GraphicalEditorComponentProps<N, E>, GraphicalEditorComponentState> {

    /**
     * The Element containing the Network.
     */
    public network: IUndoRedoGraph<N, E>;
    public layout: ILayout;

    public graphOptions: IVisjsOptions;

    constructor(props) {
        super(props);
    }

    public tabs: { [index: string]: INetwork<N, E> } = {};

    /**
     * Function to add the Addons of the Graph:
     * @param network 
     */
    public addGraphAddons(network: IUndoRedoGraph<N, E>) {
        makeMeMultiSelect(network);
    }

    /**
     * Function to add an Edge.
     * @param edgeData 
     * @param callback 
     */
    protected async _addEdge(edgeData: IBaseEdgeOptions, callback: (edge: IBaseEdgeOptions) => void) {

        // Defaulty prevent self connections.
        if (this.props.allowSelfConnection || edgeData.from !== edgeData.to) {

            let addEdge = true;

            // If multiple Connections from one node to the same node are
            // for bidden, test if there already exists such an edge.
            if (!this.props.allowDoubleConnections) {
                this.network.edges.forEach((edge) => {
                    if (edge.from === edgeData.from && edge.to === edgeData.to) {
                        addEdge = false;
                    }
                });
            }

            // If adding an Edge is allowed => Add the Edge.
            if (addEdge) {

                // Manually add an ID to the Edge
                edgeData.id = generateID();

                // Extract the From and To Node.
                const from = this.network.getNode(edgeData.from);
                const to = this.network.getNode(edgeData.to);

                const _this = this;
                const _addEdge = (type: IEdgeTypeDefinition<N, E>) => {
                    if (typeof type.customAddFunction == 'function') {

                        type.customAddFunction(from, to, _this.network);

                    } else {
                        // Define the Edge Object
                        let edge = Object.assign(deepClone(type.visual), {
                            from: edgeData.from,
                            to: edgeData.to
                        });

                        // Add the Edge.
                        callback(edge);
                    }
                }

                switch (edgeData.type) {
                    case 'automatic':
                        // Search for Possible Connection Types.
                        const possibleEdges: IEdgeTypeDefinition<N, E>[] = [];

                        for (const edgeType of this.props.edgeTypes) {
                            if ((edgeType.fromTypes.includes(from.type) || edgeType.fromTypes.length === 0) && (edgeType.toTypes.includes(to.type) || edgeType.toTypes.length === 0)) {
                                possibleEdges.push(edgeType);
                            }
                        }

                        // Test if Edges has been found
                        switch (possibleEdges.length) {
                            case 0:
                                this.layout.showToast('No Matching Edge Type has been found. Please select the corresponding Edge Type manually', 'info');
                                return;
                            case 1:
                                // Define the New Edge.
                                return _addEdge(possibleEdges[0]);
                            default:
                                if (this.props.showEdgeSelction) {
                                    // Create a Popup, on which the corresponding edge can be selected:
                                    try {
                                        const type: IEdgeTypeDefinition<N, E> = await this.layout.getDialogData({
                                            content: {
                                                props: {
                                                    possibleEdges
                                                },
                                                component(props: {
                                                    possibleEdges: IEdgeTypeDefinition<N, E>[],
                                                    onSubmit: (data: IEdgeTypeDefinition<N, E>) => void;
                                                    onCancel: (error: any) => void
                                                }) {
                                                    return (
                                                        <>
                                                            Select a possible edge by clicking on the item
                                                            <ListGroup>
                                                                {
                                                                    props.possibleEdges.map((item, idx) => {
                                                                        <ListGroup.Item key={idx} onClick={_ => props.onSubmit(item)}>{item.type}</ListGroup.Item>
                                                                    })
                                                                }
                                                            </ListGroup>
                                                            <Button variant="danger" onClick={e => props.onCancel(e)}>Cancel </Button>
                                                        </>
                                                    )
                                                },
                                            },
                                            header: 'Please select an edge-type!',
                                            closeButton: true
                                        });

                                        // Add the Edge itself.
                                        return _addEdge(type);
                                    } catch (e) {
                                        // The User has aborted the selection.
                                        return;
                                    }
                                }
                                this.layout.showToast('Mulitple Valid Edge Type has been found. Please select the corresponding Edge Type manually', 'info');
                        }
                        return;
                    default:
                        for (const type of this.props.edgeTypes) {
                            if ((edgeData.type === type.type) && (type.fromTypes.includes(from.type) || type.fromTypes.length === 0) && (type.toTypes.includes(to.type) || type.toTypes.length === 0)) {
                                // Quit the Method
                                return _addEdge(type);
                            }
                        }
                        this.layout.showToast('You are not allowed to connect the desired Nodes with this edges', 'error');
                        break;
                }

            } else {
                this.layout.showToast('Double Connections not allowed! You can not connect these elements again', 'error');
            }
        } else {
            this.layout.showToast('Self-Connections forbidden! You can not connect elements to itself', 'error');
        }
    }

    public async initEditor(ref: React.RefObject<any>, mainRef: React.RefObject<any>) {

        if (process.browser){
            const _this = this;

            // Based on the Provided Options =>
            // select the corresponding graph options,
            // either use th provided one or use the 
            // default options.
            if (this.props.graphOptions) {
                this.graphOptions = this.props.graphOptions;
            } else {
                this.graphOptions = generateGraphOptions();
            }
    
            if (!rgetattr(this.graphOptions, 'manipulation.enabled', false)) {
                rsetattr(this.graphOptions, 'manipulation.enabled', false);
            }
    
            // Create the Network
            _this.network = new UndoRedoGraph(ref, mainRef, this.graphOptions);
    
            // Add a new item if possible. 
            const editItemIfPossible = (params) => {
                if (params.nodes.length === 1) {
                    // A Node was Selected. Figure out, whether it is a cluster or not.
                    const nodeID = params.nodes[0];
    
                    if (_this.network.network.isCluster(nodeID)) {
                        // Uncluster the Nodes
                        _this.network.network.openCluster(nodeID);
                    } else {
                        // Open up the Settings Tab
                        const selection = getSubElements(_this.network, false);
                        _this.updateNode(selection);
                    }
                } else if (params.edges.length === 1) {
                    // A Node was Selected. Figure out, whether it is a cluster or not.
                    const edges = _this.network.data.edges.get(params.edges[0]);
                    _this.updateEdges(edges);
                }
            }
    
            // Make the Default behaviour adding new Nodes
            this.network.on('doubleClick', (params) => {
                if (this.props.enableEditing) {
                    // Test if no Element was Selected
                    if (params.nodes.length === 0 && params.edges.length === 0) {
                        // If so, just add the new Element
                        _this.addNode(params.pointer.canvas);
                    } else {
                        editItemIfPossible(params);
                    }
                }
            });
    
            this.network.on('oncopy', event => {
                try {
                    _this.layout.showToast('Copied')
                } catch (e) {
    
                }
            });
    
            this.network.on('onpaste', async event => {
                try {
                    _this.layout.showToast('Pasted')
                } catch (e) {
                }
            });
    
            this.network.on('oncontext', (...args) => console.log('rightclick', ...args))
    
    
            // // Make shure the Sidepanel is working correctly
            // this.network.on('select', (params) => {
            //     if (_this.graphOptions.enableEditing){
            //         if (_this.graphOptions.editOnSelect && _this.graphOptions.hidePanelOnDeselect && params.nodes.length === 0 && params.edges.length === 0){
            //             _this.layout.panels.right.hide();
            //         } else if (_this.graphOptions.editOnSelect){
            //             editItemIfPossible(params);
            //         }
            //     }
            // });
    
            // enableClusterPreview(this);
    
            // this.network.addNode(this._nodes);
            // this.network.addEdge(this._edges);
    
            // editor.resize();
    
            // Add all Addons as required.
            this.addGraphAddons(this.network);
        }        

        return this.network;
    }

    /**
     * Function to Update the Data of a Node.
     * @param selection The Selected Node.
     */
    public async updateNode(selection: Array<IBaseNodeOptions<N>>) {
        const _self = this;

        /** Extract the Component, which should be used in the Prompt */
        let componentSelector = 'default';
        if (selection.length > 0 && selection[0].editorComponentSelector) {
            componentSelector = selection[0].editorComponentSelector;
        }

        // const comp = this.editPanelDict.nodes[componentSelector];

        // if (comp) {
        //     /** Open the Window, with the Edit-Prompt */
        //     this.openEditInterface(this.editPanelDict.nodes[componentSelector], {
        //         inputTemplate: {
        //             nodes: selection,
        //             edges: [],
        //             type: 'elements'
        //         },
        //     },
        //         'Edit Node',
        //         (data) => {
        //             _self.network.updateNode(data.template.nodes);
        //         });
        // } else {
        //     this.layout.showToast('Editor is Trying to open an Unkown Edit-Component', 'error');
        // }
    }

    /**
     * Function, which is used to Update an Edge
     * @param edge The Corresponding Edge, which will be updated.
     */
    public async updateEdges(edge: E) {
        const _self = this;

        // /** Extract the Component, which should be used in the Prompt */
        // let componentSelector = 'default';
        // if (edge.editorComponentSelector) {
        //     componentSelector = edge.editorComponentSelector;
        // }

        // const comp = this.editPanelDict.edges[componentSelector];

        // if (comp) {
        //     /** Open the Window, with the Edit-Prompt */
        //     this.openEditInterface(comp, {
        //         inputTemplate: {
        //             nodes: [],
        //             edges: [edge],
        //             type: 'elements'
        //         }
        //     }, 'Edit Edge', (data) => {
        //         _self.network.updateEdge(data.template.edges);
        //     });
        // } else {
        //     throw TypeError('The Element trys to open an Unkown Edit-Component');
        // }

    }

    public addNode(pos: { x: number, y: number }) {
        const _this = this;

        // switch (this.template.type) {
        //     case 'elements':
        //         if (this.template.nodes && this.template.nodes.length > 0) {
        //             this.template.nodes[0].x = pos.x;
        //             this.template.nodes[0].y = pos.y;

        //             let componentSelector = 'default';

        //             if (this.template.nodes.length > 0 && this.template.nodes[0].editorComponentSelector) {
        //                 componentSelector = this.template.nodes[0].editorComponentSelector;
        //             }

        //             this.openEditInterface(
        //                 this.editPanelDict.nodes[componentSelector],
        //                 {
        //                     inputTemplate: deepClone(this.template as ITemplate<N, E>),
        //                 },
        //                 'Add Node'
        //                 , (data) => {
        //                     let adapted = adaptIDS(data.template);

        //                     if (typeof data.callback === 'function') {
        //                         adapted = data.callback(adapted);
        //                     }

        //                     _this.network.addNode(adapted.nodes);
        //                     _this.network.addEdge(adapted.edges);
        //                 },
        //                 'popup'
        //             );
        //         }

        //         return true;
        //     default:
        //         this.enableHotkeys();
        // }

        return false
    }

    public settings: GraphicalEditorComponentProps<N, E>

    public initializeGraph() {

        const _this = this;

        // Assign a default Network:
        const networkToRender: INetwork<N,E> = this.props.network || DEFAULT_NETWORK();

        // Initally store the Network to Render
        this.tabs[networkToRender.id] = networkToRender;

        this.settings = {
            allowUserSelect: true,

            network: networkToRender,

            onItemSelected(item) {

            },

            onMount(ref, mainRef, layout) {
                // Define the Network.
                _this.initEditor(ref, mainRef).catch(e => _this.layout.showToast(e, 'error'));

                // Load the Network:
                _this.network.loadData(networkToRender);
                _this.layout = layout;
            },

            onResize() {
                if (_this.network) {
                    _this.network.resize();
                }
            },

            async onNewTab() {
                // Function to create a new tab.
                try {
                    const label = await _this.layout.getDialogData({
                        header: 'Enter Network-Name',
                        content: {
                            component: 'DynamicForm',
                            props: {
                                schema: {
                                    type: "string",
                                    description: "Name of the Element"
                                }
                            }
                        }
                    })

                    const id = Date.now().toString();
                    // const label = id;

                    _this.tabs[id] = DEFAULT_NETWORK();

                    return {
                        id,
                        label,
                        delteable: true
                    }
                } catch (error) {
                    return false;
                }
            },

            async onTabSelect(oldTab: ITab, newTab: ITab) {
                // Contains the Main Logic for selecting another tab
                // Therefore 
                if (oldTab){
                    _this.tabs[oldTab.id] = _this.network.getData();
                }
                

                // Use the content of the new Tab as rendering:
                _this.network.loadData(_this.tabs[newTab.id]);

                return true;
            },

            async onTabDelete(tab: ITab, force: boolean) {
                // A Tab should be deleted

                if (force){
                    return true;
                }

                try {
                    const saveData = await _this.layout.getDialogData<boolean>({
                        header: 'Close Tab',
                        content: {
                            component: (props) => {
                                return (<>
                                    <p>
                                        You are about to close the tab "{tab.label}". Do you want to save changes?
                                    </p>
                                    <Button variant="success" onClick={_ => props.onSubmit(true)}>Yes</Button>{' '}
                                    <Button variant="danger" onClick={_ => props.onSubmit(false)}>No</Button>{' '}
                                    <Button variant="secondary" onClick={_ => props.onCancel(new Error("Canceled"))}>Cancel</Button>{' '}
                                </>)
                            },
                            props: {}
                        }
                    });

                    // The variable save Data contains the info, whether the user wants to save or discard the changes
                    return true;

                } catch (error) {
                    return false
                }
            },

            async onNoTabSelected() {
                // Nothing to Display!

                // Use the content of the new Tab as rendering:
                _this.network.loadData(DEFAULT_NETWORK());
            },

            onUnmount() {
                if (_this.network) {
                    // Destroy the Network.
                    _this.network.destroy();
                }
            },

            tabs: {
                active: 'start',
                items: [],
                allowNewTabs: true
            },

            generateData() {
                // Get the Current Selection.
                const selection = _this.network.network.getSelection(true);

                return {
                    network: _this.network,
                    layout: _this.layout,
                    selectedEdges: selection.edges,
                    selectedNodes: selection.nodes
                }
            },

            toolbar: defaultToolbar(),

            hotkeys: defaultHotkeys()
        };

        return this.settings;
    }

    public requestRerender() {
        this.setState({
            update: null
        })
    }

    public componentDidMount() {
        const _this = this;
    }

    public render() {
        const settings = this.initializeGraph();

        return (
            <>
                <Layout
                    selection={generateDefaultSelection<N,E>()}
                    allowUserSelect
                    onMount={settings.onMount}
                    onResize={settings.onResize}
                    onNewTab={settings.onNewTab}
                    onTabSelect={settings.onTabSelect}
                    onUnmount={settings.onUnmount}
                    onNoTabSelected={settings.onNoTabSelected}
                    onTabDelete={settings.onTabDelete}
                    tabs={settings.tabs}
                    generateData={settings.generateData}
                    toolbar={settings.toolbar}
                    hotkeys={settings.hotkeys}
                ></Layout>
            </>
        )
    }
}

export default GraphicalEditorComponent;