/* early version of some statistics for CAPA
   Copyright (C) 1992-2000 Michigan State University

   The CAPA system is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License as
   published by the Free Software Foundation; either version 2 of the
   License, or (at your option) any later version.

   The CAPA system is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public
   License along with the CAPA system; see the file COPYING.  If not,
   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.

   As a special exception, you have permission to link this program
   with the TtH/TtM library and distribute executables, as long as you
   follow the requirements of the GNU GPL in regard to all of the
   software in the executable aside from TtH/TtM.
*/

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <ctype.h>
#ifdef __sun
#include <unistd.h>
#endif
#ifdef NeXT
#include <sys/file.h>
#endif
#include <stdlib.h>

/*--------------------------------------------------------------*/
/* Constant Values for "hgr*.out".                              */
/*--------------------------------------------------------------*/
#define MAX_SECTION_NUMBER    3  /* maximum number of char for section number */
#define MAX_STUDENT_NUMBER    9  /* maximum number of char for student number */
#define MAX_STUDENT_NAME     30  /* maximum number of char for student name */
#define MAX_PROBLEM_NUMBER  150  /* maximum number of problems in a set */
#define FILE_NAME_LENGTH    256  /* maximum length of file name */
#define MAX_CLASS_SIZE     1024  /* maximum number of students in a class */


/*--------------------------------------------------------------*/
/* Constant Values for "set*.db".                               */
/*--------------------------------------------------------------*/
#define MAX_TRIES           100  /* tries 0..99 */


/*--------------------------------------------------------------*/
/* One line of record in "hgr*.out".                            */
/*--------------------------------------------------------------*/
typedef struct{
   char section[MAX_SECTION_NUMBER+1];
   char s_number[MAX_STUDENT_NUMBER+1];
   char s_name[MAX_STUDENT_NAME+1];
   char problem_number[MAX_PROBLEM_NUMBER];
   char problem_score[MAX_PROBLEM_NUMBER];
} HGR_record;

int     GradedProblems=0;      /* The number of problems in a HG'd record */
int     NumberOfUpdates=0;     /* The number of records need updated in hgr*.db */
int     NumberOfProblems=0;
char    *progname;
HGR_record hgr_record[MAX_CLASS_SIZE];


/*--------------------------------------------------------------*/
/* One line of record in "set*.out".                            */
/*--------------------------------------------------------------*/
typedef struct{
   int  valid;                          /* if a registered student */
   char s_number[MAX_STUDENT_NUMBER+1]; /* student number */
   char answer[MAX_PROBLEM_NUMBER];     /* a string of answers */
   char tries[3*MAX_PROBLEM_NUMBER];
} SET_record;

char  line1[512],line2[512],line3[512]; /* The first three lines in set*.db */
int   NumberOfStudents=0;               /* Number of students in set*.db */
int   ValidStudents=0;                  /* No. of Valid students in set*.db */
SET_record set_record[MAX_CLASS_SIZE];


/*--------------------------------------------------------------*/
/*                                                              */
/* Statistics for the number of tries in "set*.db".             */
/*                                                              */
/*--------------------------------------------------------------*/
int   s[MAX_PROBLEM_NUMBER][MAX_TRIES];
int   t[MAX_CLASS_SIZE][MAX_PROBLEM_NUMBER];
int   YesCnt[MAX_PROBLEM_NUMBER];
int   yesCnt[MAX_PROBLEM_NUMBER];
int   correct[MAX_PROBLEM_NUMBER];
int   Weight[MAX_PROBLEM_NUMBER];
int   TotalTries[MAX_PROBLEM_NUMBER];

typedef struct{
  int total;
  int score[MAX_PROBLEM_NUMBER];
}Student;

int GuyNumberOfStudents;
Student student[MAX_CLASS_SIZE];
#define PARTIAL 0
#define NOPARTIAL 1
/*--------------------------------------------------------------*/
/*                                                              */
/*                                                              */
/*--------------------------------------------------------------*/
void usage()
{
 printf("USAGE: %s [-s set] [-t LargeTry] [-n NumberLargeTry]\n", progname);
}



/*--------------------------------------------------------------*/
/*                                                              */
/* Return the maximum score of a problem.                       */
/*                                                              */
/*--------------------------------------------------------------*/
int Problem_Score(score_ptr,problem_number)
char *score_ptr;
char problem_number;
{
     char *tmp_ptr;
     char score;

     tmp_ptr=strchr(score_ptr,problem_number);
     tmp_ptr=tmp_ptr+2;
     sscanf(tmp_ptr, "%c", &score);
     return(score-'0');
}


