  // written by: Mike Puntolillo
  // assisted by: Juan Bazurto, Rich Pullose, Nick Tse
  // debugged by: Ehud Cohen

package minoritygame;

import java.util.*;
import javax.swing.*;
import org.jfree.data.category.*;
import org.jfree.data.xy.XYDataset;
import java.lang.Object;
import java.io.*;
import java.util.Date;
import java.sql.Timestamp;
import java.text.DecimalFormat;

public class GameSim
{


    public static double round(double d)
    {
        DecimalFormat twoDForm = new DecimalFormat("#.###");
        return Double.valueOf(twoDForm.format(d));
    }
    public static String FinalMessage(String winners, int[] attendance, 
            int[]scoresArray, int[] deaths, int maxScore, int minScore,
            double avgAgentScore, int medianScore, int scoreRange,
            double expected2, double stdDev, double avgAttendence,
            double attendanceStdDev, String holmes, Agent [] a)
    {
        String output="\n\n";
        output=output+winners;

        output=output+"\nAgent Scores Statistics:";
        output=output+"\nMaximum: "+maxScore;
        output=output+"\nMinimum: "+minScore;
        output=output+"\nMean: "+avgAgentScore;
        output=output+("\nMean Hit Rate: "+round(avgAgentScore/gameParameters.ROUNDS));
        output=output+("\nMedian: "+ medianScore);
        output=output+("\nRange: "+scoreRange);
        output=output+("\nSecond Moment: "+expected2);
        output=output+("\nVariance: "+round((expected2-(avgAgentScore*avgAgentScore))));
        output=output+("\nStandard Deviation: "+stdDev);
        output=output+("\n\nAverage Attendance Per Round: "+avgAttendence);
        output=output+("\n Standard Deviation of Attendance: " +attendanceStdDev);

        if(gameParameters.SHERLOCK)
        {
            output=output+("\n\nSherlock:\n");
            output=output+holmes;
        }

        output=output+"\n";
        return output;
    }
    public static void SimulationOuputWriter(String FileName, int[] attendance,
            int[]scoresArray, int[] deaths, int maxScore, int minScore,
            double avgAgentScore, int medianScore, int scoreRange,
            double expected2, double stdDev, double avgAttendence, double attendanceStdDev,
            ArrayList <Integer> winners, Agent [] a, Timestamp currentTimestamp)
	{

            try
            {
                    // Create file
                FileWriter fstream = new FileWriter(FileName);
                BufferedWriter out = new BufferedWriter(fstream);
               
                //Write the game parameters
                out.write(currentTimestamp.toString()+"\n");
                out.write("SIMULATION DATA PROFILE\n");
                out.write("\nGame Parameters: ");
                out.write("\nALPHA: " + gameParameters.ALPHA);
                out.write("\nADAPT: "+gameParameters.ADAPT);
                out.write("\nNUMBER OF AGENTS: " + gameParameters.NUMAGENTS);
                out.write("\nNUMBER OF STRATEGIES: " + gameParameters.NUMSTRAT);
                out.write("\nMEMORY SIZE PER AGENT: " + gameParameters.M);
                out.write("\nNUMBER OF ROUNDS: " + gameParameters.ROUNDS);
                out.write("\nMORTALITY: " + gameParameters.MORTALITY);

                out.write("\n\n");
                Iterator <Integer> iter = winners.iterator();
                out.write("Winners: \n");
                while(iter.hasNext())
                {
                    int x=iter.next().intValue();
                    out.write("Agent "+(x+1)+"\n");
                    out.write(a[x].strategiesToString());
                    out.write("\n");
                    a[x].printStrategies();
                }


                 out.write("\n\nAGENT SCORES STATISTICS:\n");
                out.write("\nMaximum: "+maxScore);
                out.write("\nMinimum: "+minScore);
                out.write("\nMean: "+ avgAgentScore);
                out.write("\nMean Hit Rate: "+round(avgAgentScore/gameParameters.ROUNDS));
                out.write("\nMedian: "+ medianScore);
                out.write("\nRange: "+scoreRange);
                out.write("\nSecond Moment: "+expected2);
                out.write("\nVariance: "+round((expected2-(avgAgentScore*avgAgentScore))));
                out.write("\nStandard Deviation: "+stdDev);
                out.write("\n\nAverage Attendence Per Round: "+avgAttendence);
                out.write("\nStandard Deviation of Attendance: "+attendanceStdDev);
                out.write("\n");
                //Intermediate Datasets
                //WriteDatasets(FileName,attendance,scoresArray,deaths);
                out.write("\n\nDatasets: \n");
                int i=gameParameters.ROUNDS-1;
                out.write("\nAttendance: \n");
                for (int x=0; x<gameParameters.ROUNDS; x++)
                {
                    try
                    {
                        if(x%20==0)
                            out.write("\n");

                        out.write(" "+ attendance[x]);
                    }
                    catch (IOException e)
                    {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                    }

                    i=i-1;
                }

                out.write("\n");

                //2
                int k=gameParameters.NUMAGENTS-1;
                out.write("\nScores: \n");
                for (int z=0; z<gameParameters.NUMAGENTS; z++)
                {
                    try
                    {
                            if(z%20==0)
                                out.write("\n");

                            out.write(" " + scoresArray[z]);
                    }
                    catch (IOException e)
                    {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                    }
                    k=k-1;
                }

                out.write("\n");
                if(gameParameters.MORTALITY)
                {
                    int l=gameParameters.ROUNDS-1;
                    out.write("\nDeaths: \n");
                    for (int y=0; y<gameParameters.ROUNDS; y++)
                    {
                        try
                        {
                            if(y%20==0)
                                out.write("\n");

                            out.write(" "+ deaths[y]);
                        }
                        catch (IOException e)
                        {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                        }
                        l=l-1;
                    }
                    out.write("\n");
                }
                    //Close the file
                out.close();
            }
            catch (Exception e)
            {//Catch exception if any
            System.err.println("Error: " + e.getMessage());
            }

	}


