#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <sys/types.h>
#include <dirent.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
              
#include "global.h"

#include "manhat-lib/shared_util.h"
#include "manhat-lib/shared_strtrm.h"    
#include "manhat-lib/shared_super_util.h"
#include "manhat-lib/shared_authenticate.h"
#include "manhat-lib/shared_server_string.h"
#include "manhat-lib/shared_person_list.h"
#include "manhat-lib/shared_roster_xml_parser.h"
#include "manhat-lib/shared_survey_string.h"
#include "manhat-lib/shared_cs_util.h"

#define MAX_ROSTER_LINE 200        /* max len of a line in the uploaded roster.txt file */


static void
read_parameters (char *subdir, char *course_id, char *key, COURSE_TYPE *course_type)
{
  char tmp[21];
  char new_subdir[MAX_COURSE_SUBDIR +1];
  int i;


  cs_get_required_parameter ("crs", course_id, MAX_COURSE_ID );
  cs_get_required_parameter ("id", key, MAX_KEY );
  cs_get_required_parameter ("subdir", subdir, MAX_COURSE_SUBDIR );
  cs_get_required_parameter("type", tmp, 20);

  *course_type = CENTRAL;   /* to prevent unitialized variable compiler warnings */
  i = atoi(tmp);
  switch(i)
  {
   case 0: 
	   *course_type = CENTRAL;
	   break;
   case 1:
	   *course_type = STANDALONE;
	   break;
   case 2:    
	   *course_type = TEMPLATE;
	   break;
   
   default: 
            cs_critical_error (ERR_INVALID_COURSE_TYPE, "");
            break;
  }          


  if( !strcmp(subdir, "__new__") && *course_type != TEMPLATE)
  {
      cs_get_required_parameter("new_subdir", new_subdir, MAX_COURSE_SUBDIR );
      strncpy(subdir, new_subdir, MAX_COURSE_SUBDIR +1);
  }      
  
}






/** converts carriage returns to line feeds in first bytes_read chars of buffer **/
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;
}


/** does an ASCII dec 10 appear in the first bytes_read chars of buffer?
 * **/
int
has_newlines (char *buffer, int bytes_read)
{
  char *ptr;
    int i;

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

static void
write_cgi_file (FILE *infp, const char *filepath)
{

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

  if(fstat(fileno(infp), &file_stat))
    cs_critical_error(ERR_NO_FILE, "");
  if(file_stat.st_size ==0)
    cs_critical_error(ERR_NO_FILE, "");


  outfp = fopen (filepath, "w");
  if (!outfp)
    cs_critical_error(ERR_FOPEN_WRITE_FAILED, "");


  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, infp) != 1)
	cs_critical_error (ERR_FREAD_FAILED, "");

      if (newlines == -1)
	newlines = has_newlines (buffer, bytes_read);	/* 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, outfp) != 1)
	cs_critical_error (ERR_FWRITE_FAILED, filepath);

    }
  fclose (outfp);
#undef CHUNKSIZE

}


 
 
/* writes the uploaded file to ROSTER_FNAME in course_dir 
** exits if the uploaded file is missing or empty
*/
static void
write_roster_file ( FILE *fp,  char *filepath)
{
#define MAX_MSG 500
   int fd;

   snprintf (filepath, MAX_PATH + 1, "%sCGI_DATA_XXXXXX", TMP_PATH);
    fd = mkstemp (filepath);
    if(fd == -1)
          cs_critical_error(ERR_MKSTEMP_FAILED, "");
    close(fd);
    change_mkstemp_permission(filepath);   /*shared_file_util.c */

    write_cgi_file (fp, filepath);

#undef MAX_MSG        
}



static void  verify_ascii_file(const char *file)
{
 char ch;
 FILE *fp;

 fp = fopen(file, "r");
 if(!fp)
   cs_critical_error(ERR_FOPEN_READ_FAILED, file);

 while(fread(&ch, sizeof(char), 1, fp) == 1)
  if(!isascii(ch))
  {
      fclose(fp);
      cs_critical_error(ERR_ROSTER_FILE_NOT_ASCII, "");
  }
 
 /* rewind the file */ 
 if(fseek(fp, 0 , SEEK_SET))
 {
      fclose(fp);
      cs_critical_error(ERR_FSEEK_FAILED, "");
 }
}     
 
    
/* reads a line from fp, not to exceed maxlen in length
** strips leading/trailing whitespace
*/
int
read_line (FILE * fp, char *string, int maxlen)
{
  char line[MAX_ROSTER_LINE + 1];

  if (!fgets (line, MAX_ROSTER_LINE, fp))
    return 0; 

  strtrm (line);  /* shared_strtrm.c */
  if ((int)strlen (line) > maxlen)
         return 0; 
  strncpy (string, line, maxlen + 1);
  return 1;
}