/*--------------------------------------------------------------*/
/*                                                              */
/* Check if the list of scores is valid                         */
/*                                                              */
/*--------------------------------------------------------------*/
int Valid_Score(scorelist,score_ptr)
char *scorelist;
char *score_ptr;
{
     char *problem;
     int  score=-1;
     int  value=1;
     
     GradedProblems=0;
     problem = scorelist;
     while (*problem == '('){
         GradedProblems++;
         problem++;
         score=Problem_Score(score_ptr,*problem);
         problem=problem+2;
         if (*problem != '*'&&((*problem-'0')>score)){
            value=0;  
         }
         problem=problem+3;
     }
     return(value);
}

/*--------------------------------------------------------------*/
/*                                                              */
/* Open the hand graded file "./record/set`set`.out",           */
/* and return the file pointer.                                 */
/*                                                              */
/*--------------------------------------------------------------*/
FILE *Open_Read(filename)
char  filename[FILE_NAME_LENGTH];
{
    FILE  *fp;
 
   if ((fp=fopen(filename,"r"))==NULL) {
       printf("Error: can't open %s\n",filename);
       exit(1);
    }
    return(fp);
}
 
/*--------------------------------------------------------------*/
/*                                                              */
/* Open the hand graded file "./record/set`set`.out",           */
/* and return the file pointer.                                 */
/*                                                              */
/*--------------------------------------------------------------*/
FILE *Open_Write(filename)
char  filename[FILE_NAME_LENGTH];
{
    FILE  *fp;
 
    if ((fp=fopen(filename,"w"))==NULL) {
       printf("Error: can't open %s\n",filename);
       exit(1);
    }
    return(fp);
}
 

int studentCompare(const void *a,const void *b)
{
  Student *voida,*voidb;
  voida=(Student *)a;
  voidb=(Student *)b;
  if (voida->total < voidb->total)
    return 1;
  if (voida->total > voidb->total)
    return -1;
  return 0;
}

/*--------------------------------------------------------------*/
/*                                                              */
/*                                                              */
/*--------------------------------------------------------------*/
void Read_Record(fp_set)
     FILE *fp_set;
{
  int   count,i,j;
  int   done,len;
  int   problem;
  int   problem_cnt;
  char  fmtbuf[128];
  char  nextline[512];
  char  temp;
  
  fgets(line1,511,fp_set);  
  sscanf(line1,"%d",&problem_cnt);
  
  fgets(line2,511,fp_set);  
  len=strlen(line2);              /* weight line */
  for(i=0;i<problem_cnt;i++)  {
    Weight[i] = line2[i] - '0';  /* put weight into a global array */
  }
  
  fgets(line3,511,fp_set);        /* get rid of hand grading line */
  /* here problem_cnt should be the same as NumberOfProblems */
  NumberOfProblems=strlen(line2)-1;
  sprintf(fmtbuf,"%%%dc %%%dc%%%dc",
	  MAX_STUDENT_NUMBER, NumberOfProblems+1, 3*NumberOfProblems);
  
  done=0;
  count=0;
  while (!done){
    done=!fgets(nextline,511,fp_set);
    len=strlen(nextline);
    if (!done){
      count++;
      sscanf(nextline, fmtbuf,
	     set_record[count].s_number,
	     set_record[count].answer,
	     set_record[count].tries);
      /* printf("(%s)  (%s)  (%s)\n",
	 set_record[count].s_number,
	 set_record[count].answer,
	 set_record[count].tries); */
      set_record[count].valid=0;
      for (i=0;i<NumberOfProblems;i++){
	if (set_record[count].answer[i]!='-')
	  set_record[count].valid=1;
      }
    }
  }
  NumberOfStudents=count;
  printf("%d",NumberOfStudents);
  fclose(fp_set);
  
  for(j=0,i=0;i<NumberOfStudents;j++,i++)
    {
      for(problem=0;problem<NumberOfProblems;problem++)
	{
	  temp=set_record[i].answer[problem];
	  switch (temp)
	    {
	    case 'y': 
	    case 'Y':
	      student[j].score[problem]=Weight[problem];
	      student[j].total+=Weight[problem];
	      break;
	    case 'n':
	    case 'N':
	      student[j].score[problem]=0;
	      break;
	    default:
	      if (isdigit(temp))
		{
		  student[j].score[problem]=temp-'0';
		  student[j].total+=student[j].score[problem];
		}
	      else
		{
		  student[j].score[problem]=0;
		  student[j].total+=student[j].score[problem];
		  fprintf(stderr,"Invalid score for student ");
		  fprintf(stderr,"%s, problem",set_record[i].s_number);
		  fprintf(stderr,"%d, score == ",problem);
		  fprintf(stderr,"%c skipping them\n",temp);
		  j--;
		  goto skip_student;
		}
	    }
	}
    skip_student: j=j;
    }
  GuyNumberOfStudents=j;
  qsort((char *)student,GuyNumberOfStudents,sizeof(Student),studentCompare);
}
 


