package DDMT.Nodes;

/*
 * DataSite.java
 *
 * Created on 15:52, August 2st, 2004
 */

/** Data site class.
 * @author Kun Liu
 * @version 1.1
 */

import java.lang.*;
import java.io.*;
import java.math.*;
import java.util.*;

public class DataSite
{

  private ArrayList data; // an array list of DataPoint
  private DataPoint [] centroidsV; // an array of initial centriods for every round of kmeans
  private DataPoint [] centroidsWNew; // an array of updated centroids
  private DataPoint [] centroidsWOld; // an array of old centroids from last round of kmeans
  public int [] numPointsPerCluster; // number of data points in each cluster
  private boolean sendMsg; // whether this site needs to send msg out
  private boolean receiveMsg; // whether this site will receive msg from other sites
  private int iteration; // local k-means iteration
  private int k; // k clusters
  private double gamma; // threshold
  private int numMsgReceived; // total number of messages received from all the neighbors

    /** Constructor**/
    public DataSite(int k, double gamma)
    {
        this.data = new ArrayList();
        this.centroidsV = new DataPoint[k];
        this.centroidsWNew = new DataPoint[k];
        this.centroidsWOld = new DataPoint[k];
        this.numPointsPerCluster = new int[k];
        this.sendMsg = true;
        this.receiveMsg = true;
        this.iteration = 0;
        this.k = k;
        this.gamma = gamma;
        this.numMsgReceived = 0;
    }

    /** Constructor**/
    public DataSite(int k, double gamma, ArrayList data)
    {
        this(k, gamma);
        this.data = new ArrayList(data);
    }

    public DataSite(int k, double gamma, String fileName)
    {    
        this(k,gamma);
        ArrayList da = ReadFile.read(fileName);
        this.data = new ArrayList(da);
        System.out.println("File Read Successfully "+da.size());
    }
   
    /** set up the initial centriods**/
    public void setCentroidsV(DataPoint [] initial)
    {
        for (int i=0;i<initial.length;i++)
        {
            centroidsV[i] = new DataPoint(initial[i]);
        }
    }
    
    /** get the array of updated centroids**/
    public Vector getCentroids()
    {
        Vector centroids = new Vector();
        for (int i=0;i<k;i++)
        {
            Point p = new Point(this.centroidsWNew[i].DataArray,this.centroidsWNew[i].membership);
            centroids.add(p);
        }
        return centroids;
    }
  
  
    /** simple K-means**/
    public void simpleKmeans()
    {
        // check if data empty or less than K points
        if (this.data.isEmpty() )
        {
            System.out.println("data empty");
            System.exit(0);
        }
        if (this.data.size() < this.k)
        {
            System.out.println("data less K !");
            System.exit(0);
        }

      // If run this K-means method independently, uncomment this block
       // if run this method together with P2P algorithm, comment this block
       // preselect K centroids
//       int [] randomIndex = new int[this.k];
//       randomIndex = MyUtil.randomSequence(this.k, this.data.size());
//       for (int i=0; i< randomIndex.length; i++){
//         this.centroidsV[i] = new DataPoint((DataPoint)this.data.get(randomIndex[i]));
//         ////////DEBUG BEGIN
//         //System.out.println("random: "+randomIndex[i]);
//         System.out.println("initial centroids:");
//         this.centroidsV[i].print();
//         ////////DEBUG END
//       }
       

             // clear iterations
       this.iteration = 0;

       boolean termination = false;

       while (!termination)
       {

         // clear the number of points per cluster (for new round k-means)
         for (int j=0; j < this.k ; j++){
                  this.numPointsPerCluster[j]=0;
         }

         this.centroidsWNew = new DataPoint[k];

         this.iteration++;

         ///////DEBUG BEGIN
//         System.out.println("Simple KMeans Iterations: " + this.iteration);
         ///////DEBUG END

         // assign the local points to the K centroids
         DataPoint dp;
         int index=-1;
         double tmpDist=java.lang.Double.MAX_VALUE;
         double tmp;
         for (int i=0;i<this.data.size();i++){
           dp = (DataPoint)this.data.get(i);
           for (int j=0;j<this.k;j++){
             tmp = DataPoint.euclideanDist(dp,this.centroidsV[j]);
             if (tmpDist>=tmp){
               tmpDist = tmp;
               index = j;
             }
           }
           dp.setMembership(index);
           this.numPointsPerCluster[index]++;
           // sum of the points in the same cluster
           if (this.centroidsWNew[index]==null){

             this.centroidsWNew[index] = DataPoint.sum(dp,0);
           }
           else{
             this.centroidsWNew[index] = DataPoint.sum(dp,this.centroidsWNew[index]);
           }
           tmpDist=java.lang.Double.MAX_VALUE;
         }

         // update centroids WNew
         for (int j=0;j<this.k;j++){

           if (this.numPointsPerCluster[j]!=0){
             this.centroidsWNew[j] = DataPoint.divideScalar(this.centroidsWNew[j],this.numPointsPerCluster[j]);
           }
           else{ // if no points are assigned to the centroid, the updated centroids remains the same as the initial centroid
             this.centroidsWNew[j] = new DataPoint(this.centroidsV[j]);
           }
         }

         // check threshold of \sum(WNew-WOld)^2, and set up sendMsg
         if (this.iteration == 1)
           ;
         else{
           double centroidsChange=0;
           DataPoint tmpdp;
           for(int j=0; j < this.k; j++){
             tmpdp = DataPoint.minus(this.centroidsWNew[j], this.centroidsWOld[j]);
             centroidsChange += DataPoint.normSquare(tmpdp);
           }
           if (centroidsChange > this.gamma)
             ;
           else
               termination = true;

         }

        // set centoirdsWOld = centroidsWNew. and set centroidsV = centroidsWNew
        for (int j=0; j< this.k ; j++){
           this.centroidsWOld[j] = new DataPoint (this.centroidsWNew[j]);
           this.centroidsV[j] = new DataPoint (this.centroidsWNew[j]);

        }

       }

    }

