#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "../global.h"
#include "shared_util.h"
#include "shared_access.h"
#include "shared_course_list.h"
#include "shared_authenticate.h"
#include "shared_lock.h"
#include "shared_strtrm.h"
#include "shared_cs_util.h"
#include "shared_person_list.h"


COURSE_NODE * build_users_course_list(const char *username, int only_if_teacher)
{
 COURSE_NODE *head = 0;
 COURSE_NODE *ptr, *current = 0;
 char course_file[MAX_PATH + 1];
 FILE *fp;
 SESSION user;
 char course[MAX_FILENAME + 1];
 int fd;
 char dirname;
 int can_view, can_write;
 
 dirname = sub_dir_name(username);    /* shared_authenticate.c */
 
 snprintf(course_file, MAX_PATH + 1, "../%s/%c/%s/%s",
                            USERS_DIR, dirname, username,COURSE_LIST_FNAME);

                            
 fp = fopen(course_file, "r"); 
 if(!fp)
   cs_critical_error(ERR_COURSE_LIST_OPEN_FAILED,course_file);

 fd = fileno(fp);
 get_shared_lock(fd, 1);   /* shared_lock.c */
 
 while(fgets(course,MAX_FILENAME + 1,fp))
  {
    strtrm(course);  					/* shared_strtrm.c */
    ptr = (COURSE_NODE *)malloc(sizeof(COURSE_NODE));
    if(!ptr)
        cs_critical_error(ERR_MALLOC_FAILED, "in build_users_course_list()" );
    
    strncpy(ptr->crs,course,MAX_FILENAME + 1);
    if(read_configuration_file_ok(ptr->crs, &(ptr->conf)))   /* shared_util.c */
    {
      get_username_access_rights(username, &(ptr->conf), &can_view, &can_write);   /* shared_access.c */
      if(can_view && only_if_teacher)
         can_view = get_user_info(username, &user, &(ptr->conf)) && (user.group == FACULTY);   /* get_user_info() is in shared_person_list.c */
         
      if(can_view )
       {
         if(!head)
            head = ptr;
          else
            current->next = ptr;
          current = ptr;
          current->next = 0;
       }   
      else     
        free(ptr); 
     }
    else 
      free(ptr);  
   }
  release_lock(fd);     /* shared_lock.c */
  fclose(fp);

 return head;
}



/** Check/adjust the is_unread_checked setting.
***   - Only one semester can be checked for unread messages
***   - If this is a new_login, then the first semester is the 
***       one that's checked.
***   - If more than one is checked, then use the one at the top
***     of the list.
***   - If none are checked, use the first one in the list
**/


static 
SEMESTER_SORT_NODE *adjust_unread_checking(SEMESTER_SORT_NODE *sort_head, int new_login, int *changed)
{
 SEMESTER_SORT_NODE *sptr;

  if(sort_head)
   {
    if(new_login)  /* then only the first semester needs to be checked for new messages */
      {
        for(sptr=sort_head; sptr; sptr=sptr->next)     
         {
          if(sptr == sort_head)
            {
              if(sptr->is_unread_checked != 1)
               {
                  *changed = 1;
                  sptr->is_unread_checked = 1;
               }
            }
          else
            if(sptr->is_unread_checked != 0)
             {
               *changed = 1;
               sptr->is_unread_checked = 0;
             }
         }
       }
    else /* not a new login */
     {
        for(sptr=sort_head; sptr && !(sptr->is_unread_checked); sptr=sptr->next);   /* find first checked semester */   
        if(!sptr)   /* none checked, check first item */
         {
            *changed = 1;
            sort_head -> is_unread_checked = 1;
         }
        else   /* make sure remaining items are unchecked */
         {
           for(sptr=sptr->next; sptr; sptr = sptr->next)
             {
               if(sptr->is_unread_checked)
                 {
                   *changed = 1;
                   sptr->is_unread_checked = 0;
                 }
             }
         }  
      }      
  } /* if sort_head */       

 return sort_head;
}







/** removes duplicate entries from sort list.
*** This doesn't do a thorough job, but it shouldn't 
*** be necessary in the first place.
**/

static
SEMESTER_SORT_NODE *  remove_duplicate_entries(SEMESTER_SORT_NODE *sort_head, int *changed)
{
 SEMESTER_SORT_NODE *sptr, *sptr2;
 
  for(sptr=sort_head; sptr; sptr = sptr->next)
    if(sptr->next && !strcmp(sptr->semester, sptr->next->semester))
      {
       sptr2 = sptr->next;
       sptr->next = sptr->next->next;
       free(sptr2);
       *changed = 1;
      }
      
    return sort_head;  
}






