package extrc;

import java.util.Arrays;
import java.util.Random;
import java.lang.Math;

/**
 * The Distribution class provides methods for calculating the distribution of defeasible implications (DIs) over ranks.
 */
public class Distribution{



    /**
     * Controls the distribution calculation for DIs over the ranks based on the specified distribution type.
     *
     * @param numDIs      The total number of DIs to distribute.
     * @param numRanks    The number of ranks over which DIs are distributed.
     * @param distribution The type of distribution to calculate (f: flat, lg: linear growth, ld: linear decline, r: random).
     * @return An array representing the calculated distribution of DIs over the ranks.
     */
    public static int[] distributeDIs(int numDIs, int numRanks, String distribution, int min){
        int[] ranks = new int[numRanks];

        switch (distribution){
            case "f":
                distributeFlat(numDIs, numRanks, ranks);
                break;
            case "lg":
                distributeLinearGrowth(numDIs, numRanks, ranks);
                break;
            case "ld":
                distributeLinearDecline(numDIs, numRanks, ranks);
                break;
            case "r":
                distributeRandom(numDIs, numRanks, ranks, min);
                break;
            case "n":
                System.out.println("n here");
                distributeNormal(numDIs, numRanks, ranks, min);
                break;
            case "eg":
                distributeDIsExpDecline(numDIs, numRanks, ranks);  
                break;
            case "ed":
                distributeDIsExpIncline(numDIs, numRanks, ranks);
                break;
        }
        return ranks;
    }
    
    /**
     * Calculates a flat distribution of DIs over the ranks.
     *
     * @param numDIs   The total number of DIs to distribute.
     * @param numRanks The number of ranks over which DIs are distributed.
     * @param ranks    An array to store the calculated distribution of DIs.
     */
    private static void distributeFlat(int numDIs, int numRanks, int[] ranks){
        int defImplicationsPerRank = numDIs / numRanks;
        int remainder = numDIs % numRanks;

        for (int i = 0; i < numRanks; i++){
            ranks[i] = defImplicationsPerRank;
        }

        int i = numRanks-1;
        while (remainder > 0){
            ranks[i]++;
            remainder--;
            i--;
        }
    }

    /**
     * Calculates a linear-growth distribution of DIs over the ranks.
     *
     * @param numDIs   The total number of DIs to distribute.
     * @param numRanks The number of ranks over which DIs are distributed.
     * @param ranks    An array to store the calculated distribution of DIs.
     */
    private static void distributeLinearGrowth(int numDIs, int numRanks, int[] ranks){
        int remainingDIs = numDIs;
        
        for (int i = 0; i < numRanks; i++){
            int defImplicationsToAdd = Math.min(remainingDIs, i + 1);
            ranks[i] = defImplicationsToAdd;
            remainingDIs -= defImplicationsToAdd;
        }
    
        int currentRank = numRanks - 1;
        while (remainingDIs > 0){
            if(currentRank < 0){
                currentRank = numRanks - 1;
            }
            int defImplicationsToAdd = Math.min(remainingDIs, 1);
            ranks[currentRank] += defImplicationsToAdd;
            remainingDIs -= defImplicationsToAdd;
            currentRank--;
        }
    }
    
    /**
     * Calculates a linear-decline distribution of DIs over the ranks.
     *
     * @param numDIs   The total number of DIs to distribute.
     * @param numRanks The number of ranks over which DIs are distributed.
     * @param ranks    An array to store the calculated distribution of DIs.
     */
    private static void distributeLinearDecline(int numDIs, int numRanks, int[] ranks){
        int remainingDIs = numDIs;
    
        for (int i = 0; i < numRanks; i++){
            int defImplicationsToAdd = Math.min(remainingDIs, i + 1);
            ranks[i] = defImplicationsToAdd;
            remainingDIs -= defImplicationsToAdd;
        }
    
        int currentRank = numRanks - 1;
        while (remainingDIs > 0){
            if(currentRank < 0){
                currentRank = numRanks - 1;
            }
            int defImplicationsToAdd = Math.min(remainingDIs, 1);
            ranks[currentRank] += defImplicationsToAdd;
            remainingDIs -= defImplicationsToAdd;
            currentRank--;
        }
    
        for (int i = 0; i < numRanks / 2; i++){
            int temp = ranks[i];
            ranks[i] = ranks[numRanks - i - 1];
            ranks[numRanks - i - 1] = temp;
        }
    }

