content: /* The contents of this file are subject to the terms
   * of the Common Development and Distribution License
   * (the License). You may not use this file except in
   * compliance with the License.
   *
   * You can obtain a copy of the License at
   * http://www.sun.com/cddl/cddl.html or
   * install_dir/legal/LICENSE
   * See the License for the specific language governing
   * permission and limitations under the License.
   *
   * When distributing Covered Code, include this CDDL
   * Header Notice in each file and include the License file
   * at install_dir/legal/LICENSE.
   * If applicable, add the following below the CDDL Header,
   * with the fields enclosed by brackets [] replaced by
   * your own identifying information:
   * "Portions Copyrighted [year] [name of copyright owner]"
   *
   * $Id$
   *
   * Copyright 2009 Sun Microsystems Inc. All Rights Reserved
   */
  package com.sun.faban.driver.util.timermeter;
  
  import java.io.Serializable;
  import java.util.*;
  
  /**
   * A cluster which consists of key-value pairs that "aggregate" same-valued cluster elements.
   * That is, instead of containing two items with the value 10, this cluster
   * contains one entry with key 10 and value 2. Elements cannot be removed from the cluster.
   * TODO optimise through "lazy computation" of median value and slot size
   * @author Michael Kuperberg
   * TODO test me
   * TODO integrate with WEKA and similar?
   */
  public class MeasurementGroupsCluster implements Serializable {
  	/**
  	 *
  	 */
  	private static final long serialVersionUID = 1L;
  
  	/**
  	 * the cluster elements (internal representation)
  	 */
  	private SortedMap<Long,Integer> clusterElements;
  
  	private long clusterGroupSizeMaximum;
  
  	private double clusterGroupSizeMean;
  
  //	private long clusterGroupSizeMinimum;
  
  	private long clusterGroupSizeSum;
  
  	private List<Integer> counts;
  
  	private long totalClusterValuesSum;
  
  	public MeasurementGroupsCluster() {
  		super();
  		this.clusterElements = new TreeMap<Long, Integer>();
  		this.clusterGroupSizeMaximum = Long.MIN_VALUE;
  		this.clusterGroupSizeMean = 0D;
  //		this.clusterGroupSizeMinimum = Long.MAX_VALUE;
  		this.clusterGroupSizeSum = 0L;
  		this.counts = new ArrayList<Integer>();
  		this.totalClusterValuesSum = 0L;
  	}
  
  	public MeasurementGroupsCluster(Long key, Integer value){
  		this();
  		this.addData(key, value);
  	}
  
  	public MeasurementGroupsCluster(SortedMap<Long,Integer> clusterElements){
  			this();
  			this.addData(clusterElements);
  //		this.clusterElements = clusterElements;
  //		Long currKey;
  //		Integer currCount;
  //		for (Iterator<Long> iterator = this.clusterElements.keySet().iterator(); iterator.hasNext();) {
  //			currKey = iterator.next();
  //			currCount = this.clusterElements.get(currKey);
  //			this.totalClusterElementsCount += currCount;
  //			this.totalClusterValuesSum += currCount*currKey;
  //		}
  	}
  
  	/**
  	 * TODO check for negatives?
  	 * @param key
  	 * @param value
  	 * @return
  	 */
  	public synchronized double addData(Long key, Integer value){
  		this.totalClusterValuesSum += value*key;
  		this.clusterGroupSizeSum += value;
  		Integer entry = this.clusterElements.get(key);
  		Integer valuePutIntoClusterElements = 0;
  //		boolean valueExisted = false;
  		if(entry!=null){
  			valuePutIntoClusterElements = entry+value;
  //			valueExisted = true;
  			this.counts.remove(entry);//because the entry with this key existed before!
  			this.counts.add(valuePutIntoClusterElements);
  		}else{
  			valuePutIntoClusterElements = value;
  //			valueExisted = false;
  			this.counts.add(valuePutIntoClusterElements);
  		}
  		this.clusterElements.put(key, valuePutIntoClusterElements); //if the key already exists, the data gets overwritten as intended here
  		this.clusterGroupSizeMean = (double) this.clusterGroupSizeSum/(double) this.clusterElements.size();
  
  		if(valuePutIntoClusterElements>this.clusterGroupSizeMaximum){
  			this.clusterGroupSizeMaximum=valuePutIntoClusterElements;
  		}
  //		the following computation does not work because it remembers minimum sizes that later might be increase by adding to a cluster...
  //		if(valuePutIntoClusterElements<this.clusterGroupSizeMinimum){
  //			this.clusterGroupSizeMinimum=valuePutIntoClusterElements;
  //		}
  		return this.getClusterValueMean();
  	}
  
  	public synchronized double addData(Map<Long, Integer> keyValueMap){
  		Long currKey;
  		Integer currValue;
  		for (Iterator<Long> iterator = keyValueMap.keySet().iterator(); iterator.hasNext(); ) {
  			currKey = iterator.next();
  			currValue = keyValueMap.get(currKey);
  			this.addData(currKey, currValue);
  		}
  		return this.getClusterValueMean();
  	}
  
  	public int getClassesNumberInCluster(){
  		if(this.clusterElements==null || this.clusterElements.isEmpty()){
  			return 0;
  		}else{
  			return this.clusterElements.size();//TODO check that there are no zero-counts inside...
  		}
  	}
  
  	public synchronized long getClusterGroupSizeMaximum(){
  		return this.clusterGroupSizeMaximum;
  	}
  
  	public synchronized double getClusterGroupSizeMean(){
  		return this.clusterGroupSizeMean;
  	}
  
  	public synchronized long getClusterGroupSizeMedian(){
  		Collections.sort(this.counts);
  		return this.counts.get(counts.size()/2);
  	}
  
  	public synchronized long getClusterGroupSizeMinimum(){
  		Collections.sort(this.counts);
  		return this.counts.get(0);
  	}
  
  	public synchronized long getClusterGroupSizeSum(){
  		return this.clusterGroupSizeSum;
  	}
  
  	/**
  	 * //TODO test me
  	 * @return
  	 */
  	public synchronized long getClusterValueMaximum(){
  		Long[] asArray = (Long[]) clusterElements.keySet().toArray(new Long[0]);
  		return asArray[clusterElements.size()-1];
  	}
  
  	public synchronized double getClusterValueMean(){
  		return ((double) this.totalClusterValuesSum/ (double) this.clusterGroupSizeSum);
  	}
  
  	/**
  	 * TODO more effective median *re*computation? --> think about it!
  	 * TODO test me
  	 * @return
  	 */
  	public synchronized double getClusterValueMedian(){
  		if(this.clusterGroupSizeSum==0){
  			return 0D;//new double[]{0D,0D,0D};
  		}
  //		totalClusterValuesSum=0;
  		int currentClusterElementsNumber = 0;
  		double clusterMedian=-1D;
  		Long currKey;
  		Integer currValue;
  		boolean medianNotFound = true;
  
  		for (Iterator<Long> iterator = clusterElements.keySet().iterator(); iterator.hasNext();) {
  			currKey = iterator.next();
  			currValue = clusterElements.get(currKey);
  //			totalClusterValuesSum += currKey*currValue;
  			currentClusterElementsNumber+=currValue;
  			if(medianNotFound && currentClusterElementsNumber>=((clusterGroupSizeSum/2)-1)){
  				clusterMedian = currKey;
  				medianNotFound = false;
  			}
  		}
  		return /*new double[]{
  				totalClusterValuesSum/clusterGroupSizeSum,
  				*/clusterMedian/*,
  				totalClusterValuesSum
  		}*/;
  	}
  
  	/**
  	 * TODO test me
  	 * @return
  	 */
  	public synchronized long getClusterValueMinimum(){
  		return clusterElements.keySet().iterator().next(); //TODO reengineer for performance
  	}
  
  	public synchronized long getClusterValueSum(){
  		return this.totalClusterValuesSum;
  	}
  
  	public String toString(){
  		return this.clusterElements.toString();
  	}
  }
  