package pipe.actions.gui;

import pipe.controllers.PetriNetController;
import pipe.controllers.application.PipeApplicationController;
import pipe.gui.AbstractDatum;
import pipe.gui.TokenEditorPanel;
import pipe.historyActions.MultipleEdit;
import pipe.historyActions.component.AddPetriNetObject;
import pipe.historyActions.component.ChangePetriNetComponentName;
import pipe.historyActions.component.DeletePetriNetObject;
import pipe.historyActions.token.ChangeTokenColor;
import pipe.utilities.gui.GuiUtils;
import pipe.views.PipeApplicationView;
import uk.ac.imperial.pipe.exceptions.PetriNetComponentException;
import uk.ac.imperial.pipe.exceptions.PetriNetComponentNotFoundException;
import uk.ac.imperial.pipe.models.petrinet.PetriNet;
import uk.ac.imperial.pipe.models.petrinet.PetriNetComponent;
import uk.ac.imperial.pipe.models.petrinet.Token;

import javax.swing.*;
import javax.swing.undo.UndoableEdit;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Action used to create, delete and edit tokens in the Petri net
 */
@SuppressWarnings("serial")
public class SpecifyTokenAction extends GuiAction {
    /**
     * Main PIPE application controller
     */
    private final PipeApplicationController pipeApplicationController;

    /**
     * Main PIPE application view
     */
    private final PipeApplicationView applicationView;

    /**
     * Pop up editor panel for the tokens
     */
    private TokenEditorPanel tokenEditorPanel;

    /**
     * Pop up dialog
     */
    private JDialog guiDialog;

    /**
     * Legacy action, I'm not sure what this is
     */
    private ActionEvent forcedAction;

    public SpecifyTokenAction(PipeApplicationController pipeApplicationController,
                              PipeApplicationView applicationView) {
        super("SpecifyTokenClasses", "Specify tokens (ctrl-shift-T)", KeyEvent.VK_T,
                Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() | InputEvent.SHIFT_DOWN_MASK);
        this.pipeApplicationController = pipeApplicationController;
        this.applicationView = applicationView;
    }

    /**
     * Pops up to change the petri net tokens if there is an active petri net
     *
     * @param e event 
     */
    @Override
    public void actionPerformed(ActionEvent e) {
        if (pipeApplicationController.getActivePetriNetController() != null) {
            buildTokenGuiClasses();
            finishBuildingGui();
        }
    }

    /**
     * Creates a new token editor with the tokens from the Petri net
     */
    public void buildTokenGuiClasses() {
        tokenEditorPanel = new TokenEditorPanel(pipeApplicationController.getActivePetriNetController());
        guiDialog = new TokenDialog("Tokens", true, tokenEditorPanel);
    }

