package stream;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;

import ukmer.Kmer;

import jgi.Dedupe;
import shared.Shared;
import shared.Tools;
import shared.TrimRead;
import align2.GapTools;
import align2.QualityTools;
import dna.AminoAcid;
import dna.ChromosomeArray;
import dna.Data;
import dna.Gene;

public final class Read implements Comparable<Read>, Cloneable, Serializable{
	
	/**
	 * 
	 */
	private static final long serialVersionUID = -1026645233407290096L;

	public static void main(String[] args){
		byte[] a=args[0].getBytes();
		System.out.println(new String(a));
		byte[] b=toShortMatchString(a);
		System.out.println(new String(b));
		byte[] c=toLongMatchString(b);
		System.out.println(new String(c));
		byte[] d=toLongMatchString(c);
		System.out.println(new String(d));
//		byte[] e=toShortMatchString(b);
//		System.out.println(new String(e));
		
	}
	
	public Read(byte[] bases_, byte[] quals_, long id_){
		this(bases_, quals_, id_, Long.toString(id_));
	}
	
	public Read(byte[] bases_, byte[] quals_, long id_, String name_){
		this(bases_, -1, (byte)0, 0, 0, name_, quals_, id_);
	}
	
	public Read(byte[] s_, int chrom_, byte strand_, int start_, int stop_, long id_, byte[] quality_){
		this(s_, chrom_, strand_, start_, stop_, Long.toString(id_), quality_, id_);
	}
	
//	public Read(byte[][] fasta_, byte[][] qual_, long numericID_){
//		this(fasta_[1], 0, (byte)0, 0, 0, new String(fasta_[0]), qual_[1], numericID_);
//	}
	
	public Read(byte[] s_, int chrom_, byte strand_, int start_, int stop_, String id_, byte[] quality_, long numericID_){
		this(s_, chrom_, start_, stop_, id_, quality_, numericID_, strand_);
		assert(strand_==0 || strand_==1);
		assert(start_<=stop_) : chrom_+", "+start_+", "+stop_+", "+numericID_;
	}
	
	public Read(byte[] bases_, int chrom_, int start_, int stop_, String id_, byte[] quals_, long numericID_, int flags_){
		flags=flags_&~VALIDATEDMASK;
		bases=bases_;
		quality=quals_;

		chrom=chrom_;
		start=start_;
		stop=stop_;
		
		id=id_;
		numericID=numericID_;
		
		if(VALIDATE_IN_CONSTRUCTOR){validate(true);}
	}
	
	public boolean checkQuality(){
		if(quality==null){return true;}
		for(int i=0; quality!=null && i<bases.length; i++){
			if((bases[i]=='N')!=(quality[i]<1)){
				assert(false) : (char)bases[i]+", "+quality[i]+", "+i+", "+Read.CHANGE_QUALITY+", "+Read.MIN_CALLED_QUALITY+"\n"+toFastq();
				return false;
			}
		}
		return true;
	}
	
	public boolean validate(final boolean processAssertions){
		assert(!validated());
		
		if(false){//This causes problems with error-corrected PacBio reads.
			boolean x=(quality==null || quality.length<1 || quality[0]<=80 || !FASTQ.DETECT_QUALITY || FASTQ.IGNORE_BAD_QUALITY);
			if(!x){
				if(processAssertions){
					KillSwitch.kill("Quality value ("+quality[0]+") appears too high.\n"+Arrays.toString(quality)+
							"\n"+Arrays.toString(bases)+"\n"+numericID+"\n"+id+"\n"+FASTQ.ASCII_OFFSET);
				}
				return false;
			}
		}
		
		final boolean aa=aminoacid();
		
		if(NULLIFY_BROKEN_QUALITY && quality!=null && quality.length!=bases.length){
			quality=null;
			setDiscarded(true);
		}
		
		assert(!processAssertions || quality==null || quality.length==bases.length) : 
			"\nMismatch between length of bases and qualities for read "+numericID+" (id="+id+").\n"+
			"# qualities="+quality.length+", # bases="+bases.length+"\n\n"+
			FASTQ.qualToString(quality)+"\n"+new String(bases)+"\n";
		if(quality!=null && (bases==null || bases.length!=quality.length)){
			assert(!processAssertions) : 
				"\nMismatch between length of bases and qualities for read "+numericID+" (id="+id+").\n"+
				"# qualities="+quality.length+", # bases="+bases.length+"\n\n"+
				FASTQ.qualToString(quality)+"\n"+new String(bases)+"\n";
			return false;
		}
		
		if(bases!=null && (true || (FLAG_JUNK || FIX_JUNK || U_TO_T))){
			if(aa){
				for(int i=0; i<bases.length; i++){
					byte b=bases[i];
					int num=AminoAcid.aminoToCode[b];
					if(num<0){
						if(FIX_JUNK){
							bases[i]='X';
							if(quality!=null){quality[i]=0;}
						}else{
							setJunk(true);
							break;
						}
					}
				}
			}else{
				if(U_TO_T){
					for(int i=0; i<bases.length; i++){
						if(Character.toUpperCase(bases[i])=='U'){
							bases[i]=AminoAcid.uToT[bases[i]];
						}
					}
				}
				for(int i=0; i<bases.length; i++){
					byte b=bases[i];
					int num=AminoAcid.baseToNumberExtended[b];
					if(num<0){
						if(FIX_JUNK){bases[i]='N';}
						else{
							setJunk(true);
							break;
						}
					}
				}
			}
		}else{
			boolean x=(bases==null || bases.length<2 || bases[1]=='N' || bases[1]=='.' || bases[1]=='-' || 
					Character.isLetter(bases[1]) || (aa && bases[1]=='*'));
			if(!x){
				assert(!processAssertions) : "\nAn input file appears to be misformatted.  The character with ASCII code "+bases[1]+
					" appeared where a base was expected.\n\n"+Tools.toStringSafe(bases);
				return false;
			}
		}

		if(bases!=null){
			final byte nocall=(aa ? (byte)'.' : (byte)'N');
			if(quality!=null){
				if(!aa){

					if(CHANGE_QUALITY){
						for(int i=0; i<quality.length; i++){
							byte b=bases[i];
							byte q=quality[i];
							if(AminoAcid.isFullyDefined(b)){
								if(q<MIN_CALLED_QUALITY){
									quality[i]=MIN_CALLED_QUALITY;
								}else if(q>MAX_CALLED_QUALITY){
									quality[i]=MAX_CALLED_QUALITY;
								}
							}else{
								quality[i]=0;
								if(b=='-' || b=='.' || b=='X' || b=='n'){bases[i]=nocall;}
							}
							if(TO_UPPER_CASE && b>90){bases[i]-=32;}
							else if(LOWER_CASE_TO_N && b>90){bases[i]=nocall;}
						}
					}else{
						for(int i=0; i<bases.length; i++){
							byte b=bases[i];
							if(AminoAcid.isFullyDefined(b)){
								//do nothing
							}else{
								if(b=='-' || b=='.' || b=='X' || b=='n'){bases[i]=nocall;}
							}
							if(TO_UPPER_CASE && b>90){bases[i]-=32;}
							else if(LOWER_CASE_TO_N && b>90){bases[i]=nocall;}
						}
					}
				}
			}else if(TO_UPPER_CASE){
				for(int i=0; i<bases.length; i++){
					byte b=bases[i];
					if(b>90){
						//							assert(Character.isLowerCase(bases[i])) : new String(bases);
						bases[i]-=32;
						//							assert(Character.isUpperCase(bases[i])) : new String(bases);
					}
					if(OTHER_SYMBOLS_TO_N && (b=='-' || b=='.' || b=='X') && !aa){bases[i]=nocall;}
				}
			}else if(LOWER_CASE_TO_N){
				for(int i=0; i<bases.length; i++){
					byte b=bases[i];
					if(b>90){bases[i]=nocall;}
					else if(b=='-' || b=='.' || b=='X'){bases[i]=nocall;}
				}
			}else if(OTHER_SYMBOLS_TO_N && !aminoacid()){
				for(int i=0; i<bases.length; i++){
					byte b=bases[i];
					if(b=='-' || b=='.' || b=='X'){bases[i]=nocall;}
				}
			}
		}
		
		if(FIX_HEADER){
			fixHeader();
		}
		
		setValidated(true);
		
		return true;
	}
	
	private static final int absdif(int a, int b){
		return a>b ? a-b : b-a;
	}
	
	public final void fixHeader(){
		id=Tools.fixHeader(id);
	}
	
	/** Returns true if these reads are identical, allowing at most n no-calls and m mismatches of max quality q*/
	public boolean isDuplicateByBases(Read r, int nmax, int mmax, byte qmax, boolean banSameQualityMismatch){
		return isDuplicateByBases(r, nmax, mmax, qmax, false, false);
	}
	
	
	
	/** Returns true if these reads are identical, allowing at most n no-calls and m mismatches of max quality q*/
	public boolean isDuplicateByBases(Read r, int nmax, int mmax, byte qmax, boolean banSameQualityMismatch, boolean allowDifferentLength){
		int n=0, m=0;
		assert(r.length()==bases.length) : "Merging different-length reads is supported but seems to be not useful.";
		if(!allowDifferentLength && r.length()!=bases.length){return false;}
		int minLen=Tools.min(bases.length, r.length());
		for(int i=0; i<minLen; i++){
			byte b1=bases[i];
			byte b2=r.bases[i];
			if(b1=='N' || b2=='N'){
				n++;
				if(n>nmax){return false;}
			}else if(b1!=b2){
				m++;
				if(m>mmax){return false;}
				if(quality[i]>qmax && r.quality[i]>qmax){return false;}
				if(banSameQualityMismatch && quality[i]==r.quality[i]){return false;}
			}
		}
		return true;
	}
	
	public boolean isDuplicateByMapping(Read r, boolean bothEnds, boolean checkAlignment){
		if(bases.length!=r.length()){
			return isDuplicateByMappingDifferentLength(r, bothEnds, checkAlignment);
		}
		assert(this!=r && mate!=r);
		assert(!bothEnds || bases.length==r.length());
		if(!mapped() || !r.mapped()){return false;}
//		if(chrom==-1 && start==-1){return false;}
		if(chrom<1 && start<1){return false;}
		
//		if(chrom!=r.chrom || strand()!=r.strand() || start!=r.start){return false;}
////		if(mate==null && stop!=r.stop){return false;} //For unpaired reads, require both ends match
//		if(stop!=r.stop){return false;} //For unpaired reads, require both ends match
//		return true;
		
		if(chrom!=r.chrom || strand()!=r.strand()){return false;}
		if(bothEnds){
			if(start!=r.start || stop!=r.stop){return false;}
		}else{
			if(strand()==Gene.PLUS){
				if(start!=r.start){return false;}
			}else{
				if(stop!=r.stop){return false;}
			}
		}
		if(checkAlignment){
			if(perfect() && r.perfect()){return true;}
			if(match!=null && r.match!=null){
				if(match.length!=r.match.length){return false;}
				for(int i=0; i<match.length; i++){
					byte a=match[i];
					byte b=r.match[i];
					if(a!=b){
						if((a=='D') != (b=='D')){return false;}
						if((a=='I' || a=='X' || a=='Y') != (b=='I' || b=='X' || b=='Y')){return false;}
					}
				}
			}
		}
		return true;
	}
	
	public boolean isDuplicateByMappingDifferentLength(Read r, boolean bothEnds, boolean checkAlignment){
		assert(this!=r && mate!=r);
		assert(bases.length!=r.length());
		if(bothEnds){return false;}
//		assert(!bothEnds || bases.length==r.length());
		if(!mapped() || !r.mapped()){return false;}
//		if(chrom==-1 && start==-1){return false;}
		if(chrom<1 && start<1){return false;}
		
//		if(chrom!=r.chrom || strand()!=r.strand() || start!=r.start){return false;}
////		if(mate==null && stop!=r.stop){return false;} //For unpaired reads, require both ends match
//		if(stop!=r.stop){return false;} //For unpaired reads, require both ends match
//		return true;
		
		if(chrom!=r.chrom || strand()!=r.strand()){return false;}

		if(strand()==Gene.PLUS){
			if(start!=r.start){return false;}
		}else{
			if(stop!=r.stop){return false;}
		}
		
		if(checkAlignment){
			if(perfect() && r.perfect()){return true;}
			if(match!=null && r.match!=null){
				int minLen=Tools.min(match.length, r.match.length);
				for(int i=0; i<minLen; i++){
					byte a=match[i];
					byte b=r.match[i];
					if(a!=b){
						if((a=='D') != (b=='D')){return false;}
						if((a=='I' || a=='X' || a=='Y') != (b=='I' || b=='X' || b=='Y')){return false;}
					}
				}
			}
		}
		return true;
	}
	
	public void merge(Read r, boolean mergeVectors, boolean mergeN){mergePrivate(r, mergeVectors, mergeN, true);}
	
