package jet.cluster;

import java.util.*;

/** Classe qui permet de realiser le clustering 3D des residus.*/

public class Clustering
{
	/** stocke les distances entre les résidus */
    private jet.cluster.data.DistanceMap dm;
    /** Vecteur de clusters */
    private Vector clusterList;
    /** distance maximale permettant de clusteriser deux résidus */
    private float radius;

    /** CONSTRUCTEURS */
    
    public Clustering(jet.data.datatype.Sequence3D sequence, float radius)
    {
	this.radius=radius;
	/* initialisation des distances entre les résidus */
	dm=new jet.cluster.data.DistanceMap(sequence,radius);
	clusterList=new Vector(1,1);
	
    }
    
    /** ACCESSEURS et MODIFICATEUR*/
    
    public Vector getClusterList(){return clusterList;}
    
    public void setClusterList(Vector clustList){clusterList=clustList;}
    
    public void clearClusterList(){clusterList.clear();}
    public int getClustersSize()
    {
    	int taille=0;
    	for (int i=0; i<clusterList.size(); i++)
    	{
    		for (int j=0; j<((jet.cluster.data.Cluster)clusterList.get(i)).getCoreIndex().size(); j++)
        	{
        		taille++;
        	}
    	}
    	return taille;
    }
    
    /** METHODES */
    
    /** Clusterisation des residus dont les indices sont dans index.
     *  Les indices des residus (index) doivent etre triés en fonction du score des residus (score).
     * Si traceMinCluster=true chaque cluster est étendu si son score moyen est > à seuil. Dans ce cas  
     * Sinon les residus sont clusterisés jusqu'à un taux de couverture de la surface egal à seuil. 
     * Retourne true si les clusters ont evolué false sinon. */
    
    public boolean cluster(Vector index, Vector score, Vector pc, Vector trace, double seuil, int surfSize, boolean traceMinCluster)
    {
    	boolean evolution=false;
    	boolean evolutionTemp=false;
		Iterator iter=index.iterator();
		int i;
		if (traceMinCluster)
		{
			/* clusterisation avec un score minimal pour chaque cluster */
			while(iter.hasNext())
			{
				i=((Integer)iter.next()).intValue();
				/* clusterisation du residu i */
				evolution=cluster(dm.getProxList(i),score,pc,trace,seuil)||evolution;
			}
		}
		else
		{
			/* clusterisation jusqu'à un taux de couverture de la surface egal à seuil */
			int nb=0;
			while((iter.hasNext())&&(((double)nb/(double)surfSize)<seuil))
			{
				i=((Integer)iter.next()).intValue();
				evolutionTemp=cluster(dm.getProxList(i),score,pc,trace);
				evolution=evolutionTemp||evolution;
				nb++;				
			}
			
		}
		return evolution;
    }

    /** Clusterisation basique des residus dont les indices sont dans index. 
     * Cette clusterisation ne tient compte seulement de la distance entre les residus.*/
    
    public void cluster(Vector index)
    {
    	int i=0,nb;
    	clearClusterList();
    	while(i<index.size())
    	{
    		nb=((Integer)index.get(i)).intValue();
    		cluster(dm.getProxList(nb));
    		i++;
    	}
    }
    
    /** Clusterisation du residu contenu dans pl (contient residu et distances aux autres residus) 
     * en fonction de son score et de sa distance aux residus deja clusterisés */
    
