// $Id: WidgetAligner.java,v 1.1 2003/06/03 15:37:14 cadlerun Exp $
// (c) MIT 2003.  All rights reserved.

// WidgetAligner.java -- 
// Author: Aaron Adler <cadlerun@fracas.ai.mit.edu> 
// Copyright:  Copyright (C) 2003 by MIT.  All rights reserved.
// Created: <Thu Apr 10 17:13:28 2003> 

package rec.multi;

import java.awt.Rectangle;

import java.util.TreeSet;
import java.util.Iterator;
import java.util.ArrayList;

import rec.simulate.widgets.WSimBody;
import rec.geo.GShape;
import rec.geo.GOval;
import rec.geo.GPoint;

import edu.mit.tools.watch.Logging;
import org.apache.log4j.Logger;

/***
 * This is a utility class for manipulating the widgets
 * it takes a set of widgets and can figure out the best
 * alignment for them and the best spacing
 ***/

public class WidgetAligner {

  private static final Logger LOG = Logging.SPEECH;

  private ArrayList m_listOfWidgets;
  private double m_maxX;
  private double m_minX;
  private double m_maxY;
  private double m_minY;
  private double m_averageCenterX;
  private double m_averageCenterY;
  private double m_totalHeight; // sum of height of all widgets
  private double m_totalWidth; // sum of all width of all widgets
  
  public static final int UNKNOWN_ALIGNMENT = 0;
  public static final int HORIZONTAL_ALIGNMENT = 1;
  public static final int VERTICAL_ALIGNMENT = 2;
  
  public WidgetAligner (ArrayList setOfWidgets) {
    // initialize and then preform the base set of calculations
    m_listOfWidgets = setOfWidgets;
    calculate(m_listOfWidgets);
  }

  private void calculate(ArrayList widgets) {
    // List of widgets should be set of WSimObjects
    Iterator widgetIterator = widgets.iterator();
    double numWidgets = widgets.size();

    // initialize all variables
    m_maxX = Double.MIN_VALUE;
    m_minX = Double.MAX_VALUE;
    m_maxY = Double.MIN_VALUE;
    m_minY = Double.MAX_VALUE;
    m_averageCenterX = 0;
    m_averageCenterY = 0;
    m_totalWidth = 0;
    m_totalHeight = 0;

    // go through widgets and perform calculations
    while (widgetIterator.hasNext()) {
      WSimBody wsb = (WSimBody) widgetIterator.next();
      Rectangle bounds = wsb.getBounds();
      double x1 = bounds.getX();
      double x2 = x1 + bounds.getWidth();
      double y1 = bounds.getY();
      double y2 = y1 + bounds.getHeight();
      
      m_minX = Math.min(x1, m_minX);
      m_maxX = Math.max(x2, m_maxX);
      m_minY = Math.min(y1, m_minY);
      m_maxY = Math.max(y2, m_maxY);

      m_totalWidth += bounds.getWidth();
      m_totalHeight += bounds.getHeight();

      GShape shape = wsb.getShape();
      if (shape instanceof GOval) {
	// circles and ovals are a special case
	GOval oval = (GOval) shape;
	GPoint center = oval.getCenter();
	m_averageCenterX += center.getX(); 
	m_averageCenterY += center.getY(); 
      }
      else {
	m_averageCenterX += Math.rint(x1+ (bounds.getWidth() / 2.0) );
	m_averageCenterY += Math.rint(y1+ (bounds.getHeight() / 2.0) );
      }
    }

    // do rest of calculations
    m_averageCenterX = Math.rint(m_averageCenterX / numWidgets);
    m_averageCenterY = Math.rint(m_averageCenterY / numWidgets);
    LOG.debug("average center is "+m_averageCenterX+", "+m_averageCenterY);
  }
    

  public int getBestAlignmentDirection() {
    // figure out the best alignment direction
    
    double xDiff = m_maxX - m_minX;
    double yDiff = m_maxY - m_minY;
    LOG.debug("xDiff "+xDiff);
    LOG.debug("yDiff "+yDiff);
    
    if (xDiff > yDiff) {
      // it is aligned horizontally
      return HORIZONTAL_ALIGNMENT;
    }
    else {
      // it is aligned vertically
      return VERTICAL_ALIGNMENT;
    }
  }

  public double getEqualSpacing(int alignmentDirection) {
    // get the spacing that will equally space the widgets
    // the alignment must be given
    
    // spacing should be positive (or at least 0)
    if (alignmentDirection == HORIZONTAL_ALIGNMENT) {
      double xDiff = m_maxX - m_minX;
      return Math.rint(Math.max(0.0,
				((xDiff - m_totalWidth) /
				 (m_listOfWidgets.size() - 1))));
    }
    if (alignmentDirection == VERTICAL_ALIGNMENT) {
      double yDiff = m_maxY - m_minY;
      return Math.rint(Math.max(0.0,
				((yDiff - m_totalHeight) /
				 (m_listOfWidgets.size() - 1))));
    }

    // else the alignment is unknown...
    return 0.0;
  }

