/*
 * $Id: ServerStatusView.java 7738 2011-08-03 18:38:35Z uckelman $
 *
 * Copyright (c) 2000-2009 by Rodney Kinney, Brent Easton
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License (LGPL) as published by the Free Software Foundation.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, copies are available
 * at http://www.opensource.org.
 */
package VASSAL.chat.ui;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
import java.util.concurrent.ExecutionException;

import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JToolBar;
import javax.swing.JTree;
import javax.swing.border.TitledBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

import org.jdesktop.swingworker.SwingWorker;

import VASSAL.chat.Player;
import VASSAL.chat.ServerStatus;
import VASSAL.chat.SimpleRoom;
import VASSAL.i18n.Resources;
import VASSAL.tools.ErrorDialog;

/**
 * Shows the current status of connections to the server
 */
public class ServerStatusView extends JTabbedPane implements ChangeListener, TreeSelectionListener {
  private static final long serialVersionUID = 1L;

  public static final String SELECTION_PROPERTY = "ServerStatusView.selection"; //$NON-NLS-1$
  private ServerStatus status;
  private DefaultTreeModel model;
  private DefaultTreeModel[] historicalModels;
  private JTree treeCurrent;
  private JTree[] historicalTrees;
  private int totalPlayers;

  public ServerStatusView(ServerStatus status) {
    this.status = status;
    initComponents();
  }

