#include <stdio.h>
#include <stdlib.h>   /* for free() */
#include <string.h>
#include <ctype.h>
#include <sys/stat.h>
#include <unistd.h>
#include <time.h>
#include "global.h"
#include "custom.h"

SESSION user;
CONFIG_STRUCT conf;		/* the configuration read from config file */


#include "manhat-lib/shared_util.h"
#include "manhat-lib/shared_lock.h"
#include "manhat-lib/shared_cs_util.h"
#include "manhat-lib/shared_hotpot_parser.h"
#include "manhat-lib/shared_news_util.h"
#include "manhat-lib/shared_hotpot_exam_util.h"
#include "manhat-lib/shared_strtrm.h"
#include "manhat-lib/shared_file_util.h"
#include "manhat-lib/shared_server_string.h"
#include "manhat-lib/shared_bb_manifest.h"

HDF *hdf;
static void
read_parameters (char *course, char *key, char *sender, char *fname, char *info_fname, 
								time_t *start_time, long *offset)
{
 char tmp[MAX_PATH + 1];

  cs_get_required_parameter ("crs", course, MAX_FILENAME);	/* shared_cgi_util.c */
  cs_get_required_parameter ("id", key, MAX_KEY);
  cs_get_required_parameter ("sender", sender, MAX_USERNAME);
  cs_get_required_parameter ("inf", info_fname, MAX_FILENAME);  
  cs_get_required_parameter ("test", tmp, MAX_PATH);  
  *start_time = atol(tmp);
  cs_get_required_parameter("loc", tmp, MAX_PATH);
  *offset = atol(tmp);
  
/* note we do NOT free the entries list here! **/

}


/** removes leading and trailing spaces from string
*** Also replaces multiple occurrences of whitespace characters within 
*** the string with a single space.  EG:
*** the string " This is     a string  with    spaces   "
*** becomes:
***  "This is a string with spaces"
**/
static
void despace_string(char *string)
{

 char *newstring;
 char *ptr;
 int space_found;
 int i;
 
 if(strlen(string))   /* do nothing unless the string has length */
  {
   strtrm(string);   /* shared_util.c - remove leading, trailing whitespace */
   if(strlen(string))  /* is anything left? */
    {
     newstring = (char *) malloc( (strlen(string) + 1) * sizeof(char));
     if(!newstring)
       cs_critical_error(ERR_MALLOC_FAILED, "");
     for(ptr=string, space_found = 0, i = 0; *ptr; ptr++)  /* for each char in the string */
      {
       if(isspace(*ptr))
        {
         if(!space_found)   /* if the last char processed was NOT a whitespace character */
          {
           *(newstring + i) = ' ';   /* store a single space */
           i++;                   
           space_found = 1;          /* set the flag */
          } 
        }
       else   /* we're not dealing with a whitespace character */
        {
         *(newstring + i) = *ptr;      /* store the normal character */
         i++;
         space_found = 0;              /* unset the flag */
        }
      } /* for(...) */  
      *(newstring + i) = '\0';        /* null terminate the string */
       strcpy(string,newstring);      /* return the result */
       free(newstring);
    }  /* if strlen(string) */
  }  /* if strlen(string) */
} 

char *
get_response(char *string)
{
  char *response;
  int len = strlen(string);

  response = (char *) malloc(sizeof(char) * len +1);  /* allocate just one byte */
  if(!response)
     cs_critical_error(ERR_MALLOC_FAILED, "");
  strncpy(response, string, len +1);
  *(response + len) = '\0';
  return response; 
}

      
/** Note that this does not free memory allocated by calls to cgi_val_multi()
*** This should eventually be fixed
**/      
static void
store_responses()
{
 #define MAX_TMP_NAME 30
 QUESTION_NODE *q_ptr;
 int q;
 char *value;
 int  j;
 char varname[MAX_TMP_NAME];
 char *buf;
       
 for(q=0, q_ptr=quiz_data.head; q_ptr; q++, q_ptr=q_ptr -> next)
  {
    if(q_ptr->question_type == MC)
    {
       snprintf(varname, MAX_TMP_NAME, "Query.q%d",q);   /* build the variable name as delivered by the form */
       value = hdf_get_value(global_cgi->hdf, varname, "");
       despace_string(value);     
       q_ptr->response = get_response(value);	  
    }
    else if(q_ptr->question_type == SHORT)
    {
       snprintf(varname, MAX_TMP_NAME, "Query.q%d",q);   /* build the variable name as delivered by the form */
       value = hdf_get_value(global_cgi->hdf, varname, "");
       despace_string(value);     
       q_ptr->response = get_response(value);	  
    }
    else if(q_ptr->question_type == MC_MULTI)
    {
       snprintf(varname, MAX_TMP_NAME, "Query.q%d.0",q);   /* build the variable name as delivered by the form */
       value = hdf_get_value(global_cgi->hdf, varname, 0);
       if(value) 
       {
	  buf = (char*)malloc(sizeof(char) * MAX_HOTPOT_CHOICES + 1);
	  if(!buf)
             cs_critical_error(ERR_MALLOC_FAILED, "");
	  *buf = '\0';
	  for(j =0; j < q_ptr->answer_count; j++)
	  {
            snprintf(varname, MAX_TMP_NAME, "Query.q%d.%d",q, j);   /* build the variable name as delivered by the form */
            value = hdf_get_value(global_cgi->hdf, varname, 0);
	    if(value)
	    {
              despace_string(value);     
	      strcat(buf, value);
	    }
             
	  }
          q_ptr->response = get_response(buf);	  
	  if(buf)
	     free(buf);
       }
       else
       {
          snprintf(varname, MAX_TMP_NAME, "Query.q%d",q);   /* build the variable name as delivered by the form */
          value = hdf_get_value(global_cgi->hdf, varname, 0);
          despace_string(value);     
          q_ptr->response = get_response(value);	  
       }
    }
    
  }
 #undef MAX_TMP_NAME 
}


