package simulatedAnnealing;

import java.util.*;
import popcorn.*;
import java.io.Serializable;

/**
This class is responsible of solving the tsp using simulated annealing*/
public class SimulatedAnnealingComputation extends CompoundComputation {
  private final static double TFACTOR = 0.9;

  private static int LENGTH;
  private static int WIDTH;
  private static int PACKETS_FOR_TEMPRATURE;
  private static int TEMPRATURE_FREQUENCY_CHANGE ;
  private static int DEFAULT_LENGTH = 150;
  private static int DEFAULT_WIDTH = 10;
  private static int DEFAULT_PACKETS_NUMBER = 120000;

  private Path bestPath;
  private double min;
  private double temprature = 0.5;
  private Path currentPath;
  private int packetsNumber = 0,packetsArrived = 0,packetsSent = 0;
  private ComputationObserver observer;
  private int totalPacketsNumber;
  private City cities[];

 /** constructs a computation and initializes it
  @param cities a list of cities
  @param observer the observer
  @param length the number of sequential operation that are made by a single packet
  @param width the number of 'processors'
  @param totalPacketsNumber the total amout of work made by all the packets*/
  public SimulatedAnnealingComputation(City cities[],ComputationObserver observer,
                                       int length,int width,int totalPacketsNumber) {
    this.cities = cities;
    this.observer=observer;
    PACKETS_FOR_TEMPRATURE = cities.length * 100;
    LENGTH = length;
    WIDTH = width;
    this.totalPacketsNumber = totalPacketsNumber;
    this.currentPath = TSP.randomPath(cities);
    if (observer!=null) {
      observer.updateDisplay(currentPath.order);
      observer.updateStatus(currentPath.length,packetsSent,packetsArrived);
    }
    bestPath = new Path(cities.length);
    bestPath.copy(currentPath);
  }

 /** constructs a computation and initializes it
  @param cities a list of cities
  @param observer the observer
  @param width the number of 'processors'
  @param totalPacketsNumber the total amout of work made by all the packets*/
  public SimulatedAnnealingComputation(City cities[],ComputationObserver observer,int width,int totalPacketsNumber) {
    this(cities,observer,DEFAULT_LENGTH,width,totalPacketsNumber);
  }

/** constructs a computation and initializes it
  @param cities a list of cities
  @param observer the observer
  @param width the number of 'processors'*/
   public SimulatedAnnealingComputation(City cities[],ComputationObserver observer,int width) {
    this(cities,observer,DEFAULT_LENGTH,width,DEFAULT_PACKETS_NUMBER);
  }

/** constructs a computation and initializes it
  @param cities a list of cities
  @param observer the observer*/
  public SimulatedAnnealingComputation(City cities[],ComputationObserver observer) {
    this(cities,observer,DEFAULT_LENGTH,DEFAULT_WIDTH,DEFAULT_PACKETS_NUMBER);
  }

/** This method is called after the go() method is activated to initiate the computation.
    It launches 'WIDTH' ComputationPackets in order to accomplist the computation */
  public void start() {
    min=Double.POSITIVE_INFINITY;
    Path path;
      for (int j=0;j<WIDTH;j++) {
        path = new Path(cities.length);
        path.copy(currentPath);
        new SimulatedAnnealingPacket(cities,path,temprature,LENGTH,this).go();
    }
  }

/** this method re-activate the computation if there is more work to do.
    at the beginning of each temprature it dispalys the result it already has */
  public void completed() {
    packetsNumber += WIDTH*LENGTH;
    packetsSent += WIDTH;
    currentPath.copy(bestPath);
    if (packetsNumber >= totalPacketsNumber) {
      if (observer!=null) {
        observer.updateDisplay(currentPath.order);
        observer.updateStatus(currentPath.length,packetsSent,packetsArrived);
      }
      done();
      return;
    }
    if ((packetsNumber % PACKETS_FOR_TEMPRATURE) == 0)
      temprature *= TFACTOR;
    if (observer!=null) {
      observer.updateDisplay(currentPath.order);
      observer.updateStatus(currentPath.length,packetsSent,packetsArrived);
    }
    start();
  }

/** returns information to user of computaion*/
  public Res result() {
    return (new Res(bestPath,packetsSent,packetsArrived));
  }

  synchronized void update(Path path) {
    packetsArrived++;
    if (path.length<min) {
      double realPathLength = TSP.calcPathLength(cities,path.order);
      if (java.lang.Math.abs(path.length-realPathLength) < 0.001) {
        min = path.length;
        bestPath.copy(path);
      }
    }
  }
}

class SimulatedAnnealingPacket extends ComputationPacket {
  private SimulatedAnnealingComputation resultHandler;

  public SimulatedAnnealingPacket(City cities[],Path currentPath,double temprature,
                                  int iteration_number,
				                  SimulatedAnnealingComputation resultHandler) {
   super(new SimulatedAnnealingComputelet(cities,currentPath,temprature,
                                          iteration_number),resultHandler);
   this.resultHandler = resultHandler;
 }

  public void completed() {
    resultHandler.update((Path)getResult());
    done();
  }

  public void failed() {
  }
}


class SimulatedAnnealingComputelet implements Computelet,Serializable {
  private City cities[];
  private double temprature;
  private Path currentPath;
  private int maxPathsNum;

  public SimulatedAnnealingComputelet(City cities[],Path currentPath,double temprature,
                                      int iteration_number){
    this.cities = cities;
    this.temprature = temprature;
    this.currentPath = currentPath;
    this.maxPathsNum = iteration_number;
  }

  public Object compute() {
    Random random = new Random(111);
    int citiesNotInSegmentNum;
    SegmentInfo segment = new SegmentInfo();
    for (int j=0; j<maxPathsNum; j++) {
      do {
    	segment.start = (int)((cities.length - 1) * random.nextDouble());
	    segment.end = (int)((cities.length - 1) *  random.nextDouble());
	    if (segment.start <= segment.end)
	      segment.end++;
	    citiesNotInSegmentNum = (cities.length-segment.end+segment.start-1) %
	                            cities.length;
      } while (citiesNotInSegmentNum < 3);
      int operation = Auxilary.randomBit();
      if (operation == TSP.TRANSPORT)
	    segment.newPlace = ((int)((citiesNotInSegmentNum - 2)*random.nextDouble()
		            	   )+segment.end+1) % cities.length;
      double cost = TSP.calcCost(operation,cities,currentPath.order,segment);
      boolean accept = TSP.metropolis(cost,temprature);
      if (accept) {
	    currentPath.length += cost;
	    TSP.operate(operation,currentPath.order,cities.length,segment);
      }
    }
    Path path = new Path(cities.length);
    path.copy(currentPath);
    return (path);
  }
}








