package io.compgen.ngsutils.cli.bed;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import io.compgen.cmdline.annotation.Command;
import io.compgen.cmdline.annotation.Exec;
import io.compgen.cmdline.annotation.Option;
import io.compgen.cmdline.annotation.UnnamedArg;
import io.compgen.cmdline.exceptions.CommandArgumentException;
import io.compgen.cmdline.impl.AbstractOutputCommand;
import io.compgen.common.IterUtils;
import io.compgen.common.StringUtils;
import io.compgen.ngsutils.annotation.BadReferenceException;
import io.compgen.ngsutils.annotation.BedAnnotationSource;
import io.compgen.ngsutils.annotation.GenomeSpan;
import io.compgen.ngsutils.bam.Strand;
import io.compgen.ngsutils.bed.BedReader;
import io.compgen.ngsutils.bed.BedRecord;

@Command(name="bed-nearest", desc="Given reference and query BED files, for each query region, find the nearest reference region", category="bed")
public class BedNearest extends AbstractOutputCommand {
    
    private String refFilename = null;
    private String queryFilename = null;
    
    private int extendUp = 0;
    private int extendDown = 0;

    private boolean ignoreStrand = false;
    
    @UnnamedArg(name = "REF QUERY")
    public void setFilenames(String[] filenames) throws CommandArgumentException {
        if (filenames.length != 2) {
            throw new CommandArgumentException("Invalid target/query");
        }
        this.refFilename = filenames[0];
        this.queryFilename = filenames[1];
    }
    
    @Option(name="ns", desc="Ignore strand in the query BED file")
    public void setIgnoreStrand(boolean val) {
        this.ignoreStrand = val;
    }

    @Option(name="up", desc="Allow at most this distance upstream of a reference region (strand-specific)")
    public void setExtendUp(int extendUp) throws CommandArgumentException {
        if (extendUp < 0) {
            throw new CommandArgumentException("Invalid extend value!");
        }
        this.extendUp = extendUp;
    }

    @Option(name="down", desc="Allow at most this distance downstream of a reference region (strand-specific)")
    public void setExtendDown(int extendDown) throws CommandArgumentException {
        if (extendDown < 0) {
            throw new CommandArgumentException("Invalid extend value!");
        }
        this.extendDown = extendDown;
    }

    @Exec
    public void exec() throws IOException, CommandArgumentException {
        if (refFilename == null || queryFilename == null) {
            throw new CommandArgumentException("Missing/invalid arguments!");
        }

        BedAnnotationSource ann = new BedAnnotationSource(refFilename);
        int extend = Math.max(extendUp, extendDown);
        
        for (BedRecord record: IterUtils.wrap(BedReader.readFile(queryFilename))) {
            GenomeSpan coord = record.getCoord();
            if (ignoreStrand) {
                coord = coord.clone(Strand.NONE);
            }
            
            GenomeSpan queryCoord = new GenomeSpan(coord.ref, coord.start - extend, coord.end + extend, coord.strand);
            BedRecord  best = null;
            int bestDist = 0;
            
            for (BedRecord rec: ann.findAnnotation(queryCoord)) {
                try {
                    int dist = rec.getCoord().distanceTo(coord);
                    
                    if (rec.getCoord().strand == Strand.PLUS || rec.getCoord().strand == Strand.NONE) {
                        if (dist < 0) {
                            if (dist < -extendUp) {
                                continue;
                            }
                        } else {
                            if (dist > extendDown) {
                                continue;
                            }
                        }
                    } else {
                        if (dist > 0) {
                            if (dist > extendUp) {
                                continue;
                            }
                        } else {
                            if (dist < -extendDown) {
                                continue;
                            }
                        }
                    }
                    
                    dist = Math.abs(dist);
                    if (best == null || dist < bestDist) {
                        bestDist = dist;
                        best = rec;
                    }
                    
                } catch (BadReferenceException e) {
                }
            }
            
            List<String> cols = new ArrayList<String>();
            cols.add(record.getCoord().ref);
            cols.add(""+record.getCoord().start);
            cols.add(""+record.getCoord().end);
            cols.add(""+record.getName());
            cols.add(""+record.getScore());
            cols.add(record.getCoord().strand.toString());
            if (best != null) {
                cols.add(best.getName());
                if (best.getCoord().strand == Strand.PLUS || best.getCoord().strand == Strand.NONE) {
                    try {
                        cols.add(""+best.getCoord().distanceTo(coord));
                    } catch (BadReferenceException e) {
                    }
                    cols.add("+");
                } else {
                    try {
                        cols.add(""+(-best.getCoord().distanceTo(coord)));
                    } catch (BadReferenceException e) {
                    }
                    cols.add("-");
                }
            } else {
                cols.add("*");
                cols.add("");
                cols.add("");
            }
            out.write((StringUtils.join("\t", cols)+"\n").getBytes());
        }
    }
}

@@ -3112,24 +3112,86 @@
 est = null;%0A
+            List%3CString%3E bestNames = new ArrayList%3CString%3E();%0A
             
@@ -4446,16 +4446,305 @@
  = rec;%0A
+                        bestNames.clear();%0A                        bestNames.add(best.getName());%0A                    %7D else if (dist == bestDist) %7B%0A                    %09if (!bestNames.contains(best.getName())) %7B%0A                    %09%09bestNames.add(best.getName());%0A                    %09%7D%0A
         
@@ -5274,37 +5274,55 @@
    cols.add(
-best.ge
+StringUtils.join(%22,%22, bes
 tName
-(
+s
 ));%0A        

allow multiple matches (only for NAME)