import java.util.Vector;
import java.io.*;

public class CRISPRFinder
{
	private String inputFileName;
	private String outputFileName;

 	private int screenDisplay;
	private int minNumRepeats;
    	private int minRepeatLength;
    	private int maxRepeatLength;
    	private int minSpacerLength;
    	private int maxSpacerLength;
    	private int searchWindowLength;
	private	PrintStream out;
	long totseq = 0;
	long totbase = 0;
	long totseqhit = 0;

   	DNASequence sequence = null;
   	int sequenceLength = 0;


	public CRISPRFinder(String _inputFileName, String _outputFileName, int _screenDisplay, int _minNumRepeats, int _minRepeatLength, int _maxRepeatLength, int _minSpacerLength, int _maxSpacerLength, int _searchWindowLength)
	{
		inputFileName = _inputFileName;
		outputFileName = _outputFileName;

		screenDisplay = _screenDisplay;
		minNumRepeats = _minNumRepeats;
      		minRepeatLength = _minRepeatLength;
      		maxRepeatLength = _maxRepeatLength;
      		minSpacerLength = _minSpacerLength;
      		maxSpacerLength = _maxSpacerLength;
      		searchWindowLength = _searchWindowLength;
	}


	public void goCRISPRFinder()
	{	File inputFile = new File(inputFileName);
		String seq = "";
		long repeatSearchStart = System.currentTimeMillis();
		if (inputFile.exists())
		{	System.out.println("\n\nReading file " + inputFile.getPath());
			FASTAReader fastaReader = new FASTAReader(inputFile.getPath());

			FileOutputStream outputFile;
			
			if (screenDisplay == 1)
				out = System.out;
			else
			{	if (outputFileName.equals(""))
					outputFileName = "a.out";
					
				try
				{	
					outputFile = new FileOutputStream(outputFileName);
					out = new PrintStream(outputFile);
				}
				catch (Exception e)
				{
					System.out.println ("Error processing output file: 2\n");
				}
			}

			if (fastaReader.isFASTA())
			{	fastaReader.readASeq();
				seq = fastaReader.getSequence();
				while (seq != "") 
				{
					totseq += 1;
					totbase += fastaReader.getSequence().length();
					sequence = new DNASequence(fastaReader.getSequence(), fastaReader.getHeader());
					//System.out.println(sequence.getName());
					//System.out.println(sequence.length() + " bases");

					try 
					{	findRepeats();
					}
					catch (Exception e)	
					{	
						System.out.println ("Error processing sequence: " + sequence.getName());
					}
					fastaReader.readASeq();
					seq = fastaReader.getSequence();
				}

			}
			else
			{   System.out.println("Not a valid FASTA file:  " + inputFile.getPath());
			}

			long repeatSearchEnd = System.currentTimeMillis();
			out.print("Total sequences checked: " + totseq + "\n");
			out.print("Total bases checked: " + totbase + "\n");
			out.print("Total sequences with predicted CRISPR: " + totseqhit + "\n");
			out.print("Time used: " + (repeatSearchEnd - repeatSearchStart) + " ms\n\n");
			out.close();
		}
		else
		{   System.out.println("File name does not exist:  " + inputFile.getPath());
		}
	}


