/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alibaba.compileflow.idea.graph;

import com.alibaba.compileflow.idea.graph.codec.DocumentHandler;
import com.alibaba.compileflow.idea.graph.model.BpmModel;
import com.alibaba.compileflow.idea.graph.model.EdgeModel;
import com.alibaba.compileflow.idea.graph.model.GeolocationModel;
import com.alibaba.compileflow.idea.graph.model.LoopProcessNodeModel;
import com.alibaba.compileflow.idea.graph.mxgraph.GraphComponent;
import com.alibaba.compileflow.idea.graph.nodeview.dialog.GlobalPropertyDialog;
import com.alibaba.compileflow.idea.graph.nodeview.NodeEditDialogFactory;
import com.alibaba.compileflow.idea.graph.nodeview.dialog.TransitionPropertiesDialog;
import com.alibaba.compileflow.idea.graph.mxgraph.Cell;
import com.alibaba.compileflow.idea.graph.model.TransitionModel;
import com.alibaba.compileflow.idea.graph.mxgraph.Graph;
import com.alibaba.compileflow.idea.graph.palette.EditorPalette;
import com.alibaba.compileflow.idea.graph.toolbar.FlowChartToolBar;
import com.alibaba.compileflow.idea.graph.util.Initializer;
import com.alibaba.compileflow.idea.graph.util.SwitchModel;
import com.alibaba.compileflow.idea.graph.palette.EditorPaletteModel;
import com.alibaba.compileflow.idea.graph.model.BaseNodeModel;
import com.alibaba.compileflow.idea.graph.model.EndNodeModel;
import com.alibaba.compileflow.idea.graph.model.StartNodeModel;

import com.intellij.designer.ModuleProvider;
import com.intellij.openapi.actionSystem.DataKey;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.components.JBScrollPane;
import com.intellij.ui.components.JBTabbedPane;
import com.mxgraph.model.mxCell;
import com.mxgraph.model.mxGeometry;
import com.mxgraph.model.mxGraphModel;
import com.mxgraph.swing.handler.mxRubberband;
import com.mxgraph.swing.mxGraphOutline;
import com.mxgraph.util.mxEvent;
import com.mxgraph.util.mxEventSource;
import com.mxgraph.util.mxUndoManager;
import com.mxgraph.util.mxUndoableEdit;

import javax.swing.*;

import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.List;

/**
 * @author wuxiang
 * @author xuan
 */
public class FlowChartComponent extends JPanel implements ModuleProvider {

    private GraphComponent graphComponent;
    private Graph graph;
    private final Project project;
    private final VirtualFile file;
    private Module module;
    private DocumentHandler documentHandler;

    private JTabbedPane libraryPane;
    private mxGraphOutline graphOutline;
    private mxUndoManager undoManager;

    public static final DataKey<FlowChartComponent> DATA_KEY = DataKey.create(FlowChartComponent.class.getName());

    private SwitchModel switchModel = new SwitchModel();

