package pipe.gui;

import pipe.controllers.PetriNetController;
import uk.ac.imperial.pipe.models.petrinet.RateParameter;
import uk.ac.imperial.pipe.parsers.FunctionalResults;

import javax.swing.*;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;

/**
 * This is a panel for modifying and creating rate parameters
 */
public class RateEditorPanel extends JPanel {
    private final JTable table;

    private final RateModel model;

    public RateEditorPanel(PetriNetController petriNetController) {
        model = new RateModel(petriNetController);
        table = new JTable(model);

        table.setPreferredScrollableViewportSize(new Dimension(500, 70));
        table.setFillsViewportHeight(true);

        JButton deleteButton = new JButton("Delete");
        deleteButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                int selected = table.getSelectedRow();
                model.deleteRow(selected);
            }
        });

        JScrollPane scrollPane = new JScrollPane(table);
        add(scrollPane);
        add(deleteButton);
    }

    public List<RateModel.Datum> getTableData() {
        return model.getTableData();
    }

    public boolean isDataValid() {
        return model.isValid();
    }

    public boolean isExistingRateParameter(RateModel.Datum datum) {
        return model.isExistingDatum(datum);
    }

    public Iterable<RateModel.Datum> getDeletedData() {
        return model.deletedData;
    }

    public class RateModel extends AbstractComponentTableModel<RateModel.Datum> {


        private static final int ID_COL = 0;

        private final PetriNetController petriNetController;


        public RateModel(PetriNetController petriNetController) {
            this.petriNetController = petriNetController;
            columnNames = new String[]{"Name", "Value"};
            for (RateParameter rateParameter : petriNetController.getRateParameters()) {
                Datum initial = new Datum(rateParameter.getId(), rateParameter.getExpression());
                modifiedData.add(new Datum(initial, rateParameter.getId(), rateParameter.getExpression()));
                count++;
            }
            for (int index = modifiedData.size(); index < DATA_SIZE; index++) {
                modifiedData.add(new Datum("", ""));
            }
        }

        /**
         * Updates the table the values added.
         *
         * @param value    new value
         * @param rowIndex index of row changed
         * @param colIndex index of column changed
         */
        @Override
        public void updateTableAt(Object value, int rowIndex, int colIndex) {

            String id = modifiedData.get(rowIndex).id;
            String expression = modifiedData.get(rowIndex).expression;

            if (colIndex == ID_COL) {
                id = (String) value;
            } else {
                expression = (String) value;
            }

            modifiedData.get(rowIndex).id = id;
            modifiedData.get(rowIndex).expression = expression;
        }

        /**
         * Checks the following conditions:
         * - Name is not empty
         * - Rate value is not empty
         *
         * @return true if the values in the table are valid for each rate parameter
         */
        public boolean isValid() {
            for (int row = 0; row < getRowCount(); row++) {
                Datum datum = modifiedData.get(row);
                if (isExistingRateParameter(datum)) {
                    if (datum.id.isEmpty()) {
                        showWarning("The rate name cannot be empty");
                        return false;
                    }
                    if (!isValidRate(datum.id, datum.expression)) {
                        return false;
                    }
                }
                if (datum.id.isEmpty() && !datum.expression.isEmpty()) {
                    showWarning("The rate name cannot be empty");
                    return false;
                }
                if (!datum.id.isEmpty() && datum.expression.isEmpty()) {
                    showWarning("The rate value cannot be empty");
                    return false;
                }
                if (!datum.id.isEmpty() && !isValidRate(datum.id, datum.expression)) {
                    return false;
                }
            }
            return true;
        }

        /**
         * Shows a warning to the user as a dialog
         *
         * @param warningMessage message to show
         */
        private void showWarning(String warningMessage) {
            JOptionPane.showMessageDialog(new JPanel(), warningMessage, "Warning", JOptionPane.WARNING_MESSAGE);
        }

        /**
         * Pops up an error if the rate is invalid
         *
         * @param rate
         * @return if rate is valid or not
         */
        private boolean isValidRate(String id, String rate) {
            FunctionalResults<Double> results = petriNetController.parseFunctionalExpression(rate);
            if (results.hasErrors()) {
                showWarning("Error! Invalid rate for: " + id + "\n" + "Problem is: " + results.getErrorString(", "));
                return false;
            }
            return true;
        }

        @Override
        public Object getValueAt(int rowIndex, int columnIndex) {
            if (columnIndex == ID_COL) {
                return modifiedData.get(rowIndex).id;
            }
            return modifiedData.get(rowIndex).expression;
        }

        /**
         * Holds rate parameter information in the table
         */
        public final class Datum extends AbstractDatum {
            public String expression;

            private Datum(String id, String expression) {
                super(id);
                this.expression = expression;
            }

            private Datum(Datum initial, String id, String expression) {
                super(initial, id);
                this.expression = expression;
            }

            @Override
            public boolean equals(Object o) {
                if (this == o) {
                    return true;
                }
                if (o == null || getClass() != o.getClass()) {
                    return false;
                }

                Datum datum = (Datum) o;

                if (expression != null ? !expression.equals(datum.expression) : datum.expression != null) {
                    return false;
                }
                if (id != null ? !id.equals(datum.id) : datum.id != null) {
                    return false;
                }

                return true;
            }

            @Override
            public int hashCode() {
                int result = id != null ? id.hashCode() : 0;
                result = 31 * result + (expression != null ? expression.hashCode() : 0);
                return result;
            }
        }
    }

}