	private void mergePrivate(Read r, boolean mergeVectors, boolean mergeN, boolean mergeMate){
		assert(r!=this);
		assert(r!=this.mate);
		assert(r!=r.mate);
		assert(this!=this.mate);
		assert(r.mate==null || r.mate.mate==r);
		assert(this.mate==null || this.mate.mate==this);
		assert(r.mate==null || r.numericID==r.mate.numericID);
		assert(mate==null || numericID==mate.numericID);
		mergeN=(mergeN||mergeVectors);
		
		assert(r.length()==bases.length) : "Merging different-length reads is supported but seems to be not useful.";
		
		if((mergeN || mergeVectors) && bases.length<r.length()){
			int oldLenB=bases.length;
			start=Tools.min(start, r.start);
			stop=Tools.max(stop, r.stop);
			mapScore=Tools.max(mapScore, r.mapScore);
			
			bases=KillSwitch.copyOfRange(bases, 0, r.length());
			quality=KillSwitch.copyOfRange(quality, 0, r.quality.length);
			for(int i=oldLenB; i<bases.length; i++){
				bases[i]='N';
				quality[i]=0;
			}
			match=null;
			r.match=null;
		}
		
		copies+=r.copies;
		
		
//		if(numericID==11063941 || r.numericID==11063941 || numericID==8715632){
//			System.err.println("***************");
//			System.err.println(this.toText()+"\n");
//			System.err.println(r.toText()+"\n");
//			System.err.println(mergeVectors+", "+mergeN+", "+mergeMate+"\n");
//		}
		
		boolean pflag1=perfect();
		boolean pflag2=r.perfect();

		final int minLenB=Tools.min(bases.length, r.length());
		
		if(mergeN){
			if(quality==null){
				for(int i=0; i<minLenB; i++){
					byte b=r.bases[i];
					if(bases[i]=='N' && b!='N'){bases[i]=b;}
				}
			}else{
				for(int i=0; i<minLenB; i++){
					final byte b1=bases[i];
					final byte b2=r.bases[i];
					final byte q1=Tools.max((byte)0, quality[i]);
					final byte q2=Tools.max((byte)0, r.quality[i]);
					if(b1==b2){
						if(b1=='N'){
							//do nothing
						}else if(mergeVectors){
							//merge qualities
							//						quality[i]=(byte) Tools.min(40, q1+q2);
							if(q1>=q2){
								quality[i]=(byte) Tools.min(48, q1+1+q2/4);
							}else{
								quality[i]=(byte) Tools.min(48, q2+1+q1/4);
							}
						}
					}else if(b1=='N'){
						bases[i]=b2;
						quality[i]=q2;
					}else if(b2=='N'){
						//do nothing
					}else if(mergeVectors){
						if(q1<1 && q2<1){
							//Special case - e.g. Illumina calls bases at 0 quality.
							//Possibly best to keep the matching allele if one matches the ref.
							//But for now, do nothing.
							//This was causing problems changing perfect match strings into imperfect matches.
						}else if(q1==q2){
							assert(b1!=b2);
							bases[i]='N';
							quality[i]=0;
						}else if(q1>q2){
							bases[i]=b1;
							quality[i]=(byte)(q1-q2/2);
						}else{
							bases[i]=b2;
							quality[i]=(byte)(q2-q1/2);
						}
						assert(quality[i]>=0 && quality[i]<=48);
					}
				}
			}
		}
		
		//TODO:
		//Note that the read may need to be realigned after merging, so the match string may be rendered incorrect.
		
		if(mergeN && match!=null){
			if(r.match==null){match=null;}
			else{
				if(match.length!=r.match.length){match=null;}
				else{
					boolean ok=true;
					for(int i=0; i<match.length && ok; i++){
						byte a=match[i], b=r.match[i];
						if(a!=b){
							if((a=='m' || a=='S') && b=='N'){
								//do nothing;
							}else if(a=='N' && (b=='m' || b=='S')){
								match[i]=b;
							}else{
								ok=false;
							}
						}
					}
					if(!ok){match=null;}
				}
			}
		}
		
		if(mergeMate && mate!=null){
			mate.mergePrivate(r.mate, mergeVectors, mergeN, false);
			assert(copies==mate.copies);
		}
		assert(copies>1);
		
		assert(r!=this);
		assert(r!=this.mate);
		assert(r!=r.mate);
		assert(this!=this.mate);
		assert(r.mate==null || r.mate.mate==r);
		assert(this.mate==null || this.mate.mate==this);
		assert(r.mate==null || r.numericID==r.mate.numericID);
		assert(mate==null || numericID==mate.numericID);
	}
	
	public String toString(){return toText(false).toString();}
	
	public StringBuilder toSites(){
		StringBuilder sb;
		if(numSites()==0){
			sb=new StringBuilder(2);
			sb.append('.');
		}else{
			sb=new StringBuilder(sites.size()*20);
			int appended=0;
			for(SiteScore ss : sites){
				if(appended>0){sb.append('\t');}
				if(ss!=null){
					sb.append(ss.toText());
					appended++;
				}
			}
			if(appended==0){sb.append('.');}
		}
		return sb;
	}
	
	public ByteBuilder toSitesB(ByteBuilder sb){
		if(numSites()==0){
			if(sb==null){sb=new ByteBuilder(2);}
			sb.append('.');
		}else{
			if(sb==null){sb=new ByteBuilder(sites.size()*20);}
			int appended=0;
			for(SiteScore ss : sites){
				if(appended>0){sb.append('\t');}
				if(ss!=null){
					ss.toBytes(sb);
					appended++;
				}
			}
			if(appended==0){sb.append('.');}
		}
		return sb;
	}
	
	public StringBuilder toInfo(){
		if(obj==null){return new StringBuilder();}
		if(obj.getClass()==StringBuilder.class){return (StringBuilder)obj;}
		return new StringBuilder(obj.toString());
	}
	
	public ByteBuilder toInfoB(){
		if(obj==null){return new ByteBuilder();}
		if(obj.getClass()==ByteBuilder.class){return (ByteBuilder)obj;}
		return new ByteBuilder(obj.toString());
	}
	
	public ByteBuilder toInfoB(ByteBuilder bb){
		if(obj==null){return bb;}
		if(obj.getClass()==ByteBuilder.class){return bb.append((ByteBuilder)obj);}
		return bb.append(obj.toString());
	}
	
	public StringBuilder toFastq(){
		return FASTQ.toFASTQ(this, (StringBuilder)null);
	}
	
	public ByteBuilder toFastq(ByteBuilder bb){
		return FASTQ.toFASTQ(this, bb);
	}

	public StringBuilder toFasta(){return toFasta(Shared.FASTA_WRAP);}
	public ByteBuilder toFasta(ByteBuilder bb){return toFasta(Shared.FASTA_WRAP, bb);}
	
	public StringBuilder toFasta(int wrap){
		if(wrap<1){wrap=Integer.MAX_VALUE;}
		int len=(id==null ? Tools.stringLength(numericID) : id.length())+(bases==null ? 0 : bases.length+bases.length/wrap)+5;
		StringBuilder sb=new StringBuilder(len);
		sb.append('>');
		if(id==null || FASTQ.TAG_CUSTOM){
			sb.append(FASTQ.TAG_CUSTOM ? FASTQ.customID(this) : ""+numericID);
		}else{sb.append(id);}
		sb.append('\n');
		if(bases!=null){
			final char[] buffer=Shared.getTLCB(Tools.min(wrap, bases.length));
			int j=0;
			for(int i=0; i<bases.length; i++, j++){
				if(j==wrap){
					sb.append(buffer, 0, j);
					sb.append('\n');
					j=0;
				}
				buffer[j]=(char)bases[i];
			}
			if(j>0){sb.append(buffer, 0, j);}
		}
		return sb;
	}
	
	public ByteBuilder toFasta(int wrap, ByteBuilder sb){
		if(wrap<1){wrap=Integer.MAX_VALUE;}
		int len=(id==null ? Tools.stringLength(numericID) : id.length())+(bases==null ? 0 : bases.length+bases.length/wrap)+5;
		if(sb==null){sb=new ByteBuilder(len+1);}
		sb.append('>');
		if(id==null){sb.append(numericID);}
		else{sb.append(id);}
		if(bases!=null){
			int pos=0;
			while(pos<bases.length-wrap){
				sb.append('\n');
				sb.append(bases, pos, wrap);
				pos+=wrap;
			}
			if(pos<bases.length){
				sb.append('\n');
				sb.append(bases, pos, bases.length-pos);
			}
		}
		return sb;
	}
	
	public StringBuilder toSam(){
		return new SamLine(this, pairnum()).toText();
	}
	
	public ByteBuilder toSam(ByteBuilder bb){
		SamLine sl=new SamLine(this, pairnum());
		return sl.toBytes(bb).append('\n');
	}
	
	public static CharSequence header(){

		StringBuilder sb=new StringBuilder();
		sb.append("id");
		sb.append('\t');
		sb.append("numericID");
		sb.append('\t');
		sb.append("chrom");
		sb.append('\t');
		sb.append("strand");
		sb.append('\t');
		sb.append("start");
		sb.append('\t');
		sb.append("stop");
		sb.append('\t');

		sb.append("flags");
		sb.append('\t');
		
		sb.append("copies");
		sb.append('\t');
		
		sb.append("errors,fixed");
		sb.append('\t');
		sb.append("mapScore");
		sb.append('\t');
		sb.append("length");
		sb.append('\t');
		
		sb.append("bases");
		sb.append('\t');
		sb.append("quality");
		sb.append('\t');
		
		sb.append("insert");
		sb.append('\t');
		{
			//These are not really necessary...
			sb.append("avgQual");
			sb.append('\t');
		}
		
		sb.append("match");
		sb.append('\t');
		sb.append("SiteScores: "+SiteScore.header());
		return sb;
	}
	
	public StringBuilder toText(boolean okToCompressMatch){
		
		final byte[] oldmatch=match;
		final boolean oldshortmatch=this.shortmatch();
		if(COMPRESS_MATCH_BEFORE_WRITING && !shortmatch() && okToCompressMatch){
			match=toShortMatchString(match);
			setShortMatch(true);
		}
		
		StringBuilder sb=new StringBuilder();
		sb.append(id);
		sb.append('\t');
		sb.append(numericID);
		sb.append('\t');
		sb.append(chrom);
		sb.append('\t');
		sb.append(Gene.strandCodes[strand()]);
		sb.append('\t');
		sb.append(start);
		sb.append('\t');
		sb.append(stop);
		sb.append('\t');
		
		for(int i=maskArray.length-1; i>=0; i--){
			sb.append(flagToNumber(maskArray[i]));
		}
		sb.append('\t');
		
		sb.append(copies);
		sb.append('\t');

		sb.append(errors);
		sb.append('\t');
		sb.append(mapScore);
		sb.append('\t');
		
		if(bases==null){sb.append('.');}
		else{
			for(int i=0; i<bases.length; i++){
				byte b=bases[i];
				if(b<4){
					assert(b>=0);
					b=(byte) (b+'0');
				}
				sb.append((char)b);
			}
		}
		sb.append('\t');
		
		int qualSum=0;
		int qualMin=99999;
		
		if(quality==null){
			sb.append('.');
		}else{
			for(int i=0; i<quality.length; i++){
				byte q=quality[i];
				qualSum+=q;
				qualMin=Tools.min(q, qualMin);
				q=(byte) (q+ASCII_OFFSET);
				sb.append((char)q);
			}
		}
		sb.append('\t');
		
		if(insert<1){sb.append('.');}else{sb.append(insert);};
		sb.append('\t');
		
		if(quality==null){
			sb.append('.');
			sb.append('\t');
		}else{
			//These are not really necessary...
			sb.append(qualSum/quality.length);
			sb.append('\t');
		}
		
		if(match==null){sb.append('.');}
		else{for(byte b : match){sb.append((char)b);}}
		sb.append('\t');
		
		if(gaps==null){
			sb.append('.');
		}else{
			for(int i=0; i<gaps.length; i++){
				if(i>0){sb.append('~');}
				sb.append(gaps[i]);
			}
		}
		
		if(sites!=null && sites.size()>0){
			
			assert(absdif(start, stop)<3000 || (gaps==null) == (sites.get(0).gaps==null)) : 
				"\n"+this.numericID+"\n"+Arrays.toString(gaps)+"\n"+sites.toString()+"\n";
			
			for(SiteScore ss : sites){
				sb.append('\t');
				sb.append(ss==null ? "null" : ss.toText());
			}
		}
		
		if(originalSite!=null){
			sb.append('\t');
			sb.append('*');
			sb.append(originalSite.toText());
		}
		
		match=oldmatch;
		setShortMatch(oldshortmatch);
		
		return sb;
	}
	
	public ByteBuilder toText(boolean okToCompressMatch, ByteBuilder bb){
		
		final byte[] oldmatch=match;
		final boolean oldshortmatch=this.shortmatch();
		if(COMPRESS_MATCH_BEFORE_WRITING && !shortmatch() && okToCompressMatch){
			match=toShortMatchString(match);
			setShortMatch(true);
		}
		
		if(bb==null){bb=new ByteBuilder();}
		bb.append(id);
		bb.append('\t');
		bb.append(numericID);
		bb.append('\t');
		bb.append(chrom);
		bb.append('\t');
		bb.append(Gene.strandCodes2[strand()]);
		bb.append('\t');
		bb.append(start);
		bb.append('\t');
		bb.append(stop);
		bb.append('\t');
		
		for(int i=maskArray.length-1; i>=0; i--){
			bb.append(flagToNumber(maskArray[i]));
		}
		bb.append('\t');
		
		bb.append(copies);
		bb.append('\t');

		bb.append(errors);
		bb.append('\t');
		bb.append(mapScore);
		bb.append('\t');
		
		if(bases==null){bb.append('.');}
		else{bb.append(bases);}
		bb.append('\t');
		
//		int qualSum=0;
//		int qualMin=99999;
		
		if(quality==null){
			bb.append('.');
		}else{
			bb.ensureExtra(quality.length);
			for(int i=0, j=bb.length; i<quality.length; i++, j++){
				byte q=quality[i];
				bb.array[j]=(byte)(q+ASCII_OFFSET);
//				qualSum+=q;
//				qualMin=Tools.min(q, qualMin);
			}
			bb.length+=quality.length;
		}
		bb.append('\t');
		
		if(insert<1){bb.append('.');}else{bb.append(insert);};
		bb.append('\t');
		
		if(true || quality==null){
			bb.append('.');
			bb.append('\t');
		}else{
//			//These are not really necessary...
//			sb.append(qualSum/quality.length);
//			sb.append('\t');
		}
		
		if(match==null){bb.append('.');}
		else{bb.append(match);}
		bb.append('\t');
		
		if(gaps==null){
			bb.append('.');
		}else{
			for(int i=0; i<gaps.length; i++){
				if(i>0){bb.append('~');}
				bb.append(gaps[i]);
			}
		}
		
		if(sites!=null && sites.size()>0){
			
			assert(absdif(start, stop)<3000 || (gaps==null) == (sites.get(0).gaps==null)) : 
				"\n"+this.numericID+"\n"+Arrays.toString(gaps)+"\n"+sites.toString()+"\n";
			
			for(SiteScore ss : sites){
				bb.append('\t');
				if(ss==null){
					bb.append((byte[])null);
				}else{
					ss.toBytes(bb);
				}
				bb.append(ss==null ? "null" : ss.toText());
			}
		}
		
		if(originalSite!=null){
			bb.append('\t');
			bb.append('*');
			originalSite.toBytes(bb);
		}
		
		match=oldmatch;
		setShortMatch(oldshortmatch);
		
		return bb;
	}
	