/*--------------------------------------------------------------*/
/*                                                              */
/* t[i][j]: the number of tries for the `i`th student, the      */
/*          `j`th problem in set*.db file.                      */
/*          t[i][*]=set_record[i].tries                         */
/* s[i][j]: the number of students who work on the `i`th        */
/*          problem for `j` tries.                              */
/*                                                              */
/*--------------------------------------------------------------*/
void Sort_By_Tries()
{
   int   i,j,try;
   char  *tmp;

   /* initialization */
   for (i=0;i<MAX_PROBLEM_NUMBER;i++){
       YesCnt[i] = 0;
       yesCnt[i] = 0;
       correct[i] = 0;
       TotalTries[i]=0;
       for (j=0; j<MAX_TRIES; j++){
           s[i][j]=0;
       }
       for (j=0;j<MAX_CLASS_SIZE;j++){
           t[j][i]=0;
       }
   }

   for (i=0;i<NumberOfStudents;i++){
       tmp=set_record[i].tries;
       for (j=0; j<NumberOfProblems; j++){
           sscanf(tmp+3*j,"%d,",&try);
           if ((try>=0) && (try <=99)){
              s[j][try]++;
              t[i][j]=try;
              TotalTries[j]=TotalTries[j]+try;
           }
       }
   }

}


double GetDegreeOfDiscrim(int problem,int mode)
{
  int i,numTopRight=0,numBottomRight=0,twentySevenPercent;
  double dd;

  twentySevenPercent=(int)ceil(.27*(double)GuyNumberOfStudents);
  for(i=0;i<twentySevenPercent;i++)
    {
      switch (mode)
	{
	case PARTIAL:
	  if (student[i].score[problem] > 0) { numTopRight++; }
	  if (student[GuyNumberOfStudents-i].score[problem]>0){numBottomRight++;}
	  break;
	case NOPARTIAL:
	  if (student[i].score[problem] == Weight[problem]) { numTopRight++; }
	  if (student[GuyNumberOfStudents-i].score[problem] == Weight[problem])
            { numBottomRight++; }
	  break;
	}
    }
  dd=(((((double)numTopRight)/((double)twentySevenPercent))-
       (((double)numBottomRight)/((double)twentySevenPercent))));
  return 100.0*dd;
}
	  
double GetDifficulty(int problem, int mode)
{
  int i,numWrong=0;
  for(i=0;i<GuyNumberOfStudents;i++)
    {
      switch(mode)
	{
	case PARTIAL:
	  if (student[i].score[problem] == 0) { numWrong++; }
	  break;
	case NOPARTIAL:
	  if (student[i].score[problem] < Weight[problem]) { numWrong++; }
	  break;
	}
    }
  return (100.0*((double)numWrong/(double)GuyNumberOfStudents));
}

#define MAXSCORE 58
void doScoreHistogram(void)
{
  int hist[MAXSCORE],i,numGoneBy=0;
  for(i=0;i<MAXSCORE;i++)
    hist[i]=0;
  for(i=0;i<GuyNumberOfStudents;i++)
    {
      hist[student[i].total]++;
    }
  printf("Scr Num Tot\n");
  for(i=0;i<MAXSCORE;i++)
    {
      numGoneBy+=hist[i];
      printf("%3d %3d %3d\n",i,hist[i],numGoneBy);
    }
}
  
