// (c) 2000 Benjamin Fry, MIT Media Laboratory, fry@media.mit.edu
// Aesthetics + Computation Group, Massachussetts Institute of Technology

import java.io.*;


public class ClusterMethodCAST extends ClusterMethod {
  // this parameter can be set by the user
  float t = 0.5f;

  int clusterCount;
  Victor clusters[];

  Victor openCluster;
  Victor unassigned;

  float amax, amin;
  int amaxIndex, aminIndex;
  int amaxItem, aminItem;


  public ClusterMethodCAST(SimilarityMatrix matrix, OutputEnvironment output, 
			   ArrayOutputEnvironment aoutput, float t) {
    super(matrix, output, aoutput);
    this.t = t;
  }


  public void run() {
    clusters = new Victor[gcount];

    unassigned = new Victor();
    for (int i = 0; i < gcount; i++) {
      unassigned.add(i);
    }

    while (unassigned.length != 0) {
      // start a new cluster
      openCluster = new Victor();

      // reset affinity
      for (int i = 0; i < unassigned.length; i++) {
	unassigned.affinity[i] = 0;
      }

      boolean changesOccur;
      do {
	changesOccur = false;

	// ADD
	while (calcMaxAffinity() &&
	       (amax >= t * openCluster.length)) {
	  changesOccur = true;
	  unassigned.move(amaxIndex, openCluster);
	    
	  for (int i = 0; i < unassigned.length; i++) {
	    unassigned.affinity[i] += 
	      matrix.get(unassigned.item[i], amaxItem);
	  }
	  for (int i = 0; i < openCluster.length; i++) {
	    openCluster.affinity[i] += 
	      matrix.get(openCluster.item[i], amaxItem);
	  }
	  update();
	  try {
	    Thread.sleep(5);
	  } catch (InterruptedException e) { }
	  calcMaxAffinity();
	}
	

	// REMOVE
	while (calcMinAffinity() && 
	       (amin < t * openCluster.length)) {
	  changesOccur = true;
	  openCluster.move(aminIndex, unassigned);
	  
	  for (int i = 0; i < unassigned.length; i++) {
	    unassigned.affinity[i] -= 
	      matrix.get(unassigned.item[i], aminItem);
	  }
	  for (int i = 0; i < openCluster.length; i++) {
	    openCluster.affinity[i] -= 
	      matrix.get(openCluster.item[i], aminItem);
	  }
	  update();
	  try {
	    Thread.sleep(5);
	  } catch (InterruptedException e) { }
	  calcMinAffinity();
	}
      } while (changesOccur);

      clusters[clusterCount++] = openCluster;
      openCluster = null;
    }

    System.out.println(clusterCount + " clusters found");

    // output a bunch of info 
    //outputDiagnostics();

    // force an update here
    output.updateCalls = -1;
    update();
  }


  protected boolean calcMaxAffinity() {
    if (unassigned.length == 0) return false;

    amaxIndex = 0;
    amax = unassigned.affinity[0];
    for (int i = 0; i < unassigned.length; i++) {
      if (unassigned.affinity[i] > amax) {
	amax = unassigned.affinity[i];
	amaxIndex = i;
      }
    }
    amaxItem = unassigned.item[amaxIndex];
    return true;
  }

  protected boolean calcMinAffinity() {
    if (openCluster.length == 0) return false;

    aminIndex = 0;
    amin = openCluster.affinity[0];
    for (int i = 1; i < openCluster.length; i++) {
      if (openCluster.affinity[i] < amin) {
	amin = openCluster.affinity[i];
	aminIndex = i;
      }
    }
    aminItem = openCluster.item[aminIndex];
    return true;
  }


  public void update() {
    int index = 0;

    for (int i = 0; i < clusterCount; i++) {
      Victor c = clusters[i];
      for (int j = 0; j < c.length; j++) {
	permute[index++] = c.item[j];
      }
    }
    if (openCluster != null) {
      for (int j = 0; j < openCluster.length; j++) {
	permute[index++] = openCluster.item[j];
      }
    }
    for (int j = 0; j < unassigned.length; j++) {
      permute[index++] = unassigned.item[j];
    }
    output.update();
  }