	public static Read fromText(String line){
		if(line.length()==1 && line.charAt(0)=='.'){return null;}
		
		String[] split=line.split("\t");
		
		if(split.length<17){
			throw new RuntimeException("Error parsing read from text.\n\n" +
					"This may be caused be attempting to parse the wrong format.\n" +
					"Please ensure that the file extension is correct:\n" +
					"\tFASTQ should end in .fastq or .fq\n" +
					"\tFASTA should end in .fasta or .fa, .fas, .fna, .ffn, .frn, .seq, .fsa\n" +
					"\tSAM should end in .sam\n" +
					"\tNative format should end in .txt or .bread\n" +
					"If a file is compressed, there must be a compression extension after the format extension:\n" +
					"\tgzipped files should end in .gz or .gzip\n" +
					"\tzipped files should end in .zip and have only 1 file per archive\n" +
					"\tbz2 files should end in .bz2\n");
		}
		
		final String id=new String(split[0]);
		long numericID=Long.parseLong(split[1]);
		int chrom=Byte.parseByte(split[2]);
//		byte strand=Byte.parseByte(split[3]);
		int start=Integer.parseInt(split[4]);
		int stop=Integer.parseInt(split[5]);
		
		int flags=Integer.parseInt(split[6], 2);
		
		int copies=Integer.parseInt(split[7]);

		int errors;
		int errorsCorrected;
		if(split[8].indexOf(',')>=0){
			String[] estring=split[8].split(",");
			errors=Integer.parseInt(estring[0]);
			errorsCorrected=Integer.parseInt(estring[1]);
		}else{
			errors=Integer.parseInt(split[8]);
			errorsCorrected=0;
		}
		
		int mapScore=Integer.parseInt(split[9]);
		
		byte[] basesOriginal=split[10].getBytes();
		byte[] qualityOriginal=(split[11].equals(".") ? null : split[11].getBytes());
		
		if(qualityOriginal!=null){
			for(int i=0; i<qualityOriginal.length; i++){
				byte b=qualityOriginal[i];
				b=(byte) (b-ASCII_OFFSET);
				assert(b>=-1) : b;
				qualityOriginal[i]=b;
			}
		}
		
		int insert=-1;
		if(!split[12].equals(".")){insert=Integer.parseInt(split[12]);}
		
		byte[] match=null;
		if(!split[14].equals(".")){match=split[14].getBytes();}
		int[] gaps=null;
		if(!split[15].equals(".")){
			
			String[] gstring=split[16].split("~");
			gaps=new int[gstring.length];
			for(int i=0; i<gstring.length; i++){
				gaps[i]=Integer.parseInt(gstring[i]);
			}
		}
		
//		assert(false) : split[16];
		
		Read r=new Read(basesOriginal, chrom, start, stop, id, qualityOriginal, numericID, flags);
		r.match=match;
		r.errors=errors;
		r.mapScore=mapScore;
		r.copies=copies;
		r.gaps=gaps;
		r.insert=insert;
		
		int firstScore=(ADD_BEST_SITE_TO_LIST_FROM_TEXT) ? 17 : 18;
		
		int scores=split.length-firstScore;
		
		int mSites=0;
		for(int i=firstScore; i<split.length; i++){
			if(split[i].charAt(0)!='*'){mSites++;}
		}
		
		//This can be disabled to handle very old text format.
		if(mSites>0){r.sites=new ArrayList<SiteScore>(mSites);}
		for(int i=firstScore; i<split.length; i++){
			SiteScore ss=SiteScore.fromText(split[i]);
			if(split[i].charAt(0)=='*'){r.originalSite=ss;}
			else{r.sites.add(ss);}
		}
		
		if(DECOMPRESS_MATCH_ON_LOAD && r.shortmatch()){
			r.toLongMatchString(true);
		}

		assert(r.numSites()==0 || absdif(r.start, r.stop)<3000 || (r.gaps==null) == (r.topSite().gaps==null)) : 
			"\n"+r.numericID+", "+r.chrom+", "+r.strand()+", "+r.start+", "+r.stop+", "+Arrays.toString(r.gaps)+"\n"+r.sites+"\n"+line+"\n";
		
		return r;
	}

	/** Inflates gaps between contigs in a scaffold. */
	public void inflateGaps(int minGapIn, int minGapOut) {
		assert(minGapIn>0);
		if(!containsNocalls()){return;}
		final ByteBuilder bbb=new ByteBuilder();
		final ByteBuilder bbq=(quality==null ? null : new ByteBuilder());
		
		int gap=0;
		for(int i=0; i<bases.length; i++){
			byte b=bases[i];
			byte q=(quality==null ? 0 : quality[i]);
			if(b=='N'){
				gap++;
			}else{
				while(gap>=minGapIn && gap<minGapOut){
					gap++;
					bbb.append('N');
					if(bbq!=null){bbq.append(0);}
				}
				gap=0;
			}
			bbb.append(b);
			if(bbq!=null){bbq.append(q);}
		}
		
		while(gap>=minGapIn && gap<minGapOut){//Handle trailing bases
			gap++;
			bbb.append('N');
			if(bbq!=null){bbq.append(0);}
		}
		
		assert(bbb.length()>=bases.length);
		if(bbb.length()>bases.length){
			bases=bbb.toBytes();
			if(bbq!=null){quality=bbq.toBytes();}
		}
	}
	
	public ArrayList<Read> breakAtGaps(final boolean agp, final int minContig){
		ArrayList<Read> list=new ArrayList<Read>();
		byte prev='N';
		int lastN=-1, lastBase=-1;
		int contignum=1;
		long feature=1;
		StringBuilder sb=(agp ? new StringBuilder() : null);
		assert(obj==null);
		for(int i=0; i<bases.length; i++){
			final byte b=bases[i];
			if(b=='N'){
				if(prev!='N'){
					final int start=lastN+1, stop=i;
					byte[] b2=KillSwitch.copyOfRange(bases, start, stop);
					byte[] q2=(quality==null ? null : KillSwitch.copyOfRange(quality, start, stop));
					Read r=new Read(b2, q2, numericID, id+"_c"+contignum);
					if(r.length()>=minContig){list.add(r);}
					contignum++;
					
					if(sb!=null){
						sb.append(id).append('\t');
						sb.append(start+1).append('\t');
						sb.append(stop).append('\t');
						sb.append(feature).append('\t');
						feature++;
						sb.append('W').append('\t');
						sb.append(r.id).append('\t');
						sb.append(1).append('\t');
						sb.append(r.length()).append('\t');
						sb.append("+").append('\n');
					}
				}
				lastN=i;
			}else{
				if(sb!=null && prev=='N' && lastBase>=0){
					sb.append(id).append('\t');
					sb.append(lastBase+2).append('\t');
					sb.append(i).append('\t');
					sb.append(feature).append('\t');
					feature++;
					sb.append('N').append('\t');
					sb.append((i-lastBase-1)).append('\t');
					sb.append("scaffold").append('\t');
					sb.append("yes").append('\t');
					sb.append("paired-ends").append('\n');
				}
				lastBase=i;
			}
			prev=b;
		}
		if(prev!='N'){
			final int start=lastN+1, stop=bases.length;
			byte[] b2=KillSwitch.copyOfRange(bases, start, stop);
			byte[] q2=(quality==null ? null : KillSwitch.copyOfRange(quality, start, stop));
			Read r=new Read(b2, q2, numericID, id+"_c"+contignum);
			if(r.length()>=minContig){list.add(r);}
			contignum++;
			
			if(sb!=null){
				sb.append(id).append('\t');
				sb.append(start+1).append('\t');
				sb.append(stop).append('\t');
				sb.append(feature).append('\t');
				feature++;
				sb.append('W').append('\t');
				sb.append(r.id).append('\t');
				sb.append(1).append('\t');
				sb.append(r.length()).append('\t');
				sb.append("+").append('\n');
			}
		}else{
			if(sb!=null && prev=='N' && lastBase>=0){
				sb.append(id).append('\t');
				sb.append(lastBase+2).append('\t');
				sb.append(bases.length).append('\t');
				sb.append(feature).append('\t');
				feature++;
				sb.append('N').append('\t');
				sb.append((bases.length-lastBase-1)).append('\t');
				sb.append("scaffold").append('\t');
				sb.append("yes").append('\t');
				sb.append("paired-ends").append('\n');
			}
			lastBase=bases.length;
		}
		if(sb!=null){obj=sb.toString();}
		return list;
	}

//	/** Reverses the read.  Mainly for testing. 
//	 * Seems to be unused. */
//	@Deprecated
//	protected void reverse() {
//		Tools.reverseInPlace(bases);
//		Tools.reverseInPlace(quality);
//		Tools.reverseInPlace(match);
//	}

	/** Reverse-complements the read. */
	public void reverseComplement() {
		AminoAcid.reverseComplementBasesInPlace(bases);
		Tools.reverseInPlace(quality);
		setStrand(strand()^1);
	}
	
	@Override
	public int compareTo(Read o) {
		if(chrom!=o.chrom){return chrom-o.chrom;}
		if(start!=o.start){return start-o.start;}
		if(stop!=o.stop){return stop-o.stop;}
		if(strand()!=o.strand()){return strand()-o.strand();}
		return 0;
	}
	
	public SiteScore toSite(){
		assert(start<=stop) : this.toText(false);
		SiteScore ss=new SiteScore(chrom, strand(), start, stop, 0, 0, rescued(), perfect());
		if(paired()){
			ss.setSlowPairedScore(mapScore-1, mapScore);
		}else{
			ss.setSlowPairedScore(mapScore, 0);
		}
		ss.setScore(mapScore);
		ss.gaps=gaps;
		ss.match=match;
		originalSite=ss;
		return ss;
	}
	
	public SiteScore topSite(){
		final SiteScore ss=(sites==null || sites.isEmpty()) ? null : sites.get(0);
		assert(sites==null || sites.isEmpty() || ss!=null) : "Top site is null for read "+this;
		return ss;
	}
	
	public int numSites(){
		return (sites==null ? 0 : sites.size());
	}
	
	public SiteScore makeOriginalSite(){
		originalSite=toSite();
		return originalSite;
	}
	
	public void setFromSite(SiteScore ss){
		assert(ss!=null);
		chrom=ss.chrom;
		setStrand(ss.strand);
		start=ss.start;
		stop=ss.stop;
		mapScore=ss.slowScore;
		setRescued(ss.rescued);
		gaps=ss.gaps;
		setPerfect(ss.perfect);
		
		match=ss.match;
		
		if(gaps!=null){
			gaps=ss.gaps=GapTools.fixGaps(start, stop, gaps, Shared.MINGAP);
//			gaps[0]=Tools.min(gaps[0], start);
//			gaps[gaps.length-1]=Tools.max(gaps[gaps.length-1], stop);
		}
	}
	
//	public static int[] fixGaps(int a, int b, int[] gaps, int minGap){
////		System.err.println("fixGaps input: "+a+", "+b+", "+Arrays.toString(gaps)+", "+minGap);
//		int[] r=GapTools.fixGaps(a, b, gaps, minGap);
////		System.err.println("fixGaps output: "+Arrays.toString(r));
//		return r;
//	}

	public void setFromOriginalSite(){
		setFromSite(originalSite);
	}
	public void setFromTopSite(){
		final SiteScore ss=topSite();
		if(ss==null){
			clearSite();
			setMapped(false);
			return;
		}
		setMapped(true);
		setFromSite(ss);
	}
	
	public void setFromTopSite(boolean randomIfAmbiguous, boolean primary, int maxPairDist){
		final SiteScore ss0=topSite();
		if(ss0==null){
			clearSite();
			setMapped(false);
			return;
		}
		setMapped(true);
		
		if(sites.size()==1 || !randomIfAmbiguous || !ambiguous()){
			setFromSite(ss0);
			return;
		}
		
		if(primary || mate==null || !mate.mapped() || !mate.paired()){
			int count=1;
			for(int i=1; i<sites.size(); i++){
				SiteScore ss=sites.get(i);
				if(ss.score<ss0.score || (ss0.perfect && !ss.perfect) || (ss0.semiperfect && !ss.semiperfect)){break;}
				count++;
			}

			int x=(int)(numericID%count);
			if(x>0){
				SiteScore ss=sites.get(x);
				sites.set(0, ss);
				sites.set(x, ss0);
			}
			setFromSite(sites.get(0));
			return;
		}
		
//		assert(false) : "TODO: Proper strand orientation, and more.";
		//TODO: Also, this code appears to sometimes duplicate sitescores(?)
//		for(int i=0; i<list.size(); i++){
//			SiteScore ss=list.get(i);
//			if(ss.chrom==mate.chrom && Tools.min(Tools.absdifUnsigned(ss.start, mate.stop), Tools.absdifUnsigned(ss.stop, mate.start))<=maxPairDist){
//				list.set(0, ss);
//				list.set(i, ss0);
//				setFromSite(ss);
//				return;
//			}
//		}
		
		//If unsuccessful, recur unpaired.
		
		this.setPaired(false);
		mate.setPaired(false);
		setFromTopSite(randomIfAmbiguous, true, maxPairDist);
	}
	
	public void clearPairMapping(){
		clearMapping();
		if(mate!=null){mate.clearMapping();}
	}
	
	public void clearMapping(){
		clearSite();
		match=null;
		sites=null;
		setMapped(false);
		setPaired(false);
		if(mate!=null){mate.setPaired(false);}
	}
	
	public void clearSite(){
		chrom=-1;
		setStrand(0);
		start=-1;
		stop=-1;
//		errors=0;
		mapScore=0;
		gaps=null;
	}


	public void clearAnswers(boolean clearMate) {
//		assert(mate==null || (pairnum()==0 && mate.pairnum()==1)) : pairnum()+", "+mate.pairnum();
		clearSite();
		match=null;
		sites=null;
		flags=(flags&(SYNTHMASK|PAIRNUMMASK|SWAPMASK));
		if(clearMate && mate!=null){
			mate.clearSite();
			mate.match=null;
			mate.sites=null;
			mate.flags=(mate.flags&(SYNTHMASK|PAIRNUMMASK|SWAPMASK));
		}
//		assert(mate==null || (pairnum()==0 && mate.pairnum()==1)) : pairnum()+", "+mate.pairnum();
	}
	
	
	public boolean isBadPair(boolean requireCorrectStrands, boolean sameStrandPairs, int maxdist){
		if(mate==null || paired()){return false;}
		if(!mapped() || !mate.mapped()){return false;}
		if(chrom!=mate.chrom){return true;}
		
		{
			int inner;
			if(start<=mate.start){inner=mate.start-stop;}
			else{inner=start-mate.stop;}
			if(inner>maxdist){return true;}
		}
//		if(absdif(start, mate.start)>maxdist){return true;}
		if(requireCorrectStrands){
			if((strand()==mate.strand())!=sameStrandPairs){return true;}
		}
		if(!sameStrandPairs){
			if(strand()==Gene.PLUS && mate.strand()==Gene.MINUS){
				if(start>=mate.stop){return true;}
			}else if(strand()==Gene.MINUS && mate.strand()==Gene.PLUS){
				if(mate.start>=stop){return true;}
			}
		}
		return false;
	}
	
	public int countMismatches(){
		assert(match!=null);
		int x=0;
		for(byte b : match){
			if(b=='S'){x++;}
		}
		return x;
	}