	private void findRepeats()
	{
		Vector CRISPRVector = new Vector();
		sequenceLength = sequence.length();
		int actualRepeatLength;
		boolean repeatsFound = false;

		CRISPR candidateCRISPR;
		CRISPR candidateCRISPR2;
		String pattern;

		if ((searchWindowLength < 6) || (searchWindowLength > 9))
		{	searchWindowLength = 8;
			//let user know that window size has changed
		}

		double spacerToSpacerMaxSimilarity = 0.62;
		int spacerToSpacerLengthDiff = 12;
		int spacerToRepeatLengthDiff = 30;

		//the mumber of bases that can be skipped while we still guarantee that the entire search
		//window will at some point in its iteration thru the sequence will not miss a any repeat
		int skips = minRepeatLength - (2 * searchWindowLength - 1);
		if (skips < 1)
			skips = 1;

		//System.out.println("Searching for repeats...");

        	SearchUtil searchUtil = new SearchUtil();

		int searchEnd = sequenceLength - maxRepeatLength - maxSpacerLength - searchWindowLength;
		for (int j = 0; j <= searchEnd; j = j + skips)
		{
      			candidateCRISPR = new CRISPR();

         		int beginSearch = j + minRepeatLength + minSpacerLength;
			int endSearch = j + maxRepeatLength + maxSpacerLength + searchWindowLength;

			if (endSearch > sequenceLength)
				endSearch = sequenceLength;

            		if (endSearch < beginSearch)  //should never occur
            			endSearch = beginSearch;

            		String text = sequence.substring(beginSearch, endSearch);
            		pattern = sequence.substring(j, j + searchWindowLength);

			//if pattern is found, add it to candidate list and scan right for additional similarly spaced repeats
			int patternInTextIndex = searchUtil.boyer_mooreSearch(text, pattern);
			if (patternInTextIndex >= 0)
			{   
				candidateCRISPR.addRepeat(j);
				candidateCRISPR.addRepeat(beginSearch + patternInTextIndex);
				candidateCRISPR = CRISPRUtil.scanRight(candidateCRISPR, pattern, minSpacerLength, 24, searchUtil);
			}

           		if ( (candidateCRISPR.numRepeats() >= minNumRepeats) )  //make sure minNumRepeats is always at least 2
			{	
				candidateCRISPR = CRISPRUtil.getActualRepeatLength(candidateCRISPR, searchWindowLength, minSpacerLength);
                		actualRepeatLength = candidateCRISPR.repeatLength();
				//System.out.println("repeatLength " + actualRepeatLength + "maxRepeatLength " + maxRepeatLength);
				if ( (actualRepeatLength >= minRepeatLength) && (actualRepeatLength <= maxRepeatLength) )
				{	
					//System.out.println("pass minRepeatLength\n");
					if (CRISPRUtil.hasNonRepeatingSpacers(candidateCRISPR, spacerToSpacerMaxSimilarity))
					{
						//System.out.println("pass hasNonRepeatingSpacers\n");
						if (CRISPRUtil.hasSimilarlySizedSpacers(candidateCRISPR, spacerToSpacerLengthDiff, spacerToRepeatLengthDiff))
						{
							int repeatcopy1 = candidateCRISPR.numRepeats();
							//System.out.println("pass hasSimilarlySizedSpacers\n");
							candidateCRISPR = CRISPRUtil.checkFlank("left", candidateCRISPR, minSpacerLength, 30, spacerToSpacerMaxSimilarity, .70);
							//System.out.println("pass checkFlank-left\n");
							candidateCRISPR = CRISPRUtil.checkFlank("right", candidateCRISPR, minSpacerLength, 30, spacerToSpacerMaxSimilarity, .70);
							//System.out.println("pass checkFlank-right\n");

							candidateCRISPR = CRISPRUtil.trim(candidateCRISPR, minRepeatLength);
							//System.out.println("pass trim\n");

							//System.out.println("After scan, total repeats: " + candidateCRISPR.numRepeats() + " length: " + candidateCRISPR.repeatLength());
							int repeatcopy2 = candidateCRISPR.numRepeats();

							//may increase the repeats once more "candidates" are included?
							if(repeatcopy2 > repeatcopy1) {
								//candidateCRISPR2 = CRISPRUtil.getActualRepeatLength(candidateCRISPR, searchWindowLength, minSpacerLength);
								candidateCRISPR2 = CRISPRUtil.getActualRepeatLength(candidateCRISPR, candidateCRISPR.repeatLength(), minSpacerLength);
								if((candidateCRISPR2.repeatLength()) > (candidateCRISPR.repeatLength())) {
									//System.out.println("Use revised one, repeatlength: " + actualRepeatLength);
									CRISPRVector.addElement(candidateCRISPR2);
								}
								else CRISPRVector.addElement(candidateCRISPR);
		
							}
							else CRISPRVector.addElement(candidateCRISPR);
							repeatsFound = true;

							//we may skip current CRISPR (assuming CRISPRs are not interleaved)
							j = candidateCRISPR.end() + 1;
						}

					}
				}
			}
		}

		//System.out.println("Time to search for repeats:  " + (repeatSearchEnd - repeatSearchStart) + " ms");
		//System.out.println(CRISPRVector.size() + " possible CRISPR(s) found" + "\n");


		// ********************** Display CRISPR elements ********************** //
        	// ********************************************************************* //
        	try
		{	
			if (repeatsFound)
			{
				totseqhit += 1;
				out.print("SEQ: ");
				out.print(sequence.getName() + "\n");
				out.print("Bases: " + sequence.length() + "\n");
				out.print("Total CRISPR: " + CRISPRVector.size() + "\n\n\n");

				System.out.println("Found " + CRISPRVector.size() + " CRISPR in SEQ:" +  sequence.getName() + " with " + sequence.length() + "bp\n");

				int repeatLength, numRepeats, numSpacers;
				CRISPR currCRISPR;

				String repeat, spacer, prevSpacer;
				repeat = spacer = prevSpacer = "";

				//add 1 to each position, to offset programming languagues that begin at 0 rather than 1
				for (int k = 0; k < CRISPRVector.size(); k++)
				{	currCRISPR = (CRISPR)CRISPRVector.elementAt(k);
					//System.out.println("CRISPR " + (k + 1) + "   Range: " + (currCRISPR.start() + 1) + " - " +  (currCRISPR.end() + 1) + "\n");
					out.print("CRISPR " + (k + 1) + "   Range: " + (currCRISPR.start() + 1) + " - " +  (currCRISPR.end() + 1) + "\n");
					out.print(currCRISPR.toString());
					//out.print("Repeats: " + currCRISPR.numRepeats() + "\t" +  "Average Length: " + currCRISPR.averageRepeatLength() + "\t\t");
					out.print("Repeats: " + currCRISPR.numRepeats() + "\t" +  "Average Length: " + currCRISPR.getRepeatLength() + "\t\t");
					out.print("Average Length: " +  currCRISPR.averageSpacerLength() + "\n\n");
				}
				out.print("\n");
			}

			//if (!repeatsFound)
			//	out.print("No CRISPR elements were found.");

		}
		catch (Exception e)	{	System.err.println ("--Error writing to file-- \n");	}
	}
}