  public void alignWidgets(int alignmentDirection, double spacing) {
    // align the widgets to alignmentDirection with spacing for the spacing
    ArrayList centers = getAlignedWidgetCenters(alignmentDirection, spacing);

    if (centers == null) return;
    
    Iterator it = m_listOfWidgets.iterator();
    Iterator centerIt = centers.iterator();
    // centers and the m_listOfWidgets must have the same number of elements

    while (it.hasNext()) {
      if (!(centerIt.hasNext())) {
	LOG.error("Not enough elements in center list");
	return;
      }
      
      WSimBody wsb = (WSimBody) it.next();
      GPoint newLoc = (GPoint)centerIt.next();
      LOG.debug("Moving to "+newLoc);
      wsb.move(newLoc);

      // update the surface to see the results
      wsb.getSurface().getScreen().repaint();
    }
  }

  public ArrayList getAlignedWidgetCenters(int alignmentDirection, double spacing) {
    // return the new centers for the widgets when they are aligned to
    // alignmentDirection with spacing for the spacing. the position in the list
    // corresponds to the postion that they were passed into the class in 

    TreeSet sortedWidgets = null;

    if (alignmentDirection == HORIZONTAL_ALIGNMENT) {
      LOG.info("aligning widgets in a horizontal alignment with spacing "+spacing);
      sortedWidgets = new TreeSet(new HorizontalWidgetComparator());
    }
    else if (alignmentDirection == VERTICAL_ALIGNMENT) {
      LOG.info("aligning widgets in a vertical alignment with spacing "+spacing);
      sortedWidgets = new TreeSet(new VerticalWidgetComparator());
    }
    else {
      // if we don't recognize the alignment type, don't do anything
      LOG.error("don't know what to do, returning");
      return null;
    }

    LOG.debug("adding widgets to sorted set");
    // add the widgets to the set
    sortedWidgets.addAll(m_listOfWidgets);
    Iterator sortedIterator = sortedWidgets.iterator();
    ArrayList centers = new ArrayList();
    // initialize the centers list to nulls
    for (int i = 0; i < m_listOfWidgets.size(); i++) {
      centers.add(null);
    }

    LOG.debug("about to enter loop");

    // keep track of the end location from loop to loop
    // (either x or y end location depending on whether it is horizontal or vertical)
    double previousEndLocation = 0;
    
    while (sortedIterator.hasNext()) {

      LOG.debug("in sorted loop");
      
      // find the new positions for the widgets
      WSimBody wsb = (WSimBody) sortedIterator.next();
      Rectangle bound = wsb.getBounds();

      double oldCenterX;
      double oldCenterY;
      GShape shape = wsb.getShape();
      if (shape instanceof GOval) {
	// circles and ovals are a special case
	GOval oval = (GOval) shape;
	GPoint oldCenter = oval.getCenter();
	oldCenterX = oldCenter.getX();
	oldCenterY = oldCenter.getY();
      }
      else {
	oldCenterX = Math.rint(bound.getX() + (bound.getWidth() / 2.0));
	oldCenterY = Math.rint(bound.getY() + (bound.getHeight() / 2.0));
      }
      
      double newCenterX = 0;
      double newCenterY = 0;

      if (alignmentDirection == HORIZONTAL_ALIGNMENT) {
	//horizontal alignment
	double newX = 0;
	newCenterY = m_averageCenterY;

	if (previousEndLocation == 0) {
	  // placement of first shape
	  if (spacing == 0) {
	    // the shapes are touching, so we want to center them around the distribution
	    double centerOfDist = ((m_maxX + m_minX) / 2.0);
	    newX = Math.rint(centerOfDist - (m_totalWidth / 2.0));
	  }
	  else {
	    // space them evenly between the extreme shapes
	    newX = bound.getX();
	  }
	  
	  // if the center is not moving much, leave it alone for the first
	  // shape, otherwise with repeated alignment the shapes drift.
	  if (Math.abs(newCenterY - oldCenterY) <= 1) {
	    LOG.debug("not moving the center");
	    newCenterY = oldCenterY;
	  }
	}
	else {
	  newX = previousEndLocation + spacing;
	}
	
	// calculate the new x center
	newCenterX = newX - bound.getX() + oldCenterX;

	// update the previous end location
	previousEndLocation = newX + bound.getWidth();
      }
      else {
	// vertical alignement
	double newY = 0;
	newCenterX = m_averageCenterX;

	if (previousEndLocation == 0) {
	  // placement of first shape
	  if (spacing == 0) {
	    // the shapes are touching, so we want to center them around the distribution
	    double centerOfDist = ((m_maxY + m_minY) / 2.0);
	    newY = Math.rint(centerOfDist - (m_totalHeight / 2.0));
	  }
	  else {
	    // space them evely between the extreme shapes
	    newY = bound.getY();
	  }
	  
	  // if the center is not moving much, leave it alone for the first
	  // shape, otherwise with repeated alignment the shapes drift.
	  if (Math.abs(newCenterX - oldCenterX) <= 1) newCenterX = oldCenterX; 
	}
	else {
	  newY = previousEndLocation + spacing;
	}
	
	// calculate the new y center
	newCenterY = newY - bound.getY() + oldCenterY;

	// update the previous end location
	previousEndLocation = newY + bound.getHeight();
      }
	
      // move to new location
      LOG.debug("old center is "+oldCenterX+", "+oldCenterY);
      LOG.debug("new center is "+newCenterX+", "+newCenterY);
      GPoint newLoc = new GPoint(newCenterX, newCenterY);

      // add the center to the same location in the list as its widget
      centers.set(m_listOfWidgets.indexOf(wsb), newLoc);
      
    }

    // return the list of centers
    return centers;
  }

}

    