	/**
	 * @param k
	 * @return
	 */
	public int numValidKmers(int k) {
		if(bases==null){return 0;}
		int len=0, counted=0;
		for(int i=0; i<bases.length; i++){
			byte b=bases[i];
			long x=AminoAcid.baseToNumber[b];
			if(x<0){len=0;}else{len++;}
			if(len>=k){counted++;}
		}
		return counted;
	}
	

	
	/**
	 * @param match string
	 * @return Total number of match, sub, del, ins, or clip symbols
	 */
	public static final int[] matchToMsdicn(byte[] match) {
		if(match==null || match.length<1){return null;}
		int[] msdicn=new int[6];
		
		byte mode='0', c='0';
		int current=0;
		for(int i=0; i<match.length; i++){
			c=match[i];
			if(Character.isDigit(c)){
				current=(current*10)+(c-'0');
			}else{
				if(mode==c){
					current=Tools.max(current+1, 2);
				}else{
					current=Tools.max(current, 1);

					if(mode=='m'){
						msdicn[0]+=current;
					}else if(mode=='S'){
						msdicn[1]+=current;
					}else if(mode=='D'){
						msdicn[2]+=current;
					}else if(mode=='I'){
						msdicn[3]+=current;
					}else if(mode=='C' || mode=='X' || mode=='Y'){
						msdicn[4]+=current;
					}else if(mode=='N' || mode=='R'){
						msdicn[5]+=current;
					}
					mode=c;
					current=0;
				}
			}
		}
		if(current>0 || !Character.isDigit(c)){
			current=Tools.max(current, 1);
			if(mode=='m'){
				msdicn[0]+=current;
			}else if(mode=='S'){
				msdicn[1]+=current;
			}else if(mode=='D'){
				msdicn[2]+=current;
			}else if(mode=='I'){
				msdicn[3]+=current;
			}else if(mode=='C' || mode=='X' || mode=='Y'){
				msdicn[4]+=current;
			}else if(mode=='N' || mode=='R'){
				msdicn[5]+=current;
			}
		}
		return msdicn;
	}

	
	/**
	 * @param match string
	 * @return Ref length of match string
	 */
	public static final int calcMatchLength(byte[] match) {
		if(match==null || match.length<1){return 0;}
		
		byte mode='0', c='0';
		int current=0;
		int len=0;
		for(int i=0; i<match.length; i++){
			c=match[i];
			if(Character.isDigit(c)){
				current=(current*10)+(c-'0');
			}else{
				if(mode==c){
					current=Tools.max(current+1, 2);
				}else{
					current=Tools.max(current, 1);

					if(mode=='m'){
						len+=current;
					}else if(mode=='S'){
						len+=current;
					}else if(mode=='D'){
						len+=current;
					}else if(mode=='I'){ //Do nothing
						//len+=current;
					}else if(mode=='C'){
						len+=current;
					}else if(mode=='X'){ //Not sure about this case, but adding seems fine
						len+=current;
//						assert(false) : new String(match);
					}else if(mode=='Y'){ //Do nothing
						//len+=current;
//						assert(false) : new String(match);
					}else if(mode=='N' || mode=='R'){
						len+=current;
					}
					mode=c;
					current=0;
				}
			}
		}
		if(current>0 || !Character.isDigit(c)){
			current=Tools.max(current, 1);
			if(mode=='m'){
				len+=current;
			}else if(mode=='S'){
				len+=current;
			}else if(mode=='D'){
				len+=current;
			}else if(mode=='I'){ //Do nothing
				//len+=current;
			}else if(mode=='C'){
				len+=current;
			}else if(mode=='X'){ //Not sure about this case, but adding seems fine
				len+=current;
				assert(false) : new String(match);
			}else if(mode=='Y'){ //Do nothing
				//len+=current;
//				assert(false) : new String(match);
			}else if(mode=='N' || mode=='R'){
				len+=current;
			}
		}
		return len;
	}

	public final float identity() {return identity(match);}
	
	public static final float identity(byte[] match) {
		if(FLAT_IDENTITY){
			return identityFlat(match, true);
		}else{
			return identitySkewed(match, true, true, false, false);
		}
	}
	
	public final boolean hasLongInsertion(int maxlen){
		return hasLongInsertion(match, maxlen);
	}
	
	public final boolean hasLongDeletion(int maxlen){
		return hasLongDeletion(match, maxlen);
	}
	
	public static final boolean hasLongInsertion(byte[] match, int maxlen){
		if(match==null || match.length<maxlen){return false;}
		byte prev='0';
		int len=0;
		for(byte b : match){
			if(b=='I' || b=='X' || b=='Y'){
				if(b==prev){len++;}
				else{len=1;}
				if(len>maxlen){return true;}
			}else{
				len=0;
			}
			prev=b;
		}
		return false;
	}
	
	public static final boolean hasLongDeletion(byte[] match, int maxlen){
		if(match==null || match.length<maxlen){return false;}
		byte prev='0';
		int len=0;
		for(byte b : match){
			if(b=='D'){
				if(b==prev){len++;}
				else{len=1;}
				if(len>maxlen){return true;}
			}else{
				len=0;
			}
			prev=b;
		}
		return false;
	}
	
	/**
	 * Handles short or long mode.
	 * @param match string
	 * @return Identity based on number of match, sub, del, ins, or N symbols
	 */
	public static final float identityFlat(byte[] match, boolean penalizeN) {
//		assert(false) : new String(match);
		if(match==null || match.length<1){return 0;}
		
		int good=0, bad=0, n=0;
		
		byte mode='0', c='0';
		int current=0;
		for(int i=0; i<match.length; i++){
			c=match[i];
			if(Character.isDigit(c)){
				current=(current*10)+(c-'0');
			}else{
				if(mode==c){
					current=Tools.max(current+1, 2);
				}else{
					current=Tools.max(current, 1);

					if(mode=='m'){
						good+=current;
//						System.out.println("G: mode="+(char)mode+", c="+(char)c+", current="+current+", good="+good+", bad="+bad);
					}else if(mode=='R' || mode=='N'){
						n+=current;
					}else if(mode=='C' || mode=='V'){
						//Do nothing
						//I assume this is clipped because it went off the end of a scaffold, and thus is irrelevant to identity
					}else if(mode!='0'){
						assert(mode=='S' || mode=='D' || mode=='I' || mode=='X' || mode=='Y') : (char)mode;
						if(mode!='D' || current<SamLine.INTRON_LIMIT){
							bad+=current;
						}
//						System.out.println("B: mode="+(char)mode+", c="+(char)c+", current="+current+", good="+good+", bad="+bad);
					}
					mode=c;
					current=0;
				}
			}
		}
		if(current>0 || !Character.isDigit(c)){
			current=Tools.max(current, 1);
			if(mode=='m'){
				good+=current;
			}else if(mode=='R' || mode=='N'){
				n+=current;
			}else if(mode=='C' || mode=='V'){
				//Do nothing
				//I assume this is clipped because it went off the end of a scaffold, and thus is irrelevant to identity
			}else if(mode!='0'){
				assert(mode=='S' || mode=='I' || mode=='X' || mode=='Y') : (char)mode;
				if(mode!='D' || current<SamLine.INTRON_LIMIT){
					bad+=current;
				}
//				System.out.println("B: mode="+(char)mode+", c="+(char)c+", current="+current+", good="+good+", bad="+bad);
			}
		}
		

		float good2=good+n*(penalizeN ? 0.25f : 0);
		float bad2=bad+n*(penalizeN ? 0.75f : 0);
		float r=good2/Tools.max(good2+bad2, 1);
//		assert(false) : new String(match)+"\nmode='"+(char)mode+"', current="+current+", good="+good+", bad="+bad;

//		System.out.println("match="+new String(match)+"\ngood="+good+", bad="+bad+", r="+r);
//		System.out.println(Arrays.toString(matchToMsdicn(match)));
		
		return r;
	}
	
	/**
	 * Handles short or long mode.
	 * @param match string
	 * @return Identity based on number of match, sub, del, ins, or N symbols
	 */
	public static final float identitySkewed(byte[] match, boolean penalizeN, boolean sqrt, boolean log, boolean single) {
//		assert(false) : new String(match);
		if(match==null || match.length<1){return 0;}
		
		int good=0, bad=0, n=0;
		
		byte mode='0', c='0';
		int current=0;
		for(int i=0; i<match.length; i++){
			c=match[i];
			if(Character.isDigit(c)){
				current=(current*10)+(c-'0');
			}else{
				if(mode==c){
					current=Tools.max(current+1, 2);
				}else{
					current=Tools.max(current, 1);

					if(mode=='m'){
						good+=current;
//						System.out.println("G: mode="+(char)mode+", c="+(char)c+", current="+current+", good="+good+", bad="+bad);
					}else if(mode=='D'){
						if(current<SamLine.INTRON_LIMIT){
							int x;
							
							if(sqrt){x=(int)Math.ceil(Math.sqrt(current));}
							else if(log){x=(int)Math.ceil(Tools.log2(current));}
							else{x=1;}
							
							bad+=(Tools.min(x, current));
						}
						
//						System.out.println("D: mode="+(char)mode+", c="+(char)c+", current="+current+", good="+good+", bad="+bad+", x="+x);
					}else if(mode=='R' || mode=='N'){
						n+=current;
					}else if(mode=='C' || mode=='V'){
						//Do nothing
						//I assume this is clipped because it went off the end of a scaffold, and thus is irrelevant to identity
					}else if(mode!='0'){
						assert(mode=='S' || mode=='I' || mode=='X' || mode=='Y') : (char)mode;
						bad+=current;
//						System.out.println("B: mode="+(char)mode+", c="+(char)c+", current="+current+", good="+good+", bad="+bad);
					}
					mode=c;
					current=0;
				}
			}
		}
		if(current>0 || !Character.isDigit(c)){
			current=Tools.max(current, 1);
			if(mode=='m'){
				good+=current;
			}else if(mode=='R' || mode=='N'){
				n+=current;
			}else if(mode=='C' || mode=='V'){
				//Do nothing
				//I assume this is clipped because it went off the end of a scaffold, and thus is irrelevant to identity
			}else if(mode!='0'){
				assert(mode=='S' || mode=='I' || mode=='X' || mode=='Y') : (char)mode;
				if(mode!='D' || current<SamLine.INTRON_LIMIT){
					bad+=current;
				}
//				System.out.println("B: mode="+(char)mode+", c="+(char)c+", current="+current+", good="+good+", bad="+bad);
			}
		}
		
		
		float good2=good+n*(penalizeN ? 0.25f : 0);
		float bad2=bad+n*(penalizeN ? 0.75f : 0);
		float r=good2/Tools.max(good2+bad2, 1);
//		assert(false) : new String(match)+"\nmode='"+(char)mode+"', current="+current+", good="+good+", bad="+bad;

//		System.out.println("match="+new String(match)+"\ngood="+good+", bad="+bad+", r="+r);
//		System.out.println(Arrays.toString(matchToMsdicn(match)));
		
		return r;
	}
	
	public boolean failsChastity(){
		if(id==null){return false;}
		int space=id.indexOf(' ');
		if(space<0 || space+5>id.length()){return false;}
		char a=id.charAt(space+1);
		char b=id.charAt(space+2);
		char c=id.charAt(space+3);
		char d=id.charAt(space+4);
		
		if(a=='/'){
			if(b<'1' || b>'4' || c!=':'){
				KillSwitch.kill("Strangely formatted read.  Please disable chastityfilter with the flag chastityfilter=f.  id:"+id);
			}
			return d=='Y';
		}else{
			assert(a=='1' || a=='2' || a=='3' || a=='4') : id;
			assert(b==':') : id;
			assert(d==':');
			if(a<'1' || a>'4' || b!=':' || d!=':'){
				KillSwitch.kill("Strangely formatted read.  Please disable chastityfilter with the flag chastityfilter=f.  id:"+id);
			}
			return c=='Y';
		}
	}
	
	public boolean failsBarcode(HashSet<String> set, boolean failIfNoBarcode){
		if(id==null){return false;}
		
		final int loc=id.lastIndexOf(':');
		final int loc2=Tools.max(id.indexOf(' '), id.indexOf('/'));
		if(loc<0 || loc<=loc2 || loc>=id.length()-1){
			return failIfNoBarcode;
		}
		
		if(set==null){
			for(int i=loc+1; i<id.length(); i++){
				char c=id.charAt(i);
				boolean ok=(c=='+' || AminoAcid.isFullyDefined(c));
				if(!ok){return true;}
			}
			return false;
		}else{
			String code=id.substring(loc+1);
			return !set.contains(code);
		}
	}

	/** Average based on summing quality scores */
	public int avgQuality(boolean countUndefined, int maxBases){
		return AVERAGE_QUALITY_BY_PROBABILITY ? avgQualityByProbability(countUndefined, maxBases) : avgQualityByScore(maxBases);
	}
	
	/** Average based on summing error probabilities */
	public int avgQualityByProbability(boolean countUndefined, int maxBases){
		if(bases==null || bases.length==0){return 0;}
		return avgQualityByProbability(bases, quality, countUndefined, maxBases);
	}
	
	/** Average based on summing error probabilities */
	public double avgQualityByProbabilityDouble(boolean countUndefined, int maxBases){
		if(bases==null || bases.length==0){return 0;}
		return avgQualityByProbability(bases, quality, countUndefined, maxBases);
	}
	
	/** Average based on summing error probabilities */
	public double probabilityErrorFree(boolean countUndefined, int maxBases){
		if(bases==null || bases.length==0){return 0;}
		return probabilityErrorFree(bases, quality, countUndefined, maxBases);
	}
	
	/** Average based on summing error probabilities */
	public static int avgQualityByProbability(byte[] bases, byte[] quality, boolean countUndefined, int maxBases){
		if(quality==null){return 40;}
		if(quality.length==0){return 0;}
		float e=expectedErrors(bases, quality, countUndefined, maxBases);
		final int div=(maxBases<1 ? quality.length : Tools.min(maxBases, quality.length));
		float p=e/div;
		return QualityTools.probErrorToPhred(p);
	}
	
	/** Average based on summing error probabilities */
	public static double avgQualityByProbabilityDouble(byte[] bases, byte[] quality, boolean countUndefined, int maxBases){
		if(quality==null){return 40;}
		if(quality.length==0){return 0;}
		float e=expectedErrors(bases, quality, countUndefined, maxBases);
		final int div=(maxBases<1 ? quality.length : Tools.min(maxBases, quality.length));
		float p=e/div;
		return QualityTools.probErrorToPhredDouble(p);
	}

	/** Average based on summing quality scores */
	public int avgQualityByScore(int maxBases){
		if(bases==null || bases.length==0){return 0;}
		if(quality==null){return 40;}
		int x=0, limit=(maxBases<1 ? quality.length : Tools.min(maxBases, quality.length));
		for(int i=0; i<limit; i++){
			byte b=quality[i];
			x+=(b<0 ? 0 : b);
		}
		return x/limit;
	}
	
	/** Used by BBMap tipsearch. */
	public int avgQualityFirstNBases(int n){
		if(bases==null || bases.length==0){return 0;}
		if(quality==null || n<1){return 40;}
		assert(quality!=null);
		int x=0;
		if(n>quality.length){return 0;}
		for(int i=0; i<n; i++){
			byte b=quality[i];
			x+=(b<0 ? 0 : b);
		}
		return x/n;
	}
	
	/** Used by BBMap tipsearch. */
	public int avgQualityLastNBases(int n){
		if(bases==null || bases.length==0){return 0;}
		if(quality==null || n<1){return 40;}
		assert(quality!=null);
		int x=0;
		if(n>quality.length){return 0;}
		for(int i=bases.length-n; i<bases.length; i++){
			byte b=quality[i];
			x+=(b<0 ? 0 : b);
		}
		return x/n;
	}
	
	/** Used by BBDuk. */
	public int minQuality(){
		byte min=41;
		if(bases!=null && quality!=null){
			for(byte q : quality){
				min=Tools.min(min, q);
			}
		}
		return min;
	}
	
