// $Id: ManipulatePendulum.java,v 1.1 2003/06/03 15:37:14 cadlerun Exp $
// ManipulatePendulum.java -- 
// Author: Aaron Adler <cadlerun@fracas.ai.mit.edu> 
// Copyright: Copyright (C) 2003 by MIT.  All rights reserved.
// Created: <Fri May 16 18:25:42 2003> 

package rec.multi;

import java.util.Set;
import java.util.HashSet;
import java.util.Iterator;
import java.util.ArrayList;

import java.awt.Rectangle;

import rec.sys.Widget;
import rec.simulate.widgets.wm.WMSimCircle;
import rec.simulate.widgets.wm.WMSimRod;
import rec.simulate.widgets.WSimBody;
import rec.geo.GPoint;

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

/***
 * This class takes a set of pendulums (which is a set of Rods
 * and circles) and has methods for various commands to make
 * the pendulums identical, equally spaced, or touching
 ***/

public class ManipulatePendulum {
  private static Logger LOG = Logging.SPEECH;

  Set m_pendulums;
  ArrayList m_circles = new ArrayList();
  ArrayList m_rods = new ArrayList();
  
  public ManipulatePendulum(Set pendulums) {
    // initializes the set of pendulums and the lists of
    // circles and rods. the indices in the lists correspond
    // to the same pendulum
    
    m_pendulums = pendulums;
    Iterator p = m_pendulums.iterator();

    while(p.hasNext()) {
      Set pend = (Set)p.next();
      Iterator p2 = pend.iterator();
      while (p2.hasNext()) {
	Widget w = (Widget)p2.next();
	if (w instanceof WMSimCircle) {
	  m_circles.add(w);
	  LOG.debug("adding circle in manipulate pendulum");
	}
	if (w instanceof WMSimRod) {
	  m_rods.add(w);
	  LOG.debug("adding rod in manipulate pendulum");
	}
      }
	
    }
  }

  public void makeIdentical() {
    // do this in two step... first resize the circles,
    // then we need to move the rods to the right location.

    // for now rotate the rods to vertical. in the future this might
    // not be right if the pendulums aren't hanging vertically

    LOG.debug("making pendulums identical");
    
    Iterator cirIt = m_circles.iterator();
    Iterator rodIt = m_rods.iterator();
    double numPendulums = m_circles.size();
    
    double totalW = 0.0;
    double totalH = 0.0;
    double length = 0;

    // do the circle and rod calculations
    while (cirIt.hasNext()) {
      //circle calculations
      WSimBody w = (WSimBody)cirIt.next();
      Rectangle bounds = w.getBounds();
      totalW = totalW + bounds.getWidth();
      totalH = totalH + bounds.getHeight();

      // rod calculations
      WMSimRod r = (WMSimRod) rodIt.next();
      double l = r.size();
      LOG.debug ("my length is "+l);
      length += l;      
    }

    double wScale = totalW / numPendulums;
    double hScale = totalH / numPendulums;
    length = length / numPendulums;
    
    // now rescale the shapes
    cirIt = m_circles.iterator();
    rodIt = m_rods.iterator();
    
    while (cirIt.hasNext()) {
      //scale the circle
      WSimBody w = (WSimBody) cirIt.next();
      Rectangle bounds = w.getBounds();
      // we can only scale in one direction at the moment --
      // not sure what to do about that
      double cScaleFactor = wScale / bounds.getWidth();
      w.resize(cScaleFactor);

      // scale the rod
      WMSimRod r = (WMSimRod) rodIt.next();
      double mySize = r.size();
      double rScaleFactor = length / mySize;
      LOG.debug("my size " + mySize +" scale "+rScaleFactor);
      r.resize(rScaleFactor);

      double myAngle = r.angle();
      LOG.debug("my angle is "+myAngle);
      
      // calculating rotation amount
      // normalize angle (angle differs based on drawing direction)
      if (myAngle < 0) myAngle += Math.PI;
      if (myAngle > Math.PI) myAngle -= Math.PI;
      //double rotateBy = (Math.PI / 2) - myAngle;
      double rotateBy = myAngle - (Math.PI / 2);
      LOG.debug("rotate by "+rotateBy);
      
      //get the point that it is attached to the body
      GPoint rPt = r.getAttachmentPoint(w);
      if (rPt == null) {
	LOG.error("no attachment point -- don't have point to rotate around");
      }
      else {
	// rotate the rod
	r.rotate(rPt, rotateBy);

	// now line the rod up with the middle of the top of the circle
	Rectangle cirBounds = w.getBounds();
	double rodX = cirBounds.getX() + (cirBounds.getWidth() / 2.0);
	double rodY = cirBounds.getY();

	double xShift = rodX - rPt.getX();
	double yShift = rodY - rPt.getY();

	LOG.debug("shift is: "+xShift+", "+yShift);
	
	GPoint oldCenter = r.getLocation().getCenter();
	GPoint newCenter = new GPoint ((oldCenter.getX() + xShift), (oldCenter.getY() + yShift));

	r.move(newCenter);
      }
      
      // update the surface to see the results
      w.getSurface().getScreen().repaint();
    }    
  }

  public void equallySpace() {
    // base the alignement on the circles of the pendulums
    WidgetAligner wa = new WidgetAligner(m_circles);

    int alignment = wa.getBestAlignmentDirection();
    double spacing = wa.getEqualSpacing(alignment);

    ArrayList centers = wa.getAlignedWidgetCenters(alignment, spacing);
    if (centers == null) return;
    
    for (int i = 0; i < m_circles.size(); i++) {
      WMSimCircle cir = (WMSimCircle)m_circles.get(i);
      WMSimRod rod = (WMSimRod)m_rods.get(i);
      GPoint oldCirLoc = cir.getCenter();
      GPoint oldRodLoc = rod.getLocation().getCenter();
      GPoint newCirLoc = (GPoint)centers.get(i);
      GPoint newRodLoc = new GPoint( (newCirLoc.getX() - oldCirLoc.getX() + oldRodLoc.getX()),
				     (newCirLoc.getY() - oldCirLoc.getY() + oldRodLoc.getY()) );
      
      LOG.debug("Moving circle to "+newCirLoc);
      cir.move(newCirLoc);
      LOG.debug("Moving rod to "+newRodLoc);
      rod.move(newRodLoc);

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

  public void touching() {
    // base the alignement on the circles of the pendulums
    WidgetAligner wa = new WidgetAligner(m_circles);

    int alignment = wa.getBestAlignmentDirection();
    double spacing = 0.0;

    ArrayList centers = wa.getAlignedWidgetCenters(alignment, spacing);
    if (centers == null) return;
    
    for (int i = 0; i < m_circles.size(); i++) {
      WMSimCircle cir = (WMSimCircle)m_circles.get(i);
      WMSimRod rod = (WMSimRod)m_rods.get(i);
      GPoint oldCirLoc = cir.getCenter();
      GPoint oldRodLoc = rod.getLocation().getCenter();
      GPoint newCirLoc = (GPoint)centers.get(i);
      GPoint newRodLoc = new GPoint( (newCirLoc.getX() - oldCirLoc.getX() + oldRodLoc.getX()),
				     (newCirLoc.getY() - oldCirLoc.getY() + oldRodLoc.getY()) );
      
      LOG.debug("Moving circle to "+newCirLoc);
      cir.move(newCirLoc);
      LOG.debug("Moving rod to "+newRodLoc);
      rod.move(newRodLoc);

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