  int movieFrame = 0;
  protected void writeMovieFrame() {
    try {
      int wide = output.extent + aoutput.eextent;
      int high = output.extent;
      
      OutputStream os = new BufferedOutputStream(new FileOutputStream("frames/frame-" + movieFrameNumber(movieFrame++) + ".tif"));
      Utilities.writeTiffHeader(os, wide, high);
      
      int index1 = 0; 
      int index2 = 0;
      for (int j = 0; j < output.extent; j++) {
	for (int i = 0; i < output.extent; i++) {
	  int point = output.pixels[index1++];
	  os.write((point >> 16) & 0xff);
	  os.write((point >> 8) & 0xff);
	  os.write(point & 0xff);
	}
	for (int i = 0; i < aoutput.eextent; i++) {
	  int point = aoutput.pixels[index2++];
	  os.write((point >> 16) & 0xff);
	  os.write((point >> 8) & 0xff);
	  os.write(point & 0xff);
	}
      }
      os.flush();
      os.close();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  // ha ha dumb code! 
  // right way == 5 minutes, wrong way == 15 seconds
  // it's 5 am!
  protected String movieFrameNumber(int m) {
    if (m >= 1000) return String.valueOf(m);
    if (m >= 100) return "0" + m;
    if (m >= 10) return "00" + m;
    return "000" + m;
  }


  public void outputDiagnostics() {
    System.out.println("writing final data image and text...");

    try {
      PrintStream out = new PrintStream(new BufferedOutputStream(new FileOutputStream("output.xls")));
      for (int i = 0; i < clusterCount; i++) {
	Victor c = clusters[i];
	for (int j = 0; j < c.length; j++) {
	  int pj = c.item[j];
	  out.print(matrix.array.id[pj]);
	  if (matrix.array.name != null) {
	    out.println("\t" + matrix.array.name[pj]);
	  } else {
	    out.println();
	  }
	}
	out.println();
	out.println();
      }
      out.flush();
    } catch (IOException e) {
      e.printStackTrace();
    }

    try {
      OutputStream os = new FileOutputStream("all.tif");

      int ecount = output.array.ecount;
      int percent = 0;

      Utilities.writeTiffHeader(os, ecount, gcount);
      float maxColor = aoutput.maxColor;
      float minColor = aoutput.minColor;

      for (int j = 0; j < gcount; j++) {
	int pj = permute[j];
	for (int i = 0; i < ecount; i++) {
	  float value = output.array.data[pj][i];
	  int point = (value == value) ? aoutput.color(value) : 0;
	  aoutput.minColor = minColor;
	  aoutput.maxColor = maxColor;
	  os.write((point >> 16) & 0xff);
	  os.write((point >> 8) & 0xff);
	  os.write(point & 0xff);
	}
	//percent = reportPercent(j, gcount, percent);
      }
      os.flush();
      os.close();

      
      int square = 4;

      int largeIndex = 0;
      int largeCount = clusters[0].length;
      int largestIndex = 0;
      int largestCount = clusters[0].length;

      for (int i = 1; i < clusterCount; i++) {
	if (clusters[i].length > largestCount) {
	  largeIndex = largestIndex;
	  largeCount = largestCount;
	  largestIndex = i;
	  largestCount = clusters[i].length;
	}
      }

      // the illustrator stuff is a personal library 
      // that isn't being released at this time
      //FileOutputStream fos = new FileOutputStream("largest.ai");
      //Illustrator ai = new Illustrator(fos, 1000, square*largestCount);
      os = new FileOutputStream("largest.tif");

      //ai.setFont("Courier", 3);

      Utilities.writeTiffHeader(os, ecount, largestCount);

      Victor c = clusters[largestIndex];
      for (int j = 0; j < c.length; j++) {
	int pj = permute[c.item[j]];
	for (int i = 0; i < ecount; i++) {
	  float value = output.array.data[pj][i];
	  int point = (value == value) ? aoutput.color(value) : 0;
	  aoutput.minColor = minColor;
	  aoutput.maxColor = maxColor;
	  os.write((point >> 16) & 0xff);
	  os.write((point >> 8) & 0xff);
	  os.write(point & 0xff);
	}
	//ai.drawString(matrix.array.id[pj], 
	//      (ecount+1)*square, j*square - 2);

	//ai.drawString(matrix.array.name[pj], 
	//      70 + (ecount+1)*square, j*square - 2);
      }
      os.flush();
      os.close();
      //ai.end();
      
      System.out.println("...done writing");

    } catch (IOException e) {
      e.printStackTrace();
    }    
  }
}


