package lazybones.gui.components.timeline;
import static java.awt.GridBagConstraints.BOTH;
import static java.awt.GridBagConstraints.HORIZONTAL;
import static java.awt.GridBagConstraints.NONE;
import static java.awt.GridBagConstraints.SOUTHWEST;
import static java.awt.GridBagConstraints.WEST;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.Set;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.ListSelectionModel;
import javax.swing.UIManager;
import lazybones.LazyBones;
import lazybones.LazyBonesTimer;
import lazybones.TimerManager;
import lazybones.conflicts.Conflict;
import lazybones.conflicts.ConflictFinder;
import lazybones.conflicts.ConflictResolver;
import lazybones.conflicts.ConflictUnresolvableException;
import lazybones.utils.Utilities;
import util.programmouseevent.ProgramMouseEventHandler;
import util.settings.PluginPictureSettings;
import util.ui.ProgramList;
import devplugin.Program;
public class ConflictPanel extends JPanel implements Observer, ActionListener {
private static final Insets DEFAULT_INSETS = new Insets(5, 5, 5, 5);
private static final int NO_PADDING = 0;
private static final int NO_WEIGHT = 0;
private static final int FULL_WEIGHT = 1;
private static final String MSG_NO_SOLUTION = LazyBones.getTranslation("timer_conflict_no_solution", "No solution found");
private static final String MSG_SOLUTION = LazyBones.getTranslation("timer_conflict_solution", "Possible solution found");
private TimerManager timerManager;
private Calendar calendar;
private Set<Conflict> conflictsToday = new HashSet<>();
private Conflict displayedConflict;
private DefaultListModel<Program> programListModel = new DefaultListModel<>();
private ProgramList programList;
private JScrollPane programListScrollPane;
private JTextArea description;
private JLabel dummy;
private JButton reprogramTimersButton = new JButton(LazyBones.getTranslation("timer_conflict_change_timers", "Change timers"));
public ConflictPanel(TimerManager timerManager) {
this.timerManager = timerManager;
timerManager.addObserver(this);
initGUI();
}
private void initGUI() {
setLayout(new GridBagLayout());
JLabel header = new JLabel(LazyBones.getTranslation("timer_conflict_resolution", "Conflict resolution"));
header.setFont(new Font("SansSerif", Font.PLAIN, 18));
header.setPreferredSize(new Dimension(280, 30));
add(header, new GridBagConstraints(0, 0, 1, 1, FULL_WEIGHT, NO_WEIGHT, WEST, HORIZONTAL, DEFAULT_INSETS, NO_PADDING, NO_PADDING));
description = new JTextArea(MSG_NO_SOLUTION);
description.setEditable(false);
description.setRows(2);
description.setLineWrap(true);
description.setWrapStyleWord(true);
description.setBackground(UIManager.getColor("Panel.background"));
description.setBorder(null);
JScrollPane scrollPane = new JScrollPane(description);
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
scrollPane.setBorder(null);
scrollPane.setPreferredSize(new Dimension(0, 50));
add(scrollPane, new GridBagConstraints(0, 1, 1, 1, FULL_WEIGHT, FULL_WEIGHT, WEST, HORIZONTAL, DEFAULT_INSETS, NO_PADDING, NO_PADDING));
programList = new ProgramList(programListModel, new PluginPictureSettings(PluginPictureSettings.NO_PICTURE_TYPE));
programList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
programList.addMouseListener(new ProgramListMouseAdapter());
programListScrollPane = new JScrollPane(programList);
programListScrollPane.setMinimumSize(new Dimension(0, 100));
programListScrollPane.setPreferredSize(new Dimension(0, 250));
add(programListScrollPane, new GridBagConstraints(0, 2, 1, 1, FULL_WEIGHT, NO_WEIGHT, SOUTHWEST, BOTH, DEFAULT_INSETS, NO_PADDING, NO_PADDING));
reprogramTimersButton.addActionListener(this);
add(reprogramTimersButton, new GridBagConstraints(0, 3, 1, 1, NO_WEIGHT, NO_WEIGHT, WEST, NONE, DEFAULT_INSETS, NO_PADDING, NO_PADDING));
dummy = new JLabel();
add(dummy, new GridBagConstraints(0, 4, 1, 1, FULL_WEIGHT, FULL_WEIGHT, WEST, BOTH, DEFAULT_INSETS, NO_PADDING, NO_PADDING));
}
private void update() {
List<LazyBonesTimer> allTimers = timerManager.getTimers();
Set<Conflict> conflicts = new ConflictFinder().findConflictingTimers(allTimers);
conflictsToday.clear();
for (Conflict conflict : conflicts) {
for (LazyBonesTimer timer : conflict.getInvolvedTimers()) {
if (Utilities.timerRunsOnDate(timer, calendar)) {
conflictsToday.add(conflict);
}
}
}
if (conflictsToday.isEmpty()) {
setVisible(false);
} else {
setVisible(true);
description.setText(MSG_NO_SOLUTION);
for (Conflict conflict : conflictsToday) {
try {
List<Program> solution = new ConflictResolver(conflict, timerManager.getTimers()).solveConflict();
description.setText(MSG_SOLUTION);
setPrograms(solution);
dummy.setVisible(false);
programListScrollPane.setVisible(true);
reprogramTimersButton.setVisible(true);
displayedConflict = conflict;
break;
} catch (ConflictUnresolvableException e) {
description.setText(MSG_NO_SOLUTION);
programListScrollPane.setVisible(false);
reprogramTimersButton.setVisible(false);
dummy.setVisible(true);
}
}
}
}
private void setPrograms(List<Program> solution) {
programListModel.removeAllElements();
for (Program program : solution) {
programListModel.addElement(program);
}
}
public void setCalendar(Calendar calendar) {
this.calendar = calendar;
this.calendar.set(Calendar.HOUR_OF_DAY, 0);
this.calendar.set(Calendar.MINUTE, 0);
this.calendar.set(Calendar.SECOND, 0);
this.calendar.set(Calendar.MILLISECOND, 0);
update();
}
@Override
public void update(Observable o, Object arg) {
if (o instanceof TimerManager) {
update();
}
}
class ProgramListMouseAdapter extends MouseAdapter {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() >= 2) {
int index = programList.locationToIndex(e.getPoint());
programList.setSelectedIndex(index);
Program prog = (Program) programList.getSelectedValue();
ProgramMouseEventHandler.handleProgramClick(prog, LazyBones.getInstance(), false, e);
}
}
@Override
public void mousePressed(MouseEvent e) {
if (e.isPopupTrigger()) {
int index = programList.locationToIndex(e.getPoint());
programList.setSelectedIndex(index);
Program prog = (Program) programList.getSelectedValue();
JPopupMenu popup = LazyBones.getPluginManager().createPluginContextMenu(prog, null);
popup.setLocation(e.getPoint());
popup.show(e.getComponent(), e.getX(), e.getY());
}
}
}
@Override
public void actionPerformed(ActionEvent e) {
if(e.getSource() == reprogramTimersButton) {
int result = JOptionPane.showConfirmDialog(LazyBones.getInstance().getMainDialog(), LazyBones.getTranslation("timer_conflict_change_confirm", ""),
"", JOptionPane.YES_NO_OPTION);
if (result != JOptionPane.OK_OPTION) {
return;
}
// save solution for the timer creation later
List<Program> solution = new ArrayList<>();
Enumeration<Program> en = programListModel.elements();
while (en.hasMoreElements()) {
solution.add(en.nextElement());
}
// delete the old timers:
// sort by descending timer id, so that we can delete them from highest ID to lowest
// otherwise we might get the error "Timer x not defined"
List<LazyBonesTimer> timers = new ArrayList<>(displayedConflict.getInvolvedTimers());
Collections.sort(timers, new Comparator<LazyBonesTimer>() {
@Override
public int compare(LazyBonesTimer o1, LazyBonesTimer o2) {
if (o1.getID() > o2.getID()) {
return -1;
} else if (o1.getID() < o2.getID()) {
return 1;
} else {
throw new RuntimeException("2 timers with same ID. This should'nt happen");
}
}
});
for (LazyBonesTimer timer : timers) {
timerManager.deleteTimer(timer);
}
// create timers for programs from the conflict-free solution
for (Program program : solution) {
timerManager.createTimer(program, true);
}
}
}
}