import java.io.EOFException;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.PrintStream;
import java.io.File;
import java.util.Vector;

public class Unique {
	byte[][] count;
	int[] nucNum;  // table for converting nucleotide to number
	int[] compNum; // table for converting complement nucleotide to number
	int[] primeNumbers;
	int max; 
	int window = 20;  // must be 20
	int trial; // number of prime numbers;
	int type;
	long magic;
	int maxSize;
	int[] binomial;			
	long index;
	long reverseIndex;
	Vector<File> inputFile;
	Vector<File> outputFile;

	
	public static void main(String[] args) throws IOException{
		long millis = System.currentTimeMillis();
		
		Unique unique = new Unique();
		boolean succeed = unique.setFileNames(args);
		if( !succeed ){
			unique.Usage();
			System.exit(0);
		}
                System.out.print("Prime: ");
                System.out.println(unique.primeNumbers[0]);
		// 1st path ... counting occurrences
		for(int i=0;i<unique.inputFile.size();i++){
			BufferedReader d0 = new BufferedReader(new FileReader(unique.inputFile.elementAt(i)));
	        try{
	            while(true){
	                String str = d0.readLine();
                	unique.put(str);
	            }
	        }catch(EOFException e){   // end of file
	        }catch(NullPointerException e){}
	        catch(FileNotFoundException e){}
	        d0.close();
		}

		// 2nd path ... finding unique words
		unique.resetIndex();
		for(int i=0;i<unique.inputFile.size();i++){
			BufferedReader d0 = new BufferedReader(new FileReader(unique.inputFile.elementAt(i)));
			PrintStream p0 = new PrintStream(unique.outputFile.elementAt(i));
	        try{
	            while(true){
	                String str = d0.readLine();
                	p0.println(unique.uniqueCheck(str));
	            }
	        }
	        catch(EOFException e){   /* end of file */ }
	        catch(NullPointerException e){}
	        catch(FileNotFoundException e){}
	        d0.close();
	        p0.close();
		}
		
        //total
		for(int k=0;k<unique.binomial.length;k++){
	        System.out.print(k);
	        System.out.print("\t");
	        System.out.print(unique.binomial[k]);
	        System.out.println();
		}
		System.out.print(System.currentTimeMillis()-millis);

	}

	public void Usage() {
		System.out.println("Usage:");
		System.out.println("java Unique -i inputfile1.txt inputfile2 -o outputDir");
	}

	public Unique(){  // constructor 
		// nucleotides to number
		nucNum = new int[Character.MAX_VALUE];
		type = 5; // number of nucleotide types
		nucNum['U']=nucNum['u']=nucNum['T']=nucNum['t']=1;
		nucNum['C']=nucNum['c']=2;
		nucNum['A']=nucNum['a']=3;
		nucNum['G']=nucNum['g']=4;
		//reverse (compliment)
		compNum = new int[Character.MAX_VALUE];
		compNum['U']=compNum['u']=compNum['T']=compNum['t']=nucNum['A'];
		compNum['C']=compNum['c']=nucNum['G'];
		compNum['A']=compNum['a']=nucNum['T'];
		compNum['G']=compNum['g']=nucNum['C'];
		
		
		index = reverseIndex = 0; // initialize
		inputFile = new Vector<File>();
		outputFile = new Vector<File>();
		trial = 1; // number of prime numbers
		max = 322122539; // the largest prime number we can use
		primeNumbers = new int[trial];
		primeNumbers[0]=max;// default;
	}

	public void resetIndex(){
		index = reverseIndex = 0; // initialize		
	}
	
	public void put(String seq) throws NullPointerException{  // put sequence and count unique words
		//check the data
		if ( seq==null ) throw new NullPointerException();  // no data
        if ( seq.length()==0 ) return; // no data
        if ( seq.charAt(0)=='>') return ; // title
		//work
		int tag = 0; // for calculation
		for(int i=0;i<seq.length();i++){
			char c = seq.charAt(i);
			long forward = index(c);//forward
			long reverse = reverseIndex(c);//reverse
			for(int k=0;k<trial;k++){
				//forward
				tag = (int) (forward % primeNumbers[k]); // residue
    			if(count[k][tag]<100) count[k][tag]++;  // avoid overflow
    			//reverse
				tag = (int) (reverse % primeNumbers[k]); // residue
    			if(count[k][tag]<100) count[k][tag]++;  // avoid overflow
			}
		}
	}