    public FlowChartComponent(Graph g, GraphComponent graphComponent, Project project, Module module,
        VirtualFile file) {

        this.project = project;
        this.graph = g;
        this.module = module;
        this.file = file;

        this.setName(this.getClass().getSimpleName());
        this.graphComponent = graphComponent;
        this.setLayout(new BorderLayout());
        this.add(this.graphComponent, "Center");
        this.graphComponent.setDragEnabled(true);

        this.graphComponent.getGraphControl().addMouseListener(new MouseAdapter() {
            @Override
            public void mouseReleased(MouseEvent e) {
                Object cell = graphComponent.getCellAt(e.getX(), e.getY());

                if (cell != null) {
                    if (cell instanceof mxCell) {
                        mxCell mxCell = (mxCell)cell;

                        if (mxCell.isVertex()) {
                            ApplicationManager.getApplication().invokeLater(() -> {
                                if (switchModel.isPass() && null != module) {
                                    DialogWrapper dialog = NodeEditDialogFactory.getDialog(project, mxCell, graph);
                                    if (null != dialog) {
                                        dialog.show();
                                    }
                                }
                            });

                        } else if (mxCell.isEdge()) {
                            ApplicationManager.getApplication().invokeLater(() -> {
                                if (null != module) {
                                    TransitionPropertiesDialog dialog = new TransitionPropertiesDialog(project,
                                        graphComponent, mxCell);
                                    dialog.pack();
                                    dialog.setVisible(true);
                                }
                            });
                        }
                    }
                } else {
                    ApplicationManager.getApplication().invokeLater(() -> {
                        if (switchModel.isPass() && null != module) {
                            GlobalPropertyDialog globalPropertyDialog = new GlobalPropertyDialog(project, graph);
                            globalPropertyDialog.show();
                        }
                    });
                }
                super.mouseReleased(e);
            }
        });

        // Read bpm file
        this.documentHandler = new DocumentHandler(graph, project, file);
        this.documentHandler.readFromFile();
        // Keeps the selection in sync with the command history
        mxEventSource.mxIEventListener undoHandler = (o, evt) -> undoManager.undoableEditHappened((mxUndoableEdit)evt
            .getProperty("edit"));

        undoManager = new mxUndoManager();
        undoManager.addListener(mxEvent.UNDO, undoHandler);
        undoManager.addListener(mxEvent.REDO, undoHandler);

        // Adds the command history to the model and view
        graph.getModel().addListener(mxEvent.UNDO, undoHandler);
        graph.getView().addListener(mxEvent.UNDO, undoHandler);

        // Creates the graph outline component
        graphOutline = new mxGraphOutline(graphComponent);
        libraryPane = new JBTabbedPane();

        JSplitPane inner = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
            libraryPane, graphOutline);
        inner.setDividerLocation(320);
        inner.setResizeWeight(1);
        inner.setDividerSize(6);
        inner.setBorder(null);

