package pipe.gui;

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

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

/**
 * @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 TokenEditorPanel extends JPanel {
    private final TableModel model;


    public TokenEditorPanel(final PetriNetController petriNetController) {
        model = new TableModel(petriNetController);
        final JTable table = new JTable(model);

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

        table.setDefaultRenderer(Color.class, new ColorDrawer(true));
        table.setDefaultEditor(Color.class, new ColorPicker());

        JButton deleteButton = new JButton("Delete");
        deleteButton.addActionListener(new ActionListener() {
            /**
             * Only delete data if it is not the last token left!
             * @param e
             */
            //TODO: This does not take into account adding new tokens before pressing ok
            //       make sure it does....
            @Override
            public void actionPerformed(ActionEvent e) {
                int selected = table.getSelectedRow();
                if (model.count > 1) {
                    model.deleteRow(selected);
                }
            }
        });

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

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

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

    public Collection<Datum> getDeletedData() {
        return model.getDeletedData();
    }

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

    /**
     * Private class to hold the data in the table.
     */
    public static class Datum extends AbstractDatum {

        /**
         * Color of the token
         */
        public Color color;

        public Datum(String name, Color color) {
            super(name);
            this.color = color;
        }

        public Datum(Datum initial, String name, Color color) {
            super(initial, name);
            this.color = color;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (!(o instanceof Datum)) {
                return false;
            }
            if (!super.equals(o)) {
                return false;
            }

            Datum datum = (Datum) o;

            if (!color.equals(datum.color)) {
                return false;
            }

            return true;
        }

        @Override
        public int hashCode() {
            int result = super.hashCode();
            result = 31 * result + color.hashCode();
            return result;
        }
    }

    /**
     * TableModel for editing the Tokens
     */
    public static class TableModel extends AbstractComponentTableModel<Datum> {

        private static final int NAME_COL = 0;

        public TableModel(PetriNetController petriNetController) {
            columnNames = new String[]{"Token Name Details", "Token Colour"};
            for (Token token : petriNetController.getNetTokens()) {
                Datum initial = new Datum(token.getId(), token.getColor());
                modifiedData.add(new Datum(initial, token.getId(), token.getColor()));
                count++;
            }
            initialiseEmptyRowColours(modifiedData.size());
        }

        /**
         * Sets the first 6 rows as basic different colors. The rest of the rows are assigned
         * to a random color
         */
        private void initialiseEmptyRowColours(int startIndex) {

            Random randomNumberGenerator = new Random();
            for (int i = startIndex; i < DATA_SIZE; i++) {
                Color color;
                switch (i) {
                    case 0:
                        color = Color.black;
                        break;
                    case 1:
                        color = Color.RED;
                        break;
                    case 2:
                        color = Color.BLUE;
                        break;
                    case 3:
                        color = Color.YELLOW;
                        break;
                    case 4:
                        color = Color.GREEN;
                        break;
                    case 5:
                        color = Color.ORANGE;
                        break;
                    case 6:
                        color = Color.PINK;
                        break;
                    default:
                        color = new Color(randomNumberGenerator.nextInt(256), randomNumberGenerator.nextInt(256),
                                randomNumberGenerator.nextInt(256));
                }
                modifiedData.add(new Datum("", color));
            }
        }

        @Override
        public Class<?> getColumnClass(int c) {
            return getValueAt(0, c).getClass();
        }

        @Override
        public Object getValueAt(int row, int col) {
            if (col == NAME_COL) {
                return modifiedData.get(row).id;
            }
            return modifiedData.get(row).color;

        }

        /**
         * Sets modifiedData with the new value
         * Also performs checks on the new value and displays
         * warnings if it is likely to be invalid
         *
         * @param value new value of the changed cell
         * @param row   row that has changed
         * @param col   column that has changed
         */
        @Override
        public void updateTableAt(Object value, int row, int col) {
            String id = modifiedData.get(row).id;
            Color color = modifiedData.get(row).color;

            if (col == NAME_COL) {
                id = (String) value;
                for (int i = 0; i < modifiedData.size(); i++) {
                    if (i != row && modifiedData.get(i).id.equals(value)) {
                            JOptionPane.showMessageDialog(new JPanel(), "Another token exists with that name",
                                    "Warning", JOptionPane.WARNING_MESSAGE);
                            return;
                    }
                }
            } else {
                color = (Color) value;
            }


            //            //TODO: DO THIS IN A BETTER WAY
            //            if (row < initialData.length) {
            //                Datum initial = initialData[row];
            //                String originalTokenName = initial.name;
            //                for (Place place : petriNetController.getPetriNet().getPlaces()) {
            //                    for (Map.Entry<Token, Integer> entry : place.getTokenCounts().entrySet()) {
            //                        if (entry.getKey().getId().equals(originalTokenName)) {
            //                            if (entry.getValue() > 0) {
            //                                JOptionPane.showMessageDialog(new JPanel(), "Places exist that use this token. "
            //                                        + "Such markings must be removed before this class can be edited", "Warning",
            //                                        JOptionPane.WARNING_MESSAGE);
            //                                return;
            //                            }
            //                        }
            //                    }
            //                }
            //            }

            modifiedData.get(row).id = id;
            modifiedData.get(row).color = color;
        }

        /**
         * Checks to see if all the tokens in the table are valid.
         * If they are not it will print the relevant error message for the first
         * invalid token it comes across.
         *
         * @return true if all the tokens in the table are valid, false if not
         */
        public boolean isValid() {
            return true;
        }
    }

}