#include <stdio.h>
#include <unistd.h>

#include "ClearSilver.h"
#include "global.h"
#include "grade.h"

#include "custom.h"
#include "manhat-lib/shared_util.h"
#include "manhat-lib/shared_person_list.h"
#include "manhat-lib/shared_grade_book_release.h"
#include "manhat-lib/shared_cs_util.h"




	
static void
read_parameters (char *course, char *key)
{
   cs_get_required_parameter("crs", course, MAX_PATH);
   cs_get_required_parameter("id", key, MAX_KEY);

}






static
char *read_uploaded_file(CONFIG_STRUCT *conf)
{
    char db_path[MAX_PATH +1];
    FILE *fp;
    char *buf;
    struct stat file_stat;

    /* get size of the uploaded text file */
    snprintf(db_path, MAX_PATH + 1, "%s%s/%s", conf->course_path, GRADE_BOOK_DIR, GRADES_NEW_TAB_FNAME);
    if(stat(db_path, &file_stat))
        cs_critical_error( ERR_STAT_FAILED, db_path);

    /* allocate a buffer one byte larger than the length of the file */
    buf = (char*)malloc(file_stat.st_size + 1);
    if(!buf)
        cs_critical_error( ERR_MALLOC_FAILED, "");


    fp = fopen(db_path, "r");
    if(!fp)
       return 0;     /* can't open file */
       
    if(get_shared_lock(fileno(fp), 0))     /* shared_lock.c */
      {
        fclose(fp);
        return 0;
      }  
       
       
    /* read the entire file into the buffer */   
    if(fread(buf, file_stat.st_size, 1, fp) != 1)
	 cs_critical_error(ERR_FREAD_FAILED, db_path);
	 
    release_lock(fileno(fp));   /* shared_lock.c */
    fclose(fp);

    *(buf + file_stat.st_size) = '\0';  /* null terminate the buf string */
    
    return buf;
}






static
int contains_digits(const char *id)
{
    int i, len;
    len = strlen(id);
    for(i =0; i<len; i++)
    {
        if(isdigit(id[i]))
	   return 1;
    }
    return 0;
}






static int
mark_this_student(P_NODE *head,  char *id,   int *extra_count, int *dup_count)
{
    P_NODE *ptr;
    int find =0;
    char stdname[MAX_NAME +1];

    for(ptr = head; ptr; ptr = ptr->next)
    {
         if( (ptr->data.group != FACULTY) && !strcmp(ptr->data.id, id) && (*(ptr->data.realname) != '*'))
	 {
	    if(ptr->data.team == 1)   /* if not already found in uploaded file */
	         find = -1;           /* means this person is duplicated in file */
	    else
	    {
	       ptr->data.team = 1;    /* mark this person as included in uploaded file */
	       find = 1;
	    }
	     break;
	 }
    }


    if(find == 0)    /* person was listed in file, but not in our classroom */
    {
        snprintf(stdname, MAX_NAME +1, "extra.%d.id", *extra_count);
        cs_set_value(stdname, id);
        (*extra_count)++;
        return 0;	
    }
    else if(find == -1)
    {
        snprintf(stdname, MAX_NAME +1, "dup.%d.id", *dup_count);
        cs_set_value( stdname, id);
        (*dup_count)++;
        return 0;	
    }
    return 1; 
}






static 
int all_students_have_grades(P_NODE *head)
{
    P_NODE *ptr;
    int i =0;
    char name[MAX_NAME +1];

    for(ptr = head; ptr; ptr = ptr->next)
    {
         if(ptr->data.group != FACULTY && *(ptr->data.realname) != '*')
	 {
             if(ptr->data.team != 1)
	     {
                snprintf(name, MAX_NAME +1, "miss.%d.id", i);
                cs_set_value( name, ptr->data.id);
                snprintf(name, MAX_NAME +1, "miss.%d.name", i);
                cs_set_value(name, ptr->data.realname);
                i++;
	     }
	 }
    }
   if(i ==0)
	return 1;
  return 0; 
}






/* we'll use the 'team' field in the linked list to record
 * ** whether or not that person is listed in the uploaded tab file
 * ** This function sets the team to 0 for all nodes, meaning that
 * ** the person is not yet found in the tab file
 * */