/*--------------------------------------------------------------*/
/*                                                              */
/* This is only used to print out the statistics in s[][],      */
/* which stores the number of occurences in a form of           */
/* s[problem_number-1][tries]. This is not normally used.       */
/* For a  global view only.                                     */ 
/*                                                              */
/*--------------------------------------------------------------*/
void Print_moment(avg,sd,sd3,m,students)
float avg[MAX_PROBLEM_NUMBER];
float sd[MAX_PROBLEM_NUMBER];
float sd3[MAX_PROBLEM_NUMBER];
int   m[MAX_PROBLEM_NUMBER];
int   students[MAX_PROBLEM_NUMBER];
{
   int i;
   float dod,disP,disNP,difP,difNP;

   printf("\nThis is the statistics for each problem:\n");
   printf("Prob#   MxTries   avg.     s.d.    s.k.   #Stdnts ");
   printf("  #Yes   #yes   Tries  DoDiff  Dis(P)  Dif(P) Dis(NP) Dif(NP)\n");
   for (i=0;i<NumberOfProblems;i++){
       dod=1.0-((float)(YesCnt[i]+yesCnt[i]+correct[i])/(float)TotalTries[i]);
       disP=GetDegreeOfDiscrim(i,PARTIAL);
       difP=GetDifficulty(i,PARTIAL);
       disNP=GetDegreeOfDiscrim(i,NOPARTIAL);
       difNP=GetDifficulty(i,NOPARTIAL);
       printf("P %2d:",i+1);
       printf("%7d  %8.2f  %7.2f  %6.2f   %5d   %5d  %5d    %5d  %5.2f   %5.2f   %5.2f  %6.2f %6.2f",
              m[i],avg[i],sd[i],sd3[i],students[i],YesCnt[i],yesCnt[i],
              TotalTries[i],dod,disP,difP,disNP,difNP);
       printf("\n");
   }
   doScoreHistogram();
}


/*--------------------------------------------------------------*/
/*                                                              */
/* This is only used to print out the statistics in s[][],      */
/* which stores the number of occurences in a form of           */
/* s[problem_number-1][tries]. This is not normally used.       */
/* For a  global view only.                                     */ 
/*                                                              */
/*--------------------------------------------------------------*/
void Print_Tries()
{
   int i,j;

   printf("\nThis is the sumary of tries:\n");
   for (i=0;i<NumberOfProblems;i++){
       printf("P %d:",i+1);
       for (j=0; j<MAX_TRIES; j++)
           if (s[i][j]!=0)
              printf("%d( %d)  ",j,s[i][j]);
       printf("\n");
   }
}

/*--------------------------------------------------------------*/
/*                                                              */
/* This is only used to print out the statistics in s[][],      */
/* which stores the number of occurences in a form of           */
/* s[problem_number-1][tries]. This is not normally used.       */
/* For a  global view only.                                     */
/*                                                              */
/*--------------------------------------------------------------*/

void Average_Tries()
{
   float avg[MAX_PROBLEM_NUMBER];
   float sd[MAX_PROBLEM_NUMBER];
   float sd3[MAX_PROBLEM_NUMBER];
   int   students[MAX_PROBLEM_NUMBER];
   int   m[MAX_PROBLEM_NUMBER];
   int   i,j;
   float tmp1,tmp2;
   float sum;

   /* max tries for each problem */
   for (i=0;i<NumberOfProblems;i++){
       m[i]=0;
       for (j=0;j<NumberOfStudents;j++){
           if (t[j][i]>m[i]){
              m[i]=t[j][i];
           }
       }
   }
   for (i=0;i<NumberOfStudents;i++){
       if (set_record[j].valid){
          ValidStudents++;
       }
   }

   /* first moment */
   for (i=0;i<NumberOfProblems;i++){
       avg[i]=0.0;                       /* initialization */
       students[i]=0;
       for (j=1;j<NumberOfStudents;j++){ 
           if (set_record[j].valid){
              avg[i]=avg[i]+t[j][i];     /* sumation actually */
              students[i]=students[i]+1;
           }
       }
       avg[i]=avg[i]/students[i];        /* real average */
   }

   /* second moment */
   for (i=0;i<NumberOfProblems;i++){ 
       sd[i]=0.0;
       sum=0.0;
       for (j=0;j<NumberOfStudents;j++){
           if (set_record[j].valid){                                  
              tmp1=(float)t[j][i];
              tmp2=(tmp1-avg[i])*(tmp1-avg[i]);
              sum=sum+tmp2;
           }
           sd[i]=sum/(float)(students[i]-1);
           sd[i]=sqrt((double) sd[i]); 
       }
   }

   /* third moment, skewness */
   for (i=0;i<NumberOfProblems;i++){ 
       sd3[i]=0.0;
       sum=0.0;
       for (j=0;j<NumberOfStudents;j++){
           if (set_record[j].valid){     
              tmp1=(float)t[j][i];
              tmp2=(tmp1-avg[i])*(tmp1-avg[i])*(tmp1-avg[i]);
              sum=sum+tmp2;
           }
           sd3[i]=sum/(float)(students[i]);
           sd3[i]=sd3[i]/(sd[i]*sd[i]*sd[i]); 
       }
   }
   Print_moment(avg,sd,sd3,m,students);     /* print mean, sd, skewness */
}