    /**
     * Provides the set up for what the token editor panel will look like
     */
    public void finishBuildingGui() {
        guiDialog.setSize(600, 200);
        guiDialog.setLocationRelativeTo(null);
        tokenEditorPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
        tokenEditorPanel.setOpaque(true);

        JPanel buttonPane = new JPanel();
        buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS));
        buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10));
        buttonPane.add(Box.createHorizontalGlue());
        JButton ok = new JButton("OK");
        ok.addActionListener((ActionListener) guiDialog);
        buttonPane.add(ok);
        buttonPane.add(Box.createRigidArea(new Dimension(10, 0)));
        JButton cancel = new JButton("Cancel");
        cancel.addActionListener((ActionListener) guiDialog);
        buttonPane.add(cancel);

        guiDialog.add(tokenEditorPanel, BorderLayout.CENTER);
        guiDialog.add(buttonPane, BorderLayout.PAGE_END);
        tokenEditorPanel.setVisible(true);

        if (forcedAction != null) {
            forceContinue();
        } else {
            guiDialog.setVisible(true);
        }
    }

    /**
     * Legacy code
     */
    private void forceContinue() {
        ((TokenDialog) guiDialog).actionPerformed(forcedAction);
        forcedAction = null;
    }

    /**
     * @author Alex Charalambous, June 2010: ColorDrawer, ColorPicker,
     *         TokenPanel and TokenDialog are four classes used
     *         to display the Token Classes dialog (accessible through the button
     *         toolbar).
     */

    public class TokenDialog<T extends PetriNetComponent> extends JDialog implements ActionListener {

        /**
         * Class logger
         */
        private final Logger logger = Logger.getLogger(TokenDialog.class.getName());

        /**
         * Editor panel
         */
        private TokenEditorPanel tokenEditorPanel;

        /**
         *
         * @param title token dialog title
         * @param modal dialog 
         * @param tokenEditorPanel panel 
         */
        public TokenDialog(String title, boolean modal, TokenEditorPanel tokenEditorPanel) {
            super(applicationView, title, modal);
            this.tokenEditorPanel = tokenEditorPanel;
        }

        /**
         * Processes the changing of Petri net tokens
         * @param e event 
         */
        @Override
        public void actionPerformed(ActionEvent e) {
            if (e.getActionCommand().equals("OK")) {
                if (tokenEditorPanel.isDataValid()) {
                    updateFromTable(tokenEditorPanel.getTableData());
                    removeDeletedData(tokenEditorPanel.getDeletedData());
                    setVisible(false);
                }
            } else if (e.getActionCommand().equals("Cancel")) {
                setVisible(false);
            }
        }

        //TODO: ONCE PETRINET CAN GET COMPONENT BY ID YOU CAN MAKE THIS WHOLE CLASS ABSTRACT
        //      AND SHARE IT WITH RATE EDITOR

        /**
         * Removes tokens from the Petri net.
         * <p>
         * Creates error message if a token cannot be removed for some reason, for
         * example if places still contain tokens of its type. It will apply all other
         * changes.
         * </p>
         * @param deletedData contains Datum items that were deleted from the table
         */
        private void removeDeletedData(Iterable<TokenEditorPanel.Datum> deletedData) {
            PetriNetController petriNetController = pipeApplicationController.getActivePetriNetController();
            PetriNet petriNet = petriNetController.getPetriNet();
            List<UndoableEdit> undoableEdits = new LinkedList<>();
            for (TokenEditorPanel.Datum datum : deletedData) {
                if (tokenEditorPanel.isExistingDatum(datum)) {
                    try {
                        Token token = petriNet.getComponent(datum.id, Token.class);
                        petriNet.removeToken(token);
                        UndoableEdit historyItem = new DeletePetriNetObject(token, petriNet);
                        undoableEdits.add(historyItem);
                    } catch (PetriNetComponentNotFoundException e) {
                    	logger.log(Level.SEVERE, e.getMessage());
                    } catch (PetriNetComponentException e) {
                        StringBuilder messageBuilder = new StringBuilder();
                        messageBuilder.append(e.getMessage());
                        messageBuilder.append("\n");
                        messageBuilder.append("All other changes will be applied but this token will not be deleted!");
                        GuiUtils.displayErrorMessage(null, messageBuilder.toString());
                    }
                }
            }
            if (undoableEdits.size() > 0) {
                registerUndoEvent(new MultipleEdit(undoableEdits));
            }
        }

        /**
         * Update the Petri net component from the table data item
         * @param data for update 
         */
        private void updateFromTable(Iterable<TokenEditorPanel.Datum> data) {
            PetriNetController petriNetController = pipeApplicationController.getActivePetriNetController();
            List<UndoableEdit> undoableEdits = new LinkedList<>();
            for (TokenEditorPanel.Datum modified : data) {
                if (tokenEditorPanel.isExistingDatum(modified)) {
                    AbstractDatum initial = modified.initial;
                    if (!modified.equals(initial) && modified.hasBeenSet()) {
                        try {
                            Token token = petriNetController.getToken(initial.id);
                            undoableEdits.add(new ChangePetriNetComponentName(token, initial.id, modified.id));
                            undoableEdits.add(new ChangeTokenColor(token, token.getColor(), modified.color));
                            petriNetController.updateToken(initial.id, modified.id, modified.color);
                        } catch (PetriNetComponentNotFoundException petriNetComponentNotFoundException) {
                            GuiUtils.displayErrorMessage(null, petriNetComponentNotFoundException.getMessage());
                        }
                    }
                } else if (modified.hasBeenSet()) {
                    petriNetController.createNewToken(modified.id, modified.color);
                    try {
                        Token token = petriNetController.getToken(modified.id);
                        undoableEdits.add(new AddPetriNetObject(token, petriNetController.getPetriNet()));
                    } catch (PetriNetComponentNotFoundException e) {
                        logger.log(Level.SEVERE, e.getMessage());
                    }

                }
            }
            if (undoableEdits.size() > 0) {
                registerUndoEvent(new MultipleEdit(undoableEdits));
            }
        }


    }
}