  private void initComponents() {
    JPanel current = new JPanel(new BorderLayout());
    JToolBar toolbar = new JToolBar();

    toolbar.setFloatable(false);
    JButton b = new JButton(Resources.getString("Chat.refresh")); //$NON-NLS-1$
    b.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        refresh();
      }
    });
    toolbar.add(b);
    current.add(toolbar, BorderLayout.NORTH);
    treeCurrent = createTree();
    current.add(new JScrollPane(treeCurrent), BorderLayout.CENTER);
    model = (DefaultTreeModel) treeCurrent.getModel();
    addTab(Resources.getString("Chat.current"), current); //$NON-NLS-1$
    addChangeListener(this);
    setBorder(new TitledBorder(Resources.getString("Chat.server_connections"))); //$NON-NLS-1$
    setStatusServer(status);
  }

  private void buildHistoricalTabs() {
    while (getTabCount() > 1) {
      removeTabAt(getTabCount()-1);
    }
    if (status != null) {
      String[] supported = status.getSupportedTimeRanges();
      historicalTrees = new JTree[supported.length];
      historicalModels = new DefaultTreeModel[supported.length];
      for (int i = 0; i < supported.length; i++) {
        historicalTrees[i] = createTree();
        historicalModels[i] = (DefaultTreeModel) historicalTrees[i].getModel();
        addTab(supported[i], new JScrollPane(historicalTrees[i]));
      }
    }
  }

  private JTree createTree() {
    DefaultMutableTreeNode root = new DefaultMutableTreeNode(Resources.getString(Resources.VASSAL));
    DefaultTreeModel m = new DefaultTreeModel(root, true);
    JTree tree = new JTree(m);
    tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
    tree.setCellRenderer(new Render());
    tree.expandRow(0);
    tree.setLargeModel(true);
    tree.setRowHeight(18);  // FIXME: check whether this is necessary
    tree.addTreeSelectionListener(this);
    tree.addTreeExpansionListener(new TreeExpansionListener() {
      public void treeExpanded(TreeExpansionEvent event) {
        JComponent c = (JComponent) event.getSource();
        c.setSize(c.getPreferredSize());
        c.revalidate();
      }

      public void treeCollapsed(TreeExpansionEvent event) {
      }
    });
    return tree;
  }

  public void stateChanged(ChangeEvent e) {
    if (status == null) return;
    refresh(getSelectedIndex());
    fireSelectionChanged();
  }

  public void valueChanged(TreeSelectionEvent e) {
    fireSelectionChanged();
  }

  private void fireSelectionChanged() {
    Object selection = null;
    TreePath path = null;
    int sel = getSelectedIndex();
    switch (sel) {
    case 0:
      path = treeCurrent.getSelectionPath();
      break;
    default:
      path = historicalTrees[sel-1].getSelectionPath();
      break;
    }
    if (path != null) {
      selection = path.getLastPathComponent();
    }
    if (selection instanceof DefaultMutableTreeNode) {
      selection = ((DefaultMutableTreeNode) selection).getUserObject();
    }
    firePropertyChange(SELECTION_PROPERTY, null, selection);
  }

  public void refresh() {
    refresh(0);
  }

  private SwingWorker<ServerStatus.ModuleSummary[],Void> cur_request = null;
  private SwingWorker<ServerStatus.ModuleSummary[],Void> hist_request = null;

  private void refresh(final int page) {
    if (page == 0) {
      if (cur_request != null && !cur_request.isDone()) return;

      setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));

      cur_request = new SwingWorker<ServerStatus.ModuleSummary[],Void>() {
        @Override
        public ServerStatus.ModuleSummary[] doInBackground() {
          return status.getStatus();
        }

        @Override
        protected void done() {
          try {
            if (getSelectedIndex() == 0) {
              refresh(model, get());
              fireSelectionChanged();
            }
          }
          catch (InterruptedException ex) {
            ErrorDialog.bug(ex);
          }
          // FIXME: review error message
          catch (ExecutionException ex) {
            ex.printStackTrace();
          }

          if (hist_request == null || hist_request.isDone())
            setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));

          cur_request = null;
        }
      };

      cur_request.execute();
    }
    else {
      if (hist_request != null && !hist_request.isDone()) return;

      setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));

      hist_request = new SwingWorker<ServerStatus.ModuleSummary[],Void>() {
        @Override
        public ServerStatus.ModuleSummary[] doInBackground() {
          return status.getHistory(getTitleAt(page));
        }

        @Override
        protected void done() {
          final int sel = getSelectedIndex();
          if (sel == page) {
            // page didn't change, refresh with what we computed
            try {
              refresh(historicalModels[sel-1], get());
            }
            // FIXME: review error message
            catch (InterruptedException ex) {
              ex.printStackTrace();
            }
            // FIXME: review error message
            catch (ExecutionException ex) {
              ex.printStackTrace();
            }

            fireSelectionChanged();
          }
          else if (sel != 0) {
            // page changed, refresh that page instead
            hist_request = null;
            refresh(sel);
          }

          if (cur_request == null || cur_request.isDone())
            setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
        }
      };

      hist_request.execute();
    }
  }

  private void refresh(DefaultTreeModel m,
                       ServerStatus.ModuleSummary[] modules) {
    final MutableTreeNode root = (MutableTreeNode) m.getRoot();
    totalPlayers = 0;

    while (root.getChildCount() > 0) {
      m.removeNodeFromParent((MutableTreeNode) root.getChildAt(0));
    }

    if (modules.length == 0) {
      final DefaultMutableTreeNode n = new DefaultMutableTreeNode(
        Resources.getString("Chat.no_connections")); //$NON-NLS-1$
      n.setAllowsChildren(false);
    }
    else {
      for (ServerStatus.ModuleSummary s : modules) {
        m.insertNodeInto(createNode(s), root, root.getChildCount());
      }
    }

    // append total number of players on server to root node
    root.setUserObject(
      Resources.getString(Resources.VASSAL) + " (" + totalPlayers + ")");
  }

  private DefaultMutableTreeNode createNode(Object o) {
    Object[] children = null;
    if (o instanceof ServerStatus.ModuleSummary) {
      final ServerStatus.ModuleSummary ms = (ServerStatus.ModuleSummary) o;

      children = ms.getRooms();

      final int players = ms.numPlayers();
      ms.setModuleName(ms.getModuleName() + " (" + players + ")");
      totalPlayers += players;
    }
    else if (o instanceof SimpleRoom) {
      final SimpleRoom r = (SimpleRoom) o;

      final List<Player> l = r.getPlayerList();

      // append the number of players to each room name
      r.setName(r.getName() + " (" + l.size() + ")");
      children = l.toArray(new Player[l.size()]);
    }

    final DefaultMutableTreeNode node = new DefaultMutableTreeNode(o);

    if (children != null) {
      for (Object c : children) {
        node.add(createNode(c));
      }
    }

    node.setAllowsChildren(children != null);
    return node;
  }

  public static class Render extends DefaultTreeCellRenderer {
    private static final long serialVersionUID = 1L;

    public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
      super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
      if (leaf) {
        setIcon(null);
      }
      putClientProperty(Resources.getString("ServerStatusView.4"), Boolean.TRUE); //$NON-NLS-1$
      return this;
    }
  }

  public void setStatusServer(ServerStatus status) {
    this.status = status;
    buildHistoricalTabs();
    setEnabled(status != null);
  }
}