/** used to read the first four lines of the roster.txt file, 
*** pointed to by fp;
*** line_no indicates which line we're working with, so the value
*** returned in 'result' can be checked 
**/

static void
read_opening_line(FILE *fp,  char *result, int line_no)
{
 char line[MAX_ROSTER_LINE + 1];
 int len;
 char msg[10];
 int max_string_len[4] = {MAX_COURSE_NO, MAX_COURSE_TITLE, MAX_SEMESTER, MAX_INSTRUCTOR};

 char read_error_msg[4][50] =   {ERR_ERROR_READING_COURSE_CODE,
                              ERR_ERROR_READING_COURSE_TITLE,
                              ERR_ERROR_READING_SEMESTER,
                              ERR_ERROR_READING_TEACHER_NAME};

 char empty_value_msg[4][50] =    {ERR_COURSE_CODE_EMPTY ,
 				ERR_COURSE_TITLE_EMPTY,
 				ERR_SEMESTER_EMPTY,
 				ERR_TEACHER_NAME_EMPTY };

 char long_value_msg[4][50] = 	{ ERR_COURSE_CODE_TOO_LONG,
                                  ERR_COURSE_TITLE_TOO_LONG,
                                  ERR_SEMESTER_TOO_LONG,
                                  ERR_TEACHER_NAME_TOO_LONG };
                                  
 
 /* this function can only deal with lines 1-4 */
 if(line_no > 4 || line_no < 1)
    cs_critical_error(ERR_GENERAL_ERROR, "");
    
    
  if (!fgets (line, MAX_ROSTER_LINE, fp))
    cs_critical_error(read_error_msg[line_no - 1], "");


  strtrm(line);                    /* shared_strtrm.c */
  len = strlen(line);
  if(!len)
    cs_critical_error(empty_value_msg[line_no - 1], "");  
    
  if(len > max_string_len[line_no - 1])
  {
      snprintf(msg, 9, "%d",  max_string_len[line_no - 1]);
      cs_critical_error(long_value_msg[line_no-1], msg);
  } 
   strcpy(result, line);
}   


static void
get_info(FILE *fp, XML_COURSE_INFO * info)
{
    read_opening_line(fp,  info->course_code, 1);
    read_opening_line(fp,  info->title, 2);
    read_opening_line(fp, info->semester, 3);
    read_opening_line(fp, info->instructor_title, 4);
}
static void
fill_course_info(FILE * fp, XML_COURSE_INFO *info, 
		const char *subdir, const char *course)
{
   strncpy(info->subdir, subdir, MAX_COURSE_SUBDIR +1);
   strncpy(info->dir_id, course, MAX_COURSE_ID +1);
   get_info(fp, info);
}

     


/* does a quick check of the format of the person's id
*/
static
void  check_id_format(char *id)
{
 char *ptr;
 char msg[20];
 int found;
 
 
 if(strlen(id) > MAX_ID)
  {
   snprintf(msg, 19,  "%d", MAX_ID);
   cs_critical_error(ERR_MAX_ID_LEN_IS_X, msg);
  }
  
  
 /*** an ID must have contain at least one digit 0-9 */ 
 for(found = 0, ptr = id; !found && *ptr; ptr++)
     found = isdigit(*ptr);
 if(!found)       
    cs_critical_error(ERR_ID_MUST_HAVE_DIGIT,"");
   
 /* check for some strange characters **/
 for(ptr = id; *ptr; ptr++)
   if(strchr(ILLEGAL_ID_CHARS, *ptr))   /* global.h */
   {
     snprintf(msg, 19,  ILLEGAL_ID_CHARS);
     cs_critical_error(ERR_ID_HAS_BANNED_CHARS_X, msg);
  }
     
}           



/* quick check to see if the 'name' is reasonable' */
static void
check_name_format(char *name)
{
 char msg[20];
 char banned_characters[] = ":/\\;,?";
 char *ptr;
 
 if(strlen(name) > MAX_NAME)
  {
   snprintf(msg, 19,  "%d", MAX_NAME);
   cs_critical_error(ERR_MAX_NAME_LEN_IS_X, msg);
  }
  
 for(ptr=name; *ptr; ptr++)
  if(strchr(banned_characters,*ptr))
   {
     snprintf(msg, 10,  banned_characters);
     cs_critical_error(ERR_NAME_HAS_BANNED_CHARS_X, msg);
  }
   
}