	/** Used by BBMap tipsearch. */
	public byte minQualityFirstNBases(int n){
		if(bases==null || bases.length==0){return 0;}
		if(quality==null || n<1){return 41;}
		assert(quality!=null && n>0);
		if(n>quality.length){return 0;}
		byte x=quality[0];
		for(int i=1; i<n; i++){
			byte b=quality[i];
			if(b<x){x=b;}
		}
		return x;
	}
	
	/** Used by BBMap tipsearch. */
	public byte minQualityLastNBases(int n){
		if(bases==null || bases.length==0){return 0;}
		if(quality==null || n<1){return 41;}
		assert(quality!=null && n>0);
		if(n>quality.length){return 0;}
		byte x=quality[bases.length-n];
		for(int i=bases.length-n; i<bases.length; i++){
			byte b=quality[i];
			if(b<x){x=b;}
		}
		return x;
	}
	
	public boolean containsNonM(){
		assert(match!=null && valid());
		for(int i=0; i<match.length; i++){
			byte b=match[i];
			assert(b!='M');
			if(b>'9' && b!='m'){return true;}
		}
		return false;
	}
	
	public boolean containsNonNM(){
		assert(match!=null && valid());
		for(int i=0; i<match.length; i++){
			byte b=match[i];
			assert(b!='M');
			if(b>'9' && b!='m' && b!='N'){return true;}
		}
		return false;
	}
	
	public boolean containsVariants(){
		assert(match!=null && valid()) : (match==null)+", "+(valid())+"\n"+obj+"\n";
		for(int i=0; i<match.length; i++){
			byte b=match[i];
			assert(b!='M');
			if(b>'9' && b!='m' && b!='N' && b!='C'){return true;}
		}
		return false;
	}
	
	/**
	 * @return {m,S,C,N,I,D};
	 */
	public int[] countMatchSymbols(){
		int m=0, S=0, C=0, N=0, I=0, D=0;
		int current=0;
		for(byte b : match){
			if(Character.isDigit(b)){
				current=current*10+b-'0';
			}else{
				current=Tools.max(current, 1);
				if(b=='m'){
					m+=current;
				}else if(b=='S'){
					S+=current;
				}else if(b=='C'){
					C+=current;
				}else if(b=='N'){
					N+=current;
				}else if(b=='I'){
					I+=current;
				}else if(b=='D'){
					D+=current;
				}else{
					assert(false) : "Unhandled symbol "+(char)b;
				}
			}
		}
		return new int[] {m,S,C,N,I,D};
	}
	
	public boolean containsNonNMXY(){
		assert(match!=null && valid());
		for(int i=0; i<match.length; i++){
			byte b=match[i];
			assert(b!='M');
			if(b>'9' && b!='m' && b!='N' && b!='X' && b!='Y'){return true;}
		}
		return false;
	}
	
	public boolean containsSDI(){
		assert(match!=null && valid());
		for(int i=0; i<match.length; i++){
			byte b=match[i];
			assert(b!='M');
			if(b=='S' || b=='s' || b=='D' || b=='I'){return true;}
		}
		return false;
	}
	
	public boolean containsNonNMS(){
		assert(match!=null && valid());
		for(int i=0; i<match.length; i++){
			byte b=match[i];
			assert(b!='M');
			if(b>'9' && b!='m' && b!='s' && b!='N' && b!='S'){return true;}
		}
		return false;
	}
	
	public boolean containsConsecutiveS(int num){
		assert(match!=null && valid() && !shortmatch());
		int cnt=0;
		for(int i=0; i<match.length; i++){
			byte b=match[i];
			assert(b!='M');
			if(b=='S'){
				cnt++;
				if(cnt>=num){return true;}
			}else{
				cnt=0;
			}
		}
		return false;
	}
	
	public boolean containsIndels(){
		assert(match!=null && valid());
		for(int i=0; i<match.length; i++){
			byte b=match[i];
			if(b=='I' || b=='D' || b=='X' || b=='Y'){return true;}
		}
		return false;
	}
	
	public int countSubs(){
		assert(match!=null && valid() && !shortmatch()) : (match!=null)+", "+valid()+", "+shortmatch();
		return countSubs(match);
	}
	
	public boolean containsInMatch(char c){
		assert(match!=null && valid());
		for(int i=0; i<match.length; i++){
			byte b=match[i];
			if(b==c){return true;}
		}
		return false;
	}
	
	public boolean containsNocalls(){
		for(int i=0; i<bases.length; i++){
			byte b=bases[i];
			if(b=='N'){return true;}
		}
		return false;
	}
	
	public int countNocalls(){
		return countNocalls(bases);
	}
	
	public static int countSubs(byte[] match){
		int x=0;
		assert(match!=null);
		for(int i=0; i<match.length; i++){
			byte b=match[i];
			if(b=='S'){x++;}
			assert(!Character.isDigit(b));
		}
		return x;
	}
	
	public static boolean containsSubs(byte[] match){
		int x=0;
		assert(match!=null);
		for(int i=0; i<match.length; i++){
			byte b=match[i];
			if(b=='S'){return true;}
		}
		return false;
	}
	
	public static int countNocalls(byte[] match){
		int n=0;
		for(int i=0; i<match.length; i++){
			byte b=match[i];
			if(b=='N'){n++;}
		}
		return n;
	}
	
	public static int countInsertions(byte[] match){
		int n=0;
		for(int i=0; i<match.length; i++){
			byte b=match[i];
			if(b=='I'){n++;}
		}
		return n;
	}
	
	public static int countDeletions(byte[] match){
		int n=0;
		for(int i=0; i<match.length; i++){
			byte b=match[i];
			if(b=='D'){n++;}
		}
		return n;
	}
	
	public static int countInsertionEvents(byte[] match){
		int n=0;
		byte prev='N';
		for(int i=0; i<match.length; i++){
			byte b=match[i];
			if(b=='I' && prev!=b){n++;}
			prev=b;
		}
		return n;
	}
	
	public static int countDeletionEvents(byte[] match){
		int n=0;
		byte prev='N';
		for(int i=0; i<match.length; i++){
			byte b=match[i];
			if(b=='D' && prev!=b){n++;}
			prev=b;
		}
		return n;
	}
	
	public boolean containsNonACGTN(){
		if(bases==null){return false;}
		for(byte b : bases){
			if(AminoAcid.baseToNumberACGTN[b]<0){return true;}
		}
		return false;
	}
	
	public boolean containsUndefined(){
		if(bases==null){return false;}
		for(byte b : bases){
			if(AminoAcid.baseToNumber[b]<0){return true;}
		}
		return false;
	}
	
	public int countUndefined(){
		if(bases==null){return 0;}
		int n=0;
		for(byte b : bases){
			if(AminoAcid.baseToNumber[b]<0){n++;}
		}
		return n;
	}
	
	public boolean hasMinConsecutiveBases(final int min){
		if(bases==null){return min<=0;}
		int len=0;
		for(byte b : bases){
			if(AminoAcid.baseToNumber[b]<0){len=0;}
			else{
				len++;
				if(len>=min){return true;}
			}
		}
		return false;
	}
	
	
	/**
	 * @return The number of occurrences of the rarest base.
	 */
	public int minBaseCount(){
		if(bases==null){return 0;}
		int a=0, c=0, g=0, t=0;
		for(byte b : bases){
			if(b=='A'){a++;}
			else if(b=='C'){c++;}
			else if(b=='G'){g++;}
			else if(b=='T'){t++;}
		}
		return Tools.min(a, c, g, t);
	}
	
	public boolean containsXY(){
		assert(match!=null && valid());
		return containsXY(match);
	}
	
	public static boolean containsXY(byte[] match){
		if(match==null){return false;}
		for(int i=0; i<match.length; i++){
			byte b=match[i];
			if(b=='X' || b=='Y'){return true;}
		}
		return false;
	}
	
	public boolean containsXY2(){
		if(match==null || match.length<1){return false;}
		boolean b=(match[0]=='X' || match[match.length-1]=='Y');
		assert(!valid() || b==containsXY());
		return b;
	}
	
	public boolean containsXYC(){
		if(match==null || match.length<1){return false;}
		boolean b=(match[0]=='X' || match[match.length-1]=='Y');
		assert(!valid() || b==containsXY());
		return b || match[0]=='C' || match[match.length-1]=='C';
	}
	
	/** Replaces 'B' in match string with 'S', 'm', or 'N' */
	public boolean fixMatchB(){
		assert(match!=null);
		final ChromosomeArray ca;
		if(Data.GENOME_BUILD>=0){
			ca=Data.getChromosome(chrom);
		}else{
			ca=null;
		}
		boolean originallyShort=shortmatch();
		if(originallyShort){match=toLongMatchString(match);}
		int mloc=0, cloc=0, rloc=start;
		for(; mloc<match.length; mloc++){
			byte m=match[mloc];
			
			if(m=='B'){
				byte r=(ca==null ? (byte)'?' : ca.get(rloc));
				byte c=bases[cloc];
				if(r=='N' || c=='N'){
					match[mloc]='N';
				}else if(r==c || Character.toUpperCase(r)==Character.toUpperCase(c)){
					match[mloc]='m';
				}else{
					if(ca==null){
						if(originallyShort){
							match=toShortMatchString(match);
						}
						for(int i=0; i<match.length; i++){
							if(match[i]=='B'){match[i]='N';}
						}
						return false;
					}
					match[mloc]='S';
				}
				cloc++;
				rloc++;
			}else if(m=='m' || m=='S' || m=='N' || m=='s' || m=='C'){
				cloc++;
				rloc++;
			}else if(m=='D'){
				rloc++;
			}else if(m=='I' || m=='X' || m=='Y'){
				cloc++;
			}
		}
		if(originallyShort){match=toShortMatchString(match);}
		return true;
	}
	
	public float expectedTipErrors(boolean countUndefined, int maxBases){
		return expectedTipErrors(bases, quality, countUndefined, maxBases);
	}
	
	public float expectedErrorsIncludingMate(boolean countUndefined){
		float a=expectedErrors(countUndefined, length());
		float b=(mate==null ? 0 : mate.expectedErrors(countUndefined, mate.length()));
		return a+b;
	}
	
	public float expectedErrors(boolean countUndefined, int maxBases){
		return expectedErrors(bases, quality, countUndefined, maxBases);
	}
	
	public static float probabilityErrorFree(byte[] bases, byte[] quality, boolean countUndefined, int maxBases){
		if(quality==null){return 0;}
		final int limit=(maxBases<1 ? quality.length : Tools.min(maxBases, quality.length));
		final float[] array=QualityTools.PROB_CORRECT;
		assert(array[0]>0 && array[0]<1);
		float product=1;
		for(int i=0; i<limit; i++){
			byte b=bases[i];
			byte q=quality[i];
			if(AminoAcid.isFullyDefined(b)){
				product*=array[q];
			}else if(countUndefined){
				return 0;
			}
		}
		return product;
	}
	
	public static float expectedErrors(byte[] bases, byte[] quality, boolean countUndefined, int maxBases){
		if(quality==null){return 0;}
		final int limit=(maxBases<1 ? quality.length : Tools.min(maxBases, quality.length));
		final float[] array=QualityTools.PROB_ERROR;
		assert(array[0]>0 && array[0]<1);
		float sum=0;
		for(int i=0; i<limit; i++){
			byte b=bases[i];
			boolean d=AminoAcid.isFullyDefined(b);
			//assert((quality[i]==0)==d) : "Q="+quality[i]+" for base "+(char)b;
			if(d || countUndefined){
				byte q=(d ? quality[i] : 0);
				sum+=array[q];
			}
		}
		return sum;
	}
	
	/** Runs backwards instead of forwards */
	public static float expectedTipErrors(byte[] bases, byte[] quality, boolean countUndefined, int maxBases){
		if(quality==null){return 0;}
		final int limit;
		{
			final int limit0=(maxBases<1 ? quality.length : Tools.min(maxBases, quality.length));
			limit=quality.length-limit0;
		}
		final float[] array=QualityTools.PROB_ERROR;
		assert(array[0]>0 && array[0]<1);
		float sum=0;
		for(int i=quality.length-1; i>=limit; i--){
			byte b=bases[i];
			byte q=quality[i];
			if(AminoAcid.isFullyDefined(b)){
				sum+=array[q];
			}else{
				assert(q==0);
				if(countUndefined){sum+=0.75f;}
			}
		}
		return sum;
	}

	public int estimateErrors() {
		if(quality==null){return 0;}
		assert(match!=null) : this.toText(false);
		
		int count=0;
		for(int ci=0, mi=0; ci<bases.length && mi<match.length; mi++){
			
//			byte b=bases[ci];
			byte q=quality[ci];
			byte m=match[mi];
			if(m=='m' || m=='s' || m=='N'){
				ci++;
			}else if(m=='X' || m=='Y'){
				ci++;
				count++;
			}else if(m=='I'){
				ci++;
			}else if(m=='D'){
				
			}else if(m=='S'){
				ci++;
				if(q<19){
					count++;
				}
			}
			
		}
		return count;
	}
	
	/** {M, S, D, I, N, splice} */
	public int[] countErrors(int minSplice) {
		assert(match!=null) : this.toText(false);
		int m=0;
		int s=0;
		int d=0;
		int i=0;
		int n=0;
		int splice=0;
		
		byte prev=' ';
		int streak=0;
		minSplice=Tools.max(minSplice, 1);
		
		for(int pos=0; pos<match.length; pos++){
			final byte b=match[pos];
			
			if(b==prev){streak++;}else{streak=1;}
			
			if(b=='m'){
				m++;
			}else if(b=='N' || b=='C'){
				n++;
			}else if(b=='X' || b=='Y'){
				i++;
			}else if(b=='I'){
				i++;
			}else if(b=='D'){
				d++;
				if(streak==minSplice){splice++;}
			}else if(b=='S'){
				s++;
			}else{
				if(Character.isDigit(b) && shortmatch()){
					System.err.println("Warning! Found read in shortmatch form during countErrors():\n"+this); //Usually caused by verbose output.
					if(mate!=null){System.err.println("mate:\n"+mate.id+"\t"+new String(mate.bases));}
					System.err.println("Stack trace: ");
					new Exception().printStackTrace();
					match=toLongMatchString(match);
					setShortMatch(false);
					return countErrors(minSplice);
				}else{
					throw new RuntimeException("\nUnknown symbol "+(char)b+":\n"+new String(match)+"\n"+this+"\nshortmatch="+this.shortmatch());
				}
			}
			
			prev=b;
		}
		
//		assert(i==0) : i+"\n"+this+"\n"+new String(match)+"\n"+Arrays.toString(new int[] {m, s, d, i, n, splice});
		
		return new int[] {m, s, d, i, n, splice};
	}
	
	public static boolean isShortMatchString(byte[] match){
		byte last=' ';
		int streak=0;
		for(int i=0; i<match.length; i++){
			byte b=match[i];
			if(Character.isDigit(b)){return true;}
			if(b==last){
				streak++;
				if(streak>3){return true;}
			}else{
				streak=0;
				last=b;
			}
		}
		return false;
	}
	
	public void toShortMatchString(boolean doAssertion){
		if(shortmatch()){
			assert(!doAssertion);
			return;
		}
		match=toShortMatchString(match);
		setShortMatch(true);
	}
	