/** adds items from course list that aren't already in sort list
**/

static 
SEMESTER_SORT_NODE *add_new_courses(SEMESTER_SORT_NODE *sort_head, COURSE_NODE *course_head, int *changed)
{
COURSE_NODE *cptr;
SEMESTER_SORT_NODE *sptr, *new_node;
int found;

  for(cptr = course_head; cptr; cptr=cptr->next)
  {
    for(sptr=sort_head, found = 0; !found && sptr; sptr=sptr->next)
        found = !strcmp(sptr->semester, cptr->conf.semester);
    if(!found)   /* add the new item to the top of the list */
     {
      *changed = 1;
      new_node = (SEMESTER_SORT_NODE *) malloc(sizeof(SEMESTER_SORT_NODE));
      if(!new_node)
           cs_critical_error(ERR_MALLOC_FAILED, "in add_new_courses()");   /* shared_cs_util.c */
      strncpy(new_node->semester, cptr->conf.semester, MAX_SEMESTER + 1);
      new_node->is_expanded = 1;
      new_node->is_unread_checked = 0;    /* Don't check this for unread messages */
      if(!sort_head)
         {
          sort_head = new_node;
          sort_head->next = 0;
         }
       else
         {
          new_node->next = sort_head;
          sort_head = new_node;
         }
         
       
     } /* if (!found) */     
       
    } /* for cptr= */                    

return sort_head;
} 





/* removes items from sort_head list that do not apear in course_head_list
*/
static
SEMESTER_SORT_NODE *remove_removed_courses(SEMESTER_SORT_NODE *sort_head, COURSE_NODE *course_head, int *changed)
{
 SEMESTER_SORT_NODE *sptr, *sptr2;
 COURSE_NODE *cptr;
 int found;

 if(sort_head)
  {
    sptr=sort_head;
    do
    {
      for(cptr = course_head, found = 0; !found && cptr; cptr=cptr->next)  /* for each course in the list */
           found = !strcmp(sptr->semester, cptr->conf.semester);

      if(!found)  /* then remove it from the sort list */
       {
         *changed = 1;
         if(sptr == sort_head)
           {
             sort_head = sort_head->next;
             free(sptr);
             sptr = sort_head;   /* sptr points to next one to be processed */
           }
         else
           {
             for(sptr2=sort_head; sptr2 && sptr2->next != sptr; sptr2 = sptr2->next);
             if(!sptr2)
                 cs_critical_error(ERR_GENERAL_ERROR, "in remove_removed_courses()");   /* shared_cs_util.c */
             sptr2->next = sptr->next;
             free(sptr);
             sptr= sptr2->next;   /* sptr points to next node to be processed */
           }    
        }
       else 
          sptr=sptr->next;      /* sptr points to next node to be processed */
    } while (sptr);
  }    /* if (sort_head)  */
  
  return sort_head;
}


static void
write_sort_list(SEMESTER_SORT_NODE *sort_head, const char *sort_path)
{
FILE *fp;
SEMESTER_SORT_NODE *sptr;

     fp = fopen(sort_path, "w");
     if(!fp)
      cs_critical_error(ERR_FOPEN_WRITE_FAILED, sort_path);   /* shared_cs_util.c */
     for(sptr= sort_head; sptr; sptr=sptr->next)
       fprintf(fp,"%s\n%d%s\n", sptr->semester, sptr->is_expanded?1:0,sptr->is_unread_checked?"*":"");
     fclose(fp);
}






/* compares the sort list to the course list
** remove items from sort list with semesters that do not appear in course list
** add items to sort list that appear in course list, but not in sort list
** if any changes are made, to sort list, rewrites the sort data file 
*/
static
SEMESTER_SORT_NODE *check_sort_file(SEMESTER_SORT_NODE *sort_head, COURSE_NODE *course_head, 
                                                              const char *sort_path, int new_login)
{
 int changed;
  
  
 changed = 0; 
 
 /* remove items from sort list that are no longer in course list */
 sort_head = remove_removed_courses(sort_head, course_head, &changed);
 
 /* add any new entries that are in course list but not in sort list */
 sort_head = add_new_courses(sort_head, course_head, &changed);

 /** remove any duplicate entries in the sort_list
 ** this should never happen, but it's worth a few cycles 
 */
 sort_head = remove_duplicate_entries(sort_head, &changed);
 

 /* only one semester can be checked for unread msgs = which one? */
 sort_head = adjust_unread_checking(sort_head, new_login, &changed);
 
 
 if(changed)
    write_sort_list(sort_head, sort_path);

  return (sort_head);
} 