/** parses 'line' into id, first,last, fullname fields
*** while checking for valid data
**/


static void
parse_person_line(char *line, char *id, char *first, 
                               char *last)
{
  char *ptr, *ptr2, tmp;
  char msg[20];

  /*** handle ID ****/
  for (ptr = line; !(isspace (*ptr)) && *ptr; ptr++);	/* find first whitespace */
  if (!*ptr)
     cs_critical_error(ERR_PERSON_LINE_HAS_BAD_FORMAT, line);
     
  tmp = *ptr;
  *ptr = '\0';
  
  check_id_format(line);
  strncpy (id, line, MAX_ID + 1);



  /*** Handle first name ****/
  *ptr = tmp;
  for (ptr++; *ptr && isspace (*ptr); ptr++);	/* find next non-blank */
  if (!*ptr)
     cs_critical_error(ERR_PERSON_LINE_HAS_BAD_FORMAT, line);     


  for (ptr2 = ptr; *ptr2 && !isspace (*ptr2) ; ptr2++);	/*ptr2->next non-space */
  if (!*ptr2)
      cs_critical_error(ERR_PERSON_LINE_HAS_BAD_FORMAT, line);


  tmp = *ptr2;
  *ptr2 = '\0';

  check_name_format(ptr);
  strncpy (first, ptr, MAX_NAME + 1);
  *ptr2 = tmp;


 /** handle last name ***/
  for (ptr2++; *ptr2 && isspace (*ptr2) ; ptr2++);	/* find next non-blank */
  if (!*ptr2)
	  cs_critical_error(ERR_PERSON_LINE_HAS_BAD_FORMAT, line);

  check_name_format(ptr2);
  strncpy (last, ptr2, MAX_NAME + 1);

  if((strlen(first) + strlen(last) + 1) > MAX_NAME)
    {
      snprintf(msg, 19,  "%d", MAX_NAME);
      cs_critical_error( ERR_MAX_NAME_LEN_IS_X, msg);
    }  
//  snprintf(fullname, MAX_NAME + 1, "%s %s", first, last);  
}

static
void check_invalid_chars(const char *string)
{
  const char *cptr;
  char msg[20];

   for(cptr = string; *cptr; cptr++)
   {
       if((*cptr != '_')  && !isdigit(*cptr) && !isalpha(*cptr))
       {
         snprintf(msg, 19,  string);
         cs_critical_error(ERR_COURSE_X_HAS_INVALID_CHARS, msg);
       }
    }
}


/* assumes strings have been trimmed
 * ** in read_parameters()
 * */

static
void check_internal_course_names(const char *subdir, COURSE_TYPE course_type)
{
       if( course_type == CENTRAL || course_type == STANDALONE)
       {
          if(*(subdir) == 'T')
	      cs_critical_error(ERR_COURSE_CANT_START_WITH_T, "");

       }
       check_invalid_chars(subdir);
}

XML_COURSE_INFO *
build_course_info(const char *prefix, const char *subdir, const char *course)
{
  XML_COURSE_INFO  *course_data;
  char path[MAX_PATH +1];
  FILE *fp;
  char line[MAX_ROSTER_LINE +1];

  snprintf(path, MAX_PATH +1, "%s", prefix);
  fp = fopen(path, "r");
  if(!fp)
    cs_critical_error(ERR_FOPEN_READ_FAILED, "");

  course_data = (XML_COURSE_INFO*) malloc(sizeof(XML_COURSE_INFO));

  fill_course_info( fp, course_data, subdir, course);   /* reads first four lines of the file */
  
  /* the fifth line represents the teacher */
  
  if(!fgets(line, MAX_ROSTER_LINE, fp))
     cs_critical_error(ERR_TEACHER_ID_LINE_MISSING, "");
   strtrm(line);   /* shared_strtrm.c */
   if(!strlen(line))
       cs_critical_error(ERR_TEACHER_ID_LINE_MISSING, "");
   course_data->user_head = (P_NODE *)malloc(sizeof(P_NODE));
   course_data->user_head->next = 0;
   if(!course_data->user_head)
       cs_critical_error(ERR_MALLOC_FAILED, "");
       	   
   parse_person_line(line, course_data->user_head->data.id, 
		   course_data->user_head->data.username, 
		   course_data->user_head->data.lastname);
   course_data->user_head->data.group = FACULTY;
   course_data->user_current = course_data->user_head;
   
   /* remaining lines are students */
   while(fgets(line, MAX_ROSTER_LINE, fp))
   {
      strtrm(line);   /* shared_strtrm.c */
      if(strlen(line))    /* ignore blank lines */
       {
         course_data->user_current->next = (P_NODE *)malloc(sizeof(P_NODE));
         course_data->user_current = course_data->user_current->next;
         course_data->user_current->next =0;
         parse_person_line(line, course_data->user_current->data.id, 
	 course_data->user_current->data.username, 
	 course_data->user_current->data.lastname);
         course_data->user_current->data.group = STUDENT;
       }  
    }
   fclose(fp);
   unlink(path);
   return course_data;
}