	public static byte[] toShortMatchString(byte[] match){
		if(match==null){return null;}
		assert(match.length>0);
		StringBuilder sb=new StringBuilder(10);
		
		byte prev=match[0];
		int count=1;
		for(int i=1; i<match.length; i++){
			byte m=match[i];
			assert(Character.isLetter(m) || m==0) : new String(match);
			if(m==0){System.err.println("Warning! Converting empty match string to short form.");}
			if(m==prev){count++;}
			else{
				sb.append((char)prev);
				if(count>2){sb.append(count);}
				else if(count==2){sb.append((char)prev);}
				prev=m;
				count=1;
			}
		}
		sb.append((char)prev);
		if(count>2){sb.append(count);}
		else if(count==2){sb.append((char)prev);}
		
		byte[] r=new byte[sb.length()];
		for(int i=0; i<sb.length(); i++){r[i]=(byte)sb.charAt(i);}
		return r;
	}
	
	public void toLongMatchString(boolean doAssertion){
		if(!shortmatch()){
			assert(!doAssertion);
			return;
		}
		match=toLongMatchString(match);
		setShortMatch(false);
	}
	
	public static byte[] toLongMatchString(byte[] shortmatch){
		if(shortmatch==null){return null;}
		assert(shortmatch.length>0);
		
		int count=0;
		int current=0;
		for(int i=0; i<shortmatch.length; i++){
			byte m=shortmatch[i];
			if(Character.isLetter(m)){
				count++;
				count+=(current>0 ? current-1 : 0);
				current=0;
			}else{
				assert(Character.isDigit(m));
				current=(current*10)+(m-48); //48 == '0'
			}
		}
		count+=(current>0 ? current-1 : 0);
		
		
		byte[] r=new byte[count];
		current=0;
		byte lastLetter='?';
		int j=0;
		for(int i=0; i<shortmatch.length; i++){
			byte m=shortmatch[i];
			if(Character.isLetter(m)){
				while(current>1){
					r[j]=lastLetter;
					current--;
					j++;
				}
				current=0;
				
				r[j]=m;
				j++;
				lastLetter=m;
			}else{
				assert(Character.isDigit(m));
				current=(current*10)+(m-48); //48 == '0'
			}
		}
		while(current>1){
			r[j]=lastLetter;
			current--;
			j++;
		}
		
		assert(r[r.length-1]>0);
		return r;
	}
	
	public String parseCustomRname(){
		for(int i=0, under=0; i<id.length(); i++){
			if(id.charAt(i)=='_'){
				under++;
				if(under==6){
					if(id.length()>i+1){
						return id.substring(i+1);
					}
					return null;
				}
			}
		}
		return null;
	}

	/** Bases of the read. */
	public byte[] bases;
	
	/** Quality of the read. */
	public byte[] quality;
	
	/** Alignment string.  E.G. mmmmDDDmmm would have 4 matching bases, then a 3-base deletion, then 3 matching bases. */
	public byte[] match;
	
	public int[] gaps;
	
	public String id;
	public long numericID;
	public int chrom;
	public int start;
	public int stop;
	
	public int copies=1;

	/** Errors detected */
	public int errors=0;
	
	/** Alignment score from BBMap.  Assumed to max at approx 100*bases.length */
	public int mapScore=0;
	
	public ArrayList<SiteScore> sites;
	public SiteScore originalSite; //Origin site for synthetic reads
	public Serializable obj=null; //For testing only
	public Read mate;
	
	public int flags;
	
	/** -1 if invalid.  TODO: Currently not retained through most processes. */
	private int insert=-1;
	
	/** A random number for deterministic usage.
	 * May decrease speed in multithreaded applications.
	 */
	public double rand=-1;

	public long time(){
		assert(obj!=null && obj.getClass()==Long.class) : obj;
		return ((Long)obj).longValue();
	}
	public int length(){return bases==null ? 0 : bases.length;}
	public int qlength(){return quality==null ? 0 : quality.length;}
	public int mateLength(){return mate==null ? 0 : mate.length();}
	public int mateCount(){return mate==null ? 0 : 1;}
	public boolean mateMapped(){return mate==null ? false : mate.mapped();}
	public long countMateBytes(){return mate==null ? 0 : mate.countBytes();}
	public long countMateFastqBytes(){return mate==null ? 0 : mate.countFastqBytes();}
	
	//In memory
	public long countBytes(){
		long sum=125; //Approximate per-read overhead
		sum+=(bases==null ? 0 : bases.length+16);
		sum+=(quality==null ? 0 : quality.length+16);
		sum+=(id==null ? 0 : id.length()*2+16);
		return sum;
	}
	
	//On disk
	public long countFastqBytes(){
		long sum=6;
		sum+=(bases==null ? 0 : bases.length);
		sum+=(quality==null ? 0 : quality.length);
		sum+=(id==null ? 0 : id.length());
		return sum;
	}
	
	public boolean untrim(){
		if(obj==null || obj.getClass()!=TrimRead.class){return false;}
		((TrimRead)obj).untrim();
		obj=null;
		return true;
	}
	
	public int trailingLowerCase(){
		for(int i=bases.length-1; i>=0;){
			if(Character.isLowerCase(bases[i])){
				i--;
			}else{
				return bases.length-i-1;
			}
		}
		return bases.length;
	}
	public int leadingLowerCase(){
		for(int i=0; i<bases.length; i++){
			if(!Character.isLowerCase(bases[i])){return i;}
		}
		return bases.length;
	}

	public char strandChar(){return Gene.strandCodes2[strand()];}
	public byte strand(){return (byte)(flags&1);}
	public boolean mapped(){return (flags&MAPPEDMASK)==MAPPEDMASK;}
	public boolean paired(){return (flags&PAIREDMASK)==PAIREDMASK;}
	public boolean synthetic(){return (flags&SYNTHMASK)==SYNTHMASK;}
	public boolean ambiguous(){return (flags&AMBIMASK)==AMBIMASK;}
	public boolean perfect(){return (flags&PERFECTMASK)==PERFECTMASK;}
//	public boolean semiperfect(){return perfect() ? true : list!=null && list.size()>0 ? list.get(0).semiperfect : false;} //TODO: This is a hack.  Add a semiperfect flag.
	public boolean rescued(){return (flags&RESCUEDMASK)==RESCUEDMASK;}
	public boolean discarded(){return (flags&DISCARDMASK)==DISCARDMASK;}
	public boolean invalid(){return (flags&INVALIDMASK)==INVALIDMASK;}
	public boolean swapped(){return (flags&SWAPMASK)==SWAPMASK;}
	public boolean shortmatch(){return (flags&SHORTMATCHMASK)==SHORTMATCHMASK;}
	public boolean insertvalid(){return (flags&INSERTMASK)==INSERTMASK;}
	public boolean hasadapter(){return (flags&ADAPTERMASK)==ADAPTERMASK;}
	public boolean secondary(){return (flags&SECONDARYMASK)==SECONDARYMASK;}
	public boolean aminoacid(){return (flags&AAMASK)==AAMASK;}
	public boolean junk(){return (flags&JUNKMASK)==JUNKMASK;}
	public boolean validated(){return (flags&VALIDATEDMASK)==VALIDATEDMASK;}
	
	/** For paired ends: 0 for read1, 1 for read2 */
	public int pairnum(){return (flags&PAIRNUMMASK)>>PAIRNUMSHIFT;}
	public boolean valid(){return !invalid();}

	public boolean getFlag(int mask){return (flags&mask)==mask;}
	public int flagToNumber(int mask){return (flags&mask)==mask ? 1 : 0;}
	
	public void setFlag(int mask, boolean b){
		flags=(flags&~mask);
		if(b){flags|=mask;}
	}
	
	public void setStrand(int b){
		assert(b==1 || b==0);
		flags=(flags&(~1))|b;
	}
	
	/** For paired ends: 0 for read1, 1 for read2 */
	public void setPairnum(int b){
//		System.err.println("Setting pairnum to "+b+" for "+id);
//		assert(!id.equals("2_chr1_0_1853883_1853982_1845883_ecoli_K12") || b==1);
		assert(b==1 || b==0);
		flags=(flags&(~PAIRNUMMASK))|(b<<PAIRNUMSHIFT);
//		assert(pairnum()==b);
	}
	
	public void setPaired(boolean b){
		flags=(flags&~PAIREDMASK);
		if(b){flags|=PAIREDMASK;}
	}
	
	public void setSynthetic(boolean b){
		flags=(flags&~SYNTHMASK);
		if(b){flags|=SYNTHMASK;}
	}
	
	public void setAmbiguous(boolean b){
		flags=(flags&~AMBIMASK);
		if(b){flags|=AMBIMASK;}
	}
	
	public boolean setPerfectFlag(int maxScore){
		final SiteScore ss=topSite();
		if(ss==null){
			setPerfect(false);
		}else{
			assert(ss.slowScore<=maxScore) : maxScore+", "+ss.slowScore+", "+ss.toText();
			
			if(ss.slowScore==maxScore || ss.perfect){
				assert(testMatchPerfection(true)) : "\n"+ss+"\n"+maxScore+"\n"+this+"\n"+mate+"\n";
				setPerfect(true);
			}else{
				boolean flag=testMatchPerfection(false);
				setPerfect(flag);
				assert(flag || !ss.perfect) : "flag="+flag+", ss.perfect="+ss.perfect+"\nmatch="+new String(match)+"\n"+this.toText(false);
				assert(!flag || ss.slowScore>=maxScore) : "\n"+ss+"\n"+maxScore+"\n"+this+"\n"+mate+"\n";
			}
		}
		return perfect();
	}
	
	private boolean testMatchPerfection(boolean returnIfNoMatch){
		if(match==null){return returnIfNoMatch;}
		boolean flag=(match.length==bases.length);
		if(shortmatch()){
			flag=(match.length==0 || match[0]=='m');
			for(int i=0; i<match.length && flag; i++){flag=(match[i]=='m' || Character.isDigit(match[i]));}
		}else{
			for(int i=0; i<match.length && flag; i++){flag=(match[i]=='m');}
		}
		for(int i=0; i<bases.length && flag; i++){flag=(bases[i]!='N');}
		return flag;
	}

	/**
	 * @return
	 */
	public float gc() {
		if(bases==null || bases.length<1){return 0;}
		int at=0, gc=0;
		for(byte b : bases){
			int x=AminoAcid.baseToNumber[b];
			if(x>-1){
				if(x==0 || x==3){at++;}
				else{gc++;}
			}
		}
		if(gc<1){return 0;}
		return gc*1f/(at+gc);
	}

	/**
	 * @param swapFrom
	 * @param swapTo
	 * @return number of swaps
	 */
	public int swapBase(byte swapFrom, byte swapTo) {
		if(bases==null){return 0;}
		int swaps=0;
		for(int i=0; i<bases.length; i++){
			if(bases[i]==swapFrom){
				bases[i]=swapTo;
				swaps++;
			}
		}
		return swaps;
	}

	/**
	 * @param remap Table of new values
	 */
	public void remap(byte[] remap) {
		if(bases==null){return;}
		for(int i=0; i<bases.length; i++){
			bases[i]=remap[bases[i]];
		}
	}

	/**
	 * @param remap Table of new values
	 */
	public int remapAndCount(byte[] remap) {
		if(bases==null){return 0;}
		int swaps=0;
		for(int i=0; i<bases.length; i++){
			byte a=bases[i];
			byte b=remap[a];
			if(a!=b){
				bases[i]=b;
				swaps++;
			}
		}
		return swaps;
	}
	
	public int convertUndefinedTo(byte b){
		if(bases==null){return 0;}
		int changed=0;
		for(int i=0; i<bases.length; i++){
			if(b<0 || AminoAcid.baseToNumberACGTN[bases[i]]<0){
				changed++;
				bases[i]=b;
				if(quality!=null){quality[i]=0;}
			}
		}
		return changed;
	}
	
	public void swapBasesWithMate(){
		if(mate==null){
			assert(false);
			return;
		}
		byte[] temp=bases;
		bases=mate.bases;
		mate.bases=temp;
		temp=quality;
		quality=mate.quality;
		mate.quality=temp;
	}
	
	public int insert(){
		return insertvalid() ? insert : -1;
	}
	
	public int insertSizeMapped(boolean ignoreStrand){
		return insertSizeMapped(this, mate, ignoreStrand);
	}
	
	public static int insertSizeMapped(Read r1, Read r2, boolean ignoreStrand){
//		assert(false) : ignoreStrand+", "+(r2==null)+", "+(r1.mapped())+", "+(r2.mapped())+", "+(r1.strand()==r2.strand())+", "+r1.strand()+", "+r2.strand();
		if(ignoreStrand || r2==null || !r1.mapped() || !r2.mapped() || r1.strand()==r2.strand()){return insertSizeMapped_Unstranded(r1, r2);}
		return insertSizeMapped_PlusLeft(r1, r2);
	}
	
	/** TODO: This is not correct when the insert is shorter than a read's bases with same-strand reads */
	public static int insertSizeMapped_PlusLeft(Read r1, Read r2){
		if(r1.strand()>r2.strand()){return insertSizeMapped_PlusLeft(r2, r1);}
		if(r1.strand()==r2.strand() || r1.start>r2.stop){return insertSizeMapped_Unstranded(r2, r1);} //So r1 is always on the left.
//		if(!mapped() || !mate.mapped()){return 0;}
		if(r1.chrom!=r2.chrom){return 0;}
		if(r1.start==r1.stop || r2.start==r2.stop){return 0;} //???
		
		int a=r1.length();
		int b=r2.length();
		int mid=r2.start-r1.stop-1;
		if(-mid>=a+b){return insertSizeMapped_Unstranded(r1, r2);} //Not properly oriented; plus read is to the right of minus read
		return mid+a+b;
	}
	
	public static int insertSizeMapped_Unstranded(Read r1, Read r2){
		if(r2==null){return r1.start==r1.stop ? 0 : r1.stop-r1.start+1;}
		
		if(r1.start>r2.start){return insertSizeMapped_Unstranded(r2, r1);} //So r1 is always on the left side.
		
//		if(!mapped() || !mate.mapped()){return 0;}
		if(r1.start==r1.stop || r2.start==r2.stop){return 0;} //???
		
		if(r1.chrom!=r2.chrom){return 0;}
		int a=r1.length();
		int b=r2.length();
		if(false && Tools.overlap(r1.start, r1.stop, r2.start, r2.stop)){
			//This does not handle very short inserts
			return Tools.max(r1.stop, r2.stop)-Tools.min(r1.start, r2.start)+1;
			
		}else{
			if(r1.start<r2.start){
				int mid=r2.start-r1.stop-1;
//				assert(false) : mid+", "+a+", "+b;
//				if(-mid>a && -mid>b){return Tools.min(a, b);} //Strange situation, no way to guess insert size
				if(-mid>=a+b){return 0;} //Strange situation, no way to guess insert size
				return mid+a+b;
			}else{
				assert(r1.start==r2.start);
				return Tools.min(a, b);
			}
		}
	}
	