/*


int get_unique_fd(const char *base_dir, const char *filetype, char *unique_fname, time_t *time_sent)
{
  char template[MAX_PATH + 1];
  char timestring[MAX_TIMESTRING + 1];
  int fd;
  char *ptr;
  
  strftime (timestring, MAX_TIMESTRING, "%m%d%Y%H%M%S",localtime (time_sent));

  snprintf (template, MAX_PATH + 1, "%s%s_%s_XXXXXX", base_dir,filetype, timestring);
  
  fd = mkstemp(template);
  if(fd == -1)
      cs_critical_error(ERR_MKSTEMP_FAILED, "");
  change_mkstemp_permission(template);     
  ptr = strrchr(template,'/');
  if(!ptr)
    cs_critical_error(ERR_GENERAL_ERROR, "");
  ptr++;
  strncpy(unique_fname, ptr,MAX_FILENAME + 1);
  if(fchmod(fd,S_IWGRP|S_IRGRP|S_IRUSR|S_IWUSR))
    cs_critical_error(ERR_CHMOD_FAILED,"");
  return fd;
}  


*/
/* note that this creates the file */
void
get_unique_fname(char *unique_fname, const char *base_dir, const char *filetype, time_t *time_sent)
{
 int fd;
 
 fd = get_unique_fd(base_dir, filetype, unique_fname, time_sent);
 close(fd);
} 





static 
int jqz_correct_answer(const char *response, const char *answer)
{
 char *new_answer;
 int result = 0;
 
 if(strlen(response))
  {
   if(strlen(answer))  
    {
     new_answer= (char *) malloc((strlen(answer) + 1) * sizeof(char));
     if(!new_answer)
        cs_critical_error(ERR_MALLOC_FAILED, "");
     strcpy(new_answer,answer);
     despace_string(new_answer);
     if(quiz_data.case_sensitive)
       result = !strcmp(response,new_answer);
     else
       result = !strcasecmp(response, new_answer);
     free(new_answer);   
    }
  }
  
 return result;
}







static
int short_question_correct(QUESTION_NODE *q_ptr)
{
 ANSWER_NODE *a_ptr;
 int  correct_found;
 
 
  for(a_ptr = q_ptr->head, correct_found = 0; a_ptr && !correct_found; a_ptr=a_ptr->next)
     correct_found = (a_ptr->is_correct) && jqz_correct_answer(q_ptr->response, a_ptr->answer);

  if(correct_found)
     return 1;
  else
     return 0;   
 }










static
int mc_question_correct(QUESTION_NODE *qptr)
{

 int correct = 0;
 
  if(qptr->correct_answer_string && *(qptr->correct_answer_string))
    correct = qptr->response && *(qptr->response) && (int)strchr(qptr->correct_answer_string, *(qptr->response));

  return correct? 1 : 0;
}


   
   


static int
mc_multi_question_correct(QUESTION_NODE *qptr)
{
 char *ptr;
 
 
 if(qptr->correct_answer_string && *(qptr->correct_answer_string))
  {
   /* every char of the person's response must be in the correct answers string */
   for(ptr=qptr->response; *ptr; ptr++)
     if(!strchr(qptr->correct_answer_string,*ptr))
          return 0;
   
   for(ptr=qptr->correct_answer_string; *ptr; ptr++)
     if(!strchr(qptr->response,*ptr))
       return 0;

   return 1;
  }
 return 0;   /* no answers are correct */
}


      
      
      
       
 







static void
get_totals(int *total_questions, int *total_correct)
{
 QUESTION_NODE *q_ptr;

 *total_correct = 0;
 for(q_ptr = quiz_data.head, *total_questions=0; q_ptr; (*total_questions)++, q_ptr=q_ptr->next)
 {
  switch(q_ptr->question_type)
   {
    case MC:     q_ptr->answer_correct = mc_question_correct(q_ptr); break;
    
    case SHORT:  q_ptr->answer_correct = short_question_correct(q_ptr); break;
    
    case MC_MULTI: q_ptr->answer_correct =  mc_multi_question_correct(q_ptr); break;
    
    default:    cs_critical_error(ERR_UNKNOWN_QUESTION_TYPE, "");
                break;
   } 
   if(q_ptr->answer_correct)
      *total_correct = *total_correct + 1;
 }     
}


