import java.util.*;

/*
TODO: Implement exponential distribution using inversion method - DONE
TODO: Implement discrete frequency distributions using Vose's Alias Method
 */

public class Plate {
    private String sourceFile;
    private List<List<Integer[]>> wells;
    private Random rand = new Random();
    private int size;
    private double error;
    private Integer[] concentrations;
    private double stdDev;
    private double lambda;
    boolean exponential = false;


    public Plate (int size, double error, Integer[] concentrations) {
        this.size = size;
        this.error = error;
        this.concentrations = concentrations;
        //this.stdDev = stdDev;
        wells = new ArrayList<>();
    }

    public Plate(String sourceFileName, List<List<Integer[]>> wells){
        this.sourceFile = sourceFileName;
        this.wells = wells;
        this.size = wells.size();
    }

    public void fillWellsExponential(String sourceFileName, List<Integer[]> cells, double lambda){
        this.lambda = lambda;
        exponential = true;
        sourceFile = sourceFileName;
        int numSections = concentrations.length;
        int section = 0;
        double m;
        int n;
        int test=0;
        while (section < numSections){
            for (int i = 0; i < (size / numSections); i++) {
                List<Integer[]> well = new ArrayList<>();
                for (int j = 0; j < concentrations[section]; j++) {
                    do {
                        m = (Math.log10((1 - rand.nextDouble()))/(-lambda)) * Math.sqrt(cells.size());
                    } while (m >= cells.size() || m < 0);
                    n = (int) Math.floor(m);
                    //n = Equations.getRandomNumber(0, cells.size());
                    // was testing generating the cell sample file with exponential dist, then sampling flat here
                    //that would be more realistic
                    //But would mess up other things in the simulation with how I've coded it.
                    if(n > test){
                        test = n;
                    }
                    Integer[] cellToAdd = cells.get(n).clone();
                    for(int k = 0; k < cellToAdd.length; k++){
                        if(Math.abs(rand.nextDouble()) < error){//error applied to each peptide
                            cellToAdd[k] = -1;
                        }
                    }
                    well.add(cellToAdd);
                }
                wells.add(well);
            }
            section++;
        }
        System.out.println("Highest index: " +test);
    }

    public void fillWells(String sourceFileName, List<Integer[]> cells, double stdDev) {
        this.stdDev = stdDev;
        sourceFile = sourceFileName;
        int numSections = concentrations.length;
        int section = 0;
        double m;
        int n;
        while (section < numSections){
            for (int i = 0; i < (size / numSections); i++) {
                List<Integer[]> well = new ArrayList<>();
                for (int j = 0; j < concentrations[section]; j++) {
                    do {
                        m = (rand.nextGaussian() * stdDev) + (cells.size() / 2);
                    } while (m >= cells.size() || m < 0);
                    n = (int) Math.floor(m);
                    Integer[] cellToAdd = cells.get(n).clone();
                    for(int k = 0; k < cellToAdd.length; k++){
                        if(Math.abs(rand.nextDouble()) < error){//error applied to each peptide
                            cellToAdd[k] = -1;
                        }
                    }
                    well.add(cellToAdd);
                }
                wells.add(well);
            }
            section++;
        }
    }

    public Integer[] getConcentrations(){
        return concentrations;
    }

    public int getSize(){
        return size;
    }

    public double getStdDev() {
        return stdDev;
    }

    public boolean isExponential(){return exponential;}

    public double getLambda(){return lambda;}

    public double getError() {
        return error;
    }

    public List<List<Integer[]>> getWells() {
        return wells;
    }

    //returns a map of the counts of the peptide at cell index pIndex, in all wells
    public Map<Integer, Integer> assayWellsPeptideP(int... pIndices){
        return this.assayWellsPeptideP(0, size, pIndices);
    }

    //returns a map of the counts of the peptide at cell index pIndex, in a specific well
    public Map<Integer, Integer> assayWellsPeptideP(int n, int... pIndices) { return this.assayWellsPeptideP(n, n+1, pIndices);}

    //returns a map of the counts of the peptide at cell index pIndex, in a range of wells
    public Map<Integer, Integer> assayWellsPeptideP(int start, int end, int... pIndices) {
        Map<Integer,Integer> assay = new HashMap<>();
        for(int pIndex: pIndices){
            for(int i = start; i < end; i++){
                countPeptides(assay, wells.get(i), pIndex);
            }
        }
        return assay;
    }
    //For the peptides at cell indices pIndices, counts number of unique peptides in the given well into the given map
    private void countPeptides(Map<Integer, Integer> wellMap, List<Integer[]> well, int... pIndices) {
        for(Integer[] cell : well) {
            for(int pIndex: pIndices){
                if(cell[pIndex] != -1){
                    wellMap.merge(cell[pIndex], 1, (oldValue, newValue) -> oldValue + newValue);
                }
            }
        }
    }

    public String getSourceFileName() {
        return sourceFile;
    }
}