    /** data check**/
    private boolean dataCheck(){
      // check if data is empty or initial centroids are set
      if (this.data.isEmpty() || this.centroidsV.length == 0){
        System.out.println("data empty or initial centroids not set up!");
        return false;
      }

      for (int i=0;i<this.centroidsV.length;i++){
        if (this.centroidsV[i] == null){
          System.out.println("initial centroids not set up!");
          return false;
        }
      }

      if (this.data.size() < this.k){
        System.out.println("data points less than K !");
        return false;
      }

      return true;
    }
    
    public DataPoint getDataPoint(int index){
      if (index >= getDataSize()){
        System.out.println("getDataPoint(): index out of bound !");
        return null;
      }
      return (DataPoint)this.data.get(index);
    }
    
    public int getDataSize()
    {
      return this.data.size();
    }
    
    public Vector getDataPoint()
    {
        Vector Points = new Vector();
        for (int i=0;i<this.data.size();i++)
        {
            DataPoint dp = (DataPoint)this.data.get(i);
            Point p = new Point (dp.DataArray,dp.getMembership());
            Points.add(p); 
        }
        return Points;
    }
    
    /** print data in this site**/
    public void printData()
    {
       DataPoint myDataPoint;
       System.out.println("Total number of data points: " + this.data.size());
       for (int i=0; i<this.data.size();i++)
       {
          myDataPoint = (DataPoint)this.data.get(i);
          myDataPoint.print();
       }
    }

    /** print clusters -- print data in each cluster separately**/
    public void printClusters()
    {
        DataPoint dp;
        
        ArrayList [] clusters = new ArrayList[this.k];
        for(int i= 0; i<this.k; i++)
        {
            clusters[i] = new ArrayList();
        }

        for (int i=0; i<this.data.size(); i++)
        {
           dp = (DataPoint)this.data.get(i);
           for (int j=0; j<this.k; j++)
           {
                if (dp.getMembership()==j)
                {
                    clusters[j].add(dp);
                    break;
                }
            }
        }
        for (int i=0;i<this.k;i++)
        {
            System.out.println("Cluster "+i);
            System.out.println("Centroids: ");
            this.centroidsWNew[i].print();
            System.out.println("# points: "+this.numPointsPerCluster[i]);
             //Print points in each cluster
//            for(int j=0; j< clusters[i].size(); j++)
//            {
//                dp=(DataPoint)clusters[i].get(j);
//            //    dp.print();
//            //    this.AppendFile("c:\\Cluster"+i+".txt", String.valueOf(dp.DataArray[0])+" "+String.valueOf(dp.DataArray[1])+" "+String.valueOf(dp.membership)+"\n");
//            }
         
       }
   }
    public  void AppendFile(String filename,String fileData) {
        try 
        {
            BufferedWriter out = new BufferedWriter(new FileWriter(filename,true));
            
            out.write(fileData);
            out.close();
        }
        catch (IOException e) {
            System.out.println("Writing exception");
        }
    }
 }