static void
get_elapsed_timestring(time_t start_time, time_t end_time, char *elapsed_timestring)
{

time_t diff;

diff = end_time - start_time;

strftime(elapsed_timestring,MAX_TIMESTRING, "%H:%M:%S", gmtime(&diff));

}

   
static
void print_return_to_assign_form( char *course,  char *key, long offset)
{
  char temp[30];
  cs_set_value("crs", course);
  cs_set_value("id", key);
  snprintf(temp, 30, "%ld", offset);
  cs_set_value("loc", temp);
  snprintf(temp, 30, "%d", ASSIGNMENTS);
  cs_set_value("grp", temp);
} 
 



static void 
show_success ( char *course,  char *key, long offset)
{
 print_return_to_assign_form(course, key, offset);
}
               

void
print_style(FILE *fp)
{
  fprintf(fp,"<style type=\"text/css\">\n");
  fprintf(fp,"body {font-size: 12px; font-family: %s ; background-color: %s }", 
		  quiz_data.font_face ? quiz_data.font_face:"verdana, Arial, Geneva, Helvetica, sans-serif" ,
                  quiz_data.pg_bg_color?quiz_data.pg_bg_color:"#ffffff" 
		  );

  fprintf(fp,"th, td {font-size: 12px}\n");
  fprintf(fp,".red_an { color: #FF0000}\n");
  fprintf(fp,".blue_an { color: #0000FF}\n");
  fprintf(fp,"h3 { color: %s }\n", quiz_data.title_color? quiz_data.title_color: "#000000");
  fprintf(fp, "</style>\n");

}


static void
do_html_header(FILE *fp, const char *symlink_fname)
{
 char server_string[MAX_PATH + 1];
 
 get_server_string(server_string, MAX_PATH + 1);    /* shared_server_string.c */
 
 fprintf(fp, "<html>\n<head>\n");
 fprintf(fp, "<title>%s</title>\n",quiz_data.title?quiz_data.title: hdf_get_value(hdf, "hotpot.page_title", ""));
 print_style(fp);
 if(*symlink_fname)
    fprintf(fp, "<base href=\"%s%slinks/%s/\">\n\n", server_string, IMAGE_PATH, symlink_fname);
 fprintf(fp, "\n</head>\n<body>\n");
}


static void
do_html_summary(FILE *fp, time_t start_time, time_t end_time, const char *elapsed_timestring, 
                                   int total_correct, int total_questions)
{
 char timestring[MAX_TIMESTRING + 1];

 fprintf(fp,"<table border=\"1\">\n");
 fprintf(fp, "<tr><th colspan=\"2\" >%s</th></tr>\n", hdf_get_value(hdf, "hotpot.results",""));
 
 /* print student info */
 fprintf(fp, "<tr><td>%s</td><td>%s (%s)</td></tr>\n", hdf_get_value(hdf, "hotpot.student_label",""), user.realname, user.username); 
 
 
 /* print start time */
 strftime (timestring, MAX_TIMESTRING, DOW_DATE_TIME_FORMAT,
	                                  localtime (&(start_time)));   

 fprintf(fp, "<tr><td>%s</td><td>%s</td></tr>\n", hdf_get_value(hdf, "hotpot.start_time",""), timestring);


 /* print end time */

 strftime (timestring, MAX_TIMESTRING, DOW_DATE_TIME_FORMAT,
	                                  localtime (&(end_time)));   

 fprintf(fp, "<tr><td>%s</td><td>%s</td></tr>\n", hdf_get_value(hdf, "hotpot.end_time",""), timestring);
 
 /* print elapsed time */
  fprintf(fp, "<tr><td>%s</td><td>%s</td></tr>\n", hdf_get_value(hdf, "hotpot.elapsed_time",""), elapsed_timestring); 

 /* print score */
  fprintf(fp, "<tr><td>%s</td><td>%d/%d</td></tr>\n", hdf_get_value(hdf, "hotpot.score",""), total_correct, total_questions); 
  
  fprintf(fp,"</table>\n");

}                                   

static void
print_html_string(FILE *fp, char *string)
{
 char *ptr;
 int nl = 0;
 
 for(ptr=string;*ptr; ptr++)
  {
   if(*ptr == '\n')
    {
     if(nl)
       fputs("<p>",fp);
//     else
//       fputs("<br>",fp);
      nl = 1;
    }
   else
     {
      fputc(*ptr,fp);
      nl = 0;
     }
  }
}




static void
do_html_instructions(FILE *fp)
 {
 
   if(quiz_data.title)
   { 
      fprintf(fp,"<h3>%s</h3>\n",quiz_data.title);
   }   

  if(quiz_data.sub_title)
   {
    print_html_string(fp,quiz_data.sub_title);
   } 
   
  if(quiz_data.instructions)
   {
     fputs("<p>",fp);
     print_html_string(fp, quiz_data.instructions);
     fputs("</p>\n<hr />",fp);
   }  

}       