	public int insertSizeOriginalSite(){
		if(mate==null){
//			System.err.println("A: "+(originalSite==null ? "null" : (originalSite.stop-originalSite.start+1)));
			return (originalSite==null ? -1 : originalSite.stop-originalSite.start+1);
		}
		
		final SiteScore ssa=originalSite, ssb=mate.originalSite;
		final int x;
		if(ssa==null || ssb==null){
//			System.err.println("B: 0");
			x=0;
		}else{
			x=insertSize(ssa, ssb, bases.length, mate.length());
		}
		
		assert(pairnum()>=mate.pairnum() || x==mate.insertSizeOriginalSite());
		return x;
	}
	
	public static int insertSize(SiteScore ssa, SiteScore ssb, int lena, int lenb){
		return insertSize(ssa.chrom, ssb.chrom, ssa.start, ssb.start, ssa.stop, ssb.stop, lena, lenb);
	}
	
	public static int insertSize(int chroma, int chromb, int starta, int startb, int stopa, int stopb, int lena, int lenb){
		
		final int x;

		//		if(mate==null || ){return bases==null ? 0 : bases.length;}
		if(chroma!=chromb){x=0;}
		else{

			if(Tools.overlap(starta, stopa, startb, stopb)){
				x=Tools.max(stopa, stopb)-Tools.min(starta, startb)+1;
//				System.err.println("C: "+x);
			}else{
				if(starta<=startb){
					int mid=startb-stopa-1;
					//				assert(false) : mid+", "+a+", "+b;
					x=mid+lena+lenb;
//					System.err.println("D: "+x);
				}else{
					int mid=starta-stopb-1;
					//				assert(false) : mid+", "+a+", "+b;
					x=mid+lena+lenb;
//					System.err.println("E: "+x);
				}
			}
		}
		return x;
	}
	
	public Read subRead(int from, int to){
		Read r=this.clone();
		r.bases=KillSwitch.copyOfRange(bases, from, to);
		r.quality=(quality==null ? null : KillSwitch.copyOfRange(quality, from, to));
		r.mate=null;
//		assert(Tools.indexOf(r.bases, (byte)'J')<0);
		return r;
	}
	
	public Read joinRead(){
		if(insert<1 || mate==null || !insertvalid()){return this;}
		assert(insert>9 || bases.length<20) : "Perhaps old read format is being used?  This appears to be a quality value, not an insert.\n"+this+"\n\n"+mate+"\n";
		return joinRead(this, mate, insert);
	}
	
	public Read joinRead(int x){
		if(x<1 || mate==null){return this;}
		assert(x>9 || bases.length<20) : "Perhaps old read format is being used?  This appears to be a quality value, not an insert.\n"+this+"\n\n"+mate+"\n";
		return joinRead(this, mate, x);
	}
	
	public static Read joinRead(Read a, Read b, int insert){
		assert(a!=null && b!=null && insert>0);
		final int lengthSum=a.length()+b.length();
		final int overlap=Tools.min(insert, lengthSum-insert);
		
//		System.err.println(insert);
		final byte[] bases=new byte[insert], abases=a.bases, bbases=b.bases;
		final byte[] aquals=a.quality, bquals=b.quality;
		final byte[] quals=(aquals==null || bquals==null ? null : new byte[insert]);
		assert(aquals==null || (aquals.length==abases.length && bquals.length==bbases.length));
		
		int mismatches=0;
		
		int start, stop;
		
		if(overlap<=0){//Simple join in which there is no overlap
			int lim=insert-b.length();
			if(quals==null){
				for(int i=0; i<a.length(); i++){
					bases[i]=abases[i];
				}
				for(int i=a.length(); i<lim; i++){
					bases[i]='N';
				}
				for(int i=0; i<b.length(); i++){
					bases[i+lim]=bbases[i];
				}
			}else{
				for(int i=0; i<a.length(); i++){
					bases[i]=abases[i];
					quals[i]=aquals[i];
				}
				for(int i=a.length(); i<lim; i++){
					bases[i]='N';
					quals[i]=0;
				}
				for(int i=0; i<b.length(); i++){
					bases[i+lim]=bbases[i];
					quals[i+lim]=bquals[i];
				}
			}
			
			start=Tools.min(a.start, b.start);
//			stop=start+insert-1;
			stop=Tools.max(a.stop, b.stop);
			
//		}else if(insert>=a.length() && insert>=b.length()){ //Overlapped join, proper orientation
//			final int lim1=a.length()-overlap;
//			final int lim2=a.length();
//			for(int i=0; i<lim1; i++){
//				bases[i]=abases[i];
//				quals[i]=aquals[i];
//			}
//			for(int i=lim1, j=0; i<lim2; i++, j++){
//				assert(false) : "TODO";
//				bases[i]='N';
//				quals[i]=0;
//			}
//			for(int i=lim2, j=overlap; i<bases.length; i++, j++){
//				bases[i]=bbases[j];
//				quals[i]=bquals[j];
//			}
		}else{ //reads go off ends of molecule.
			if(quals==null){
				for(int i=0; i<a.length() && i<bases.length; i++){
					bases[i]=abases[i];
				}
				for(int i=bases.length-1, j=b.length()-1; i>=0 && j>=0; i--, j--){
					byte ca=bases[i], cb=bbases[j];
					if(ca==0 || ca=='N'){
						bases[i]=cb;
					}else if(ca==cb){
					}else{
						bases[i]=(ca>=cb ? ca : cb);
						if(ca!='N' && cb!='N'){mismatches++;}
					}
				}
			}else{
				for(int i=0; i<a.length() && i<bases.length; i++){
					bases[i]=abases[i];
					quals[i]=aquals[i];
				}
				for(int i=bases.length-1, j=b.length()-1; i>=0 && j>=0; i--, j--){
					byte ca=bases[i], cb=bbases[j];
					byte qa=quals[i], qb=bquals[j];
					if(ca==0 || ca=='N'){
						bases[i]=cb;
						quals[i]=qb;
					}else if(cb==0 || cb=='N'){
						//do nothing
					}else if(ca==cb){
						quals[i]=(byte)Tools.min((Tools.max(qa, qb)+Tools.min(qa, qb)/4), MAX_MERGE_QUALITY);
					}else{
						bases[i]=(qa>qb ? ca : qa<qb ? cb : (byte)'N');
						quals[i]=(byte)(Tools.max(qa, qb)-Tools.min(qa, qb));
						if(ca!='N' && cb!='N'){mismatches++;}
					}
				}
			}
			
			if(a.strand()==0){
				start=a.start;
//				stop=start+insert-1;
				stop=b.stop;
			}else{
				stop=a.stop;
//				start=stop-insert+1;
				start=b.start;
			}
			if(start>stop){
				start=Tools.min(a.start, b.start);
				stop=Tools.max(a.stop, b.stop);
			}
		}
//		assert(mismatches>=countMismatches(a, b, insert, 999));
//		System.err.println(mismatches);
		if(a.chrom==0 || start==stop || (!a.mapped() && !a.synthetic())){start=stop=a.chrom=0;}
		
//		System.err.println(bases.length+", "+start+", "+stop);
		
		Read r=new Read(bases, a.chrom, start, stop, a.id, null, a.numericID, a.flags);
		r.quality=quals; //This prevents quality from getting capped.
		if(a.chrom==0 || start==stop || (!a.mapped() && !a.synthetic())){r.setMapped(true);}
		r.setInsert(insert);
		r.setPaired(false);
		r.copies=a.copies;
		r.mapScore=a.mapScore+b.mapScore;
		if(overlap<=0){
			r.mapScore=a.mapScore+b.mapScore;
			r.errors=a.errors+b.errors;
			//TODO r.gaps=?
		}else{//Hard to calculate
			r.mapScore=(int)((a.mapScore*(long)a.length()+b.mapScore*(long)b.length())/insert);
			r.errors=a.errors;
		}
		
		
		assert(r.insertvalid()) : "\n\n"+a.toText(false)+"\n\n"+b.toText(false)+"\n\n"+r.toText(false)+"\n\n";
		assert(r.insert()==r.length()) : r.insert()+"\n\n"+a.toText(false)+"\n\n"+b.toText(false)+"\n\n"+r.toText(false)+"\n\n";
//		assert(false) : "\n\n"+a.toText(false)+"\n\n"+b.toText(false)+"\n\n"+r.toText(false)+"\n\n";
		
		//TODO: Triggered by BBMerge in useratio mode for some reason.
//		assert(Shared.anomaly || (a.insertSizeMapped(false)>0 == r.insertSizeMapped(false)>0)) : 
//			"\n"+r.length()+"\n"+r.insert()+"\n"+r.insertSizeMapped(false)+"\n"+a.insert()+"\n"+a.insertSizeMapped(false)+
//			"\n\n"+a.toText(false)+"\n\n"+b.toText(false)+"\n\n"+r.toText(false)+"\n\n"; 
		
		return r;
	}

	/**
	 * @param minlen
	 * @param maxlen
	 * @return
	 */
	public ArrayList<Read> split(int minlen, int maxlen) {
		int len=bases==null ? 0 : bases.length;
		if(len<minlen){return null;}
		int parts=(len+maxlen-1)/maxlen;
		ArrayList<Read> subreads=new ArrayList<Read>(parts);
		if(len<=maxlen){
			subreads.add(this);
		}else{
			float ideal=Tools.max(minlen, len/(float)parts);
			int actual=(int)ideal;
			assert(false) : "TODO"; //Some assertion goes here, I forget what
			for(int i=0; i<parts; i++){
				int a=i*actual;
				int b=(i+1)*actual;
				if(b>bases.length){b=bases.length;}
//				if(b-a<)
				byte[] subbases=KillSwitch.copyOfRange(bases, a, b);
				byte[] subquals=(quality==null ? null : KillSwitch.copyOfRange(quality, a, b+1));
				Read r=new Read(subbases, -1, -1, -1, id+"_"+i, subquals, numericID, flags);
				subreads.add(r);
			}
		}
		return subreads;
	}
	
	/** Generate and return an array of canonical kmers for this read */
	public long[] toKmers(final int k, final int gap, long[] kmers, boolean makeCanonical, Kmer longkmer) {
		if(gap>0){throw new RuntimeException("Gapped reads: TODO");}
		if(k>31){return toLongKmers(k, kmers, makeCanonical, longkmer);}
		if(bases==null || bases.length<k+gap){return null;}
		
		final int arraylen=bases.length-k+1;
		if(kmers==null || kmers.length!=arraylen){kmers=new long[arraylen];}
		Arrays.fill(kmers, -1);
		
		final int shift=2*k;
		final int shift2=shift-2;
		final long mask=~((-1L)<<shift);
		long kmer=0, rkmer=0;
		int len=0;
		
		for(int i=0; i<bases.length; i++){
			byte b=bases[i];
			long x=Dedupe.baseToNumber[b];
			long x2=Dedupe.baseToComplementNumber[b];
			kmer=((kmer<<2)|x)&mask;
			rkmer=(rkmer>>>2)|(x2<<shift2);
			if(AminoAcid.isFullyDefined(b)){len++;}else{len=0;}
			if(len>=k){
				kmers[i-k+1]=makeCanonical ? Tools.max(kmer, rkmer) : kmer;
			}
		}
		return kmers;
	}
	
//	/** Generate and return an array of canonical kmers for this read */
//	public long[] toKmers(final int k, final int gap, long[] kmers, boolean makeCanonical, Kmer longkmer) {
//		if(gap>0){throw new RuntimeException("Gapped reads: TODO");}
//		if(k>31){return toLongKmers(k, kmers, makeCanonical, longkmer);}
//		if(bases==null || bases.length<k+gap){return null;}
//		
//		final int kbits=2*k;
//		final long mask=~((-1L)<<(kbits));
//		
//		int len=0;
//		long kmer=0;
//		final int arraylen=bases.length-k+1;
//		if(kmers==null || kmers.length!=arraylen){kmers=new long[arraylen];}
//		Arrays.fill(kmers, -1);
//		
//		for(int i=0; i<bases.length; i++){
//			byte b=bases[i];
//			int x=AminoAcid.baseToNumber[b];
//			if(x<0){
//				len=0;
//				kmer=0;
//			}else{
//				kmer=((kmer<<2)|x)&mask;
//				len++;
//
//				if(len>=k){
//					kmers[i-k+1]=kmer;
//				}
//			}
//		}
//		
////		System.out.println(new String(bases));
////		System.out.println(Arrays.toString(kmers));
//		
//		if(makeCanonical){
//			this.reverseComplement();
//			len=0;
//			kmer=0;
//			for(int i=0, j=bases.length-1; i<bases.length; i++, j--){
//				byte b=bases[i];
//				int x=AminoAcid.baseToNumber[b];
//				if(x<0){
//					len=0;
//					kmer=0;
//				}else{
//					kmer=((kmer<<2)|x)&mask;
//					len++;
//
//					if(len>=k){
//						assert(kmer==AminoAcid.reverseComplementBinaryFast(kmers[j], k));
//						kmers[j]=Tools.max(kmers[j], kmer);
//					}
//				}
//			}
//			this.reverseComplement();
//			
////			System.out.println(Arrays.toString(kmers));
//		}
//		
//		
//		return kmers;
//	}
	
	/** Generate and return an array of canonical kmers for this read */
	public long[] toLongKmers(final int k, long[] kmers, boolean makeCanonical, Kmer kmer) {
		assert(k>31) : k;
		assert(makeCanonical);
		if(bases==null || bases.length<k){return null;}
		kmer.clear();
		
		final int arraylen=bases.length-k+1;
		if(kmers==null || kmers.length!=arraylen){kmers=new long[arraylen];}
		Arrays.fill(kmers, -1);
		
		for(int i=0; i<bases.length; i++){
			byte b=bases[i];
			kmer.addRight(b);
			if(!AminoAcid.isFullyDefined(b)){kmer.clear();}
			if(kmer.len>=k){
				kmers[i-k+1]=kmer.xor();
			}
		}
		
		return kmers;
	}
	
//	/** Generate and return an array of canonical kmers for this read */
//	public long[] toLongKmers(final int k, long[] kmers, boolean makeCanonical, Kmer longkmer) {
//		assert(k>31) : k;
//		if(bases==null || bases.length<k){return null;}
//		
//		final int kbits=2*k;
//		final long mask=Long.MAX_VALUE;
//		
//		int len=0;
//		long kmer=0;
//		final int arraylen=bases.length-k+1;
//		if(kmers==null || kmers.length!=arraylen){kmers=new long[arraylen];}
//		Arrays.fill(kmers, -1);
//		
//		
//		final int tailshift=k%32;
//		final int tailshiftbits=tailshift*2;
//		
//		for(int i=0; i<bases.length; i++){
//			byte b=bases[i];
//			int x=AminoAcid.baseToNumber[b];
//			if(x<0){
//				len=0;
//				kmer=0;
//			}else{
//				kmer=Long.rotateLeft(kmer, 2);
//				kmer=kmer^x;
//				len++;
//
//				if(len>=k){
//					long x2=AminoAcid.baseToNumber[bases[i-k]];
//					kmer=kmer^(x2<<tailshiftbits);
//					kmers[i-k+1]=kmer;
//				}
//			}
//		}
//		if(makeCanonical){
//			this.reverseComplement();
//			len=0;
//			kmer=0;
//			for(int i=0, j=bases.length-1; i<bases.length; i++, j--){
//				byte b=bases[i];
//				int x=AminoAcid.baseToNumber[b];
//				if(x<0){
//					len=0;
//					kmer=0;
//				}else{
//					kmer=Long.rotateLeft(kmer, 2);
//					kmer=kmer^x;
//					len++;
//
//					if(len>=k){
//						long x2=AminoAcid.baseToNumber[bases[i-k]];
//						kmer=kmer^(x2<<tailshiftbits);
//						kmers[j]=mask&(Tools.max(kmers[j], kmer));
//					}
//				}
//			}
//			this.reverseComplement();
//		}else{
//			assert(false) : "Long kmers should be made canonical here because they cannot be canonicized later.";
//		}
//		
//		return kmers;
//	}
	