SEMESTER_SORT_NODE *build_semester_sort_list(COURSE_NODE *course_head, const char *username, int new_login)
{
#define MAX_LINE 100

 char line[MAX_LINE + 1];
 char dirname;
 FILE *fp;
 char sort_file[MAX_PATH + 1];
 SEMESTER_SORT_NODE *sort_head = 0, *ptr=0;

 dirname = sub_dir_name(username);    /* shared_authenticate.c */
 
 snprintf(sort_file, MAX_PATH + 1, "../%s/%c/%s/%s",
                            USERS_DIR, dirname, username,SEMESTER_SORT_FNAME);

                            
 fp = fopen(sort_file, "r"); 
 if(fp)
  {
    while(fgets(line,MAX_LINE, fp))
     {
      strtrm(line);  /* shared_strtrm.c*/
      if(!sort_head)
       {
        sort_head = (SEMESTER_SORT_NODE *) malloc(sizeof(SEMESTER_SORT_NODE));
        if(!sort_head)
           cs_critical_error(ERR_MALLOC_FAILED, "in build_semester_sort_list()");
        ptr = sort_head;
       }
      else
       {
        ptr->next = (SEMESTER_SORT_NODE *) malloc(sizeof(SEMESTER_SORT_NODE));
        if(!ptr->next)
           cs_critical_error(ERR_MALLOC_FAILED, "in build_semester_sort_list()");     
        ptr = ptr->next;
       }
       strncpy(ptr->semester, line, MAX_SEMESTER + 1);

       /* next line contains info on whether or not this semester is 'expanded',
       ** and whether or not we are checking this semester for unread messages
       */
       if(fgets(line, MAX_LINE, fp))
        {
         ptr->is_expanded = (*line == '1');
         ptr->is_unread_checked = strchr(line, '*')?1:0;
        } 
       else
        {
         ptr->is_expanded = 1;
         ptr->is_unread_checked = 0;
        } 

       ptr->next = 0;  
     } /* while (fgets..) */     
  
  fclose(fp);
 } 
 
 sort_head = check_sort_file(sort_head, course_head, sort_file, new_login);
 
 return sort_head;

#undef MAX_LINE
}

static int
course_cmp(COURSE_NODE *a, COURSE_NODE *b, SEMESTER_SORT_NODE *sort_head)
{
 SEMESTER_SORT_NODE *sptr;
 int compare = 0;
  
 if(strcmp(a->conf.semester, b->conf.semester))  /* the courses are from different semesters.  which one appears first in  the sort list? */
  {
   for(sptr = sort_head; sptr; sptr=sptr->next)
    {
     if(!strcmp(a->conf.semester, sptr->semester))
      {
         compare = -1;   /* 'a' appeared first  on list */
         break;
      }   
     else
       if(!strcmp(b->conf.semester, sptr->semester))
        {
          compare = 1;    /* b appeared first on list */
          break;
        }  
    }
   if(compare == 0)  /* since the sort list is already guaranteed to contain all of the semesters */
       cs_critical_error(ERR_GENERAL_ERROR, "in course_cmp()");    /* shared_cs_util.c */
   }                 
 else  /* same semester, return comparison of courses */
   compare = strcmp(a->conf.course_no, b->conf.course_no);
   
 return compare;   
}       




COURSE_NODE *
sort_users_course_list(COURSE_NODE *head, SEMESTER_SORT_NODE *sort_head)
{
  COURSE_NODE *q, *tail, *p, *r;


  if (head != NULL)
    {
      tail = head;
      while (tail->next)
	{
	  q = tail->next;
	  if (course_cmp(q, head,sort_head) < 0)
	    {
	      tail->next = q->next;
	      q->next = head;
	      head = q;
	    }
	  else
	    {
	      r = head;
	      p = r->next;
	      while (course_cmp(q, p,sort_head) > 0)
		{
		  r = p;
		  p = r->next;
		}
	      if (q == p)
		tail = q;
	      else
		{
		  tail->next = q->next;
		  q->next = p;
		  r->next = q;
		}
	    }
	}
    }
    return  head;
}



void
free_users_course_list(COURSE_NODE *head)
{
   COURSE_NODE *ptr;
   while (head)
    {
      ptr = head->next;
      free (head);
      head = ptr;
    }
}




void
free_semester_sort_list(SEMESTER_SORT_NODE *head)
{
   SEMESTER_SORT_NODE  *ptr;
   while (head)
    {
      ptr = head->next;
      free (head);
      head = ptr;
    }
}

