/*
 * Copyright 2010 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed 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 org.optaplanner.examples.dinnerparty.swingui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;

import org.optaplanner.examples.common.swingui.SolutionPanel;
import org.optaplanner.examples.common.swingui.components.LabeledComboBoxRenderer;
import org.optaplanner.examples.dinnerparty.domain.DinnerParty;
import org.optaplanner.examples.dinnerparty.domain.Gender;
import org.optaplanner.examples.dinnerparty.domain.Guest;
import org.optaplanner.examples.dinnerparty.domain.Hobby;
import org.optaplanner.examples.dinnerparty.domain.HobbyPractician;
import org.optaplanner.examples.dinnerparty.domain.Seat;
import org.optaplanner.examples.dinnerparty.domain.SeatDesignation;
import org.optaplanner.examples.dinnerparty.domain.Table;
import org.optaplanner.swing.impl.SwingUtils;
import org.optaplanner.swing.impl.TangoColorFactory;

public class DinnerPartyPanel extends SolutionPanel<DinnerParty> {

    public static final String LOGO_PATH = "/org/optaplanner/examples/dinnerparty/swingui/dinnerPartyLogo.png";
    public static final int MALE_FEMALE_ICON_VARIATION = 5;

    private GridLayout gridLayout;
    private Map<Hobby, ImageIcon> hobbyImageIconMap;
    private List<ImageIcon> maleImageIconList;
    private List<ImageIcon> femaleImageIconList;

    public DinnerPartyPanel() {
        gridLayout = new GridLayout(0, 1);
        setLayout(gridLayout);
        Hobby[] hobbies = Hobby.values();
        hobbyImageIconMap = new HashMap<>(hobbies.length);
        for (Hobby hobby : hobbies) {
            String imageIconFilename;
            switch (hobby) {
                case TENNIS:
                    imageIconFilename = "hobbyTennis.png";
                    break;
                case GOLF:
                    imageIconFilename = "hobbyGolf.png";
                    break;
                case MOTORCYCLES:
                    imageIconFilename = "hobbyMotorcycles.png";
                    break;
                case CHESS:
                    imageIconFilename = "hobbyChess.png";
                    break;
                case POKER:
                    imageIconFilename = "hobbyPoker.png";
                    break;
                default:
                    throw new IllegalArgumentException("The hobby (" + hobby + ") is not supported.");
            }
            hobbyImageIconMap.put(hobby, new ImageIcon(getClass().getResource(imageIconFilename)));
        }
        maleImageIconList = new ArrayList<>(MALE_FEMALE_ICON_VARIATION);
        femaleImageIconList = new ArrayList<>(MALE_FEMALE_ICON_VARIATION);
        for (int i = 0; i < MALE_FEMALE_ICON_VARIATION; i++) {
             maleImageIconList.add(new ImageIcon(getClass().getResource("guestMale" + i + ".png")));
             femaleImageIconList.add(new ImageIcon(getClass().getResource("guestFemale" + i + ".png")));
        }
    }

    @Override
    public void resetPanel(DinnerParty dinnerParty) {
        removeAll();
        TangoColorFactory tangoColorFactory = new TangoColorFactory();
        gridLayout.setColumns((int) Math.ceil(Math.sqrt(dinnerParty.getTableList().size())));
        Map<Seat, SeatPanel> seatPanelMap = new HashMap<>(dinnerParty.getSeatList().size());
        SeatPanel unassignedPanel = new SeatPanel(null);
        seatPanelMap.put(null, unassignedPanel);
        for (Table table : dinnerParty.getTableList()) {
            // Formula: 4(columns - 1) = tableSize
            int edgeLength = (int) Math.ceil(((double) (table.getSeatList().size() + 4)) / 4.0);
            JPanel tablePanel = new JPanel(new GridLayout(0, edgeLength));
            tablePanel.setBorder(BorderFactory.createCompoundBorder(
                    BorderFactory.createEmptyBorder(5, 5, 5, 5),
                    BorderFactory.createTitledBorder("Table " + table.getTableIndex())
            ));
            add(tablePanel);
            for (int y = 0; y < edgeLength; y++) {
                for (int x = 0; x < edgeLength; x++) {
                    int index;
                    if (y == 0) {
                        index = x;
                    } else if (x == (edgeLength - 1)) {
                        index = (edgeLength - 1) + y;
                    } else if (y == (edgeLength - 1)) {
                        index = 2 * (edgeLength - 1) + (edgeLength - 1 - x);
                    } else if (x == 0) {
                        index = 3 * (edgeLength - 1) + (edgeLength - 1 - y);
                    } else {
                        index = Integer.MAX_VALUE;
                    }
                    if (index < table.getSeatList().size()) {
                        Seat seat = table.getSeatList().get(index);
                        SeatPanel seatPanel = new SeatPanel(seat);
                        tablePanel.add(seatPanel);
                        seatPanelMap.put(seat, seatPanel);
                    } else {
                        tablePanel.add(new JPanel());
                    }
                }
            }
        }
        for (SeatDesignation seatDesignation : dinnerParty.getSeatDesignationList()) {
            SeatPanel seatPanel = seatPanelMap.get(seatDesignation.getSeat());
            seatPanel.setBackground(tangoColorFactory.pickColor(seatDesignation.getGuestJobType()));
            seatPanel.setSeatDesignation(seatDesignation);
        }
    }

    private class SeatPanel extends JPanel {

        public SeatPanel(Seat seat) {
            setLayout(new BorderLayout(5, 0));
            setBorder(BorderFactory.createCompoundBorder(
                    BorderFactory.createLineBorder(Color.DARK_GRAY),
                    BorderFactory.createEmptyBorder(2, 2, 2, 2)));
            SeatDesignation dummySeatDesignation = new SeatDesignation();
            dummySeatDesignation.setGuest(null);
            dummySeatDesignation.setSeat(seat);
            setSeatDesignation(dummySeatDesignation);
        }

        public void setSeatDesignation(SeatDesignation seatDesignation) {
            removeAll();
            Guest guest = seatDesignation.getGuest();
            if (guest == null) {
                add(new JLabel("Empty seat"), BorderLayout.CENTER);
                return;
            }
            JButton button = SwingUtils.makeSmallButton(new JButton(new SeatDesignationAction(seatDesignation)));
            add(button, BorderLayout.WEST);
            JPanel infoPanel = new JPanel(new GridLayout(0, 1));
            infoPanel.setOpaque(false);
            infoPanel.add(new JLabel(guest.getName()));
            JPanel jobPanel = new JPanel();
            jobPanel.setLayout(new BoxLayout(jobPanel, BoxLayout.Y_AXIS));
            jobPanel.setOpaque(false);
            jobPanel.add(new JLabel(guest.getJob().getJobType().getCode()));
            JLabel jobLabel = new JLabel("  " + guest.getJob().getName());
            jobLabel.setFont(jobLabel.getFont().deriveFont(jobLabel.getFont().getSize() - 2.0F));
            jobPanel.add(jobLabel);
            infoPanel.add(jobPanel);
            add(infoPanel, BorderLayout.CENTER);
            JPanel hobbyPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
            hobbyPanel.setOpaque(false);
            hobbyPanel.setAlignmentX(CENTER_ALIGNMENT);
            for (HobbyPractician hobbyPractician : guest.getHobbyPracticianList()) {
                Hobby hobby = hobbyPractician.getHobby();
                JLabel hobbyLabel = new JLabel(hobbyImageIconMap.get(hobby));
                hobbyLabel.setToolTipText(hobby.getLabel());
                hobbyPanel.add(hobbyLabel);
            }
            add(hobbyPanel, BorderLayout.SOUTH);
        }

    }

    private ImageIcon determineGuestIcon(SeatDesignation seatDesignation) {
        Guest guest = seatDesignation.getGuest();
        if (guest == null) {
            return null;
        }
        List<ImageIcon> imageIconList = guest.getGender() == Gender.MALE ? maleImageIconList : femaleImageIconList;
        return imageIconList.get(guest.getId().intValue() % imageIconList.size());
    }

    private class SeatDesignationAction extends AbstractAction {

        private SeatDesignation seatDesignation;

        public SeatDesignationAction(SeatDesignation seatDesignation) {
            super(null, determineGuestIcon(seatDesignation));
            this.seatDesignation = seatDesignation;
            Seat seat = seatDesignation.getSeat();
            if (seat != null) {
                // Tooltip
                putValue(SHORT_DESCRIPTION, "<html>Guest: " + seatDesignation.getGuest().getName() + "<br/>"
                        + "Table: " + seat.getTable().getTableIndex() + "<br/>"
                        + "Seat: " + seat.getSeatIndexInTable()
                        + "</html>");
            }
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            List<SeatDesignation> seatDesignationList = getSolution().getSeatDesignationList();
            // Add 1 to array size to add null, which makes the entity unassigned
            JComboBox seatDesignationListField = new JComboBox(
                    seatDesignationList.toArray(new Object[seatDesignationList.size() + 1]));
            LabeledComboBoxRenderer.applyToComboBox(seatDesignationListField);
            seatDesignationListField.setSelectedItem(seatDesignation);
            int result = JOptionPane.showConfirmDialog(DinnerPartyPanel.this.getRootPane(), seatDesignationListField,
                    "Select seat designation to switch with", JOptionPane.OK_CANCEL_OPTION);
            if (result == JOptionPane.OK_OPTION) {
                SeatDesignation switchSeatDesignation = (SeatDesignation) seatDesignationListField.getSelectedItem();
                if (seatDesignation != switchSeatDesignation) {
                    solutionBusiness.doSwapMove(seatDesignation, switchSeatDesignation);
                }
                solverAndPersistenceFrame.resetScreen();
            }
        }

    }

}