static 
void do_html_mc_answers(FILE *fp, QUESTION_NODE *q_ptr)
{
      
  ANSWER_NODE *a_ptr;      
  int a;
  
  /* print the answers in a table */
  fputs("<table border=\"0\">\n",fp);
  for(a_ptr = q_ptr->head, a=0; a_ptr; a_ptr = a_ptr->next, a++)
   {
    fputs("<tr>",fp);   /* one row per answer */
    
    /* print "your answer -->" in first column, if appropriate */
  //  fputs("<td valign=\"top\" align=\"right\">",fp);
    if(q_ptr->response && *(q_ptr->response) &&
            strchr(q_ptr->response, hotpot_answer_letters[a])) /* if student selected this item */
                             fprintf(fp, "<td valign=\"top\" align=\"right\" class=\"blue_an\">%s",hdf_get_value(hdf, "hotpot.your_answer_label", ""));
    else
      fputs("<td valign=\"top\" align=\"right\">",fp);

    fputs("</td>",fp);
    
    /* print the answer text in the second column */
    fprintf(fp,"<td valign=\"top\">%c</td>\n<td valign=\"top\">",hotpot_answer_letters[a]);
    print_html_string(fp,a_ptr->answer);
    fputs("</td>",fp);
    
    /* if this answer was a correct answer (according to the answer key),
    ** put "<--- Correct answer " in third column
    */
    fputs("<td valign=\"top\" align=\"left\">",fp);
    if(a_ptr->is_correct) /* if answer is correct */
          fprintf(fp,"<span class=\"red_an\">%s</span>", hdf_get_value(hdf, "hotpot.correct_answer",""));
    fputs("</td>\n</tr>",fp);
    
   }

   /* print correct, incorrect, etc */
   fprintf(fp,"<tr><td colspan=\"4\" align=\"center\">\n");
   if(!*(q_ptr->response))
    fprintf(fp,"<span class=\"red_an\">%s</span>", hdf_get_value(hdf, "hotpot.no_answer",""));
   else
   if(q_ptr->answer_correct)
     fprintf(fp,"<span class=\"blue_an\"><b>%s</b></span>", hdf_get_value(hdf, "hotpot.correct", ""));
   else
    fprintf(fp,"<span class=\"red_an\"><b>%s</b></span>", hdf_get_value(hdf, "hotpot.wrong", ""));     
   fputs("</td></tr>",fp);   

   fputs("</table>",fp);
}


static 
void do_html_short_answers(FILE *fp, QUESTION_NODE *q_ptr)
{
      
  ANSWER_NODE *a_ptr;      

  fputs("<table border=\"0\">",fp);
  
  /* print student's answer in first row */
  fprintf(fp,"<tr><td valign=\"top\"><span class=\"blue_an\">%s</span></td><td valign=\"top\">%s<td></tr>\n",
                             hdf_get_value(hdf, "hotpot.your_answer_label",""), q_ptr->response);


  if(q_ptr->answer_correct)
    fprintf(fp,"<tr><td valign=\"top\" align=\"center\" colspan=\"2\"><span class=\"blue_an\"><b>%s</b></span></td></tr>\n", 
                             hdf_get_value(hdf, "hotpot.correct",""));
  else
   {
    /* print all the correct answers */
    for(a_ptr = q_ptr->head; a_ptr; a_ptr=a_ptr->next)                                
        if(a_ptr->is_correct)
           fprintf(fp,"<tr><td valign=\"top\">%s</td><td valign=\"top\" align=\"left\"><span class=\"red_an\">%s</span></td></tr>\n",
                              a_ptr->answer,hdf_get_value(hdf, "hotpot.correct_answer",""));
    fprintf(fp,"<tr><td valign=\"top\" align=\"center\" colspan=\"2\"><span class=\"red_an\"><b>%s</b></span></td></tr>\n", 
                             hdf_get_value(hdf, "hotpot.wrong",""));
   }                           
 fputs("</table>\n",fp);
}

   
   


static void
do_html_questions(FILE *fp)
{
  int q;
  QUESTION_NODE *q_ptr;

  
 for(q_ptr = quiz_data.head, q=0 ; q_ptr; q_ptr=q_ptr->next, q++)
 {
  /* print the actual question */
  fprintf(fp, "<p><b>%s%d: ", hdf_get_value(hdf, "hotpot.q", "Qii"),q+1);
  print_html_string(fp, q_ptr->question);
  fputs("</b><br>",fp);

  switch(q_ptr->question_type)
  {
   case MC_MULTI:
   case MC:     do_html_mc_answers(fp, q_ptr);   
                break;
   case SHORT:   do_html_short_answers(fp, q_ptr);    
                 break;
    
   default:    cs_critical_error(ERR_UNKNOWN_QUESTION_TYPE, "");
                break;

  }
 }

}







