
package org.fda.alignment;


import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.concurrent.BlockingQueue;
import org.fda.basicstats.BasicStatsComputeFromList;
import org.fda.data.Utilities;
import org.fda.exceptions.IllegalIntervalException;


/**
 *
 * @author Gokhan.Yavas
 */
public abstract class AlignmentFilter implements Runnable{
    private BlockingQueue inQueue = null;
    private BlockingQueue outQueue = null;
    protected String id="AbstractFilter"; 
    
    //private Map<NLevel, Integer> lmap=null;
    private BasicStatsComputeFromList bsc=null;
    public AlignmentFilter(BlockingQueue queue,  BlockingQueue oqueue){
        this.inQueue = queue;
        this.outQueue = oqueue;
        
    }
//    public AlignmentFilter(BlockingQueue queue, InputDataType idt, BlockingQueue oqueue, Map<NLevel, Integer> lmap){
    public AlignmentFilter(BlockingQueue queue,  BlockingQueue oqueue, BasicStatsComputeFromList bsc){
    
        this.inQueue = queue;
        this.outQueue = oqueue;
        
        this.bsc = bsc;
        //this.lmap = bsc.getNLevels();
    }
    
    protected int diffAligns(Alignment i, Alignment j){
        return
        ( ( j.getRefSt() < i.getRefSt() ) ? Math.abs (j.getRefEnd() - i.getRefSt()) : Math.abs (i.getRefEnd() - j.getRefSt()) ) +
        ( ( j.getContSt() < j.getContSt() ) ? Math.abs (j.getContEnd() - i.getContSt()) : Math.abs (i.getContEnd() - j.getContSt()) );
    }
    protected boolean updateBest(AlignmentWrapper[] lis, long size, List<Integer> allbest, double epsilon){
        if ( size == 0 )
            return false;
        int best, i;
        //-- Find the best
        for ( best = 0; best < size; ++ best )
            if ( ! lis[best].getUsed() )
                break;
        for ( i = best + 1; i < size; ++ i )
            if ( ! lis[i].getUsed() && ( lis[i].getScore() > lis[best].getScore() || (lis[i].getScore() == lis[best].getScore() && lis[i].getDiff()  <  lis[best].getDiff()) ) )
                best = i;

        //-- Nonequivalent
        if ( ! allbest.isEmpty() && (best == size || (epsilon < 0 && lis[best].getScore() < lis[allbest.get(0)].getScore())
            || (epsilon >= 0 && (double)(lis[allbest.get(0)].getScore() - lis[best].getScore()) / (double)(lis[allbest.get(0)].getScore()) * 100.0 > epsilon)) )
            return false;

        //-- Equivalent
        allbest.add(best);

        for ( i = best; i >= 0  &&  i < size; i = lis[i].getFrom() )
            lis[i].setUsed(true);
        return true;
    }
    
