  // written by: Mike Puntolillo
  // assisted by: Juan Bazurto
  // debugged by: Ehud Cohen

package minoritygame;

import java.util.*;

public class Agent
{
    private int score;//game score
    private Strategy [] strategies =  new Strategy[gameParameters.NUMSTRAT];
    private Random generator = new Random();
    private char [] mem =  new char [gameParameters.M];//short term memory
    private int currstrat=-1;
    private int age=0;
    private int barVisits=0;


    @SuppressWarnings({ "rawtypes", "unchecked" })
    public Agent()
    {
        score=0;
        ArrayList rand = new ArrayList();
        for (int x=0; x<gameParameters.NUMSTRAT;x++)
        {
            int tmp=(int)Math.pow(2,Math.pow(2,gameParameters.M));

            //Use for generating larger strategies
            //long tmp=Math.abs(gen.nextLong()%((int)Math.pow(2,Math.pow(2,gameParameters.M))));
            //int r = generator.nextInt(generator.nextInt((int)Math.pow(2,Math.pow(2,gameParameters.M))));
            int r = generator.nextInt(tmp);
            //this loop ensures that duplicate strategies are not used
            while(rand.contains(new Integer(r)))
            {
                //r = generator.nextInt(generator.nextInt((int)Math.pow(2,Math.pow(2,gameParameters.M))));
                r=generator.nextInt(tmp);
            }

            rand.add(new Integer(r));
           
            strategies[x] = new Strategy(((Integer)rand.get(x)).intValue());
        }

        //instantiates the short term memory as all zeros
        for (int x =0; x<gameParameters.M;x++)
        {
            mem[x]='0';
        }

    }

    
    @SuppressWarnings("unchecked")
	public int newRound()
    {

        @SuppressWarnings("rawtypes")
		ArrayList strat = new ArrayList();
        double max=-1;

        for(int x=0; x<gameParameters.NUMSTRAT;x++)
        {
            double s=strategies[x].getScore();
            if(s>max)
            {
                max=s;
                strat.clear();
                strat.add(new Integer(x));
                
            }
            else if (s==max)
            {
                strat.add(new Integer(x));
            }
            
        }

        if(strat.size()==1)
        {
            currstrat=((Integer)strat.get(0)).intValue();
        }
        else
        {

            currstrat=((Integer)strat.get(generator.nextInt(strat.size()))).intValue();
        }

        age++;
        if (strategies[currstrat].decision(mem)==1)
        {
            barVisits++;
        }
        
        return strategies[currstrat].decision(mem);
        

    }
    
    //allows driver to report the winning decision to the agent
    @SuppressWarnings("unchecked")
	public void report(int r)
    {       
        updateStrategyScore(r);

        if (strategies[currstrat].decision(mem)==r)
        {
            score++;
            updateMem(1);
        }
        else
        {
            updateMem(0);
        }
        

        //throw out poorly performing strategies
        if (gameParameters.ADAPT && age>0 && (age%gameParameters.ADAPTPERIOD)==0)
        {
            @SuppressWarnings("rawtypes")
	    ArrayList rand = new ArrayList();
            double totalStrategyScore=0;
            
            //gets sum of scores of all strategies. keeps track of which strategies
            //are in use so none get repeated.
            for(int x=0;x<gameParameters.NUMSTRAT;x++)
            {
                totalStrategyScore=totalStrategyScore+strategies[x].getScore();
                rand.add(new Integer(strategies[x].getStrategyID()));
            }
            
            int nonzero=gameParameters.NUMSTRAT;//number of strategies that are not removed.
            for(int x=0;x<gameParameters.NUMSTRAT;x++)
            {
                //strategy[x] is not performing well. it will be replaced
                if((strategies[x].getScore()/totalStrategyScore)<gameParameters.ADAPTTHRESHOLD)
                {   
                    nonzero--;
                    totalStrategyScore=totalStrategyScore-strategies[x].getScore();
                    //Use for generating larger strategies
                    //long tmp=Math.abs(gen.nextLong()%((int)Math.pow(2,Math.pow(2,gameParameters.M))));
                    //int n = generator.nextInt(generator.nextInt((int)Math.pow(2,Math.pow(2,gameParameters.M))));
                    int tmp=(int)Math.pow(2,Math.pow(2,gameParameters.M));                   
                    int n = generator.nextInt(tmp);
                    //this loop ensures that duplicate strategies are not used
                    while(rand.contains(new Integer(n)))
                    {
                        //n = generator.nextInt(generator.nextInt((int)Math.pow(2,Math.pow(2,gameParameters.M))));
                        n=generator.nextInt(tmp);
                    }

                    rand.add(new Integer(n));

                    //strategies[x] = new Strategy(((Integer)rand.get(x)).intValue());
                    strategies[x] = new Strategy(n);
                }
            }
            //average score of all nonzero strategies
            double avg=totalStrategyScore/nonzero;

            //if a strategy has a score of 0, it has recently been replaced
            //its score is changed to the average of all strategies with scores
            for(int x=0;x<gameParameters.NUMSTRAT;x++)
            {
                if (strategies[x].getScore()==0)
                {
                    strategies[x].setScore(avg);
                }
            }
            
                       
        }
        //*************************************************************************************
        
    }

    //updates short term memory
    private void updateMem(int r)
    {
        for(int x=0; x<gameParameters.M-1;x++)
        {
            mem[x]=mem[x+1];
        }

        mem[gameParameters.M-1]=Character.forDigit(r, 2);
    }

    private void updateStrategyScore(int r)
    {

        for(int x = 0; x<gameParameters.NUMSTRAT;x++)
        {
            strategies[x].adjustScore();

            if(strategies[x].decision(mem)==r)
            {
                strategies[x].incrementScore();
                
            }
        }

    }

    public int getScore()
    {
        return score;
    }

    public void printStrategies()
    {
        for(int x=0; x<gameParameters.NUMSTRAT;x++)
        {
            System.out.print("Strategy " + strategies[x].getStrategyID());
            System.out.println("; SCORE: "+strategies[x].getScore());
            strategies[x].printStrategy();

        }
    }
    
    public String strategiesToString()
    {
        StringBuffer s= new StringBuffer();
        for(int x=0; x<gameParameters.NUMSTRAT;x++)
        {
            s.append("Strategy " + strategies[x].getStrategyID());
            s.append("; SCORE: "+strategies[x].getScore()+"\n");

        }
        
        return s.toString();
    }


   public int getAge()
   {
       return age;
   }

   public int getVisits()
   {
       return barVisits;
   }

   public char[] getMem()
    {
       return mem;
   }

}