    /**
     * Calculates a random distribution of DIs over the ranks.
     *
     * @param numDIs   The total number of DIs to distribute.
     * @param numRanks The number of ranks over which DIs are distributed.
     * @param ranks    An array to store the calculated distribution of DIs.
     */
    private static void distributeRandom(int numDIs, int numRanks, int[] ranks, int min){
        int remainingDIs = numDIs - numRanks * 2;
        if(min <2){Arrays.fill(ranks, 2);}
        else{Arrays.fill(ranks, min);}

        while(remainingDIs > 0){
            int i = (int)(Math.random() * ranks.length);
            ranks[i]++;
            remainingDIs--;
        }
    }
     
    /**
     * Calculates a normal distribution of DIs over the ranks.
     *
     * @param numDIs   The total number of DIs to distribute.
     * @param numRanks The number of ranks over which DIs are distributed.
     * @param ranks    An array to store the calculated distribution of DIs.
     */
{/*  private static void distributeNormal(int numDIs, int numRanks, int[] ranks, int min) {
        int halfRanks = numRanks / 2;
        int QuarterRanks = halfRanks / 2; // Quarter of ranks
    
        // Use percentages as fractions
        int centerQuarters = (int) ((numDIs * 34.13) / 100);
        int lowerQ = (int) ((numDIs * 15.87) / 100);
    
        int[] p1 = new int[QuarterRanks];
        int[] p2 = new int[QuarterRanks];
        int[] p3 = new int[QuarterRanks];
        int[] p4 = new int[QuarterRanks];
    
        // Distribute DIs linearly in each part
        distributeLinearGrowth(lowerQ, QuarterRanks, p1);
        distributeLinearGrowth(centerQuarters, QuarterRanks, p2);
        distributeLinearDecline(centerQuarters, QuarterRanks, p3);
        distributeLinearDecline(lowerQ, QuarterRanks, p4);
    
        // Populate ranks array
        for (int i = 0; i < p1.length; i++) {
            ranks[i] = p1[i] + min;
        }
        for (int i = 0; i < p2.length; i++) {
            ranks[i + p1.length] = p2[i] + min;
        }
        for (int i = 0; i < p3.length; i++) {
            ranks[i +  p2.length] = p3[i] + min;
        }
        for (int i = 0; i < p4.length; i++) {
            ranks[i +  p3.length] = p4[i] + min;
        }
    }*/}
    private static void distributeNormal(int numDIs, int numRanks, int[] ranks, int min) {
        int numberOfIntervals = numRanks;
        double[] weights = new double[numberOfIntervals];
        
        int remainingDIs = numDIs - (numRanks * min);
    
        if (remainingDIs <= 0) {
            Arrays.fill(ranks, min);
            
        }
    
        // Calculate weights with the center having the highest value
        double sumWeights = 0;
        int center = numRanks / 2;
    
        // Assign higher weights to values closer to the center
        for (int i = 0; i < numberOfIntervals; i++) {
            int distanceFromCenter = Math.abs(i - center);
            weights[i] = (double)(numberOfIntervals - distanceFromCenter); // Higher weight for closer to center
            sumWeights += weights[i];
        }
    
        // Distribute remainingDIs proportionally based on weights
        double totalAssigned = 0;
        for (int i = 0; i < numberOfIntervals; i++) {
            double proportion = weights[i] / sumWeights;
            ranks[i] = min + (int) Math.round(proportion * remainingDIs);
            totalAssigned += ranks[i];
        }
    
        // Adjust the last element to ensure the total sum is exact
        int totalSum = Arrays.stream(ranks).sum();
        if (totalSum != numDIs) {
            ranks[numberOfIntervals - 1] += (numDIs - totalSum);
        }
    
        // Output ranks for visualization
        for (int i = 0; i < ranks.length; i++) {
            //System.out.println("*" + ranks[i]);
            
        }
    }
    
  
    /**
     * Calculates the minimum number of DIs needed for a normal distribution.
     *
     * @param numRanks The number of ranks over which DIs are distributed.
     * @return The minimum number of DIs needed for a normal distribution.
     */
    public static int minDIsNormal(int numRanks, int min) {
        int sum = 0;
        boolean odd = numRanks % 2 != 0; 
        int center;
    
        // Calculate the center index
        if (odd) {
            center = numRanks / 2 + 1;
        } else {
            center = numRanks / 2;
        }
    
        // Calculate one-third of the center rank
        int oneThirdRank = center / 3;
    
        // This function should be defined elsewhere in your code
        int oneThirdDIs = oneThirdRank*2;
    
        // Assuming minDIsLinear returns a value in the range needed, and applying the scaling
        sum = (oneThirdDIs * 100) / 5; 
    
        return sum + 2*numRanks + min*numRanks;
    }

