package areaidentification;



import java.io.*;
import java.util.Vector;
import java.util.Hashtable;
import java.util.Comparator;
/**
 *
 * @author Ilias Foudalis
 */
public class Merge {
    
    AreaIdentification parent;
    PublicFunctions pf = new PublicFunctions();
    
    Vector<Area> areas = new Vector<Area>();
    
    float threshold;
    int dimX, dimY;
    
    public Merge(AreaIdentification parent)
    {
        this.parent = parent;
        this.dimX = parent.dimX;
        this.dimY = parent.dimY;
        this.threshold  = parent.threshold;
        System.out.println("Merging starts with threshold: "+threshold);
        for(int i = 0; i < parent.finalAreas.size(); i++)
        {
            Area alpha = new Area(parent.finalAreas.get(i).areaID);
            alpha = parent.finalAreas.get(parent.finalAreas.indexOf(alpha));
            areas.add(alpha);
        }
        merge();
        //print area map
        parent.exportAreas(areas, "merged");
        parent.printAreaMap(areas,"mmerged");
    }
    
    public void merge()
    {        
        AreaSizeComparator sizeComp = new AreaSizeComparator();
        AreaScoreComparator scoreComp = new AreaScoreComparator();
        
        Vector<Short> uniqueIDs = new Vector<Short>();
        
        
        int mergings = 0;
        Vector<Area> geoConnectedAreas = new Vector<Area>();
        while(true)
        {
            //sort the areas by size
            java.util.Collections.sort(areas,sizeComp);
            
            short mergeWith1 = -1, mergeWith2 = -1;
            for(int i = 0; i < areas.size(); i++) //find the largest area that can be merged
            {
                Area alpha = areas.get(i); 
                //first of all for a given area get its geo connected neighbors
                geoConnectedAreas.removeAllElements();
                Vector<Short> geoConnectedAreaIDs = findNeighbors(alpha);
                for(int j = 0; j < geoConnectedAreaIDs.size(); j++)
                {
                    Area dummy = new Area(geoConnectedAreaIDs.get(j));
                    dummy = areas.get(areas.indexOf(dummy));
                    geoConnectedAreas.add(dummy);
                }
                //now compute their score
                for(int j = 0; j < geoConnectedAreas.size(); j++)
                {
                    float score = mergedScore(alpha, geoConnectedAreas.get(j));
                    geoConnectedAreas.get(j).score = score;
                }
                java.util.Collections.sort(geoConnectedAreas,scoreComp);
                //and test to see if we can merge this guy
                if(geoConnectedAreas.size() > 0)
                {
                    if(geoConnectedAreas.firstElement().score > threshold)
                    {
                        mergeWith1 = geoConnectedAreas.firstElement().areaID;
                        mergeWith2 = alpha.areaID;
                        break;
                    }                
                }                
            }
            //if we found someone to merge
            if(mergeWith1 != -1)
            {
                Area mergedArea = new Area((short)-1);
                mergedArea.areaID = mergeWith1;
                int index = areas.indexOf(mergedArea);
                Area alpha = areas.remove(index);
                mergedArea.areaID = mergeWith2;
                index = areas.indexOf(mergedArea);
                Area beta = areas.remove(index);
                //print stats here
                if(!uniqueIDs.contains(alpha.areaID))
                {
                    uniqueIDs.add(alpha.areaID);
                    try
                    {
                      PrintWriter out = new PrintWriter(new FileWriter("mergedSizes.txt",true));  
                      out.println(alpha.nodes.size());
                      out.close();
                    }
                    catch(IOException ioe)
                    {}
                    
                }
                if(!uniqueIDs.contains(beta.areaID))
                {
                    uniqueIDs.add(beta.areaID);
                    try
                    {
                      PrintWriter out = new PrintWriter(new FileWriter("mergedSizes.txt",true));  
                      out.println(beta.nodes.size());
                      out.close();
                    }
                    catch(IOException ioe)
                    {}
                    
                }
                
                //now merge
                mergedArea.areaID = mergeWith2;
                mergedArea.nodes = new Vector<Short>();
                mergedArea.nodes.addAll(alpha.nodes);
                mergedArea.nodes.addAll(beta.nodes);
                areas.add(mergedArea);
                mergings++;                                        
                
            }
            else
                break; //exit no more merging is possible.
        }
        System.out.println(mergings+" areas were merged");
        System.out.println(areas.size()+" are the final areas");
        parent.printAreaSizes(areas, "afterMerge");
    }
    
    
    public Vector<Short> findNeighbors(Area alpha)
    {
        Vector<Short> neighIds = new Vector<Short>();
        for(int i = 0; i < alpha.nodes.size(); i++)
        {
            Vector<Short> cellNeighs = pf.getNeighborsCross(alpha.nodes.get(i), parent.dimX, parent.dimY);
            for(int j = 0; j < cellNeighs.size(); j++)
            {
                Short cellID = new Short(cellNeighs.get(j));
                if(!alpha.nodes.contains(cellID))
                {                    
                    for(int x = 0; x < areas.size(); x++)
                    {
                        Area beta = areas.get(x);
                        if(beta.nodes.contains(cellID))
                        {                            
                            if(!neighIds.contains(beta.areaID))
                            {               
                                neighIds.add(new Short(beta.areaID));
                            }
                        }
                    }
                }
            }
        }
        return neighIds;
    }
    
    
    public float mergedScore(Area alpha, Area beta)
    {
        //average cross correlation between cells in alpha and beta
        //cross correlations inside alpha
        
        float score = 0, edges = 0;
        for(int i = 0; i < alpha.nodes.size(); i++)
        {
            Short from = alpha.nodes.get(i);
            for(int j = (i+1); j < alpha.nodes.size(); j++)
            {
                Short to = alpha.nodes.get(j);
                score+= (Float)parent.cellGraph[from].get(to);
                edges++;
            }
        }
        //cross correlations inside beta
        for(int i = 0; i < beta.nodes.size(); i++)
        {
            Short from = beta.nodes.get(i);
            for(int j = (i+1); j < beta.nodes.size(); j++)
            {
                Short to = beta.nodes.get(j);
                score+= (Float)parent.cellGraph[from].get(to);
                edges++;
            }
        }
        //cross correlations from cells of alpha to cells of beta
        int bad = 0;
        for(int i = 0; i < alpha.nodes.size(); i++)
        {
            Short from = alpha.nodes.get(i);
            for(int j = 0; j < beta.nodes.size(); j++)
            {
                Short to = beta.nodes.get(j);
                try
                {
                    score+= (Float)parent.cellGraph[from].get(to);
                    edges++;
                }
                catch(NullPointerException npe)
                {}
            }
        }
        
        //test expected edges
        float n1 = (alpha.nodes.size()*(alpha.nodes.size()-1))/2;
        float n2 = (beta.nodes.size()*(beta.nodes.size()-1))/2;
        float n3 = alpha.nodes.size()*beta.nodes.size();
        if((n1+n2+n3)!= edges)
            System.out.println("Check here");
        //
        
        score/=edges;
        return score;                                
    }

    
}


class AreaScoreComparator implements Comparator<Area>
{
    public int compare(Area alpha, Area beta)
    {
        if(alpha.score > beta.score)
            return -1;
        else if(alpha.score < beta.score)
            return 1;
        else return 0;
    }
}

class AreaSizeComparator implements Comparator<Area>
{
    public int compare(Area alpha, Area beta)
    {
        if(alpha.nodes.size() > beta.nodes.size())
            return -1;
        else if(alpha.nodes.size() < beta.nodes.size())
            return 1;
        else return 0;
    }
}