	public static final boolean CHECKSITES(Read r, byte[] basesM){
		return CHECKSITES(r.sites, r.bases, basesM, r.numericID, true);
	}
	
	public static final boolean CHECKSITES(Read r, byte[] basesM, boolean verifySorted){
		return CHECKSITES(r.sites, r.bases, basesM, r.numericID, verifySorted);
	}
	
	public static final boolean CHECKSITES(ArrayList<SiteScore> list, byte[] basesP, byte[] basesM, long id){
		return CHECKSITES(list, basesP, basesM, id, true);
	}
	
	public static final boolean CHECKSITES(ArrayList<SiteScore> list, byte[] basesP, byte[] basesM, long id, boolean verifySorted){
		return true; //Temporarily disabled
//		if(list==null || list.isEmpty()){return true;}
//		SiteScore prev=null;
//		for(int i=0; i<list.size(); i++){
//			SiteScore ss=list.get(i);
//			if(ss.strand==Gene.MINUS && basesM==null && basesP!=null){basesM=AminoAcid.reverseComplementBases(basesP);}
//			byte[] bases=(ss.strand==Gene.PLUS ? basesP : basesM);
//			if(verbose){System.err.println("Checking site "+i+": "+ss);}
//			boolean b=CHECKSITE(ss, bases, id);
//			assert(b) : id+"\n"+new String(basesP)+"\n"+ss+"\n";
//			if(verbose){System.err.println("Checked site "+i+" = "+ss+"\nss.p="+ss.perfect+", ss.sp="+ss.semiperfect);}
//			if(!b){
////				System.err.println("Error at SiteScore "+i+": ss.p="+ss.perfect+", ss.sp="+ss.semiperfect);
//				return false;
//			}
//			if(verifySorted && prev!=null && ss.score>prev.score){
//				if(verbose){System.err.println("verifySorted failed.");}
//				return false;
//			}
//			prev=ss;
//		}
//		return true;
	}
	
	/** Makes sure 'bases' is for correct strand. */
	public static final boolean CHECKORDER(ArrayList<SiteScore> list){
		if(list==null || list.size()<2){return true;}
		SiteScore prev=list.get(0);
		for(int i=0; i<list.size(); i++){
			SiteScore ss=list.get(i);
			if(ss.score>prev.score){return false;}
			prev=ss;
		}
		return true;
	}
	
	/** Makes sure 'bases' is for correct strand. */
	public static final boolean CHECKSITE(SiteScore ss, byte[] basesP, byte[] basesM, long id){
		return CHECKSITE(ss, ss.plus() ? basesP : basesM, id);
	}
	
	/** Make sure 'bases' is for correct strand! */
	public static final boolean CHECKSITE(SiteScore ss, byte[] bases, long id){
		return true; //Temporarily disabled
//		if(ss==null){return true;}
////		System.err.println("Checking site "+ss+"\nss.p="+ss.perfect+", ss.sp="+ss.semiperfect+", bases="+new String(bases));
//		if(ss.perfect){assert(ss.semiperfect) : ss+"\n"+new String(bases);}
//		if(ss.gaps!=null){
//			if(ss.gaps[0]!=ss.start || ss.gaps[ss.gaps.length-1]!=ss.stop){return false;}
////			assert(ss.gaps[0]==ss.start && ss.gaps[ss.gaps.length-1]==ss.stop);
//		}
//		
//		if(!(ss.pairedScore<1 || (ss.slowScore<=0 && ss.pairedScore>ss.quickScore ) || ss.pairedScore>ss.slowScore)){
//			System.err.println("Site paired score violation: "+ss.quickScore+", "+ss.slowScore+", "+ss.pairedScore);
//			return false;
//		}
//		
//		final boolean xy=ss.matchContainsXY();
//		if(bases!=null){
//			
//			final boolean p0=ss.perfect;
//			final boolean sp0=ss.semiperfect;
//			final boolean p1=ss.isPerfect(bases);
//			final boolean sp1=(p1 ? true : ss.isSemiPerfect(bases));
//
//			assert(p0==p1 || (xy && p1)) : p0+"->"+p1+", "+sp0+"->"+sp1+", "+ss.isSemiPerfect(bases)+
//				"\nnumericID="+id+"\n"+new String(bases)+"\n\n"+Data.getChromosome(ss.chrom).getString(ss.start, ss.stop)+"\n\n"+ss+"\n\n";
//			assert(sp0==sp1 || (xy && sp1)) : p0+"->"+p1+", "+sp0+"->"+sp1+", "+ss.isSemiPerfect(bases)+
//				"\nnumericID="+id+"\n"+new String(bases)+"\n\n"+Data.getChromosome(ss.chrom).getString(ss.start, ss.stop)+"\n\n"+ss+"\n\n";
//			
////			ss.setPerfect(bases, false);
//			
//			assert(p0==ss.perfect) : 
//				p0+"->"+ss.perfect+", "+sp0+"->"+ss.semiperfect+", "+ss.isSemiPerfect(bases)+"\nnumericID="+id+"\n\n"+new String(bases)+"\n\n"+
//				Data.getChromosome(ss.chrom).getString(ss.start, ss.stop)+"\n"+ss+"\n\n";
//			assert(sp0==ss.semiperfect) : 
//				p0+"->"+ss.perfect+", "+sp0+"->"+ss.semiperfect+", "+ss.isSemiPerfect(bases)+"\nnumericID="+id+"\n\n"+new String(bases)+"\n\n"+
//				Data.getChromosome(ss.chrom).getString(ss.start, ss.stop)+"\n"+ss+"\n\n";
//			if(ss.perfect){assert(ss.semiperfect);}
//		}
//		if(ss.match!=null && ss.matchLength()!=ss.mappedLength()){
//			if(verbose){System.err.println("Returning false because matchLength!=mappedLength:\n"+ss.matchLength()+", "+ss.mappedLength()+"\n"+ss);}
//			return false;
//		}
//		return true;
	}
	
	public void setPerfect(boolean b){
		flags=(flags&~PERFECTMASK);
		if(b){flags|=PERFECTMASK;}
	}
	
	public void setRescued(boolean b){
		flags=(flags&~RESCUEDMASK);
		if(b){flags|=RESCUEDMASK;}
	}
	
	public void setMapped(boolean b){
		flags=(flags&~MAPPEDMASK);
		if(b){flags|=MAPPEDMASK;}
	}
	
	public void setDiscarded(boolean b){
		flags=(flags&~DISCARDMASK);
		if(b){flags|=DISCARDMASK;}
	}
	
	public void setInvalid(boolean b){
		flags=(flags&~INVALIDMASK);
		if(b){flags|=INVALIDMASK;}
	}
	
	public void setSwapped(boolean b){
		flags=(flags&~SWAPMASK);
		if(b){flags|=SWAPMASK;}
	}
	
	public void setShortMatch(boolean b){
		flags=(flags&~SHORTMATCHMASK);
		if(b){flags|=SHORTMATCHMASK;}
	}
	
	public void setInsertValid(boolean b){
		flags=(flags&~INSERTMASK);
		if(b){flags|=INSERTMASK;}
	}
	
	public void setHasAdapter(boolean b){
		flags=(flags&~ADAPTERMASK);
		if(b){flags|=ADAPTERMASK;}
	}
	
	public void setSecondary(boolean b){
		flags=(flags&~SECONDARYMASK);
		if(b){flags|=SECONDARYMASK;}
	}
	
	public void setAminoAcid(boolean b){
		flags=(flags&~AAMASK);
		if(b){flags|=AAMASK;}
	}
	
	public void setJunk(boolean b){
		flags=(flags&~JUNKMASK);
		if(b){flags|=JUNKMASK;}
	}
	
	public void setValidated(boolean b){
		flags=(flags&~VALIDATEDMASK);
		if(b){flags|=VALIDATEDMASK;}
	}
	
	public void setInsert(int x){
		if(x<1){x=-1;}
//		assert(x==-1 || x>9 || length()<20) : x+", "+length(); //Invalid assertion for synthetic reads.
		insert=x;
		setInsertValid(x>0);
		if(mate!=null){
			mate.insert=x;
			mate.setInsertValid(x>0);
		}
	}

	private static int[] makeMaskArray(int max) {
		int[] r=new int[max+1];
		for(int i=0; i<r.length; i++){r[i]=(1<<i);}
		return r;
	}
	


	public static byte[] getFakeQuality(int len){
		if(len>=QUALCACHE.length){
			byte[] r=new byte[len];
			Arrays.fill(r, (byte)30);
			return r;
		}
		if(QUALCACHE[len]==null){
			synchronized(QUALCACHE){
				if(QUALCACHE[len]==null){
					QUALCACHE[len]=new byte[len];
					Arrays.fill(QUALCACHE[len], (byte)30);
				}
			}
		}
		return QUALCACHE[len];
	}
	
	public byte[] getScaffoldName(boolean requireSingleScaffold){
		byte[] name=null;
		if(mapped()){
			if(!requireSingleScaffold || Data.isSingleScaffold(chrom, start, stop)){
				int idx=Data.scaffoldIndex(chrom, (start+stop)/2);
				name=Data.scaffoldNames[chrom][idx];
//				int scaflen=Data.scaffoldLengths[chrom][idx];
//				a1=Data.scaffoldRelativeLoc(chrom, start, idx);
//				b1=a1-start1+stop1;
			}
		}
		return name;
	}
	
	public void bisulfite(boolean AtoG, boolean CtoT, boolean GtoA, boolean TtoC){
		for(int i=0; i<bases.length; i++){
			final int x=AminoAcid.baseToNumber[bases[i]];
			if(x==0 && AtoG){bases[i]='G';}
			else if(x==1 && CtoT){bases[i]='T';}
			else if(x==2 && GtoA){bases[i]='A';}
			else if(x==3 && TtoC){bases[i]='C';}
		}
	}
	
	public Read copy(){
		Read r=clone();
		r.bases=(r.bases==null ? null : r.bases.clone());
		r.quality=(r.quality==null ? null : r.quality.clone());
		r.match=(r.match==null ? null : r.match.clone());
		r.gaps=(r.gaps==null ? null : r.gaps.clone());
		r.originalSite=(r.originalSite==null ? null : r.originalSite.clone());
		r.sites=(ArrayList<SiteScore>) (r.sites==null ? null : r.sites.clone());
		r.mate=null;
		
		if(r.sites!=null){
			for(int i=0; i<r.sites.size(); i++){
				r.sites.set(i, r.sites.get(i).clone());
			}
		}
		return r;
	}
	
	public Read clone(){
		try {
			return (Read) super.clone();
		} catch (CloneNotSupportedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		throw new RuntimeException();
	}
	
	/**
	 * @return This protein in canonical nucleotide space.
	 */
	public Read aminoToNucleic() {
		assert(aminoacid()) : "This read is not flagged as an amino acid sequence.";
		Read r=this.clone();
		r.setAminoAcid(false);
		r.bases=AminoAcid.toNTs(r.bases);
		if(quality!=null){
			byte[] ntquals=new byte[r.quality.length*3];
			for(int i=0; i<quality.length; i++){
				byte q=quality[i];
				byte q2=(byte)Tools.min(q+5, MAX_CALLED_QUALITY);
				ntquals[i]=ntquals[i+1]=ntquals[i+2]=q2;
			}
			r.quality=ntquals;
		}
		return r;
	}
	
	private static final byte[][] QUALCACHE=new byte[1000][];
	

	public static final int STRANDMASK=1;
	public static final int MAPPEDMASK=(1<<1);
	public static final int PAIREDMASK=(1<<2);
	public static final int PERFECTMASK=(1<<3);
	public static final int AMBIMASK=(1<<4);
	public static final int RESCUEDMASK=(1<<5);
//	public static final int COLORMASK=(1<<6); //TODO:  Change to semiperfectmask?
	public static final int SYNTHMASK=(1<<7);
	public static final int DISCARDMASK=(1<<8);
	public static final int INVALIDMASK=(1<<9);
	public static final int SWAPMASK=(1<<10);
	public static final int SHORTMATCHMASK=(1<<11);
	
	public static final int PAIRNUMSHIFT=12;
	public static final int PAIRNUMMASK=(1<<PAIRNUMSHIFT);

	public static final int INSERTMASK=(1<<13);
	public static final int ADAPTERMASK=(1<<14);
	public static final int SECONDARYMASK=(1<<15);
	public static final int AAMASK=(1<<16);
	public static final int JUNKMASK=(1<<17);
	public static final int VALIDATEDMASK=(1<<18);
	
	private static final int[] maskArray=makeMaskArray(18); //Be sure this is big enough for all flags!

//	public static byte ASCII_OFFSET=33;
	private static final byte ASCII_OFFSET=33;
	public static boolean CHANGE_QUALITY=true; //Cap all quality values between MIN_CALLED_QUALITY and MAX_CALLED_QUALITY
	public static byte MIN_CALLED_QUALITY=2;
	public static byte MAX_CALLED_QUALITY=41;
	public static byte MAX_MERGE_QUALITY=41;
	public static boolean TO_UPPER_CASE=false;
	public static boolean LOWER_CASE_TO_N=false;
	public static final boolean OTHER_SYMBOLS_TO_N=true;
	public static boolean AVERAGE_QUALITY_BY_PROBABILITY=true;
	public static boolean FIX_HEADER=false;
	
	public static boolean FLAG_JUNK=false;
	public static boolean FIX_JUNK=false;
	public static boolean U_TO_T=false;
	public static boolean COMPRESS_MATCH_BEFORE_WRITING=true;
	public static boolean DECOMPRESS_MATCH_ON_LOAD=true; //Set to false for some applications, like sorting, perhaps
	
	public static boolean ADD_BEST_SITE_TO_LIST_FROM_TEXT=true;
	public static boolean NULLIFY_BROKEN_QUALITY=false;
	public static boolean FLAT_IDENTITY=true;
	public static boolean VALIDATE_IN_CONSTRUCTOR=true;
	
	public static boolean verbose=false;
}