static 
P_NODE * clear_team_fields(P_NODE *head)
{
	 P_NODE *ptr;

	  for(ptr = head; ptr; ptr=ptr->next)
		       ptr -> data.team = 0;
	   return head;
}







static 
P_NODE *build_grade_receiver_list( CONFIG_STRUCT *conf, int *error)
{
    P_NODE *head;
    int len =0, ss, done, i;
    char *grades_line = 0, 
         *buf, 
         *ptrID, 
         id[MAX_ID +1 ];

    int big = MAX_GRADES_LINELEN;
    int extra_count = 0,  /* count of records in uploaded file,with no matching students in classroom */
        dup_count = 0;     /* count of records in uploaded file, which were duplicates */

    head = build_option_person_list(conf);     /* shared_person_list.c */
    head = clear_team_fields(head);    
    buf =  read_uploaded_file(conf);


    grades_line = (char*)malloc(sizeof(char) * (MAX_GRADES_LINELEN + 1));
    if(!grades_line)
       cs_critical_error(ERR_MALLOC_FAILED, "");
    *grades_line = '\0';   

    len = strlen(buf);
    for(ss =0; ss < len; ss++)
    {
        i = 0;
	while( (ss < len) && (buf[ss] != '\n'))    /* handle one line at a time */
	{
	   if((int)strlen(grades_line) >= big)
	   {
	          grades_line = realloc(grades_line, (big * 2) + 1);
		  big = big * 2;
	   } 
	   grades_line[i] = buf[ss];
	   grades_line[i + 1] = '\0';
	   i++;
	   ss++;
	}
	
	
	if(*grades_line)  /* if the line contains characters */
	{
	  done = 0;
	  
	  /* find a tab character */
	  ptrID = strchr(grades_line, '\t');
	  if(ptrID)
	     *ptrID ='\0';
	  else
	     done = 1;
	     
	  if(!done)
	   {
	       /* first field is an ID number */
	       strncpy(id, grades_line, MAX_ID + 1);
               *(id + MAX_ID) = '\0';
	          
	       if(contains_digits(id))
	       {
		if(!mark_this_student(head, id, &extra_count, &dup_count))
		    (*error)++;
	       }
	   }
	    
	  free(grades_line);
	  grades_line =0;
	  big = MAX_GRADES_LINELEN;
          grades_line = (char*)malloc(sizeof(char) * MAX_GRADES_LINELEN +1);
          if(!grades_line)
              cs_critical_error(ERR_MALLOC_FAILED, "");
          *grades_line = '\0';
        }  /* if(*grades_line) */	  
    }  /* for each character in the file */
    free(buf);
    
    if(!all_students_have_grades(head))
	   (*error)++; 
    return head;
}






static 
void deliver_new_file_to_students(CONFIG_STRUCT *conf, P_NODE *head)
{
   P_NODE *ptr;
   for(ptr = head; ptr; ptr = ptr->next)
   {
	if(ptr->data.team == 1)
	  write_student_newfile (ptr->data.username, conf);  /* shared_grade_book_release.c */
   }
}







static 
int has_newlines (char *buffer, int bytes_read, int *tab)
{
  char *ptr;
  int i;
  int has_new_line =0;

  for (ptr = buffer, i = 1; i <= bytes_read; ptr++, i++)
   {
    if (*ptr == 10)
      has_new_line++;
   
    if(*ptr == '\t')
      (*tab)++;
   }
  
  if(has_new_line)
	return 1;
  
  return 0;
}



static void
cr_to_nl (char *buffer, int bytes_read)
{

  char *ptr;
  int i;

  for (ptr = buffer, i = 1; i <= bytes_read; ptr++, i++)
    if (*ptr == 13)
      *ptr = 10;
}


static
void rename_temp_tab_file(CONFIG_STRUCT *conf)
{
 char old_fname[MAX_PATH + 1];
 char new_fname[MAX_PATH + 1 ];
   
 snprintf(old_fname, MAX_PATH +1, "%s%s/%s", conf->course_path, GRADE_BOOK_DIR, GRADES_NEW_TAB_FNAME);   
 snprintf(new_fname, MAX_PATH +1, "%s%s/%s", conf->course_path, GRADE_BOOK_DIR, GRADES_TAB_FNAME);
 
 if(rename(old_fname, new_fname))
     cs_critical_error(ERR_RENAME_FAILED, old_fname);
   

}