static void
do_html_footer(FILE *fp)
{
 //fprintf(fp, "</td></tr></table>\n");
 fprintf(fp, "</body>\n</html>\n");
}


static void
write_result_html(FILE *fp, time_t start_time, time_t end_time, int total_questions, 
                        int total_correct, const char *elapsed_timestring, const char *symlink_fname)
{
 do_html_header(fp, symlink_fname);
 do_html_summary(fp, start_time, end_time, elapsed_timestring, total_correct, total_questions);
 do_html_instructions(fp);
 
 do_html_questions(fp);
 do_html_footer(fp);
}


                        




 
static void
write_website(char *unique_fname, char *orig_fname, time_t time_sent, const char *assign_sender, const char *dirname,
                    time_t start_time, int total_questions, int total_correct, const char *elapsed_timestring)
{
 char new_website_path[MAX_PATH + 1];
 char orig_exam_path[MAX_PATH + 1];
 char manifest[MAX_PATH + 1];
 char *base = 0;
 struct stat buffer;
 char *cmd;
 FILE *fp;
   
 /* get a unique directory name to hold the website */
 get_hotpot_unique_fname (unique_fname, discussion_path,NORMAL_WEBSITE_DIR_PREFIX, &time_sent);  /* shared_hotpot_exam_util.c */

 /* build full path to the new directory */
  snprintf (new_website_path, MAX_PATH + 1, "%s%s", discussion_path,unique_fname);
  unlink(new_website_path);   /*get_hotpot_unique_fname created a regular file.  We need to delete it */

 /* build full path to the teacher's directory that holds the original exam */
   snprintf (orig_exam_path, MAX_PATH + 1, "%s%s/%s/%s/%s", 
                    conf.course_path, PEOPLE_DIR, assign_sender, ASSIGNMENTS_DIR, dirname);

  /* use the cp -a shell command to make a complete copy of the original dir */
  cmd = (char *) malloc( (strlen(new_website_path) + strlen(orig_exam_path) + 20) * sizeof(char));
  if(!cmd)
     cs_critical_error(ERR_MALLOC_FAILED, "");
     
   sprintf(cmd, "cp -pR %s %s", orig_exam_path, new_website_path);
   if(system(cmd))
       cs_critical_error(ERR_GENERAL_ERROR, "");
   free(cmd);
   
   snprintf(orig_fname,MAX_FILENAME + 1, "%s_%ld.html", user.username, (long)time_sent);
   snprintf(manifest, MAX_PATH +1, "%s/%s", new_website_path, "imsmanifest.xml");
   if( !stat(manifest, &buffer))
       base = get_bb_image_base(manifest);  /* shared_bb_manifest.c */
   if(base)
   {
      /* Blackboard 6 exams will not actually have a base directory if there are
      ** no images in the exam, so we need to make sure the directory is actually
      ** there
      */
      if(!isdirectory(new_website_path, base)) /* shared_file_util.c */
       {
         strcat(new_website_path,"/");
         strcat(new_website_path, base);
         if(mkdir(new_website_path, 0770))
              cs_critical_error(ERR_MKDIR_FAILED, new_website_path);
       }
      else
      {       
         strcat(new_website_path,"/");
         strcat(new_website_path, base);
      }
   
      free(base);
   }
   strcat(new_website_path,"/");
   strcat(new_website_path, orig_fname);
   fp = fopen(new_website_path, "w");
   if(!fp)
      cs_critical_error(ERR_FOPEN_WRITE_FAILED, new_website_path);
   
   write_result_html(fp, start_time, time_sent, total_questions, total_correct, elapsed_timestring,"");
   fclose(fp);
}

   


static void
write_msg_file(const char *sender, time_t start_time, time_t end_time, 
						char *msg_fname, char *subject_line, int total_questions,
						int total_correct, const char *elapsed_timestring)
{
 char filepath[MAX_PATH + 1];
 FILE *fp;
 char timestring[MAX_TIMESTRING + 1];
 
 
  get_unique_fname(msg_fname, discussion_path, "msg", &end_time);
  snprintf (filepath, MAX_PATH + 1, "%s/%s", discussion_path, msg_fname);
  fp = fopen(filepath,"w");
  if(!fp)
     cs_critical_error(ERR_FOPEN_WRITE_FAILED, "");

 snprintf(subject_line, MAX_SUBJECT + 1, "%d/%d %s %s", total_correct, total_questions, 
         hdf_get_value(hdf, "hotpot.in", ""), elapsed_timestring);

 fprintf(fp, "%s\n\n", hdf_get_value(hdf, "hotpot.results", ""));
 
  /* print student info */
 fprintf(fp, "%s %s (%s)\n", hdf_get_value(hdf, "hotpot.student_label", ""), user.realname, user.username); 
 
 /* print start time */
 strftime (timestring, MAX_TIMESTRING, DOW_DATE_TIME_FORMAT,
	                                  localtime (&(start_time)));   

 fprintf(fp, "%s %s\n", hdf_get_value(hdf, "hotpot.start_time",""), timestring);


 /* print end time */
 strftime (timestring, MAX_TIMESTRING, DOW_DATE_TIME_FORMAT,
	                                  localtime (&(end_time)));   

 fprintf(fp, "%s %s\n", hdf_get_value(hdf, "hotpot.end_time", ""), timestring);
 
 /* print elapsed time */
  fprintf(fp, "%s %s\n", hdf_get_value(hdf, "hotpot.elapsed_time", ""), elapsed_timestring); 

 /* print score */
  fprintf(fp, "%s %d/%d\n", hdf_get_value(hdf, "hotpot.score", ""), total_correct, total_questions); 

 fclose(fp);  
}       




