/*
Online Java - IDE, Code Editor, Compiler
Online Java is a quick and easy tool that helps you to build, compile, test your programs
online.
*/
// This has been created to ensure I can utilize any random functions more efficiently.
// It is a creation of the combinations (with replacement) calculator.
// It has used techniques I learnt including recursion and also memoization to speed up execution.
// I will incorporate this into Java applications I created previously..


//included numbers  4,2,6 in X     
//N=6....


import java.math.*;
import java.util.*;

class Staircase 
{
    int N = 6;     // this can be changed by end user, size of the staircase
    
    
    int[] X;      // this is the array containing the steps
    int p;       // 
    int total;   // this is total of the steps
    int cycles=0;  // this is number of cycles completed for each
    Set<String> s;  // this will contain the combinations for achieving total N
    
    Random rand = new Random();  // object created of type Random.
    
      
    // there is no sense of X getting smaller since its with replacement....
    // need to only add the stringJoiner (converted into string) into the set if the total is equal to N
    // if total goes over, it needs to reset the StringJoiner and the total...
    
    
    // Combinations is total of combinations for C^R(n,r)
    // r is the sample size... This can exceed n.
    
    public Staircase(long combinations, int[] X, int r, Set<String> s)
    {
        this.X=X;
        this.s=s;
        
        System.out.println("Combinations: " + combinations);
        
        StringJoiner sj = new StringJoiner(",");  // creating StringJoiner
        
        int currentSetSize=0;  // holds size of set before entry added
        int newSetSize;      // holds size of set after entry added
        int count;
        int q;
        
        do
        {
            count=1;   
            //sets count back to 1 when it has finished.  This is visual check to ensure that r is not exceeded
        
          int steps;
           
           //System.out.println()
           
           for (q=0; q<r;q++)      // processing up to r only since this is the sample size
           {
                p= rand.nextInt(X.length);  // if length 3, it will give index numbers 0-2... This is suitable
           
           steps = X[p];  // step value
           
           
           
           System.out.println("\nrandom step is: " + steps);
           
           sj.add(Integer.toString(X[p]));  //adds the step into StringJoiner
           
           currentSetSize=s.size();  //current size of set
           
           total=total+steps;               //keeps running total
               
               
               System.out.println("This is performing: " + (q+1) +" of " + r);
               //reminding end user value N and running total
               
           System.out.println("TOTAL HERE:          " + total  +  "      N Value is:  " +  N);
           
           //if (total==N /*&& count<=r*/)
           if (total==N)   // running total equals to N (steps in staircase)
           {
           System.out.println("This is stringjoiner right now: " + sj.toString());
           System.out.println("Count:" + count);
           System.out.println("current:" + currentSetSize);
           System.out.println("TOTAL: " + total);
           System.out.println("VALUE OF N: " + N);
           System.out.println("COUNT: " + count);
           System.out.println("r: " + r);
           
           s.add(sj.toString());  // it will only add it if the conditions are met above (size of r and Total=N)
           
           count++;
           
           
           }
           
           if (q==r-1)  // if its on the last loop..
           {
               sj = new StringJoiner(",");
               total=0;
           }
           
           
           }
           
          
           
           
           if (total>N)
           {
               total=0;   //resets total
               sj = new StringJoiner(",");  //creates new instance, resets contents...
           }
           
           
           
          
           
           
           newSetSize = s.size();    // new set size
           System.out.println("new size: "+ newSetSize);
           
        
           if (newSetSize>currentSetSize)   //if larger, i.e a new combination, it will inform end user..
           {
               System.out.println("This has been added into the set: " + sj.toString());
           }
        
      
        cycles++;  //increases cycles
     
        System.out.println("Number of cycles: " + cycles + "\n");
        
        }while (cycles<combinations*10);
        
        // can not set this... since it is giving total combinations (with replacement) and not necessarily
        // related to obtaining a total of 6 with each...
        // This can only be placed in speculatively like throwing dice (a fixed time) and getting total....
        //the safest option for the maximum iterations is combinations x 10.

         // this is now getting the String in the set...
                 for (String g: s)
                 {
                     System.out.println(g.toString());
                 }
        
        
    }
}


