package lotto;

import edu.csusb.danby.stat.*;
import edu.csusb.danby.math.ProbMath;

/** 
* LottoModel.java is the mathematical code
* for the lotto applet to illustrate the
* hypergeometric distribution
*/
public class LottoModel  extends HistogramAdapter {
    
    int N;  //number of items
    int r=0;  // number of items picked
    int rPlay =r;
    int k;  // number of winners for computer to choose
    boolean [] chosen;   // which items have been chosen
    boolean [] winner;  // which items are winners
    int lowValue; //max(r+k-N,0);
    int bins;  //1+ min of r and k - lowValue
    int[] count; //for histogram, should be [maxrk+1]
    double[] theoreticalCount; // for histogram
    double[] unscaledCount; // unscaled values for histogram
    
    int totalNrepetitions=0;  // for histogram
    public static final int NEITHER=0;
    public static final int CHOSEN=1;
    public static final int WINNER=2;
    public static final int CHOSEN_WINNER=3;

/**
* constructor for LottoModel class
* @param aN is the number of choices on the lotto board
* @param ar is the number of player picks
* @param ak is the number of winning numbers to be chosen
*/
    public LottoModel( int aN, int ar, int ak){
        N = aN;
        r = ar;
        k = ak;
        chosen = new boolean[N];
        winner = new boolean[N];
        lowValue = (r+k-N>0)?r+k-N:0;
        bins = (r<k)? r+1-lowValue: k+1-lowValue;	
        count = new int[bins];
        unscaledCount = new double[bins];
        resetUnscaled(N,r,k,bins);  // Calculate theoretical probabilities
    }

/**
* default constructor: N=12, r=0, k=4;
*/
    public  LottoModel() {	
        this( 12, 0, 4);
    }

/**
* play calculates winners if parameters have not changed
* @param n_repetitions is the number of times to play
* @param N is the number of objects
* @param r is the number of objects chosen
* @param k is the number of winners to pick
*/
    public void play(int n_repetitions) {
        //first see if parameter r has changed
        if (rPlay != r){
            updateModel();
            rPlay = r;
        }
        for (int j=0; j< n_repetitions; j++) {
            int X = chooseWinners( N, k);
            count[X-lowValue]++;
        }
        totalNrepetitions += n_repetitions;
        for (int i=0; i< bins; i++) {
            theoreticalCount[i] =  
                 unscaledCount[i]*totalNrepetitions;
        }
    }

    /**
    * update the model parameters and reset statistics
    */
    public void updateModel(){
        
        winner = new boolean[N];
        lowValue = (r+k-N>0)?r+k-N:0;
        bins = (r<k)? r+1-lowValue: k+1-lowValue;
        count = new int[bins];  
        theoreticalCount= new double[bins];
        unscaledCount = new double[bins];
        resetUnscaled( N,  r, k, bins);
        totalNrepetitions=0;
    }

    public void resetModel(){
        chosen = new boolean[N];
        r=0;
        rPlay=0;
        updateModel();
        
    }
    public int getMode(int index){
        int mode;
        if ( chosen[index] && winner[index]) {
            mode = CHOSEN_WINNER;
        }
        else if( chosen[index] && !winner[index]){
            mode = CHOSEN;
        }
        else if ( !chosen[index] && winner[index]){
            mode = WINNER;
        }
        else {
            mode = NEITHER;
        }
            
    return mode;	
    }	
    
    public void toggleChosen(int index){
        if( chosen[index] = !chosen[index]) { r++;} 
        else {r--;}
    }

    public void setN(int aN){
        N=aN;
        r=0;
        resetModel();
    }

    public int getN(){ return N;}
    
    public void setr(int _r){r =_r;}

    public int getr(){ return r;}

    public void setk(int ak){
        k = ak;
        updateModel();
    }

    public int getk(){ return k; }
     
        
    private void resetUnscaled(int N, int r, int k, int bins) {
        double cri, cnrki, cnk;
        for (int j=0; j< bins; j++) {
            int i = j;
            cri = ProbMath.combin(r,i);
            cnrki = ProbMath.combin(N-r, k-i);
            cnk = ProbMath.combin(N,k);
            unscaledCount[j] = cri*cnrki/cnk;
            }
        }




/*
* These are the Histogram Interface methods
*/

    public int getNumberBins(){
        return bins;
    }

    public boolean drawTheoretical(){
        return true;
    }

    public int[] getBinCount(){
        return count;
    }

    public int getLowValue(){
        return lowValue; 
    }

    public int getBinWidth(){
        return 1;
    }

    public double[] getTheoreticalCount(){
        return theoreticalCount;
    }

/*
* chooseWinners: pick k winners at random
* @param k is the number of winners to choose
* @param N is the number of items to choose from
* 
*/
    private  int chooseWinners( int N,  int k) {
        int X=0; //number of chosen winners
        //clear winner list
        for (int index=0; index<N; index++){
            winner[index]=false;
        }
        //availableCells is a list of which indices
        // are not yet winners 
        int[] availableCells = new int[N];
        int upper = N; // largest index to use
        int index;  // random index to pick
        for (int j=0; j< N; j++) {
            availableCells[j]=j;
        }		
        for (int j=0; j< k; j++) {
            int aci;
            index = unifRandom(upper)-1;
            aci = availableCells[index];
            winner[aci]=true;
            if (chosen[aci]) {X++;}
            //remove index from list of available indices
            if (index != (upper-1)) {
                for (int i=index; i< upper-1; i++) {
                    availableCells[i]=availableCells[i+1];
                }
            }
            upper--;
        }
        //System.out.println("N = "+N+" r= "+r+" k= "+k+" X="+X);
        return X;
    }	


/*
* unifRandom returns a random integer between 1 and n
* @param n is the upper bound for the random integer
*/
    private int unifRandom( int n) {
        int result;
        result = (int)Math.floor(1+Math.random()*n);
        return result;
        }

}
        