    private boolean cluster(jet.cluster.data.ProxList pl, Vector score, Vector pc, Vector trace, double minTraceCluster)
    {
	boolean evolution=false;
	Vector selectedCluster=new Vector(1,1);
	double scoreMoy=0.0;
	double traceMoy=0.0;
	double pcMoy=0.0;
	Vector core=new Vector(1,1);
	jet.cluster.data.Cluster cluster;
	int i,j,position,nb;

	/* Sélection du ou des clusters dont la distance avec le residu est inferieur à radius */
	for(i=0;i<clusterList.size();i++) 
	    {
		cluster=(jet.cluster.data.Cluster)clusterList.get(i);
		if(cluster.isNeighbour(pl.getResidue()))
		    {
			selectedCluster.add(clusterList.remove(i--)); 
		    }
	    }
	
	if(selectedCluster.size()>0)
	{
		scoreMoy=0.0;
		traceMoy=0.0;
		pcMoy=0.0;
		position=0;
		nb=0;
		
		/* Calcul du differents score moyen des clusters selectionnés */
		for (i=0; i<selectedCluster.size();i++)
	    {
			core=((jet.cluster.data.Cluster)selectedCluster.get(i)).getCoreIndex();
			for (j=0;j<core.size();j++)
			{
				position=((Integer)core.get(j)).intValue();
				scoreMoy=scoreMoy+(Double)score.get(position);
				traceMoy=traceMoy+(Double)trace.get(position);
				pcMoy=pcMoy+(Double)pc.get(position);
				nb++;
			}
	    }
		/* Ajout aux scores du clusters des scores du residu */
		scoreMoy=scoreMoy+(Double)score.get(new Integer(pl.getId()));
		traceMoy=traceMoy+(Double)trace.get(new Integer(pl.getId()));
		pcMoy=pcMoy+(Double)pc.get(new Integer(pl.getId()));
		pcMoy=pcMoy/(nb+1);
		traceMoy=traceMoy/(nb);
		scoreMoy=scoreMoy/(nb);
		
		/* clusterisation du residu si le score moyen du cluster est > à minTraceCluster */
		if ((scoreMoy>=minTraceCluster))
		{
			/* Si plusieurs clusters sélectionnés il faut les merger */
			while(selectedCluster.size()>1)
		    {
				cluster=(jet.cluster.data.Cluster)selectedCluster.remove(0);
				cluster.merge((jet.cluster.data.Cluster)selectedCluster.remove(0));
				selectedCluster.add(cluster);
			
		    }
			/* Et ajouter le residu à ces clusters mergés */
			((jet.cluster.data.Cluster)selectedCluster.get(0)).addResidue(pl);
			((jet.cluster.data.Cluster)selectedCluster.get(0)).setScoreMoy(scoreMoy);
			((jet.cluster.data.Cluster)selectedCluster.get(0)).setTraceMoy(traceMoy);
			((jet.cluster.data.Cluster)selectedCluster.get(0)).setpcMoy(pcMoy);
			clusterList.add((jet.cluster.data.Cluster)selectedCluster.get(0));
			evolution=true;
	    }
		else
		{
			clusterList.addAll(selectedCluster);
		}
	}
	else
	{
		/* si pas de cluster selectionnés création d'un cluster contenant seulement le residu */
		if (((Double)score.get(new Integer(pl.getId()))>=minTraceCluster))
		{
			cluster=new jet.cluster.data.Cluster(radius);
			cluster.addResidue(pl);
			((jet.cluster.data.Cluster)cluster).setScoreMoy((Double)score.get(new Integer(pl.getId())));
			((jet.cluster.data.Cluster)cluster).setTraceMoy((Double)trace.get(new Integer(pl.getId())));
			((jet.cluster.data.Cluster)cluster).setpcMoy((Double)pc.get(new Integer(pl.getId())));
			clusterList.add(cluster);
			evolution=true;
		}
    }
	return evolution;
    }
    
    /** Clusterisation du residu contenu dans pl en fonction seulement de sa distance aux residus deja clusterisés */
    
    private boolean cluster(jet.cluster.data.ProxList pl, Vector score, Vector pc, Vector trace)
    {
	boolean evolution=false;
	Vector selectedCluster=new Vector(1,1);
	double scoreMoy=0.0;
	double traceMoy=0.0;
	double pcMoy=0.0;
	Vector core=new Vector(1,1);
	
	jet.cluster.data.Cluster cluster;
	int i,j,position,nb;

	for(i=0;i<clusterList.size();i++) 
	    {
		cluster=(jet.cluster.data.Cluster)clusterList.get(i);
		if(cluster.isNeighbour(pl.getResidue()))
		    {
			selectedCluster.add(clusterList.remove(i--)); 
		    }
	    }
	
	if(selectedCluster.size()>0)
	{
		scoreMoy=0.0;
		traceMoy=0.0;
		pcMoy=0.0;
		position=0;
		nb=0;
		for (i=0; i<selectedCluster.size();i++)
	    {
			core=((jet.cluster.data.Cluster)selectedCluster.get(i)).getCoreIndex();
			for (j=0;j<core.size();j++)
			{
				position=((Integer)core.get(j)).intValue();
				scoreMoy=scoreMoy+(Double)score.get(position);
				traceMoy=traceMoy+(Double)trace.get(position);
				pcMoy=pcMoy+(Double)pc.get(position);
				nb++;
			}
	    }
		scoreMoy=scoreMoy+(Double)score.get(new Integer(pl.getId()));
		traceMoy=traceMoy+(Double)trace.get(new Integer(pl.getId()));
		pcMoy=pcMoy+(Double)pc.get(new Integer(pl.getId()));
		pcMoy=pcMoy/(nb);
		traceMoy=traceMoy/(nb);
		scoreMoy=scoreMoy/(nb);
		
		while(selectedCluster.size()>1)
		{
			cluster=(jet.cluster.data.Cluster)selectedCluster.remove(0);
			cluster.merge((jet.cluster.data.Cluster)selectedCluster.remove(0));
			selectedCluster.add(cluster);
		}
		((jet.cluster.data.Cluster)selectedCluster.get(0)).addResidue(pl);
		((jet.cluster.data.Cluster)selectedCluster.get(0)).setScoreMoy(scoreMoy);
		((jet.cluster.data.Cluster)selectedCluster.get(0)).setTraceMoy(traceMoy);
		((jet.cluster.data.Cluster)selectedCluster.get(0)).setpcMoy(pcMoy);
		clusterList.add((jet.cluster.data.Cluster)selectedCluster.get(0));
		evolution=true;
	}
	else
	{
		cluster=new jet.cluster.data.Cluster(radius);
		cluster.addResidue(pl);
		((jet.cluster.data.Cluster)cluster).setScoreMoy((Double)score.get(new Integer(pl.getId())));
		((jet.cluster.data.Cluster)cluster).setTraceMoy((Double)trace.get(new Integer(pl.getId())));
		((jet.cluster.data.Cluster)cluster).setpcMoy((Double)pc.get(new Integer(pl.getId())));
		clusterList.add(cluster);
		evolution=true;
    }
	return evolution;
    }
    