	public String uniqueCheck(String seq)  throws NullPointerException {
		//check the data
		if ( seq==null ) throw new NullPointerException();  // no data
        if ( (seq.length()==0) || (seq.charAt(0)=='>') ) {
        	return seq; // title
        }
        //work
		StringBuffer result = new StringBuffer();
		int tag=0;
		for(int i=0;i<seq.length();i++){
			boolean isUnique = false;
			char c = seq.charAt(i);
			//forward
			long forward = index(c);//forward
			for(int k=0;k<trial;k++){
				tag = (int) (forward % primeNumbers[k]); // residue
    			if(count[k][tag]==1) isUnique = true;  // unique
			}
			//reverse
			long reverse = reverseIndex(c);//reverse
			for(int k=0;k<trial;k++){
				tag = (int) (reverse % primeNumbers[k]); // residue
    			if(count[k][tag]==1) isUnique = true;  // unique
			}
			if(isUnique){  // if at least one is unique
				int multiple = 0;
                for(int k=0;k<trial;k++){
                	//*forward
    				tag = (int) (forward % primeNumbers[k]); 
                    if(count[k][tag]>1) multiple++;  // count hash collision
                	//reverse
    				tag = (int) (reverse % primeNumbers[k]); // residue
                    if(count[k][tag]>1) multiple++;  // count hash collision
                }
                binomial[multiple]++;
			}
//			if(isUnique) System.out.print(Character.toUpperCase(c));
//			else	     System.out.print(Character.toLowerCase(c));				
			if(isUnique) result.append(Character.toUpperCase(c));
			else	     result.append(Character.toLowerCase(c));				
		}
//		System.out.println();
		return result.toString();
	}

	public long index(char c){
		index *= type; // shift
		index += nucNum[c];
		index %= magic;
		return index;
	}

	public long reverseIndex(char c){
		reverseIndex += magic * compNum[c];
		reverseIndex /= type;
		return reverseIndex;
	}
	
	public boolean setFileNames(String[] args){
		boolean success = false;
		if(args.length==0) return false;  // no data
		int input = 1;
		int inputDir = 2;
		int outputDir = 3;
		int maxNumber = 4;
		int primeNumber = 5;
		int argState = input; //default
		String inputPath = System.getProperty("user.dir");  // current dir
		String outputPath = "output";
		Vector<String> fileList = new Vector<String>();
		for(int i=0;i<args.length;i++){
			if (args[i].charAt(0)=='-') { // flag
				if( args[i].equals("-i") )  argState = input;
				if( args[i].equals("-w") )  argState = inputDir;				
				if( args[i].equals("-o") )  argState = outputDir;				
				if( args[i].equals("-m") )  argState = maxNumber;				
				if( args[i].equals("-n") )  argState = primeNumber;				
			}else{
				if(argState==input)     fileList.add(args[i]);
				if(argState==inputDir ) inputPath  = args[i];
				if(argState==outputDir) outputPath = args[i];
				if(argState==maxNumber) max = (new Integer(args[i])).intValue();
				if(argState==primeNumber) trial = (new Integer(args[i])).intValue();
			}
		}
		File f = new File(outputPath);
		if( !(f.exists()) ) f.mkdirs();
		for(int i=0;i<fileList.size();i++){
			System.out.println(fileList.elementAt(i));
			inputFile.add(new File(inputPath,fileList.elementAt(i)));
			outputFile.add(new File(outputPath,fileList.elementAt(i)));
		}
		

		byte[] c = new byte[max+1];		
		double sqrt = Math.sqrt(max);
		// sieve of Erastotenes
		for(int i=2;i<=sqrt;i++){
			if(c[i]==0){ // not marked
				for(int j=2;j*i<=max;j++){
					c[j*i]=1;
				}
			}
		}
		//output
		long mem = Runtime.getRuntime().maxMemory();
		System.out.println(mem);
		primeNumbers = new int[trial];
        int n=0;
        for(int i=max;i>2;i--){
            if(c[i]==0) {  // prime number
               if(n<trial) primeNumbers[n]=i;
               n++;
            }
        }
		magic =1; // type ^ window size 
		for(int i=0;i<window;i++) magic *= type;  
		maxSize = 1;
		for(int k=0;k<trial;k++) {
			if(maxSize<primeNumbers[k]) maxSize = primeNumbers[k]+1; // memory size
		}
		count = new byte[trial][maxSize]; // save the memory (int uses 4 bytes)		
		binomial = new int[trial*2]; // both strand
		success = true;
		return success;
	}
	
	
}