/** dirname is the name of the directory, within assign_sender's assign directory, which holds the exam
**/ 
 
 
static void
write_graded_quiz( char *course,  char *key, const char *assign_sender,  
                     long offset,  int total_questions, int total_correct, time_t start_time, time_t end_time, 
                      const char *elapsed_timestring, const char *dirname)
{
 FILE *inbox_fp;
 int inbox_fd;
 int info_fd;
 ATTACHMENT web_attachment;
 
 char subject[MAX_SUBJECT + 1];
 char msg_fname[MAX_FILENAME + 1];
 char info_fname[MAX_FILENAME + 1];
 char inbox_fname[MAX_PATH + 1];
 
 
 CENTRAL_INBOX_RECORD inbox_record, new_inbox_record;
  
   write_msg_file(assign_sender, start_time, end_time, msg_fname, subject,
                          total_questions, total_correct, elapsed_timestring);
 
   write_website(web_attachment.unique_fname, web_attachment.orig_fname, end_time, assign_sender, dirname,
                   start_time, total_questions, total_correct, elapsed_timestring);
 
 
  /* set up as much of the new inbox record as we can before locking
  ** the inbox.dat file
  */  
  strncpy(new_inbox_record.info.msg_fname, msg_fname, MAX_FILENAME + 1);
  strncpy(new_inbox_record.info.sender, user.username, MAX_USERNAME + 1);
  strncpy(new_inbox_record.info.sender_realname, hdf_get_value(hdf, "hotpot.handler", ""), MAX_NAME + 1);
  strncpy(new_inbox_record.info.receiver_username,user.username,MAX_USERNAME + 1);  /* since this is a student sending the message */
  strncpy(new_inbox_record.info.subject, subject, MAX_SUBJECT + 1);
  new_inbox_record.info.time_sent = end_time;

  new_inbox_record.info.attachments = -1;   /* since there's a website attached */

  new_inbox_record.info.sender_team = user.team;

 
  /* get the name of the 'inf' file to do so, we need to open
  ** (create) the file
  */
  info_fd = get_unique_fd (discussion_path,"inf", info_fname, &end_time);
  /* don't bother to lock it, since no one knows it's there yet! */

  strncpy(new_inbox_record.info_fname,info_fname,MAX_FILENAME + 1);
  new_inbox_record.release_time = 0;       /* no release time set */
  new_inbox_record.unrelease_time = 0;     /* no unrelease time either */
  new_inbox_record.status =  TEACHER_HIDDEN;       /* since we don't want the student to see the results! */

 /* now open and lock the inbox.dat file */
  snprintf(inbox_fname,MAX_PATH + 1,"%s%s",central_discussion_path,INBOX_FNAME);
  inbox_fp = fopen(inbox_fname,"r+");
  if(!inbox_fp)                              /* couldn't open or create inbox.dat */
    cs_critical_error(ERR_FOPEN_WRITE_FAILED,"");
  inbox_fd = fileno(inbox_fp);
  get_exclusive_lock(inbox_fd, 1);                      /* shared_lock.c */
   
  /* now seek to the message that held the original exam, so we can get the topic_id */ 
  if(fseek(inbox_fp,offset,SEEK_SET) == -1)
       cs_critical_error(ERR_FSEEK_FAILED, "");
  if(fread( &inbox_record, sizeof(CENTRAL_INBOX_RECORD), 1, inbox_fp) != 1)
       cs_critical_error(ERR_FREAD_FAILED, "");
    
 /* this msg is a reply to the original assignment */   
 new_inbox_record.info.reply_to =  inbox_record.info.msg_id;
 new_inbox_record.info.topic_id = inbox_record.info.msg_id;
 
 
 /* find the msg_id for this message, by seeking to the last
 *  message and incrementing 
 */
 
  if(fseek(inbox_fp,-sizeof(CENTRAL_INBOX_RECORD),SEEK_END) == -1)
      cs_critical_error(ERR_FSEEK_FAILED, "");
  if(fread( &inbox_record, sizeof(CENTRAL_INBOX_RECORD), 1, inbox_fp) != 1)
       cs_critical_error(ERR_FREAD_FAILED, "");
     
     
  new_inbox_record.info.msg_id = inbox_record.info.msg_id + 1;

  /* we have all the data we need, so we append the record to the
  ** end of the central inbox
  */
  if(fseek(inbox_fp,0,SEEK_END) == -1)
     cs_critical_error(ERR_FSEEK_FAILED, "");
  if(fwrite(&new_inbox_record, sizeof(CENTRAL_INBOX_RECORD),1,inbox_fp) != 1)
      cs_critical_error(ERR_FWRITE_FAILED, "");

 /* write the info file */   
  if (write (info_fd, &new_inbox_record.info, sizeof (NEWS_INFO)) != sizeof(NEWS_INFO))
    cs_critical_error (ERR_FWRITE_FAILED, "");
  if(write(info_fd, &web_attachment, sizeof(ATTACHMENT)) != sizeof(ATTACHMENT))
    cs_critical_error (ERR_FWRITE_FAILED, "");
      
  close (info_fd);

   release_lock(inbox_fd);     /* shared_lock.c */
   fclose(inbox_fp);
  
   show_success(course,key,offset);
} 