    public static void run(String[] args)
    {

        char[] past =new char[gameParameters.M];
        int current; //Used to denote what the minority did to Human Player
        int [] deaths = new int[gameParameters.ROUNDS];
        Agent a[] = new Agent[gameParameters.NUMAGENTS];
        Sherlock sherlock=null ;//= new Sherlock();

        if (gameParameters.SHERLOCK)
            sherlock=new Sherlock();
        if(gameParameters.HUMAN)//Creates an array of the results length memory
        {//                       that starts with all values at 0

                for(int i=0; i<gameParameters.M; i++)
                        past[i]='x';

        }

        for(int x=0; x<gameParameters.NUMAGENTS; x++)
        {
            a[x]=new Agent();
        }
        int score=0;
        int attendance[] = new int[gameParameters.ROUNDS];
        for(int rounds=0; rounds<gameParameters.ROUNDS; rounds++)
        {
            //System.out.println(rounds);
            int rnds=rounds+1;
            attendance[rounds]=0;

            int sum=0;
            int go=0;

            int sherlockSays=0;
            String fromsherlock="";
            if(gameParameters.SHERLOCK)
            {
                sherlockSays=sherlock.predict(a);

            //Get suggestion from sherlock
            
                if(sherlockSays>0)
                   fromsherlock="go";
                else
                    fromsherlock="stay";
            }
            if(gameParameters.HUMAN)
            {
            	String showResult="";
				for(int i=0; i<gameParameters.M; i++) //Puts the result array into a string
					showResult=showResult+" "+past[i];
                if(gameParameters.SHERLOCK)
                    go = JOptionPane.showConfirmDialog(null, "Previous Results:\n1=Minority went\n0=Minority stayed\n"+showResult+"\nSherlock suggests you "+fromsherlock+ "!\n\nWant to go?", "Current Score: "+score + " Current Round: "+rnds , JOptionPane.YES_NO_OPTION);
                else
                    go = JOptionPane.showConfirmDialog(null, "Previous Results:\n1=Minority went\n0=Minority stayed\n"+showResult, "Current Score: "+score + " Current Round: "+rnds , JOptionPane.YES_NO_OPTION);

                if(go==1){
                	sum = 1;
                	attendance[rounds]++;
                }

                if(go==0)
                sum = -1;
            }//end if human

            for(int x=0; x<gameParameters.NUMAGENTS; x++)
            {
                int temp = a[x].newRound();// so that we dont call newRound() twice
                sum=sum+temp;
                if(temp == 1){//they decided to go
                	attendance[rounds]++;
                }
                if(gameParameters.SHERLOCK)
                    sherlock.updateUsage(x, a[x].getMem(),temp);
            }

            int r=0;//result
            if(sum>0)//more people went than those who did not
            {
            	current=0;//minority didn't go
                r=-1;// people who didnt go were winners
            }

            else
            {
            	current=1;//minority went
                r=1;//people who went were winners
            }

            if(gameParameters.SHERLOCK)
                sherlock.updateScores(a, r);

            for(int x=0; x<gameParameters.NUMAGENTS; x++)
            {
                a[x].report(r);
            }

            if (gameParameters.MORTALITY)
            {
                Random gen = new Random();
                for(int x =0; x<gameParameters.NUMAGENTS;x++)
                {
                    int age=a[x].getAge();
                    if(age>119)
                    {
                        age=119;
                    }
                    if(gen.nextDouble()<(gameParameters.LIFETABLE[age]+gameParameters.PROBADJUST*a[x].getVisits()))
                    {
                        a[x]= new Agent();
                        deaths[rounds]++;
                    }
                }//end Agent loop
            }//end Mortality loop

            if(gameParameters.HUMAN)
            {
                if(go==1 && sum<=0)
                {
                    System.out.println("You win!");
                    score=score+1;
                }

                else if(go==0 && sum>1)
                {
                    System.out.println("You win!");
                    score=score+1;
                }

                else
                {
                    System.out.println("You lost");
                }

                System.out.println("Final score: "+score);

                if (go==-1)
                {
                    gameParameters.HUMAN=false;
                    EFBar.HmnPly.setSelected(false);
                    gameParameters.NUMAGENTS=gameParameters.NUMAGENTS-1;
                    EFBar.numA.setText(""+gameParameters.NUMAGENTS);
                }

                for(int x=0; x<gameParameters.M-1; x++)//moves results over one
					past[x]=past[x+1];

				past[gameParameters.M-1]=new Integer(current).toString().charAt(0);//adds newest result in

            } //end if human
        } //end rounds


        double avgAgentScore=0;
        double avgAttendance=0;
        double scoresExpected2=0;
        double attendanceExpected2=0;
        double attendanceStdDev=0;
        double stdDev=0;
        int medianScore=0;
        int scoreRange=0;
        int maxScore=0;
        int minScore=gameParameters.ROUNDS;
        int [] scoresArray = new int [gameParameters.NUMAGENTS];
        ArrayList <Integer> winners= new ArrayList <Integer>();
        for(int x=0; x<gameParameters.NUMAGENTS;x++)
        {
            int tmpScore=a[x].getScore();
            scoresArray[x]=tmpScore;
            if (tmpScore>maxScore)
            {
                maxScore=tmpScore;
                winners.clear();
                winners.add(x);
            }
            else if(tmpScore==maxScore)
            {
                winners.add(x);
            }
            if(tmpScore<minScore)
            {
                minScore=tmpScore;
            }
            avgAgentScore=avgAgentScore+tmpScore;
            scoresExpected2=scoresExpected2+(tmpScore*tmpScore);
        }

        avgAgentScore/=gameParameters.NUMAGENTS;
        avgAgentScore=round(avgAgentScore);
        
        scoresExpected2/=gameParameters.NUMAGENTS;
        scoresExpected2=round(scoresExpected2);

        stdDev=Math.sqrt(scoresExpected2-(avgAgentScore*avgAgentScore));
        stdDev=round(stdDev);


        DefaultCategoryDataset dataset1 = new DefaultCategoryDataset(); //Graph1 for Attendance
        DefaultCategoryDataset dataset4 = new DefaultCategoryDataset();
        int i=gameParameters.ROUNDS-1;
        for (int x=0; x<gameParameters.ROUNDS; x++){
        	int j=gameParameters.ROUNDS-i;
			dataset1.setValue(attendance[x], "Attendance", ""+j);
                        dataset4.setValue(deaths[x], "Deaths", ""+j);
                        avgAttendance=avgAttendance+attendance[x];
                        attendanceExpected2=attendanceExpected2+(attendance[x]*attendance[x]);
        	i=i-1;
        }
        
        avgAttendance/=gameParameters.ROUNDS;
        avgAttendance=round(avgAttendance);
        attendanceExpected2/=gameParameters.ROUNDS;
        attendanceStdDev=Math.sqrt(attendanceExpected2-(avgAttendance*avgAttendance));
        attendanceStdDev=round(attendanceStdDev);


        if (EFBar.graph1==true){
                Graphs Graph1 = new Graphs();
                Graph1.CreateGraph("Attendance per Round","Round","Attendance", dataset1,"line");
        }
        DefaultCategoryDataset dataset2 = new DefaultCategoryDataset(); //Graph1 for Attendance
        int k=gameParameters.NUMAGENTS-1;
        for (int x=0; x<gameParameters.NUMAGENTS; x++){
        	int j=gameParameters.NUMAGENTS-k;
			dataset2.setValue(scoresArray[x], "Score", ""+j);
        	k=k-1;
        }
        if (EFBar.graph2==true){
                Graphs Graph2 = new Graphs();
                Graph2.CreateGraph("Scores for Agents","Agent","Score",dataset2,"bar");
        }

        if(EFBar.graph3==true)
        {
            Graphs Graph3 = new Graphs();
            XYDataset dataset3=Graph3.CreateNormalDataset(avgAgentScore, stdDev, minScore, maxScore, gameParameters.NUMAGENTS);
            Graph3.NormalDistributionGraph("Gaussian Distribution", "Score", "Probability", dataset3);
        }

        if(EFBar.graph4==true)
        {
            Graphs Graph4 = new Graphs();
            Graph4.CreateGraph("Deaths", "Round", "Number of Deaths", dataset4, "line");

        }

        Arrays.sort(scoresArray);
        medianScore=scoresArray[(int)Math.ceil(gameParameters.NUMAGENTS/2)];
        scoreRange=maxScore-minScore;


        String winning="Winners: ";
        Iterator <Integer> iter = winners.iterator();
        while(iter.hasNext())
        {
            int x=iter.next().intValue();
            winning=winning+"\nAgent "+(x+1)+"\n";
            winning=winning+a[x].strategiesToString();
            System.out.println();
        }

        
        Calendar calendar = Calendar.getInstance();
        Date now = calendar.getTime();
        Timestamp currentTimestamp= new Timestamp(now.getTime());
        String fileName="out_"+(calendar.get(calendar.MONTH)+1) + "_"+calendar.get(calendar.DAY_OF_MONTH)+"_"+calendar.get(calendar.HOUR_OF_DAY)+"_"+calendar.get(calendar.MINUTE)+".txt";
        SimulationOuputWriter(fileName, attendance, scoresArray, deaths, maxScore, minScore, avgAgentScore, medianScore, scoreRange, scoresExpected2, stdDev, avgAttendance, attendanceStdDev, winners, a, currentTimestamp );

        String results;
        if(gameParameters.SHERLOCK)
            results=FinalMessage(winning, attendance, scoresArray, deaths, maxScore, minScore, avgAgentScore, medianScore, scoreRange, scoresExpected2, stdDev, avgAttendance, attendanceStdDev, sherlock.statsToString(), a);
        else
            results=FinalMessage(winning, attendance, scoresArray, deaths, maxScore, minScore, avgAgentScore, medianScore, scoreRange, scoresExpected2, stdDev, avgAttendance, attendanceStdDev, "", a);

        EFBar.textArea1.append(results);
    }
}