        JSplitPane outer = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, inner,
            graphComponent);
        outer.setOneTouchExpandable(true);
        outer.setDividerLocation(200);
        outer.setDividerSize(6);
        outer.setBorder(null);

        // Puts everything together
        setLayout(new BorderLayout());
        add(outer, BorderLayout.CENTER);

        // Init Editor Palette
        EditorPalette editorPalette = insertPalette(BpmModel.BPM_DEFINE_PROCESS, graphComponent);
        editorPalette.setModel(new EditorPaletteModel(file.getExtension()));

        installToolBar();
        installListeners();

        graph.getModel().addListener(mxEvent.CHANGE, (sender, evt) -> {
            Object changes = evt.getProperties().get("changes");
            if (changes instanceof ArrayList) {

                ArrayList list = (ArrayList)changes;
                for (Object o : list) {

                    if (o instanceof mxGraphModel.mxGeometryChange) {
                        mxCell mxCell = (com.mxgraph.model.mxCell)((mxGraphModel.mxGeometryChange)o).getCell();
                        // Update geometry change to node
                        mxGeometry geometry = mxCell.getGeometry();
                        if (mxCell.isVertex()) {
                            BaseNodeModel nodeModel = (BaseNodeModel)mxCell.getValue();

                            if (nodeModel.getG() != null) {
                                GeolocationModel g1 = new GeolocationModel(nodeModel.getG());
                                nodeModel.setG(GeolocationModel
                                    .toG(geometry.getX(), geometry.getY(), (int)geometry.getWidth(),
                                        (int)geometry.getHeight()));
                                if (nodeModel.getId() == null) {
                                    nodeModel.setId(Cell.createId());
                                }
                            } else {
                                if (nodeModel.getId() == null) {
                                    nodeModel.setId(Cell.createId());
                                }
                                if (nodeModel instanceof StartNodeModel || nodeModel instanceof EndNodeModel) {
                                    nodeModel.setG(GeolocationModel.toG(geometry.getX(), geometry.getY(), 30, 30));
                                } else if (nodeModel instanceof LoopProcessNodeModel) {
                                    nodeModel.setG(GeolocationModel.toG(geometry.getX(), geometry.getY(), 200, 200));
                                } else {
                                    nodeModel.setG(GeolocationModel.toG(geometry.getX(), geometry.getY(), 88, 48));
                                }
                                mxCell.setValue(nodeModel);
                            }

                        } else if (mxCell.isEdge()) {
                        }
                    } else if (o instanceof mxGraphModel.mxTerminalChange) {

                        mxCell pCell = (com.mxgraph.model.mxCell)((mxGraphModel.mxTerminalChange)o).getCell();

                        if (pCell.getValue() instanceof EdgeModel) {

                            EdgeModel edgeRelation = (EdgeModel)pCell.getValue();
                            mxCell terminalCell = (com.mxgraph.model.mxCell)((mxGraphModel.mxTerminalChange)o)
                                .getTerminal();
                            if (null == terminalCell) {
                                return;
                            }
                            BaseNodeModel tNode = (BaseNodeModel)terminalCell.getValue();
                            List<TransitionModel> toLists = new ArrayList<>();
                            TransitionModel transition = new TransitionModel();
                            transition.setTo(tNode.getId());
                            toLists.add(transition);
                            edgeRelation.setTransition(transition);
                            BaseNodeModel abstractNode = edgeRelation.getCurrentNode();
                            List<TransitionModel> olderList = abstractNode.getOutTransitions();
                            if (olderList != null) {
                                olderList.addAll(toLists);
                                abstractNode.setOutTransitions(olderList);
                            } else {
                                abstractNode.setOutTransitions(toLists);
                            }
                        } else if (pCell.getValue() instanceof String) {
                            EdgeModel edgeRelation = new EdgeModel();
                            pCell.setValue(edgeRelation);
                            mxCell terminalCell = (com.mxgraph.model.mxCell)((mxGraphModel.mxTerminalChange)o)
                                .getTerminal();
                            BaseNodeModel currentNode = (BaseNodeModel)terminalCell.getValue();
                            if (currentNode != null) {
                                edgeRelation.setCurrentNode(currentNode);
                            }
                        }
                    }
                }
            }

            documentHandler.saveToFile();
            graphComponent.validateGraph();
            graphComponent.repaint();
        });

        installHandlers();
        installListeners();

        Initializer.init();
    }

    private void installToolBar() {
        add(new FlowChartToolBar(this, JToolBar.VERTICAL), BorderLayout.EAST);
    }

    private void installHandlers() {
        //Mouse Selection Node Ability
        new mxRubberband(graphComponent);
        //Keyboard Operating Ability
        new EditorKeyboardHandler(graphComponent);
    }

    public mxUndoManager getUndoManager() {
        return undoManager;
    }

    private EditorPalette insertPalette(String title, GraphComponent graphComponent) {
        final EditorPalette palette = new EditorPalette();
        final JScrollPane scrollPane = new JBScrollPane(palette);
        scrollPane
            .setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
        scrollPane
            .setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        libraryPane.add(title, scrollPane);
        //Display scrollbars when dragging libraryPane
        libraryPane.addComponentListener(new ComponentAdapter() {
            @Override
            public void componentResized(ComponentEvent e) {
                int w = scrollPane.getWidth()
                    - scrollPane.getVerticalScrollBar().getWidth();
                palette.setPreferredWidth(w);
            }
        });

        return palette;
    }

    private void installListeners() {

        // Installs the popup menu in the graph component
        graphComponent.getGraphControl().addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                // Handles context menu on the Mac where the trigger is on mousepressed
                mouseReleased(e);
            }

            @Override
            public void mouseReleased(MouseEvent e) {
            }
        });
    }

    public GraphComponent getGraphComponent() {
        return this.graphComponent;
    }

    @Override
    public Module getModule() {
        if (null == this.module) {
            return null;
        }
        if (this.module.isDisposed()) {
            this.module = ModuleUtil.findModuleForFile(this.file, this.project);
        }
        return this.module;
    }

    @Override
    public Project getProject() {
        return this.project;
    }

    public Graph getGraph() {
        return this.graph;
    }

    public VirtualFile getFile() {
        return this.file;
    }

    public void dispose() {

    }

    public SwitchModel getSwitchModel() {
        return switchModel;
    }

}
