package pipe.gui.widgets;

import pipe.controllers.PetriNetController;
import pipe.controllers.PlaceController;
import uk.ac.imperial.pipe.models.petrinet.PetriNet;
import uk.ac.imperial.pipe.models.petrinet.Token;

import javax.swing.*;
import javax.swing.event.ChangeListener;

import java.awt.*;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Panel for editing place properties
 */
@SuppressWarnings("serial")
public class PlaceEditorPanel extends javax.swing.JPanel {
    /**
     * Class logger
     */
    private static final Logger LOGGER = Logger.getLogger(PlaceEditorPanel.class.getName());

    /**
     * Petri net controller that the place belongs to
     */
    private final PetriNetController netController;

    /**
     * Place controller
     */
    private final PlaceController placeController;

    /**
     * Graphical root pane
     */
    private final JRootPane rootPane;

    /**
     * Capacity label
     */
    private javax.swing.JLabel capacity0Label = new javax.swing.JLabel();

    /**
     * Capacity spinner for changing the place capacity
     */
    private javax.swing.JSpinner capacitySpinner = new javax.swing.JSpinner();

    /**
     * Place name text field
     */
    private javax.swing.JTextField nameTextField = new javax.swing.JTextField();

    /**
     * OK button that will perform changes when pressed
     */
    private javax.swing.JButton okButton = new javax.swing.JButton();

    /**
     * Place tokens
     */
    private List<JSpinner> inputtedMarkings = new LinkedList<>();

    /**
     * Token ids
     */
    private List<String> inputtedTokenClassNames = new LinkedList<>();


    /**
     * Creates new form PlaceEditor
     * @param rootPane root pane
     * @param placeController place controller
     * @param petriNetController Petri net controller 
     */
    public PlaceEditorPanel(JRootPane rootPane, PlaceController placeController,
                            PetriNetController petriNetController) {

        this.rootPane = rootPane;
        this.rootPane.setDefaultButton(okButton);
        this.placeController = placeController;

        netController = petriNetController;
        initComponents();
    }

    /**
     * Initializes the editor view
     */
    private void initComponents() {
        setLayout(new java.awt.GridBagLayout());
        JPanel placeEditorPanel = createPlaceEditorPanel();

        // Now set new dimension used in for loop below
        int col = 0;
        int row = 2;

        PetriNet net = netController.getPetriNet();
        for (Token token : net.getTokens()) {
                JLabel tokenClassName = new JLabel();
                JSpinner tokenClassMarking = new JSpinner();
                inputtedMarkings.add(tokenClassMarking);

                tokenClassName.setText(token.getId() + ": ");
                inputtedTokenClassNames.add(token.getId());
                GridBagConstraints tokenNameConstraints = new java.awt.GridBagConstraints();
                tokenNameConstraints.gridx = 0;
                tokenNameConstraints.gridy = row;
                tokenNameConstraints.anchor = java.awt.GridBagConstraints.EAST;
                tokenNameConstraints.insets = new java.awt.Insets(3, 3, 3, 3);
                placeEditorPanel.add(tokenClassName, tokenNameConstraints);

                tokenClassMarking.setValue(placeController.getTokenCount(token.getId()));
                tokenClassMarking.setMinimumSize(new java.awt.Dimension(50, 20));
                tokenClassMarking.setPreferredSize(new java.awt.Dimension(50, 20));
                tokenClassMarking.addChangeListener(new javax.swing.event.ChangeListener() {
                    public void stateChanged(javax.swing.event.ChangeEvent evt) {
                        markingSpinnerStateChanged(evt, inputtedMarkings.size() - 1);
                    }
                });

                GridBagConstraints tokenValueConstraints = new java.awt.GridBagConstraints();
                tokenValueConstraints.gridx = col + 1;
                tokenValueConstraints.gridy = row;
                tokenValueConstraints.gridwidth = 3;
                tokenValueConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
                tokenValueConstraints.insets = new java.awt.Insets(3, 3, 3, 3);
                placeEditorPanel.add(tokenClassMarking, tokenValueConstraints);
                row++;
            }

        initializeCapacityLabel(placeEditorPanel, row);
        initializeCapacitySpinner(placeEditorPanel, row);
        initializeCapacity0Label(placeEditorPanel, row);

        GridBagConstraints gridBagConstraints;

        JPanel buttonPanel = new JPanel();
        buttonPanel.setLayout(new java.awt.GridBagLayout());
        initializeOkButton(buttonPanel);
        initializeCancelButton(buttonPanel);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
        add(buttonPanel, gridBagConstraints);

    }

    /**
     * Sets the no capacity restriction label visible if the capacity is
     * zero
     *
     * @param capacity of the place 
     */
    private void setCapacityVisible(double capacity) {
        if (capacity == 0) {
            capacity0Label.setVisible(true);
        } else {
            capacity0Label.setVisible(false);
        }

    }