    /**
     * Calculates the minimum number of DIs needed for a normal distribution.
     *
     * @param numRanks The number of ranks over which DIs are distributed.
     * @return The minimum number of DIs needed for a normal distribution.
     */
    public static int[] getNewDistribution( int[] defImplicationDistribution, int[] oldDistribution ){
        for (int i = 0; i < oldDistribution.length; i++){
            if(defImplicationDistribution[i]-oldDistribution[i]>=0){
                defImplicationDistribution[i]=defImplicationDistribution[i]-oldDistribution[i];
            }else{
                defImplicationDistribution[i]=0;
            }
        }
        for (int j = 0; j < defImplicationDistribution.length; j++){
            if(defImplicationDistribution[j]<0){
                defImplicationDistribution[j]=1;
            }
        }
        return defImplicationDistribution;
    }

    /**
     * Calculates the minimum number of DIs needed for a linear-growth distribution.
     *
     * @param numRanks The number of ranks over which DIs are distributed.
     * @return The minimum number of DIs needed for a linear-growth distribution.
     */
    public static int minDIsLinear(int numRanks, int min){
       // int sum = numRanks * (numRanks + 1) / 2;
        int sum = numRanks * (2 * (min + 1) + (numRanks - 1)) / 2;
        return sum;
    }

    /**
     * Calculates the minimum number of DIs needed for a linear-decline distribution.
     *
     * @param numRanks The number of ranks over which DIs are distributed.
     * @return The minimum number of DIs needed for a linear-decline distribution.
     */
    public static int minDIsLinearDecline(int numRanks,int min){
        int sum = 0;
        int x = 2;
        for(int i = 0; i < numRanks; i++){
            if(x<min){x=min;}
            sum += (x);
            x++;
        }
        return sum;
    }

    /**
     * Calculates the minimum number of DIs needed for a exp-decline distribution.
     *
     * @param numRanks The number of ranks over which DIs are distributed.
     * @return The minimum number of DIs needed for a exp-decline distribution.
     */
    public static int minDIsExp(int numRanks,int min){
        Random random = new Random();
        int sum = 0;
        double decayFactor = 1.2;
        //setDecayFactor(decayFactor);
        for (int i = 0; i < numRanks; i++) {
            int DIsAtRank = (int) Math.round( Math.pow(decayFactor, i));
            if(DIsAtRank<min){DIsAtRank=min;}
            sum += DIsAtRank;
        }
      //  System.out.println(sum);
        return sum;
    }

    public static void distributeDIsExpDecline(int numDIs, int numRanks, int[] ranks ){

        int assignedDIs = 0;
        for (int i = 0; i < numRanks; i++) {
            int DIsAtRank = (int) Math.round(Math.pow(numDIs, (i-1)/(numRanks-1)));
            //(decayFactor, (i-1)/(numRanks-1)
            ranks[i] = DIsAtRank;
            assignedDIs = assignedDIs + DIsAtRank;
        }
        
        // Check if all numDIs are assigned and assign if not
        int index = 0;
        while(assignedDIs<numDIs){
            ranks[index]= ranks[index]+1;
            assignedDIs++;
            index++;
            if(index==ranks.length){
                index = 0;
            }
        }

    }

    public static void distributeDIsExpIncline(int numDIs, int numRanks, int[] ranks){
 
        distributeDIsExpDecline(numDIs, numRanks, ranks);
        int[] tempRank = new int[ranks.length];
        for(int i = 0; i < tempRank.length ; i++ ){
            tempRank[i] = ranks[(tempRank.length-1)-i];
        }

        for (int j = 0; j < tempRank.length ; j++ ){
            ranks[j] = tempRank[j];
        }


    }

}