void
write_data_to_xml( char *prefix, XML_COURSE_INFO *course_data)
{
    char path[MAX_PATH +1];
    FILE *fp;
    P_NODE *ptr;

    snprintf(path, MAX_PATH+1, "%s.xml", prefix);
    strncpy(prefix, path, MAX_PATH +1);
    fp = fopen(path, "w");
    if(!fp)
	cs_critical_error(ERR_FOPEN_WRITE_FAILED, "");
    fprintf(fp,"<?xml version=\"1.0\" encoding=\"ISO-8859-1\" ?>\n");
    fprintf(fp, "<!-- Manhattan course data -->\n");
    fprintf(fp, "<courses>\n");
    fprintf(fp, "   <course subdir=\"%s\" id=\"%s\">\n", replace_sp(course_data->subdir), replace_sp(course_data->dir_id));
    fprintf(fp, "      <course_no>%s</course_no>\n", replace_sp(course_data->course_code));
    fprintf(fp, "      <course_title>%s</course_title>\n", replace_sp(course_data->title));
    fprintf(fp, "      <teacher_title>%s</teacher_title>\n", replace_sp(course_data->instructor_title));
    fprintf(fp, "      <term>%s</term>\n", replace_sp(course_data->semester));
    fprintf(fp, "      <users>\n");
    for(ptr = course_data->user_head; ptr; ptr = ptr->next)
    {
       fprintf(fp, "      <user id=\"%s\">\n", replace_sp(ptr->data.id));
       fprintf(fp, "        <first>%s</first>\n", replace_sp(ptr->data.username));
       fprintf(fp, "        <last>%s</last>\n", replace_sp(ptr->data.lastname));
       fprintf(fp, "        <username></username>\n");
       fprintf(fp, "        <group>%s</group>\n", 
		           ptr->data.group==FACULTY?"faculty":"student");
       fprintf(fp, "      </user>\n");
    }
    fprintf(fp, "        </users>\n");
    fprintf(fp, "     </course>\n");
    fprintf(fp, "</courses>\n");
    fclose(fp);
}




        
int main()
{
  char course[MAX_COURSE_ID + 1];
  char subdir[MAX_COURSE_SUBDIR + 1];
  char key[MAX_KEY + 1];
  COURSE_TYPE course_type;
  SESSION user;
  char temp_course[MAX_COURSE_SUBDIR +1];
  char prefix_rosterfile[MAX_PATH +1];
  XML_COURSE_INFO  *course_data;
  FILE *fp;
   
  cs_cgi_init(); 
  read_parameters (subdir, course, key,  &course_type);
  validate_super_key(key, &user);   /* shared_super_util.c */

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

  check_internal_course_names(subdir,  course_type);
  check_invalid_chars(course);

  if(course_type == TEMPLATE)
  {
     if(*subdir != 'T')
     {
	snprintf(temp_course, MAX_COURSE_SUBDIR+1, "%s%s", "T", subdir);
	strncpy(subdir, temp_course, MAX_COURSE_SUBDIR +1);
     }
  }
  write_roster_file(fp, prefix_rosterfile);
  verify_ascii_file(prefix_rosterfile);                   /* verify that the uploaded file is an ASCII file */
  course_data = build_course_info(prefix_rosterfile, subdir, course);
  write_data_to_xml(prefix_rosterfile, course_data);
  free_option_person_list (course_data->user_head);
  free(course_data);
  printf("Location: %s?id=%s&roster_path=%s&c1_subdir=%s&c1_new_id=%s&c1_orig_id=%s&c1_type=%s&count=1&c1_subdir_or=%s\n\n",
	 "super_xml_course_create", key, prefix_rosterfile, subdir, course, course, 
	 (course_type==CENTRAL ? "Normal": (course_type== TEMPLATE ? "Template": "Standalone")), subdir); 
  cs_cgi_destroy(); 
  return 0;
}