    /**
     * initializes the capacity label. If he capacity is set to 0 it reminds users
     * that a capacity of 0 imposes no restrictions.
     * @param placeEditorPanel panel
     * @param row of the panel 
     */
    private void initializeCapacity0Label(JPanel placeEditorPanel, int row) {
        capacity0Label.setText("(no capacity restriction)    ");
        GridBagConstraints gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = row;
        gridBagConstraints.insets = new Insets(3, 3, 3, 3);
        placeEditorPanel.add(capacity0Label, gridBagConstraints);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.anchor = GridBagConstraints.EAST;
        gridBagConstraints.insets = new Insets(5, 8, 5, 8);
        add(placeEditorPanel, gridBagConstraints);
        setCapacityVisible(placeController.getCapacity());
    }

    /**
     * Initializes the capacity spinner
     * @param placeEditorPanel panel
     * @param row of the panel 
     */
    private void initializeCapacitySpinner(JPanel placeEditorPanel, int row) {
        capacitySpinner.setModel(new SpinnerNumberModel(placeController.getCapacity(), 0, Integer.MAX_VALUE, 1));
        capacitySpinner.setMinimumSize(new Dimension(50, 20));
        capacitySpinner.setPreferredSize(new Dimension(50, 20));
        capacitySpinner.addChangeListener(new ChangeListener() {
            public void stateChanged(javax.swing.event.ChangeEvent evt) {
                capacitySpinnerStateChanged(evt);
            }
        });

        GridBagConstraints capacityConstraints = new GridBagConstraints();
        capacityConstraints.gridx = 1;
        capacityConstraints.gridy = row;
        capacityConstraints.fill = GridBagConstraints.HORIZONTAL;
        capacityConstraints.insets = new Insets(3, 3, 3, 3);
        placeEditorPanel.add(capacitySpinner, capacityConstraints);
    }

    /**
     * Creates the editor panel
     * @return editor panel 
     */
    private JPanel createPlaceEditorPanel() {
        GridBagConstraints gridBagConstraints;
        JPanel placeEditorPanel = new JPanel();
        placeEditorPanel.setLayout(new GridBagLayout());

        placeEditorPanel.setBorder(BorderFactory.createTitledBorder("Place Editor"));

        JLabel nameLabel = new JLabel();
        nameLabel.setText("NameDetails:");
        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = GridBagConstraints.EAST;
        gridBagConstraints.insets = new Insets(3, 3, 3, 3);
        placeEditorPanel.add(nameLabel, gridBagConstraints);

        initializeNameTextField(placeEditorPanel);
        return placeEditorPanel;
    }

    /**
     * initializes the capacity label
     * @param placeEditorPanel panel
     * @param row of the panel 
     */
    private void initializeCapacityLabel(JPanel placeEditorPanel, int row) {
        GridBagConstraints gridBagConstraints;
        JLabel capacityLabel = new JLabel();
        capacityLabel.setText("Capacity:");
        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = row;
        gridBagConstraints.anchor = GridBagConstraints.EAST;
        gridBagConstraints.insets = new Insets(3, 3, 3, 3);
        placeEditorPanel.add(capacityLabel, gridBagConstraints);
    }

