package bin;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Random;

import fileIO.ByteStreamWriter;
import fileIO.FileFormat;
import shared.Parse;
import shared.Parser;
import shared.PreParser;
import shared.Shared;
import shared.Timer;
import shared.Tools;
import structures.ByteBuilder;
import structures.FloatList;
import structures.LongHashSet;

/**
 * @author Brian Bushnell
 * @date Feb 23, 2025
 *
 */
public class AllToAllVectorMaker {

	public static void main(String[] args){
		//Start a timer immediately upon code entrance.
		Timer t=new Timer();
		
		//Create an instance of this class
		AllToAllVectorMaker x=new AllToAllVectorMaker(args);
		
		//Run the object
		x.process(t);
		
		//Close the print stream if it was redirected
		Shared.closeStream(x.outstream);
	}
	
	public AllToAllVectorMaker(String[] args){
		
		{//Preparse block for help, config files, and outstream
			PreParser pp=new PreParser(args, getClass(), false);
			args=pp.args;
			outstream=pp.outstream;
		}
		loader=new DataLoader(outstream);
		
		Parser parser=new Parser();
		for(int i=0; i<args.length; i++){
			String arg=args[i];
			String[] split=arg.split("=");
			String a=split[0].toLowerCase();
			String b=split.length>1 ? split[1] : null;
			if(b!=null && b.equalsIgnoreCase("null")){b=null;}

			if(a.equals("parse_flag_goes_here")){
				//Set a variable here
			}else if(a.equals("seed")){
				seed=Long.parseLong(b);
			}else if(a.equals("rate")){
				positiveRate=Float.parseFloat(b);
			}else if(a.equals("lines")){
				lines=Parse.parseKMG(b);
			}else if(loader.parse(arg, a, b)){
				//do nothing
			}else if(SimilarityMeasures.parse(arg, a, b)){
				//do nothing
			}else if(parser.parse(arg, a, b)){
				//do nothing
			}else{
				//				throw new RuntimeException("Unknown parameter "+args[i]);
				assert(false) : "Unknown parameter "+args[i];
				outstream.println("Unknown parameter "+args[i]);
			}
		}
		
		{//Process parser fields
			Parser.processQuality();
			out1=parser.out1;
		}
		
		ffout1=FileFormat.testOutput(out1, FileFormat.TXT, null, true, true, false, false);
	}
	
	void process(Timer t){
		
		ArrayList<Contig> contigs=loader.loadData();
		if(verbose){outstream.println("Finished reading data.");}
		
		HashMap<Integer, Cluster> map=new HashMap<Integer, Cluster>();
		for(Contig c : contigs) {
			if(c.labelTaxid>0) {
				Cluster b=map.get(c.labelTaxid);
				if(b==null) {map.put(c.labelTaxid, b=new Cluster(c));}
			}
		}
		
		outputResults(contigs, map);
		
		t.stop();
		outstream.println("Time:                         \t"+t);
		outstream.println("Reads Processed:    "+loader.contigsLoaded+
				" \t"+Tools.format("%.2fk bases/sec", (loader.basesLoaded/(double)(t.elapsed))*1000000));
		assert(!errorState) : "An error was encountered.";
	}
	
	private void outputResults(ArrayList<Contig> contigs, HashMap<Integer, Cluster> map){
		LongHashSet used=new LongHashSet();
		ByteStreamWriter bsw=new ByteStreamWriter(ffout1);
		bsw.start();
		
		Random randy=Shared.threadLocalRandom(seed);
		Oracle oracle=new Oracle(999999, 0);
		if(bsw!=null) {//Print header
			vecBuffer.clear();
			oracle.toVector(contigs.get(0), contigs.get(1), vecBuffer, true);
			bsw.print("#dims\t").print(vecBuffer.size-1).tab().println(1);
		}
		
		while(linesOut<lines) {
			ByteBuilder bb=makeLine(contigs, map, used, oracle, randy);
			if(bb!=null) {
				if(bsw!=null) {
					bsw.print(bb.nl());
					bb.clear();
				}
				linesOut++;
			}
		}

		errorState=bsw.poisonAndWait() | errorState;
	}
	
	private ByteBuilder makeLine(ArrayList<Contig> contigs, HashMap<Integer, Cluster> map, 
			LongHashSet used, Oracle oracle, Random randy) {
		Contig a=null;
		while(a==null || a.labelTaxid<1) {
			int idx=randomIndex(randy, contigs.size(), 3);
			a=contigs.get(idx);
		}
		assert(a.labelTaxid>0);
		boolean positive=(randy.nextFloat()<=positiveRate);
		ArrayList<Contig> list=(positive ? map.get(a.labelTaxid).contigs : contigs);
		Contig b=findOther(a, list, used, null, randy, positive);
		if(b==null) {return null;}
		assert(b.labelTaxid>0) : a.name()+", "+b.name()+", "+positive;
		vecBuffer.clear();
		oracle.toVector(a, b, vecBuffer, true);
		return toLine(vecBuffer);
	}
	
	private ByteBuilder toLine(FloatList vector) {
		lineBuffer.clear();
		for(int i=0; i<vector.size(); i++) {
			if(i>0) {lineBuffer.tab();}
			lineBuffer.append(vector.get(i), 7, true);
		}
		return lineBuffer;
	}
	
	private Contig findOther(final Contig a, ArrayList<Contig> contigs, 
			LongHashSet used, Oracle oracle, Random randy, boolean positive) {
		for(int i=0; i<100; i++) {
			int idx=randomIndex(randy, contigs.size(), 2);
			final Contig b=contigs.get(idx);
			final long key=toKey(a.id(), b.id());
			if(a!=b && b.labelTaxid>0 && (a.labelTaxid==b.labelTaxid)==positive && !used.contains(key)) {
				if((a.labelTaxid==b.labelTaxid) || oracle==null || oracle.similarity(a, b, 1)>=0) {
					return b;
				}
			}
		}
		return null;
	}
	
	private int randomIndex(Random randy, int max, int rolls) {
		int idx=randy.nextInt(max);
		for(int i=randy.nextInt(rolls+1); i>0; i--) {
			idx=Math.min(idx, randy.nextInt(max));
		}
		return idx;
	}
	
	private static long toKey(int a, int b) {
		return (((long)Math.min(a, b))<<32)|(long)Math.max(a, b);
	}
	
	/*--------------------------------------------------------------*/
	
	/*--------------------------------------------------------------*/
	
	private String out1=null;
	
	private final FileFormat ffout1;
	
	DataLoader loader=null;
	long seed=-1;
	long lines=1000000;
	long linesOut=0;
	long posCount=0;
	long negCount=0;
	float positiveRate=0.5f;
	
	private final ByteBuilder lineBuffer=new ByteBuilder();
	private final FloatList vecBuffer=new FloatList();
	
	/*--------------------------------------------------------------*/
	
	private boolean errorState=false;
	
	/*--------------------------------------------------------------*/
	
	private java.io.PrintStream outstream=System.err;
	public static boolean verbose=false;
	
}
