content: package utils.nexus;
  
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collections;
  import java.util.Iterator;
  
  import org.apache.commons.lang.math.IntRange;
  import org.apache.log4j.Logger;
  
  public class Ranges implements Iterable<Range> {
  	private static final Logger logger = Logger.getLogger(Ranges.class);
  	protected ArrayList<Range> backend;
  
  	public Ranges() {
  		this.backend = new ArrayList<Range>();
  	}
  
  	public Ranges(Ranges template) {
  		this();
  		for(Range templateRange: template){
  			backend.add(templateRange.getCopy());
  		}	
  	}
  	
  	public Ranges getCopy() {
  		return new Ranges(this);
  	}
  
  	public void addRange(int start, int stop) {
  		int min = Math.min(start, stop);
  		int max = Math.max(start, stop);
  		addRange(new Range(min, max, 0));
  	}
  	
  	public void clearRange(int start, int stop) {
  		int min = Math.min(start, stop);
  		int max = Math.max(start, stop);
  		clearRange(new Range(min, max, 0));
  	}
  	
  	public void addRange(Range additionalRange) {
  		
  		logger.info("add=" + additionalRange);
  		
  		// first remove completely containing ones
  		ArrayList<Range> toDelete = new ArrayList<Range>();
  		for(Range range: backend){
  			if(additionalRange.containsRange(range)){
  				toDelete.add(range);
  			}
  		}
  		
  		backend.removeAll(toDelete);
  		
  		// if it within merge if same frame, otherwise cut out
  		ArrayList<Range> additionalParts = new ArrayList<Range>();
  		for(Range range: backend){
  			if(additionalRange.within(range)){
  				// if same reading frame then skip additional
  				if(additionalRange.startVal == range.getPosVal(additionalRange.start)){
  					return;
  				}
  				else{
  					Range restPart = range.cutOut(additionalRange);
  					additionalParts.add(restPart);
  				}
  			}
  		}
  		
  		backend.addAll(additionalParts);
  		
  		
  		// now adjust the existing ones making new one fit in between
  		// or merge together with overlapping existing if same reading frame
  		ArrayList<Range> toRemove = new ArrayList<Range>();
  		for(Range range: backend){
  			if(additionalRange.intersects(range)){
  				// if same reading frame then merge and remove the overlapping
  				if(additionalRange.startVal == range.getPosVal(additionalRange.start)){
  					logger.info("merge" + additionalRange);
  					additionalRange.merge(range);
  					toRemove.add(range);
  				}
  				// if not same reading frame then crop
  				else{
  					range.crop(additionalRange);
  				}
  			}
  		}
  		// remove overlapping and same frame
  		backend.removeAll(toRemove);
  		// and add the new one
  		backend.add(additionalRange);
  		
  		// and remove 0-length ones
  		removeZeroLengthOnes();
  		
  		// and sort it
  		Collections.sort(backend);
  		
  		this.debug();
  			
  	}
  
  	public void clearRange(Range rangeToRemove) {
  		
  		logger.info("remove=" + rangeToRemove);
  		
  		// first remove completely containing ones
  		ArrayList<Range> toDelete = new ArrayList<Range>();
  		for(Range range: backend){
  			if(rangeToRemove.containsRange(range)){
  				toDelete.add(range);
  			}
  		}
  		
  		backend.removeAll(toDelete);
  		
  		// if it within cut out
  		ArrayList<Range> additionalParts = new ArrayList<Range>();
  		for(Range range: backend){
  			if(rangeToRemove.within(range)){
  				Range restPart = range.cutOut(rangeToRemove);
  				additionalParts.add(restPart);
  			}
  		}
  		
  		backend.addAll(additionalParts);
  		
  		// now adjust the existing ones making new one fit in between
  		// or merge together with overlapping existing if same reading frame
  		ArrayList<Range> toRemove = new ArrayList<Range>();
  		for(Range range: backend){
  			if(rangeToRemove.intersects(range)){
  				range.crop(rangeToRemove);
  			}
  		}
  		
  		// and remove 0-length ones
  		removeZeroLengthOnes();
  		
  		// and sort it
  		Collections.sort(backend);
  		
  		this.debug();	
  	}
  	
  	private void removeZeroLengthOnes() {
  		ArrayList<Range> zeroLengthOnes = new ArrayList<Range>();
  		for(Range range: backend){
  			if(range.getLength() < 0){
  				zeroLengthOnes.add(range);
  			}
  		}
  		backend.removeAll(zeroLengthOnes);
  	}
  	
  	
  	public void debug() {
  		int count = 0;
  		for(Range range: backend){
  			logger.info("range.toString() count =" + count + " " + range.toString());
  			count ++;
  		}
  	}
  
  	public Range getRange(int pos){
  		
  		for(int n = this.backend.size() - 1; n >= 0; n--){
  			Range range = backend.get(n);
  			if(range.contains(pos)){
  				return range;
  			}
  		}
  		return null;
  	}
  	
  	public boolean contains(int pos) {
  		for(Range range: backend){
  			if(range.contains(pos)){
  				return true;
  			}
  		}
  		return false;
  	}
  	
  	public boolean intersects(Ranges testRanges) {
  		for(Range aRange: backend){
  			for(Range testRange: testRanges){
  				if(testRange.intersects(aRange)){
  					return true;
  				}
  			}
  		}
  		return false;
  	}
  	
  	public boolean intersects(int minX, int maxX) {
  		Ranges templateRanges = new Ranges();
  		templateRanges.addRange(minX, maxX);
  		return intersects(templateRanges);
  	}
  
  	public int size() {
  		return backend.size();
  	}
  
  	public Iterator<Range> iterator() {
  		  return backend.iterator();
  	}
  
  	public void reverse(int length) {
  		if(length <=0){
  			return;
  		}
  		ArrayList<Range> newBackend = new ArrayList<Range>();
  		for(Range range: backend){
  			int newStart = (length - 1) - range.start;
  			int newEnd = (length - 1) - range.end;
  			int newStartVal = range.getPosVal(range.end);
  			CodonRange reverseRange = new CodonRange(newStart, newEnd, newStartVal);
  			newBackend.add(reverseRange);
  			// and sort it
  			Collections.sort(backend);
  			// and set it
  			backend = newBackend;
  		}
  	}
  
  	public int countPositions(){
  		int length = 0;
  		for(Range range: backend){
  			length += range.getLength();
  		}
  		return length;
  	}
  
  	public int getMaximumEndPos() {
  		int max = -1;
  		for(Range range: backend){
  			max = Math.max(max, range.end);
  		}
  		return max;
  	}
  	
  	public int getMinimumStartPos() {
  		if(backend == null || backend.size() == 0){
  			return -1;
  		}
  		int min = Integer.MAX_VALUE;
  		for(Range range: backend){
  			min = Math.min(min, range.start);
  		}
  		return min;
  	}
  
  	public void deletePosition(int pos) {
  		for(int n = backend.size() - 1; n >= 0; n--){
  			Range range = backend.get(n);
  			if(range.end >= pos){
  				range.end = range.end - 1;
  			}
  			if(range.start > pos){ // if start is same then leave it
  				range.start = range.start - 1;
  			}
  		}
  		
  		// and remove 0-length ones
  		removeZeroLengthOnes();
  	}
  	
  	public void insertPosition(int pos) {
  		for(int n = backend.size() - 1; n >= 0; n--){
  			Range range = backend.get(n);
  			if(range.end >= pos){
  				range.end = range.end + 1;
  			}
  			if(range.start > pos){ // if start is same then leave it
  				range.start = range.start + 1;
  			}
  		}
  	}
  	
  	public void set(int pos, boolean boolVal){
  		if(boolVal == true){
  			addRange(new Range(pos, pos, 0));
  		}else{
  			clearRange(new Range(pos, pos, 0));
  		}
  	}
  	
  	public boolean containsAnyPosition() {
  		if(size() > 0){
  			return true;
  		}
  		return false;
  	}
  	
  	public ArrayList<NexusRange> getAsContinousNexusRanges(){
  		ArrayList<NexusRange> nexusRanges = new ArrayList<NexusRange>();
  		for(Range range: backend){
  			logger.info("range=" + range);
  			NexusRange continousRange=new NexusRange(range.start + 1, range.end + 1, range.step, range.startVal); // +1 because Nexus uses 1 as first index
  			nexusRanges.add(continousRange);
  			logger.info(continousRange.debug());
  		}
  		return nexusRanges;
  	}
  	
  }
  