    /**
     * Initialises the cancel button. When cancel is pressed the window exits and no changes
     * are saved
     * @param buttonPanel cancel button 
     */
    private void initializeCancelButton(JPanel buttonPanel) {
        GridBagConstraints gridBagConstraints;
        JButton cancelButton = new JButton();
        cancelButton.setText("Cancel");
        cancelButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                cancelButtonHandler(evt);
            }
        });

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = GridBagConstraints.WEST;
        gridBagConstraints.insets = new Insets(8, 0, 8, 10);
        buttonPanel.add(cancelButton, gridBagConstraints);
    }

    /**
     * Initializes the name text field to the place name
     * @param placeEditorPanel editor panel
     */
    private void initializeNameTextField(JPanel placeEditorPanel) {
        nameTextField.setText(placeController.getName());
        GridBagConstraints gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
        gridBagConstraints.insets = new Insets(3, 3, 3, 3);
        placeEditorPanel.add(nameTextField, gridBagConstraints);
    }

    /**
     * Initializes the OK button action, this will save the settings to the place
     * @param buttonPanel button panel 
     */
    private void initializeOkButton(JPanel buttonPanel) {
        okButton.setText("OK");
        okButton.setMaximumSize(new Dimension(75, 25));
        okButton.setMinimumSize(new Dimension(75, 25));
        okButton.setPreferredSize(new Dimension(75, 25));
        okButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                okButtonHandler(evt);
            }
        });
        okButton.addKeyListener(new java.awt.event.KeyAdapter() {
            public void keyPressed(java.awt.event.KeyEvent evt) {
                okButtonKeyPressed(evt);
            }
        });

        GridBagConstraints gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.gridwidth = java.awt.GridBagConstraints.RELATIVE;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
        gridBagConstraints.insets = new java.awt.Insets(5, 0, 5, 9);
        buttonPanel.add(okButton, gridBagConstraints);
    }

    /**
     * If the marking spinner changes then more tokens are added to the place
     *
     * This feature is not currently implemented
     * @param evt event 
     * @param posInList position in list 
     */
    private void markingSpinnerStateChanged(javax.swing.event.ChangeEvent evt,
                                            int posInList) {
/*      Integer capacity = (Integer)capacitySpinner.getValue();
      int totalMarkings = 0;
      for(JSpinner inputtedMarking:inputtedMarkings){
    	  totalMarkings += (Integer)inputtedMarking.getValue();
      }
      int markingOfCurrentSpinner = (Integer)inputtedMarkings.get(posInList).getValue();
      if (capacity > 0) {
         if (totalMarkings > capacity) {
        	 int overMarkingLimit = totalMarkings - capacity;
        	 inputtedMarkings.get(posInList).setValue(markingOfCurrentSpinner - overMarkingLimit);
         }
      }*/
    }

    /**
     * Performs the ok action
     * @param evt event 
     */
    private void okButtonKeyPressed(java.awt.event.KeyEvent evt) {
        if (evt.getKeyCode() == java.awt.event.KeyEvent.VK_ENTER) {
            doOK();
        }
    }

    /**
     * Saves the changes to the place controller
     */
    private void doOK() {

        Map<String, Integer> newTokenValues = getNewTokenValues();
        if (canSetCapacity() && canSetNameValue()) {

            placeController.startMultipleEdits();
            int newCapacity = (Integer) capacitySpinner.getValue();
            placeController.setCapacity(newCapacity);
            String newName = nameTextField.getText();
            placeController.setId(newName);
            placeController.setTokenCounts(newTokenValues);
            placeController.finishMultipleEdits();
            exit();
        }
    }

    /**
     *
     * @return true
     */
    private boolean canSetCapacity() {
        return true;
    }

    /**
     *
     * @return the capacity spinner value
     */
    private Double getCapacitySpinnerValue() {
        return (Double) capacitySpinner.getValue();
    }

    /**
     * @return return false if could not set name
     */
    private boolean canSetNameValue() {
        String newName = nameTextField.getText();
        if (newName.equals(placeController.getName())) {
            return true;
        }
        if (!netController.isUniqueName(newName)) {
            JOptionPane.showMessageDialog(null, "There is already a component named " + newName, "Error",
                    JOptionPane.WARNING_MESSAGE);
            return false;
        }
        return true;
    }

    /**
     * Sets the token values on the place
     *
     * @return new token counts, empty if could not change
     */
    private  Map<String, Integer> getNewTokenValues() {
        Map<String, Integer> newTokenCounts = new HashMap<>();
        int totalCount = calculateTokenCount();
        if (placeController.hasCapacityRestriction() && totalCount > getCapacitySpinnerValue()) {
            JOptionPane.showMessageDialog(null,
                    "Token counts exceed the capacity of place. Please alter capacity or tokens");
            return newTokenCounts;
        }

        for (int tokenIndex = 0; tokenIndex < inputtedMarkings.size(); tokenIndex++) {
            String tokenName = inputtedTokenClassNames.get(tokenIndex);
            int newTokenCount = (Integer) inputtedMarkings.get(tokenIndex).getValue();

            try {
                if (newTokenCount < 0) {
                    JOptionPane.showMessageDialog(null, "Marking cannot be less than 0. Please re-enter");
                    newTokenCounts.clear();
                    return newTokenCounts;
                }

                if (placeController.getTokenCount(tokenName) != newTokenCount) {
                    newTokenCounts.put(tokenName, newTokenCount);
                }

            } catch (NumberFormatException ignored) {
                JOptionPane.showMessageDialog(null, "Please enter a positive integer greater or equal to 0.",
                        "Invalid entry", JOptionPane.ERROR_MESSAGE);
                newTokenCounts.clear();
                return newTokenCounts;
            } catch (HeadlessException e) {
                LOGGER.log(Level.SEVERE, e.getMessage());
                JOptionPane.showMessageDialog(null, "Please enter a positive integer greater or equal to 0.",
                        "Invalid entry", JOptionPane.ERROR_MESSAGE);
                newTokenCounts.clear();
                return newTokenCounts;
            }
        }

        return newTokenCounts;
    }

    /**
     * @return the total number of tokens declared for the place
     */
    private int calculateTokenCount() {
        int sum = 0;
        for (JSpinner inputtedMarking : inputtedMarkings) {
            Object value = inputtedMarking.getValue();
            sum += (Integer) value;
        }
        return sum;
    }

    /**
     * Performs the OK event
     * @param evt OK event 
     */
    private void okButtonHandler(java.awt.event.ActionEvent evt) {
        doOK();
    }

    /**
     * Exits the dialog
     */
    private void exit() {
        rootPane.getParent().setVisible(false);
    }

    /**
     * Exits the dialog
     * @param evt cancel event 
     */
    private void cancelButtonHandler(java.awt.event.ActionEvent evt) {
        exit();
    }

    /**
     * Listens for changes in the capacity spinner
     * @param evt change capacity event 
     */
    private void capacitySpinnerStateChanged(javax.swing.event.ChangeEvent evt) {
        Double capacity = (Double) capacitySpinner.getValue();
        setCapacityVisible(capacity);
    }

}