public class Combination
{
    public static void main(String[] args) 
    {
        System.out.println("Welcome to Online IDE!! Happy Coding :)");
        
        // this is going to be slight challenge...
        int originalNumber=0;   // for moment it is declared a 0 to keep next line content... This variable is based on the size of n in outer for loop...
        int n=originalNumber;   // this can stay here.... 
        int r;   // this does not need be in for loop
        Set <String> s = new HashSet<>();
        
        int [] X = new int []{1,2,3};  //just to keep this loop flowing, I will complete exercise inline with 3 values in k..

        // Require a nested loop as follows and execution has to run separately

        //These are all scenarios
        //n=1 to  n< X.length()
        //r=1 to r<=n

        //need to understand that r can be greater than n in replacement.......
        //r is taken from n objects
        // in this example, r can get larger....

        Map <Integer, Long> m;  // this can stay outside loop since its not impacted by size of n and r
        
        //for (n=0; n<=X.length; n++)    
        
        n=X.length;
        
        // this should go from 1 to 3
        // this is fucking mistake! 
        // only meant to be fixed at value..... X.length()!!!!  objects.....
        // it's only in future if the X array grows, this will provide 
        
        //{
            
            System.out.println("Steps are: " + Arrays.toString(X));
            
            originalNumber=n;  // this will remain constant during a full iteration..
            
             m= new HashMap<>();  //new instance is required to start process again...
             
             for (r=0; r<=n+5; r++)  // for moment r is set 5 more than objects available.. This bit can be experimented later..
             {
                 System.out.println("\n***COMBINATIONS*** (WITH REPLACEMENT)");
                 System.out.println("C^R(n + r) = " + "(n+r-1)! / r!(n-1)!");
                 System.out.println("C^R(" + n+","+r+") = " + (n+r-1)+ "!" + " / " + r+"!"+"("+(n-1)+")!");
                 
                 
                 Staircase sc = new Staircase (Combinations (n,r,originalNumber, m), X, r,s);
                 //System.out.println(Combinations (n,r,originalNumber, m));
                 
             }
       
        
    }

public static long Combinations (int n, int r, int originalNumber, Map factorialResults)
{
    long result=0;
    int denominator1; //denominator split two parts since there are two factorial calculations
    int denominator2; //denominator split two parts since there are two factorial calculations
    int Numerator=n+r-1; // Numerator
    int zero=0;
    long zeroFactorial = 1;

    // if no sample or objects, there are no outcomes...
    if (originalNumber==0 && r==0)
    {
        System.out.println("n and r can not both be equal to zero");
//System.exit(0);
return 0;
}
//this situation would occur if n is 0 only and r is any positive number accept 0 (if statement above)
//for instance (C^R (n,r)) = (0,3) 0+3-1 = 2 2<3
if (originalNumber==0 && originalNumber+r-1<r)
{
System.out.println("n+r-1 must be > or = to r");
//System.exit(0);
return 0;
}
if (Numerator>=1)

{

result = ((n+r-1)* (Combinations (n-1, r,originalNumber, factorialResults))); // this
//completes factorial for numerator
factorialResults.put(Numerator,result); //result stored in the Map
//factorialResults.put(n-1,result); //result stored in the Map
//System.out.println("getting result back out numerator: " + (Numerator) + " " + factorialResults.get(n+r-1));
if (n==originalNumber) // this will occur once
{
denominator1 = r;

denominator2 = originalNumber-1;

factorialResults.put(zero,zeroFactorial); //0! is equal to 1

if (factorialResults.containsKey(denominator1) &&
factorialResults.containsKey(denominator2))
{
long returnValue = result / ((long)factorialResults.get(denominator1) * (long)factorialResults.get(denominator2));
return returnValue;
}
}
return result;
}
return 1; // it will reach here only when condition not met (Numerator>=1)
}
}