/*
 * 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.pas.swingui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.Icon;
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 javax.swing.SwingConstants;

import org.optaplanner.examples.common.swingui.SolutionPanel;
import org.optaplanner.examples.common.swingui.components.LabeledComboBoxRenderer;
import org.optaplanner.examples.common.swingui.timetable.TimeTablePanel;
import org.optaplanner.examples.pas.domain.AdmissionPart;
import org.optaplanner.examples.pas.domain.Bed;
import org.optaplanner.examples.pas.domain.BedDesignation;
import org.optaplanner.examples.pas.domain.Department;
import org.optaplanner.examples.pas.domain.Gender;
import org.optaplanner.examples.pas.domain.GenderLimitation;
import org.optaplanner.examples.pas.domain.Night;
import org.optaplanner.examples.pas.domain.Patient;
import org.optaplanner.examples.pas.domain.PatientAdmissionSchedule;
import org.optaplanner.examples.pas.domain.RequiredPatientEquipment;
import org.optaplanner.examples.pas.domain.Room;
import org.optaplanner.examples.pas.domain.RoomEquipment;
import org.optaplanner.swing.impl.SwingUtils;
import org.optaplanner.swing.impl.TangoColorFactory;

import static org.optaplanner.examples.common.swingui.timetable.TimeTablePanel.HeaderColumnKey.*;
import static org.optaplanner.examples.common.swingui.timetable.TimeTablePanel.HeaderRowKey.*;

public class PatientAdmissionSchedulePanel extends SolutionPanel<PatientAdmissionSchedule> {

    public static final String LOGO_PATH = "/org/optaplanner/examples/pas/swingui/pasLogo.png";

    private final ImageIcon anyGenderIcon;
    private final ImageIcon maleIcon;
    private final ImageIcon femaleIcon;
    private final ImageIcon sameGenderIcon;

    private TimeTablePanel<Night, Bed> timeTablePanel;
    private TangoColorFactory equipmentTangoColorFactory;

    public PatientAdmissionSchedulePanel() {
        anyGenderIcon = new ImageIcon(getClass().getResource("anyGender.png"));
        maleIcon = new ImageIcon(getClass().getResource("male.png"));
        femaleIcon = new ImageIcon(getClass().getResource("female.png"));
        sameGenderIcon = new ImageIcon(getClass().getResource("sameGender.png"));
        setLayout(new BorderLayout());
        timeTablePanel = new TimeTablePanel<>();
        add(timeTablePanel, BorderLayout.CENTER);
    }

    @Override
    public void resetPanel(PatientAdmissionSchedule patientAdmissionSchedule) {
        timeTablePanel.reset();
        equipmentTangoColorFactory = new TangoColorFactory();
        defineGrid(patientAdmissionSchedule);
        fillCells(patientAdmissionSchedule);
        repaint(); // Hack to force a repaint of TimeTableLayout during "refresh screen while solving"
    }

    private void defineGrid(PatientAdmissionSchedule patientAdmissionSchedule) {
        JButton footprint = SwingUtils.makeSmallButton(new JButton("Patient9999"));
        int footprintWidth = footprint.getPreferredSize().width;
        timeTablePanel.defineColumnHeaderByKey(HEADER_COLUMN_GROUP2); // Department Header
        timeTablePanel.defineColumnHeaderByKey(HEADER_COLUMN_GROUP1); // Room Header
        timeTablePanel.defineColumnHeaderByKey(HEADER_COLUMN); // Bed Header
        for (Night night : patientAdmissionSchedule.getNightList()) {
            timeTablePanel.defineColumnHeader(night, footprintWidth);
        }
        timeTablePanel.defineRowHeaderByKey(HEADER_ROW); // Night header
        timeTablePanel.defineRowHeader(null); // Unassigned bed
        for (Bed bed : patientAdmissionSchedule.getBedList()) {
            timeTablePanel.defineRowHeader(bed);
        }
    }

    private void fillCells(PatientAdmissionSchedule patientAdmissionSchedule) {
        timeTablePanel.addCornerHeader(HEADER_COLUMN_GROUP2, HEADER_ROW, createHeaderPanel(new JLabel("Department")));
        timeTablePanel.addCornerHeader(HEADER_COLUMN_GROUP1, HEADER_ROW, createHeaderPanel(new JLabel("Room")));
        timeTablePanel.addCornerHeader(HEADER_COLUMN, HEADER_ROW, createHeaderPanel(new JLabel("Bed")));
        fillNightCells(patientAdmissionSchedule);
        fillBedCells(patientAdmissionSchedule);
        fillBedDesignationCells(patientAdmissionSchedule);
    }

    private void fillNightCells(PatientAdmissionSchedule patientAdmissionSchedule) {
        for (Night night : patientAdmissionSchedule.getNightList()) {
            timeTablePanel.addColumnHeader(night, HEADER_ROW,
                    createHeaderPanel(new JLabel(night.getLabel(), SwingConstants.CENTER)));
        }
    }

    private void fillBedCells(PatientAdmissionSchedule patientAdmissionSchedule) {
        timeTablePanel.addRowHeader(HEADER_COLUMN_GROUP2, null, HEADER_COLUMN, null,
                createHeaderPanel(new JLabel("Unassigned")));
        for (Department department : patientAdmissionSchedule.getDepartmentList()) {
            List<Room> roomList = department.getRoomList();
            List<Bed> firstRoomBedList = roomList.get(0).getBedList();
            List<Bed> lastRoomBedList = roomList.get(roomList.size() - 1).getBedList();
            timeTablePanel.addRowHeader(HEADER_COLUMN_GROUP2, firstRoomBedList.get(0),
                    HEADER_COLUMN_GROUP2, lastRoomBedList.get(lastRoomBedList.size() - 1),
                    createHeaderPanel(new JLabel(department.getLabel())));
            for (Room room : roomList) {
                List<Bed> bedList = room.getBedList();
                JLabel roomLabel = new JLabel(room.getLabel(), new PatientOrRoomIcon(room), SwingConstants.RIGHT);
                timeTablePanel.addRowHeader(HEADER_COLUMN_GROUP1, bedList.get(0),
                        HEADER_COLUMN_GROUP1, bedList.get(bedList.size() - 1),
                        createHeaderPanel(roomLabel));
                for (Bed bed : bedList) {
                    timeTablePanel.addRowHeader(HEADER_COLUMN, bed,
                            createHeaderPanel(new JLabel(bed.getLabelInRoom(), SwingConstants.RIGHT)));
                }
            }
        }
    }

    private void fillBedDesignationCells(PatientAdmissionSchedule patientAdmissionSchedule) {
        for (BedDesignation bedDesignation : patientAdmissionSchedule.getBedDesignationList()) {
            JButton button = SwingUtils.makeSmallButton(new JButton(new BedDesignationAction(bedDesignation)));
            AdmissionPart admissionPart = bedDesignation.getAdmissionPart();
            timeTablePanel.addCell(admissionPart.getFirstNight(), bedDesignation.getBed(),
                    admissionPart.getLastNight(), bedDesignation.getBed(), button);
        }
    }

    private JPanel createHeaderPanel(JLabel label) {
        JPanel headerPanel = new JPanel(new BorderLayout());
        headerPanel.add(label, BorderLayout.NORTH);
        headerPanel.setBorder(BorderFactory.createCompoundBorder(
                BorderFactory.createLineBorder(TangoColorFactory.ALUMINIUM_5),
                BorderFactory.createEmptyBorder(2, 2, 2, 2)));
        return headerPanel;
    }

    private class BedDesignationAction extends AbstractAction {

        private final BedDesignation bedDesignation;

        public BedDesignationAction(BedDesignation bedDesignation) {
            super(bedDesignation.getAdmissionPart().getPatient().getName(),
                    new PatientOrRoomIcon(bedDesignation.getAdmissionPart().getPatient()));
            this.bedDesignation = bedDesignation;
            Patient patient = bedDesignation.getPatient();
            // Tooltip
            putValue(SHORT_DESCRIPTION, "<html>Patient: " + patient.getName() + "<br/>"
                    + "Gender: " + patient.getGender().getCode() + " (see icon)<br/>"
                    + "Age: " + patient.getAge() + "<br/>"
                    + "Preferred maximum room capacity: " + patient.getPreferredMaximumRoomCapacity() + "<br/>"
                    + "Requires " + patient.getRequiredPatientEquipmentList().size() + " equipments (shown as rectangles)<br/>"
                    + "Prefers " + patient.getPreferredPatientEquipmentList().size() + " equipments"
                    + "</html>");
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            JPanel listFieldsPanel = new JPanel(new GridLayout(2, 1));
            List<Bed> bedList = getSolution().getBedList();
            // Add 1 to array size to add null, which makes the entity unassigned
            JComboBox bedListField = new JComboBox(
                    bedList.toArray(new Object[bedList.size() + 1]));
            LabeledComboBoxRenderer.applyToComboBox(bedListField);
            bedListField.setSelectedItem(bedDesignation.getBed());
            listFieldsPanel.add(bedListField);
            int result = JOptionPane.showConfirmDialog(PatientAdmissionSchedulePanel.this.getRootPane(),
                    listFieldsPanel, "Select bed for " + bedDesignation.getAdmissionPart().getPatient().getName(),
                    JOptionPane.OK_CANCEL_OPTION);
            if (result == JOptionPane.OK_OPTION) {
                Bed toBed = (Bed) bedListField.getSelectedItem();
                solutionBusiness.doChangeMove(bedDesignation, "bed", toBed);
                solverAndPersistenceFrame.resetScreen();
            }
        }

    }

    private class PatientOrRoomIcon implements Icon {

        private static final int EQUIPMENT_ICON_WIDTH = 8;

        private final ImageIcon genderIcon;
        private final List<Color> equipmentColorList;

        private PatientOrRoomIcon(Patient patient) {
            genderIcon = determinePatientGenderIcon(patient.getGender());
            List<RequiredPatientEquipment> equipmentList = patient.getRequiredPatientEquipmentList();
            equipmentColorList = new ArrayList<>(equipmentList.size());
            for (RequiredPatientEquipment equipment : equipmentList) {
                equipmentColorList.add(equipmentTangoColorFactory.pickColor(equipment.getEquipment()));
            }
        }

        private PatientOrRoomIcon(Room room) {
            genderIcon = determineRoomGenderIcon(room.getGenderLimitation());
            List<RoomEquipment> equipmentList = room.getRoomEquipmentList();
            equipmentColorList = new ArrayList<>(equipmentList.size());
            for (RoomEquipment equipment : equipmentList) {
                equipmentColorList.add(equipmentTangoColorFactory.pickColor(equipment.getEquipment()));
            }
        }

        @Override
        public int getIconWidth() {
            return genderIcon.getIconWidth() + equipmentColorList.size() * EQUIPMENT_ICON_WIDTH;
        }

        @Override
        public int getIconHeight() {
            return genderIcon.getIconHeight();
        }

        @Override
        public void paintIcon(Component c, Graphics g, int x, int y) {
            genderIcon.paintIcon(c, g, x, y);
            int innerX = x + genderIcon.getIconWidth();
            int equipmentIconHeight = genderIcon.getIconHeight();
            for (int i = 0; i < equipmentColorList.size(); i++) {
                g.setColor(equipmentColorList.get(i));
                g.fillRect(innerX + 1, y + 1, EQUIPMENT_ICON_WIDTH - 2, equipmentIconHeight - 2);
                g.setColor(TangoColorFactory.ALUMINIUM_5);
                g.drawRect(innerX + 1, y + 1, EQUIPMENT_ICON_WIDTH - 2, equipmentIconHeight - 2);
                innerX += EQUIPMENT_ICON_WIDTH;
            }
        }

    }

    private ImageIcon determineRoomGenderIcon(GenderLimitation genderLimitation) {
        switch (genderLimitation) {
            case ANY_GENDER:
                return anyGenderIcon;
            case MALE_ONLY:
                return maleIcon;
            case FEMALE_ONLY:
                return femaleIcon;
            case SAME_GENDER:
                return sameGenderIcon;
            default:
                throw new IllegalStateException("The genderLimitation (" + genderLimitation + ") is not implemented.");
        }
    }

    private ImageIcon determinePatientGenderIcon(Gender gender) {
        switch (gender) {
            case MALE:
                return maleIcon;
            case FEMALE:
                return femaleIcon;
            default:
                throw new IllegalStateException("The gender (" + gender + ") is not implemented.");
        }
    }

}
