package org.ggp.base.apps.server.scheduling;

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ScrollPaneConstants;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.DefaultTableModel;

import org.ggp.base.server.event.ServerMatchUpdatedEvent;
import org.ggp.base.util.match.Match;
import org.ggp.base.util.observer.Event;
import org.ggp.base.util.observer.Observer;
import org.ggp.base.util.presence.PlayerPresence;
import org.ggp.base.util.ui.JLabelBold;

@SuppressWarnings("serial")
public final class SchedulingPanel extends JPanel implements Observer, ListSelectionListener, TableModelListener
{
    private final JTable queueTable;

    private final JButton viewSaved;
    private final JButton viewPublished;
    private final JButton remove;

    // Track the external filenames and URLs for each match, so that they can
    // be opened for viewing as needed.
    private final Map<String,String> matchIdToURL = new HashMap<String,String>();
    private final Map<String,String> matchIdToFilename = new HashMap<String,String>();

    public SchedulingPanel()
    {
        super(new BorderLayout());

        DefaultTableModel model = new DefaultTableModel();
        model.addColumn("ID");
        model.addColumn("Game");
        model.addColumn("Clock");
        model.addColumn("Status");
        model.addColumn("Players");
        model.addColumn("Goals");
        model.addColumn("Errors");
        model.addColumn("Step");

        queueTable = new JTable(model)
        {
            @Override
            public boolean isCellEditable(int rowIndex, int colIndex)
            {
                return false;
            }
        };
        queueTable.setShowHorizontalLines(true);
        queueTable.setShowVerticalLines(true);
        queueTable.getColumnModel().getColumn(0).setPreferredWidth(1);
        queueTable.getColumnModel().getColumn(1).setPreferredWidth(60);
        queueTable.getColumnModel().getColumn(2).setPreferredWidth(40);
        queueTable.getColumnModel().getColumn(3).setPreferredWidth(55);
        queueTable.getColumnModel().getColumn(4).setPreferredWidth(145);
        queueTable.getColumnModel().getColumn(5).setPreferredWidth(40);
        queueTable.getColumnModel().getColumn(6).setPreferredWidth(45);
        queueTable.getColumnModel().getColumn(7).setPreferredWidth(40);
        queueTable.getSelectionModel().addListSelectionListener(this);
        queueTable.getModel().addTableModelListener(this);

        JPanel buttonPanel = new JPanel();
        viewSaved = new JButton(viewSavedMatchButtonMethod());
        viewSaved.setEnabled(false);
        viewPublished = new JButton(viewPublishedMatchButtonMethod());
        viewPublished.setEnabled(false);
        remove = new JButton(removeMatchButtonMethod());
        remove.setEnabled(false);
        buttonPanel.add(viewSaved);
        buttonPanel.add(viewPublished);
        buttonPanel.add(remove);

        add(new JLabelBold("Scheduling Queue"), BorderLayout.NORTH);
        add(new JScrollPane(queueTable, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED), BorderLayout.CENTER);
        add(buttonPanel, BorderLayout.SOUTH);
    }

    private Scheduler scheduler;
    public void setScheduler(Scheduler scheduler) {
        this.scheduler = scheduler;
    }

    private AbstractAction viewPublishedMatchButtonMethod() {
        return new AbstractAction("View Published") {
            @Override
            public void actionPerformed(ActionEvent evt) {
                if (queueTable.getSelectedRow() >= 0) {
                    String matchId = queueTable.getModel().getValueAt(queueTable.getSelectedRow(), 0).toString();
                    if (matchIdToURL.containsKey(matchId)) {
                        try {
                            java.awt.Desktop.getDesktop().browse(java.net.URI.create(matchIdToURL.get(matchId)));
                        } catch (IOException ie) {
                            ie.printStackTrace();
                        }
                    }
                }
            }
        };
    }

    private AbstractAction viewSavedMatchButtonMethod() {
        return new AbstractAction("View Saved") {
            @Override
            public void actionPerformed(ActionEvent evt) {
                if (queueTable.getSelectedRow() >= 0) {
                    String matchId = queueTable.getModel().getValueAt(queueTable.getSelectedRow(), 0).toString();
                    if (matchIdToFilename.containsKey(matchId)) {
                        try {
                            java.awt.Desktop.getDesktop().browse(java.net.URI.create("file://" + matchIdToFilename.get(matchId)));
                        } catch (IOException ie) {
                            ie.printStackTrace();
                        }
                    }
                }
            }
        };
    }

    private AbstractAction removeMatchButtonMethod() {
        return new AbstractAction("Cancel Match") {
            @Override
            public void actionPerformed(ActionEvent evt) {
                if (queueTable.getSelectedRow() >= 0) {
                    String matchId = queueTable.getModel().getValueAt(queueTable.getSelectedRow(), 0).toString();
                    String state = queueTable.getModel().getValueAt(queueTable.getSelectedRow(), 3).toString();
                    if (state.equals("pending")) {
                        ;
                    } else if (state.equals("active")) {
                        if (scheduler != null) {
                            scheduler.abortOngoingMatch(matchId);
                        }
                    } else if (state.equals("aborted") || state.equals("done")) {
                        int[] nRows = queueTable.getSelectedRows();
                        queueTable.clearSelection();
                        Arrays.sort(nRows);
                        for (int i = nRows.length-1; i >= 0; i--) {
                            ((DefaultTableModel)queueTable.getModel()).removeRow(nRows[i]);
                        }
                    }
                }
            }
        };
    }