static int
write_temporary_tab_file (FILE *fp, CONFIG_STRUCT *conf)
{

#define CHUNKSIZE 2048
  FILE *out;
  int bytes_read;
  int bytes_left;
  char buffer[CHUNKSIZE];
  char file[MAX_PATH + 1];
  int newlines = -1; 		/* does this file contain at least one newline ASCII 10 a Macintosh kludge */
  int  no_tabs_found = 0;
  struct stat file_stat;
  struct stat dir_stat;

  if(fstat(fileno(fp), &file_stat))
      cs_critical_error(ERR_NO_FILE, "");


  /* create a directory for the grade book within the courses directory, 
  ** if it doesn't already exist
  */
  snprintf(file, MAX_PATH +1, "%s%s", conf->course_path, GRADE_BOOK_DIR);
  if(stat(file, &dir_stat))
      if(mkdir(file, 0770))
         cs_critical_error(ERR_MKDIR_FAILED,file);


   snprintf(file, MAX_PATH +1, "%s%s/%s", conf->course_path, GRADE_BOOK_DIR, GRADES_NEW_TAB_FNAME);
   out = fopen(file, "w");
   if (!out)
      cs_critical_error(ERR_FOPEN_WRITE_FAILED, file);


  bytes_left = file_stat.st_size;
  bytes_read = CHUNKSIZE;
  while (bytes_left)
    {
      if (bytes_left < CHUNKSIZE)
	bytes_read = bytes_left;
      if (fread (buffer, bytes_read, 1, fp) != 1)
          cs_critical_error(ERR_FREAD_FAILED, file);

      if (newlines == -1)
	newlines = has_newlines (buffer, bytes_read, &no_tabs_found);	/* do this once */

      if (!newlines)		/* assume it's a macintosh file and convert */
	cr_to_nl (buffer, bytes_read);

      bytes_left -= bytes_read;
      if (fwrite (buffer, bytes_read, 1, out) != 1)
          cs_critical_error(ERR_FWRITE_FAILED, file);

    }

  fclose (out);

  if(no_tabs_found)
    return 1; 

  return  0;
#undef CHUNKSIZE
}






static 
void  delete_upload_file(CONFIG_STRUCT *conf)
{
   char file[MAX_PATH +1];

   snprintf(file, MAX_PATH +1, "%s%s/%s", conf->course_path, GRADE_BOOK_DIR, GRADES_NEW_TAB_FNAME);
    unlink(file);
}




int
main ()
{
  char course[MAX_PATH + 1];
  char key[MAX_KEY + 1];
  SESSION user;
  CONFIG_STRUCT conf;
  FILE *fp;
  int  error = 0;
  P_NODE *head =0;

  cs_cgi_init();                   /* shared_cs_util.c */
  read_parameters (course, key);  
  read_configuration_file (course, &conf); /* shared_util.c */	
  validate_key (key, &user, &conf);	   /* shared_util.c */	

  if(user.group != FACULTY)
	  cs_critical_error(ERR_REQUEST_DENIED, "");   /* shared_cs_util.h */
	  
  cs_set_course_info(&conf);      /* shared_cs_util.h */
  cs_set_current_time();          /* shared_cs_util.h */
  cs_set_back_url( course, key, "grades_fac_menu");   /* shared_cs_util.h */

  fp = cgi_filehandle (global_cgi, "grades_file");   	/* ClearSilver library */
  if(!fp)
     cs_critical_error(ERR_UPLOAD_FILE, "");   		/* shared_cs_util.h */

  if(!write_temporary_tab_file(fp,&conf))
      cs_critical_error(ERR_GRADES_UPLOAD_FORMAT, "");   /** shared_cs_util.h */
   
  head = build_grade_receiver_list( &conf, &error);
  
  if(error)
  {
      cs_set_back_url( course, key, "grades_upload_form");
      delete_upload_file(&conf);
      cs_cgi_display ("grade_upload_error", 1);
  }
  else
  {
     delete_redfiles (head, &conf);                 /* shared_grade_book_release.c */
     deliver_new_file_to_students(&conf, head);    
     rename_temp_tab_file(&conf);
     printf("Location: %s?crs=%s&id=%s&released=1\n\n", "grades_view", course, key);

  }

  cs_cgi_destroy();
  
  return 0;			/* exit successfully */

}