    protected int pickBest (AlignmentWrapper[] lis, List<Integer> allbest, double epsilon){
        int RAND_MAX= 32767;
        int size = allbest.size();
        Random r = new Random();
        
        if ( epsilon < 0 && size != 0 )
        {            
            int eqc = 0;
            for ( ; eqc < size; ++ eqc ){
                if ( lis[allbest.get(eqc)].getDiff() != lis[allbest.get(0)].getDiff() ){                    
                    break;
                }
            }           
            return (int)((double)eqc*r.nextInt(RAND_MAX) / (RAND_MAX + 1.0));
            //return (int)((double)eqc*1 / (RAND_MAX + 1.0));
        }
        return size;
    }
    protected List<AlignmentWrapper> createAlignmentWrappers(List<Alignment> as){
        List<AlignmentWrapper> aw = new ArrayList();
        for(int i =0; i<as.size(); i++){
            if(100*as.get(i).getIdentity() >= Utilities.alignmentIdentityThreshold && 
                    (as.get(i).getContigAlignmentLength() >= Utilities.alignmentLengthThreshold && as.get(i).getRefAlignmentLength()>= Utilities.alignmentLengthThreshold))
                aw.add(new AlignmentWrapper(as.get(i)));
        }
        
        Collections.sort(aw);
        return aw;
    }
    public abstract void filter(ScaffoldContig as);
        
    
    protected void setAlignments(ScaffoldContig as, List<Alignment> a2ret){
        int i;
        boolean put=true;
        if(a2ret.isEmpty()){
            //as.getAlignmentSetCollection().removeAlignmentSet(as.getId());            
            // Do nothing
            put = false;
        }
        else if(a2ret.size()==1){
            as.setCoverage(a2ret.get(0).getContigAlignmentLength(), a2ret.get(0).getRefAlignmentLength());
            as.setAlignments(a2ret);
        }
        else{            
            // Reverse the order of the a2ret
            Collections.reverse(a2ret);        
            // Do one more step of filtering on the a2ret and adjust the coordinates of the alignments
            Alignment a, b;
            int ov;
            int asoc, csor;
            asoc=csor=0;
            List<Integer> toremove = new ArrayList<>();
            i=0;
            while(i<a2ret.size()-1){
                a = a2ret.get(i);
                b = a2ret.get(i+1);
                // check the overlapContig
                ov = a.overlapContig(b);

                // No overlapContig
                if(ov==0){
                    asoc += a.getContigAlignmentLength();
                    csor += a.getRefAlignmentLength();
                    i++;
                    continue;
                }
                else if(ov == a.getContigAlignmentLength()){
                    // get rid of the alignment a completely
                    toremove.add(i);
                    i++;
                    continue;                    
                }
                else if(ov == b.getContigAlignmentLength()){
                    // get rid of the alignment b completely
                    toremove.add(i+1);
                    i=i+2;
                    asoc += a.getContigAlignmentLength();
                    csor += a.getRefAlignmentLength();                    
                    continue;                    
                }

                // There is some overlapContig between consecutive alignments
                // Do the adjustment on the alignment coordinates
                if((double)ov/(double)a.getContigAlignmentLength() < (double)ov/(double)b.getContigAlignmentLength()){
                    // trim the ends of alignment b
                    try{
                        b.setContSt(a.getContEnd()+1);
                    }
                    catch(IllegalIntervalException ex){
                        toremove.add(i+1);
                        i=i+2;
                    }
                }
                else{
                    // trim alignment a
                    try{
                        a.setContEnd(b.getContSt()-1);
                    }
                    catch(IllegalIntervalException ex){
                        toremove.add(i);
                        i++;
                    }
                }
                asoc += a.getContigAlignmentLength();
                csor += a.getRefAlignmentLength();
                i++;
            }
            Collections.sort(toremove);
            for(i=(toremove.size()-1); i>=0; i--){
                a2ret.remove((int)toremove.get(i));
            }

            asoc += a2ret.get(a2ret.size()-1).getContigAlignmentLength();
            csor += a2ret.get(a2ret.size()-1).getRefAlignmentLength();

            
//            System.out.println(as.toString(false));
//            System.out.println(asoc);
//            if(idt == InputDataType.Read)
//                resetDeltas(a2ret);
            as.setAlignments(a2ret);
            if(Utilities.assignQuality)
                as.setCoverage(asoc, csor);
            
        }
        
        try{
            if(put){  
                if(Utilities.assignQuality)
                    as.assignQuality();
                else
                    as.setMisassembly();
                outQueue.put(as);                
            }                
        }
        catch(InterruptedException e){e.printStackTrace();}
    }
    private void resetDeltas(List<Alignment> a2ret){
        for(Alignment a : a2ret)
            resetDelta(a);
    }
    private void resetDelta(Alignment alignment){
        if(alignment instanceof NucmerAlignment)
            //if(((NucmerAlignment)alignment).getInputDataType()==InputDataType.Read)
                ((NucmerAlignment)alignment).resetDelta();                                    
    }
    
    @Override
    public void run() {
        Object as;
        while(true){
            try{
                as = inQueue.take();
                if(as instanceof ScaffoldContig){                                       
                    filter((ScaffoldContig)as);
                    //System.out.println(id+" filtered: "+((ScaffoldContig)as).getId());
                }
                else
                    break;
            }
            catch(Exception ex){/* Do nothing about it*/ ex.printStackTrace();}            
        }
    }
}