    @Override
    public void tableChanged(TableModelEvent arg0) {
        updateButtonState();
    }

    @Override
    public void valueChanged(ListSelectionEvent arg0) {
        updateButtonState();
    }

    private void updateButtonState() {
        if (queueTable.getSelectedRow() >= 0) {
            String matchId = queueTable.getModel().getValueAt(queueTable.getSelectedRow(), 0).toString();
            String state = queueTable.getModel().getValueAt(queueTable.getSelectedRow(), 3).toString();
            viewSaved.setEnabled(matchIdToFilename.containsKey(matchId));
            viewPublished.setEnabled(matchIdToURL.containsKey(matchId));
            if (state.equals("pending")) {
                remove.setEnabled(false);
                // TODO: Add support for canceling pending matches.
            } else if (state.equals("active")) {
                remove.setText("Cancel Match");
                remove.setEnabled(true);
            } else if (state.equals("aborted") || state.equals("done")) {
                remove.setText("Hide Match");
                remove.setEnabled(true);
            }
        } else {
            viewSaved.setEnabled(false);
            viewPublished.setEnabled(false);
            remove.setEnabled(false);
        }
    }

    public void addPendingMatch(PendingMatch spec) {
        DefaultTableModel model = (DefaultTableModel) queueTable.getModel();
        model.addRow(new Object[]{spec.matchID,spec.theGame.getKey(),spec.startClock + "," + spec.playClock,"pending",getLinebreakString(getNamesForPlayers(spec.thePlayers)),"","",0});
        queueTable.setRowHeight(model.getRowCount()-1, spec.thePlayers.size()*20);
    }

    @Override
    public void observe(Event genericEvent) {
        if (!(genericEvent instanceof ServerMatchUpdatedEvent)) return;
        ServerMatchUpdatedEvent event = (ServerMatchUpdatedEvent)genericEvent;
        Match match = event.getMatch();

        DefaultTableModel model = (DefaultTableModel) queueTable.getModel();
        for (int i = 0; i < model.getRowCount(); i++) {
            String rowMatchId = model.getValueAt(i, 0).toString();
            if (rowMatchId.equals(match.getMatchId())) {
                String status = "active";
                if (match.isAborted()) status = "aborted";
                if (match.isCompleted()) status = "done";
                model.setValueAt(status, i, 3);
                if (match.isCompleted()) {
                    model.setValueAt(getLinebreakString(match.getGoalValues()), i, 5);
                }
                List<Integer> errorCounts = new ArrayList<Integer>();
                List<String> errorCountStrings = new ArrayList<String>();
                for (int j = 0; j < match.getPlayerNamesFromHost().size(); j++) {
                    errorCounts.add(0);
                }
                for (List<String> errors : match.getErrorHistory()) {
                    for (int j = 0; j < errors.size(); j++) {
                        if (!errors.get(j).isEmpty()) {
                            errorCounts.set(j, errorCounts.get(j) + 1);
                        }
                    }
                }
                for (int errorCount : errorCounts) {
                    if (errorCount > 0) {
                        errorCountStrings.add("<font color=red>" + errorCount + "</font>");
                    } else {
                        errorCountStrings.add("0");
                    }
                }
                model.setValueAt(getLinebreakString(errorCountStrings), i, 6);
                model.setValueAt(match.getStateHistory().size()-1, i, 7);

                if (event.getExternalPublicationKey() != null) {
                    matchIdToURL.put(match.getMatchId(), "http://www.ggp.org/view/all/matches/" + event.getExternalPublicationKey() + "/");
                }
                if (event.getExternalFilename() != null) {
                    matchIdToFilename.put(match.getMatchId(), event.getExternalFilename());
                }

                return;
            }
        }

        // Couldn't find the match in the existing list -- add it.
        model.addRow(new Object[]{match.getMatchId(),match.getGame().getKey(),match.getStartClock() + "," + match.getPlayClock(),"starting",getLinebreakString(match.getPlayerNamesFromHost()),"","",0});
        queueTable.setRowHeight(model.getRowCount()-1, match.getPlayerNamesFromHost().size()*20);
    }

    private static List<String> getNamesForPlayers(List<PlayerPresence> players) {
        List<String> playerNames = new ArrayList<String>();
        for (PlayerPresence player : players) {
            playerNames.add(player.getName());
        }
        return playerNames;
    }

    private static String getLinebreakString(List<?> objects) {
        String renderedString = "<html>";
        for (Object object : objects) {
            renderedString += (object == null ? "?" : object.toString()) + "<br>";
        }
        return renderedString.substring(0, renderedString.length()-"<br>".length())+"</html>";
    }
}