    private void cluster(jet.cluster.data.ProxList pl)
    {
	
	Vector selectedCluster=new Vector(1,1);
	jet.cluster.data.Cluster cluster;
	int i;

	for(i=0;i<clusterList.size();i++) 
	{
		cluster=(jet.cluster.data.Cluster)clusterList.get(i);
		if(cluster.isNeighbour(pl.getResidue()))
		{
			selectedCluster.add(clusterList.remove(i--)); 
		}
	}
	if(selectedCluster.size()>0)
	{
		while(selectedCluster.size()>1)
		{
			cluster=(jet.cluster.data.Cluster)selectedCluster.remove(0);
			cluster.merge((jet.cluster.data.Cluster)selectedCluster.remove(0));
			selectedCluster.add(cluster);
		}
		((jet.cluster.data.Cluster)selectedCluster.get(0)).addResidue(pl);
		clusterList.add((jet.cluster.data.Cluster)selectedCluster.get(0));
	}
	else
	{
		cluster=new jet.cluster.data.Cluster(radius);
		cluster.addResidue(pl);
		clusterList.add(cluster);
    }
    }
    
    /** Calcul du score moyen des residus accessibles dont les scores sont dans le vecteur trace.
     * Le score moyen d'un residu est calculé à partir des scores des résidus voisins à deux degrés. 
     * Deux residus sont voisins si ils clusterisent. */
    
    public Vector calculScoreMoy(Vector trace, Vector axs)
    {
    	Vector traceMoy=new Vector();
    	double traceVoisins1;
    	double traceVoisins2;
    	int nbVoisins1;
    	int nbVoisins2;
    	boolean present;
    	jet.cluster.data.ProxList plVoisin1;
    	Vector plVoisin2=new Vector();
    	for (int i=0; i<trace.size();i++)
    	{
    		nbVoisins1=0;
    		nbVoisins2=0;
    		int poidVoisin1,poidVoisin2;
    		plVoisin1=dm.getProxList(i);
    		traceVoisins1=0.0;
    		traceVoisins2=0.0;
    		if ((Double)axs.get(plVoisin1.getId())!=0.0)
    		{
	    		for(int j=0;j<plVoisin1.getLength();j++)
	    		{
	    			
	    			if (
	    					(plVoisin1.getProxNode(j).getDistance()< radius)
	    					&&((Double)axs.get(plVoisin1.getProxNode(j).getRef().getId())!=0.0)
	    				)
	    			{
	    				
	    				traceVoisins1=traceVoisins1+(Double)trace.get(plVoisin1.getProxNode(j).getRef().getId());
	    				nbVoisins1++;
	    				
	    				for(int l=0;l<dm.getProxList(j).getLength();l++)
	    	    		{
		    				present=false;
		    				for(int k=0;k<plVoisin2.size();k++) if (dm.getProxList(j).getProxNode(l).getRef().getId()==((jet.cluster.data.ProxNode)plVoisin2.get(k)).getRef().getId()) present=true;
		    				for(int k=0;k<plVoisin1.getLength();k++) if (dm.getProxList(j).getProxNode(l).getRef().getId()==((jet.cluster.data.ProxNode)plVoisin1.getProxNode(k)).getRef().getId()) present=true;
		    				if (dm.getProxList(j).getProxNode(l).getRef().getId()==plVoisin1.getId()) present=true;
		    				if (
		    	    				(!present)
		    	    				&&(dm.getProxList(j).getProxNode(l).getDistance()<radius)
		    	    				&&((Double)axs.get(dm.getProxList(j).getProxNode(l).getRef().getId())!=0.0)
		    	    			)
		    				{
		    	    			traceVoisins2=traceVoisins2+(Double)trace.get(dm.getProxList(j).getProxNode(l).getRef().getId());
		    					nbVoisins2++;
		    					plVoisin2.add(dm.getProxList(j).getProxNode(l));
		    				}
	    	    		}
	    			}
	    		}
	    		
	    		if (nbVoisins1!=0)
	    		{
	    			traceVoisins1=traceVoisins1/nbVoisins1;
	   				if (nbVoisins2!=0)
	   				{
	   					traceVoisins2=traceVoisins2/nbVoisins2;
	   					poidVoisin2=2;
	   				}
	   				else
	   				{
	   					traceVoisins2=0;
	   					poidVoisin2=0;
	   				}
	   				poidVoisin1=3;
	   				
	   			}
	   			else
    			{
	   				traceVoisins1=0;
	    			traceVoisins2=0;
	    			poidVoisin1=0;
	    			poidVoisin2=0;
	    		}
	    		traceMoy.add((poidVoisin1*traceVoisins1+4*(Double)trace.get(plVoisin1.getId()))/(4+poidVoisin1));
	    	}
    		else traceMoy.add(0.0);
    	}
    	return traceMoy;
    	
    }
    
    
}
