// ============================================================================
// File:               $File$
//
// Project:            
//
// Purpose:            
//
// Author:             Rammi
//
// Copyright Notice:   (c) 2008  Rammi (rammi@caff.de)
//                     This code is in the public domain.
//                     Use at own risk.
//                     No guarantees given.
//
// Latest change:      $Date$
//
// History:	       $Log$
//=============================================================================
package de.caff.asteroid.analysis;

import de.caff.asteroid.*;
import de.caff.i18n.I18n;

import javax.swing.*;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import java.awt.*;
import java.util.Collection;
import java.util.LinkedList;

/**
 *  Display information about the current frame.
 *
 *  This class is part of a solution for a
 *  <a href="http://www.heise.de/ct/creativ/08/02/details/">competition by the German computer magazine c't</a>.
 */
public class FrameKeyInfoDisplay
        extends JPanel
{
  static {
    I18n.addAppResourceBase("de.caff.asteroid.analysis.resources.FrameKeyInfoDisplay");
  }


  /** Property table columns. */
  private static final String[] COLUMNS = { I18n.getString("colProperty"), I18n.getString("colValue") };
  /** Null value for selection are changes. */
  private static final Rectangle[] NO_BOUNDS = new Rectangle[0];

  /** Tree of frame entries. */
  private JTree tree;
  /** The table where the properties are displayed. */
  private JTable propertyTable;
  /** The listeners for selection area changes. */
  private Collection<SelectionAreaListener> selectionAreaListeners = new LinkedList<SelectionAreaListener>();
  /** The toolbar with buttons und checkboxes. */
  private ButtonBar buttonBar;
  /** The frame display. */
  private EnhancedFrameDisplay frameDisplay;

  /**
   *  Constructor.
   *  @param timeLine time line
   *  @param velocityPreparer preparer used to calculate velocities (and more)
   *  @param provider         additional provider of drawable information or <code>null</code>
   *  @param simple           use simple display without information panels? 
   */
  public FrameKeyInfoDisplay(final TimeLine timeLine,
                             FramePreparer velocityPreparer,
                             DrawableProvider provider,
                             boolean simple)
  {
    setLayout(new BorderLayout());

    frameDisplay = new EnhancedFrameDisplay(timeLine, this);
    frameDisplay.setDrawableProvider(provider);
    timeLine.addFrameListener(frameDisplay);
    if (timeLine.hasFrames()) {
      frameDisplay.frameReceived(timeLine.getCurrentInfo().getFrameInfo());
    }
    timeLine.addDumpFileChangeListener(frameDisplay);

    buttonBar = new ButtonBar(timeLine, frameDisplay, velocityPreparer);
    if (!simple) {
      tree = new JTree();
      propertyTable = new JTable();
      JScrollPane treePane = new JScrollPane(tree);
      JScrollPane tablePane = new JScrollPane(propertyTable);
      treePane.setBorder(BorderFactory.createTitledBorder(I18n.getString("brdGameObjects")));
      tablePane.setBorder(BorderFactory.createTitledBorder(I18n.getString("brdObjectProps")));

      timeLine.addFrameListener(new FrameListener() {
        public void frameReceived(FrameInfo frame)
        {
          setTreeFrom(timeLine.getCurrentInfo());
        }
      });

      tree.addTreeSelectionListener(new TreeSelectionListener()
      {
        public void valueChanged(TreeSelectionEvent e)
        {
          TreePath[] paths = tree.getSelectionPaths();
          Rectangle[] bounds;
          if (paths != null) {
            if (paths.length == 1) {
              setTableFrom(((DefaultMutableTreeNode)paths[0].getLastPathComponent()).getUserObject());
            }
            else {
              setTableFrom(null);
            }
            Collection<Rectangle> boundList = new LinkedList<Rectangle>();
            for (int p = paths.length - 1;  p >= 0;  --p) {
              Object obj = ((DefaultMutableTreeNode)paths[p].getLastPathComponent()).getUserObject();
              try {
                boundList.add(((GameObject)obj).getPickBounds());
              } catch (ClassCastException x) {
                // considered okay, happens on frame node
              }
            }
            bounds = boundList.toArray(new Rectangle[boundList.size()]);
          }
          else {
            setTableFrom(null);
            bounds = NO_BOUNDS;
          }
          informSelectionAreaListeners(bounds);
        }
      });
      addSelectionAreaListener(frameDisplay);

      JSplitPane leftSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
      leftSplit.setTopComponent(treePane);
      leftSplit.setBottomComponent(tablePane);
      leftSplit.setDividerLocation(200);

      JPanel rightPane = new JPanel(new BorderLayout());
      rightPane.add(buttonBar, BorderLayout.NORTH);
      JSplitPane rightSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT);

      rightSplit.setTopComponent(new KeyInfoDisplay(timeLine));
      rightSplit.setBottomComponent(frameDisplay);
      rightPane.add(rightSplit, BorderLayout.CENTER);

      JSplitPane splitter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
      splitter.setLeftComponent(leftSplit);
      splitter.setRightComponent(rightPane);
      add(splitter, BorderLayout.CENTER);

      setTreeFrom(timeLine.getCurrentInfo());

      setMinimumSize(new Dimension(600, 300));
      setPreferredSize(new Dimension(800, 400));
    }
    else {
      add(buttonBar, BorderLayout.NORTH);
      add(frameDisplay, BorderLayout.CENTER);
    }

    frameDisplay.addMouseWheelListener(timeLine.getMouseWheelListener());

    
  }

  /**
   *  Set the tree from a new frame info.
   *  @param info new info
   */
  private void setTreeFrom(FrameKeyInfo info)
  {
    if (tree != null) {
      if (info != null) {
        DefaultMutableTreeNode frameNode = new DefaultMutableTreeNode(info.getFrameInfo());
        for (GameObject obj: info.getFrameInfo().getGameObjects()) {
          frameNode.add(new DefaultMutableTreeNode(obj));
        }
        tree.setModel(new DefaultTreeModel(frameNode));
        tree.setSelectionRow(0);
      }
      else {
        tree.setModel(new DefaultTreeModel(null));
      }
    }
  }

  /**
   *  Set the table content from a new selected object.
   *  @param userObj selected object in tree
   */
  private void setTableFrom(Object userObj)
  {
    if (propertyTable != null) {
      Object[][] data;
      if (userObj != null  &&  userObj instanceof PropertyProvider) {
        Collection<Property> props = ((PropertyProvider)userObj).getProperties();
        data = new Object[props.size()][2];
        int row = 0;
        for (Property p: props) {
          data[row][0] = p.getName();
          data[row][1] = p.getValue();
          ++row;
        }
      }
      else {
        data = new Object[0][2];
      }
      propertyTable.setModel(new DefaultTableModel(data, COLUMNS));
    }
  }

  /**
   * Add a selection area change listener.
   * It will be called if the selection changes.
   * @param listener listener to add
   */
  public void addSelectionAreaListener(SelectionAreaListener listener)
  {
    selectionAreaListeners.add(listener);
  }

  /**
   *  Remove a selection area change listener.
   *  @param listener listener to remove
   *  @return <code>true</code> if the listener was removed<br>
   *          <code>false</code> otherwise
   */
  public boolean removeSelectionAreaListener(SelectionAreaListener listener)
  {
    return selectionAreaListeners.remove(listener);
  }

  /**
   *  Call all selection area change listeners.
   *  @param selections new selected areas
   */
  protected void informSelectionAreaListeners(Rectangle[] selections)
  {
    for (SelectionAreaListener listener: selectionAreaListeners) {
      listener.selectionAreasChanged(selections);
    }
  }

  /**
   *  Select the given user objects.
   *  @param userObjects user objects to select
   */
  public void setSelectedUserObjects(Object[] userObjects)
  {
    if (tree != null) {
      TreePath[] selectionPaths;
      if (userObjects.length > 0) {
        DefaultMutableTreeNode root = (DefaultMutableTreeNode)tree.getModel().getRoot();
        Collection<TreePath> selection = new LinkedList<TreePath>();
        collectSelectionPaths(root, selection, userObjects);
        selectionPaths = selection.toArray(new TreePath[selection.size()]);
      }
      else {
        selectionPaths = new TreePath[0];
      }
      tree.setSelectionPaths(selectionPaths);
    }
  }

  /**
   *  Helper method used in {@link #setSelectedUserObjects(Object[])}.
   *  @param node        current node
   *  @param selection   collection for collecting selection paths
   *  @param userObjects user objects to select
   */
  private static void collectSelectionPaths(DefaultMutableTreeNode node, Collection<TreePath> selection, Object[] userObjects)
  {
    for (Object o: userObjects) {
      if (node.getUserObject() == o) {
        selection.add(new TreePath(node.getPath()));
        break;
      }
    }
    for (int ch = 0;  ch < node.getChildCount(); ++ch) {
      collectSelectionPaths(((DefaultMutableTreeNode)node.getChildAt(ch)), selection, userObjects);
    }
  }

  /**
   * Start animation if it is not running.
   */
  public void startAnimation()
  {
    buttonBar.startAnimation();
  }

  /**
   * Stop animation if it is running.
   */
  public void stopAnimation()
  {
    buttonBar.stopAnimation();
  }

  /**
   *  Is the animation running?
   *  @return the answer
   */
  public boolean isAnimationRunning()
  {
    return buttonBar.isAnimationRunning();
  }

  /**
   *  Add an animation listener which is informed when the animation starts or stops.
   *  @param listener listener to add
   */
  public void addAnimationListener(AnimationListener listener)
  {
    buttonBar.addAnimationListener(listener);
  }

  /**
   *  Remove an animation listener.
   *  @param listener listener to remove
   *  @return was the listener found and removed?
   */
  public boolean removeAnimationListener(AnimationListener listener)
  {
    return buttonBar.removeAnimationListener(listener);
  }

  /**
   *  Show a message in the frame display.
   *  @param message message to show
   */
  public void showMessage(String message)
  {
    frameDisplay.setMessage(message);
  }

  /**
   *  Clear the message in the string display.
   */
  public void clearMessage()
  {
    frameDisplay.clearMessage();
  }
}
