import java.util.Vector;

/**
 * Title:        PlacementApplet
 * Description:  Animation of VLSI Module Placement
 * Copyright:    Copyright (c) 2001
 * Company:      Lafayette College
 * @author John Nestor
 * @version 1.0
 */

public class PAnnealer {
  private Vector tempHistory = new Vector(500);
  private UIAnnealer myUI;
  private PLayout myLayout;
  private double currentTemperature;

  public PAnnealer(PLayout pl) { myLayout = pl; }

  void setUIAnnealer(UIAnnealer u) { myUI = u; }

  /** @return number of temperatures on this annealing run */
  public int numTemperatures() { return tempHistory.size(); }

  public void addTempHistory(PTempHistory p) {
    tempHistory.addElement(p);
  }

  public PTempHistory getTempHistory(int i) {
    return (PTempHistory)tempHistory.elementAt(i);
  }

  public void applyMove() { myLayout.applyMove(); }

  private static final int SIGMA_MULTIPLIER = 20;

  private double getT() { return currentTemperature; }

  private void setT(double temp) {
    currentTemperature = temp;
    PMove.setT(temp);
  }

  public void setT0(double temp) { 
    currentTemperature = temp; 
    PMove.setT0(temp);
  }

  public void findT0(boolean animate) throws InterruptedException {
    setT0(Double.POSITIVE_INFINITY);
    PTempHistory thisTemp = new PTempHistory(currentTemperature);
    metropolis(thisTemp, animate);
    double sigma = thisTemp.getStdev();
    setT0(sigma * SIGMA_MULTIPLIER);
    System.out.println("PAnnealer.findT0: sigma=" + sigma + " T0=" + currentTemperature);
  }

  /** check whether cost has improved more than given percentage during last N */
  public boolean terminate(int percentImp, int lastN) {
    int numTemps = numTemperatures();
    if (numTemps < lastN) return false;
    else {
      double lastCost = getTempHistory(numTemps-1).getMinCost();
      for (int i = numTemps-2; i >= numTemps - lastN; i--) {
        double prevCost = getTempHistory(i).getMinCost();
        if ( (lastCost < prevCost) && (
             ((int)((prevCost - lastCost)/prevCost * 100)) > percentImp) )
               return false;
        lastCost = prevCost;
      }
    }
    return true;
  }

  /** alternate form - returns true when no CHANGE (plus OR minus) for N temps */
  public boolean terminate(int lastN) {
    int numTemps = numTemperatures();
    if (numTemps < lastN) return false;
    else {
      double lastCost = getTempHistory(numTemps-1).getMinCost();
      for (int i = numTemps-2; i >= numTemps - lastN; i--) {
        double prevCost = getTempHistory(i).getMinCost();
        if (lastCost != prevCost)  return false;
      }
    }
    return true;
  }

  public boolean accept(double deltaCost) {
    if (deltaCost <= 0) return true;
    else if (getT() == Double.POSITIVE_INFINITY) return true;
    else if (Math.random() < Math.exp( (double)(-deltaCost) / getT())) return true;
    else return false;
  }

  public void metropolis(PTempHistory thisTemp, boolean animate) throws InterruptedException {
    PMove.setWindow(myLayout.currentWidth(),myLayout.currentHeight());
    int attemptsPerTemp = myUI.getOptMovesPerTemp();
    if (myUI.getOptMovesPerTempPerModule()) attemptsPerTemp *= myLayout.numModules();
    System.out.println("attemptsPerTemp = " + attemptsPerTemp);
    for (int i = 0; i < attemptsPerTemp; i++) {
      int dc = myLayout.applyMove();
      myUI.setAnnealDisplay(thisTemp);
      if (animate) myUI.animateStep();
      if (accept(dc)) thisTemp.addSample(myLayout.currentCost(),true);
      else {
        thisTemp.addSample(myLayout.currentCost(),false);
        myLayout.undoMove();
	myUI.setAnnealDisplay(thisTemp);
        if (animate) myUI.animateStep();
      }
    }
  }

  /** run the anneal.  DOES NOT alter initial temperature! */
  public void anneal(boolean animate) throws InterruptedException {
    do {
      PTempHistory thisTemp = new PTempHistory(getT());
      addTempHistory(thisTemp);
      metropolis(thisTemp,animate);
      myUI.animateStep();
      setT( getT() * myUI.getOptCoolRate() );
    } while (!terminate(3));
  }
}