/*--------------------------------------------------------------*/
/*                                                              */
/*                                                              */
/*--------------------------------------------------------------*/
void Percentage_Scores(set)
int set;
{
  int    i,j;
  int    total_weight = 0, 
         total_scores=0;
  float  percentage;
  
  for (i=0;i<NumberOfStudents;i++){
      if (set_record[i].valid){
         for (j=0;j<NumberOfProblems;j++){
             total_weight = total_weight+Weight[j];
             switch (set_record[i].answer[j]){
                case 'Y': YesCnt[j] = YesCnt[j]+1;
                          total_scores = total_scores + Weight[j];
                          break;
                case 'y': yesCnt[j] = yesCnt[j]+1;
                          total_scores = total_scores + Weight[j];
                          break;
                default : if (set_record[i].answer[j] >= '0' && 
                              set_record[i].answer[j] <= '9' ) {
                              correct[i]=correct[i]+1;
                              total_scores=total_scores+(set_record[i].answer[j]-'0');
                          } 
                          break;
             }
         }
      }
  }

  percentage = (float)total_scores / (float)total_weight;
  percentage = percentage * 100.0;
  printf("\nThe percentage score for set%d.db is %7.2f%%\n",set,percentage);
  /*printf("Total Number of Students in set%d.db is %d\n",set,ValidStudents);*/

}

/*--------------------------------------------------------------*/
/*                                                              */
/*                                                              */
/*--------------------------------------------------------------*/
void Large_Tries(LargeTry, NumberLargeTry)
int LargeTry, NumberLargeTry;
{
   int   i,j;
   int   count;
   int   credit;              /* Number of credits should be taken off */


   printf("\nHere is a list of students who have %d tries more than %d times: \n\n", LargeTry, NumberLargeTry);

   for (i=0;i<NumberOfStudents;i++){
       count=0;
       credit=0;
       for (j=0;j<NumberOfProblems;j++){
           if (t[i][j]>=LargeTry){
              count++;
              if (set_record[i].answer[j]=='Y' ||
                  set_record[i].answer[j]=='y')
                  credit ++;
           } 
       }
       if (count >= NumberLargeTry){
          printf("(%d)  %s \n",credit, set_record[i].s_number);
          printf("%s %s \n", set_record[i].answer, set_record[i].tries);
       }
   }

}

/*--------------------------------------------------------------*/
/*                                                              */
/*                                                              */
/*--------------------------------------------------------------*/
int main(argc,argv)
int argc;
char *argv[];

{

    int    set=1;
    int    inputNotOK=1;
    int    LargeTry=0,NumberLargeTry=0;
    FILE   *fp_set;
    char   set_file[FILE_NAME_LENGTH];
    char   path[FILE_NAME_LENGTH];
    char   filename[FILE_NAME_LENGTH];
   
    for( progname = *argv++; --argc; argv++) {
      if ( argv[0][0] == '-' ) {
        switch(argv[0][1]) {
           case 's':  set = atoi(argv[1]);            break;
           case 't':  LargeTry = atoi(argv[1]);       break;
           case 'n':  NumberLargeTry = atoi(argv[1]); break; 
           default:   usage();                        break;
        }
      }
    }

    while ( inputNotOK ) {
       puts("Enter the ABSOLUTE path of class");
       scanf("%s", path);
       if( access(path, F_OK) == -1 ) {
       } else {
          sprintf(filename,"%s/records",path);
          if( access(filename, F_OK) == -1 ) {
            puts("There isn't a records dir in this CLASS directory");
            puts("Please Specify another calss");
          } else {
            inputNotOK = 0;
          }
       }
    }
    chdir(path);
    sprintf(set_file, "records/set%d.db",set);
    /* sprintf(out_file, "records/set%d.out",set); */
    fp_set=Open_Read(set_file);
    /* fp_out=Open_Write(out_file); */

    Read_Record(fp_set);
   
    Sort_By_Tries();   
    /* Print_Tries(); */
    Percentage_Scores(set);
    Average_Tries();
    
    Large_Tries(LargeTry, NumberLargeTry);  
    return 0;
}