/** returns the string orig_response,
*** with commas converted to semi-colons and double-quotes
** converted to single quotes
**/
static char *
cleaned_up_response(const char *orig_response)
{
#define SINGLE_QUOTE 39
 char *cleaned_response;
 char *ptr;
 
 if(!orig_response)   /* check for NULL pointer */
  {
   cleaned_response = (char *) malloc(sizeof(char));
   if(!cleaned_response)
     cs_critical_error(ERR_MALLOC_FAILED, "");
   *cleaned_response = '\0';
  }
 else
  {    
   cleaned_response = (char *) malloc((strlen(orig_response) + 1) * sizeof(char));
   if(!cleaned_response)
      cs_critical_error(ERR_MALLOC_FAILED, "");
   strcpy(cleaned_response, orig_response);
 
   for(ptr = cleaned_response; *ptr; ptr++)
    {
     if(*ptr == '"')
        *ptr = SINGLE_QUOTE;
     if(*ptr == ',')
        *ptr = ';';
    }
 }
 
 return cleaned_response; 
#undef SINGLE_QUOTE
}                 
 


static 
void   store_exam_results(const char *username, const char *course_path,
                               const char *sender, const char *info_fname, time_t start_time, time_t end_time,
                                 const char *elapsed_timestring, int total_questions, int total_correct)
{
 char data_path[MAX_PATH + 1];
 FILE *fp;
 int fd;
 QUESTION_NODE *q_ptr;
 char *cleaned_response = 0;
 
 get_hotpot_score_path(course_path, username, sender, info_fname, data_path);  /* shared_hotpot_exam_util.c */ 

 
 fp = fopen(data_path,"a");
 if(!fp)
  cs_critical_error(ERR_FOPEN_APPEND_FAILED,"");
 fd = fileno(fp);
 get_exclusive_lock(fd,1);   /* shared_lock.c */
                                  
 fprintf(fp,"%d,%d,\"%s\",%ld,%ld,", total_correct,total_questions,elapsed_timestring,(long)start_time, (long)end_time);         
 
 for(q_ptr=quiz_data.head; q_ptr; q_ptr=q_ptr -> next) 
   {
    cleaned_response = cleaned_up_response(q_ptr->response);
    fprintf(fp,"%s%s", cleaned_response, q_ptr->next?",":"");
    if(cleaned_response)
     {
       free(cleaned_response);
       cleaned_response = 0;
     }  
   }  
  fprintf(fp,"\n");
 
 release_lock(fd);
 fclose(fp);
}    

static void
report_already_scored_error( char *course,  char *key, long offset)
{

 print_return_to_assign_form(course, key, offset);
 cs_set_int_value("already_scored", 1);
 cs_set_course_info(&conf);
 cs_set_current_time();
 cs_cgi_display("hotpot_exam_score", 1);
 cs_cgi_destroy();
 exit(0);
}



/** the form submitted that contains the exam has a 
*** 'hidden' parameter 'test' that contains the 
*** start time for the exam.  That value is used only
*** for teachers (since their data is not stored anyway)
*** The true start_time for an exam is stored at the 
*** beginning of the student's *.score datafile.
*** That start_time was written there by the 'present' program.
*** The next function retrieves that start_time.
***
*** While doing so, it also checks to make sure that results for
*** an exam with this start_time have not already been stored.
*** This is necessary to prevent students from pressing the Back
*** button on their browser and submitting the exam again.
**/
static void
verify_start_time(time_t *start_time, const char *course_path, const char *username, 
				const char *sender, const char *info_fname,  char *course,  char *key,
				long offset)
{
#define MAX_LINE 1000

 FILE *fp;
 int fd;				
 char data_path[MAX_PATH + 1];
 char line[MAX_LINE + 1];
 char stored_time_string[MAX_TIMESTRING + 1];
 char start_time_string[MAX_TIMESTRING + 1];
 time_t time_viewed;
  
 get_hotpot_score_path(course_path, username, sender, info_fname, data_path);  /* shared_hotpot_exam_util.c */ 
 fp = fopen(data_path, "r+");
 if(!fp)
     cs_critical_error(ERR_FOPEN_READ_FAILED, "");
 fd = fileno(fp);
 
 get_exclusive_lock(fd,1);   /* shared_lock.c */

 /** the time this test was viewed by the student
 *** is stored as binary bytes at the start of the file
 **/ 
 if(fread(&time_viewed, sizeof(time_t), 1, fp) != 1)
    cs_critical_error(ERR_FREAD_FAILED, "");
 
 /** It is possible that the teacher clicked the 
 *** "Allow retake" button WHILE THE STUDENT WAS IN
 *** the middle of taking the test.  The "allow retake"
 *** button sets the initial time_viewed bytes of the file
 *** to zero.
 ***
 *** If we find a zero there, we have no choice but to 
 *** trust the start_time passed to this program.  We
 *** write that value back to the file, replacing the
 *** zero values.
 **/
 if(time_viewed == 0)
  {
    if(fseek(fp, 0, SEEK_SET))
       cs_critical_error(ERR_FSEEK_FAILED, "");
    if( fwrite( start_time, sizeof(time_t), 1, fp ) != 1)
       cs_critical_error(ERR_FWRITE_FAILED,"");
  }
 else
    *start_time = time_viewed;      
    
  
 
 /* now read the rest of the data file, checking 
 ** to make sure that a score hasn't already been processed
 ** for this start_time
 */

 /* store the time THIS test was started as a string */
 snprintf(start_time_string,MAX_TIMESTRING, "%ld", (long)*start_time);
 
 while(fgets(line, MAX_LINE, fp))   /* for each exam result */
 {
  /* the start time is the fourth item in the csv line */
  if(csv_value(line,3,stored_time_string, MAX_TIMESTRING))  /* shared_hotpot_exam_util.c */
         cs_critical_error(ERR_ERROR_PARSING_SCORE, "");
  if(!strcmp(start_time_string, stored_time_string))
    {
     release_lock(fd);
     fclose(fp);
     report_already_scored_error(course,key,offset);  /* this exits the program */
   }  
 }
 release_lock(fd);
 fclose(fp);
}



int
main (void)
{
  char course[MAX_PATH + 1];	/* from crs=? command line */
  char key[MAX_KEY + 1];	/* from id=? command line */
  char sender[MAX_USERNAME + 1];	/* from user=? command line */
  char fname[MAX_FILENAME + 1];	   /* from fname=? cmd line */
  char info_fname[MAX_FILENAME + 1]; /* from info=? cmd line */
  long offset;                       /* from loc=? cmd line */
  time_t start_time;                 /* from test=? cmd line */
    
  char source_path[MAX_PATH + 1];
  char dirname[MAX_FILENAME + 1];     
  
  char symlink_fname[MAX_FILENAME + 1];
  
  time_t end_time;  
  
  int total_questions;      /* how many questions were there? */
  int total_correct;        /* how many questions did this user get correct? */
  char elapsed_timestring[MAX_TIMESTRING + 1];
  char back_url[MAX_PATH + 1];
  
  end_time = time(NULL);
  
  cs_cgi_init();  
  read_parameters (course, key, sender, fname, info_fname, &start_time, &offset);

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

  remove_stale_symlinks (user.username, course, conf.access);    /* shared_news_util.c */
  get_hotpot_exam_source_fname (source_path, dirname, conf.course_path, sender, info_fname);  /* shared_hotpot_exam_util.c */
  parse_hotpot_file(source_path);   /* shared_hotpot_parser.c */
  
  store_responses();

  get_lang_string(&hdf); 
  if(user.group != FACULTY)
     verify_start_time(&start_time, conf.course_path, user.username, sender, info_fname, course, key, offset);
  get_totals(&total_questions, &total_correct);

  get_elapsed_timestring(start_time, end_time, elapsed_timestring);  
  set_discussion_names(conf.course_path, user.username, ASSIGNMENTS);     /* shared_news_util.c */
  snprintf(back_url, MAX_PATH +1, "%s?crs=%s&amp;id=%s&amp;grp=6&amp;loc=%ld", "news_read", course, key, offset);
  cs_set_value("back_url", back_url);

  if(user.group != FACULTY)
   {
    write_graded_quiz(course,key,sender, offset, total_questions, total_correct, 
  		start_time, end_time, elapsed_timestring, dirname);
    store_exam_results(user.username, conf.course_path, sender,info_fname,start_time, end_time,
                                 elapsed_timestring, total_questions, total_correct);
    cs_set_course_info(&conf);
    cs_set_current_time();
    cs_cgi_display("hotpot_exam_score", 1);
   }
  else
    {
     create_hotpot_exam_symlink(conf.course_path, sender, dirname, &user, course, symlink_fname, conf.access); /*shared_hotpot_exam_util.c */
     send_content_type();
     write_result_html(stdout, start_time, end_time, total_questions, total_correct, elapsed_timestring, symlink_fname);     
     
    }
  hdf_destroy(&hdf);
  free_quiz_data();     /* shared_hotpot_parser.c */  
  cs_cgi_destroy();
  return 0;
}
