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


#include "../global.h"

#include "shared_course_search.h"
#include "shared_util.h"
#include "shared_strtrm.h"
#include "shared_authenticate.h"
#include "shared_cs_util.h"




static int
internal_name_matches(const char *target, const char *lowercased_search, int where)
{
 char *lowercased_target;
 char *ptr;
 int match;
 int search_len;
 
 

 search_len = strlen(lowercased_search);
 
 if(!search_len)
   return 1;     /* a blank search string matches everything */
   
 if(search_len > (int) strlen(target))  /* search string is longer than target, can't match */
       return 0;  
       
 
 /* make a lowercased copy of the target */
 lowercased_target = (char *) malloc(strlen(target) + 1);
 if(!lowercased_target)
    cs_critical_error(ERR_MALLOC_FAILED, "");
 
 strcpy(lowercased_target, target);
 for(ptr = lowercased_target; *ptr; ptr++)
    *ptr = tolower(*ptr);
 
 
  
 if( (where ==  0)  || (where == 1))  /* 0 = anywhere within string , 1= at start of string */
  {
    ptr = strstr(lowercased_target, lowercased_search);
    if(!ptr)
        match = 0;
    else if(where == 0)
        match = 1;
    else
        match = (ptr == lowercased_target);
   }
  else /* match end of string */
   {
      ptr = strrchr(lowercased_target, '\0');
      if(!ptr)    /* can't happen */
         match = 0;
      else
       {   
        ptr -= search_len;
        match = !strcmp(lowercased_search, ptr);
       } 
    }
 
  free(lowercased_target);
 return match;
 
 }  
  
 

static int
config_meets_criteria(CONFIG_STRUCT *conf, int type) 
{
 
 switch(type)
  {
   case 0:  return 1;    /* standalone or centralized */
            break;
   case 1:  return conf->standalone;    /*standalone only */
            break;
   default:  return !(conf->standalone);  /* centralized only */
            break;
  }
}                              



static
COURSE_SEARCH_NODE *
add_to_search_list(COURSE_SEARCH_NODE *result_head, CONFIG_STRUCT *conf, const char *dirname, const char *subdir)
{
 COURSE_SEARCH_NODE *new_node;
 static COURSE_SEARCH_NODE *last_node = 0;
 
    new_node = (COURSE_SEARCH_NODE *) malloc(sizeof(COURSE_SEARCH_NODE));
    if(!new_node)
      cs_critical_error(ERR_MALLOC_FAILED, "");
    new_node-> conf = *conf;
    strncpy(new_node->subdir, subdir, MAX_COURSE_SUBDIR + 1);
    strncpy(new_node->dirname, dirname, MAX_FILENAME + 1);
    new_node ->next = (COURSE_SEARCH_NODE *) 0;
    
    if(!result_head)
      {
       result_head = new_node;
       last_node = result_head;
      } 
    else
      {
       last_node->next = new_node;
       last_node = last_node -> next;
      }
  
     return result_head;


}       





static
COURSE_SEARCH_NODE *
process_course_templates(COURSE_SEARCH_NODE *head, const char *lowercased_search, int  type, int where)
{
 DIR  *template_dir_p,    /* used to open the ../templates directory  */
      *teacher_dir_p;      /* used to open a teachers directory within the ../templates directory, e.g. ../templates/Tsnarmont */
      
 struct dirent  *template_dir_entry_p,  /* used to traverse the ../templates directory */
                *teacher_dir_entry_p;   /* used to traverse a teacher's directory within the templates directory */
 
 char template_dir[MAX_PATH + 1];   /* relative path to the templates directory i.e. ../templates */
 char teacher_dir[MAX_PATH + 1];    /* relative path to one teacher's directory e.g. ../templates/Tsnarmont */

 char course_fname[MAX_PATH + 1];
 CONFIG_STRUCT conf;
 COURSE_SEARCH_NODE *result_head = head;
 char *cptr;
 

   strncpy(template_dir, TEMPLATE_PATH, MAX_PATH + 1);

   /*remove trailing slash from template_dir */
   cptr = strchr(template_dir, '\0');
   if(cptr && (cptr != template_dir))
       *(cptr - 1) = '\0';
       
       
   
   template_dir_p = opendir(template_dir);
   if(!template_dir_p)     /* the installation MUST have a ../templates dir */
      cs_critical_error (ERR_OPENDIR_FAILED, "");
    while (NULL != (template_dir_entry_p = readdir (template_dir_p)))
      {
         if( (*(template_dir_entry_p->d_name)) != '.')
          {
           snprintf(teacher_dir, MAX_PATH + 1, "%s/%s", template_dir, template_dir_entry_p->d_name);
           teacher_dir_p = opendir(teacher_dir);
           if(teacher_dir_p)     /* let's ignore an error here */
            {
	      while (NULL != (teacher_dir_entry_p = readdir (teacher_dir_p)))
                {
                 if( (*(teacher_dir_entry_p->d_name)) != '.')
                  {
		    if(internal_name_matches(teacher_dir_entry_p->d_name, lowercased_search, where))
                      {
                       snprintf(course_fname, MAX_PATH + 1, "%s/%s", template_dir_entry_p->d_name, teacher_dir_entry_p->d_name);
                       if(read_configuration_file_ok(course_fname, &conf)   /* shared_util.c */
                                   && config_meets_criteria(&conf,type) )
                            result_head = add_to_search_list(result_head, &conf, teacher_dir_entry_p->d_name, 
                            								template_dir_entry_p->d_name);
                      }
                   }                 
                 }
              closedir(teacher_dir_p);
             }
            }     
        }     
    closedir(template_dir_p);
   
    return result_head;
} 
 

COURSE_SEARCH_NODE *
process_one_subdir (COURSE_SEARCH_NODE *head, const char *lowercased_search, int type , int where, const char *subdir)
{
 DIR *dir_p;
 struct dirent *dir_entry_p;
 char dir_path[MAX_PATH + 1];
 char course_fname[MAX_PATH + 1];
 CONFIG_STRUCT conf;
 COURSE_SEARCH_NODE *result_head = head;
 
 if(!strcmp(subdir, TEMPLATES_NAME))
    return process_course_templates(head, lowercased_search, type, where);
 else
  {
   snprintf(dir_path, MAX_PATH + 1, "%s%s", COURSE_PATH, subdir);
   dir_p = opendir(dir_path);
   if(dir_p)
   {
    while (NULL != (dir_entry_p = readdir (dir_p)))
    {
         if( (*(dir_entry_p->d_name)) != '.')
         {
          if(internal_name_matches(dir_entry_p->d_name, lowercased_search, where))
            {
             snprintf(course_fname, MAX_PATH + 1, "%s/%s", subdir, dir_entry_p->d_name);
             if(read_configuration_file_ok(course_fname, &conf)   /* shared_util.c */
                    && config_meets_criteria(&conf,type) )
                            result_head = add_to_search_list(result_head, &conf, dir_entry_p->d_name, subdir);
            }
          }                                      
     }             
      closedir(dir_p);
    } /* if directory could be opened */
   } /* else not TEMPLATES_NAME */

 return result_head;
}

                        
                        
int course_node_cmp( COURSE_SEARCH_NODE *a, COURSE_SEARCH_NODE *b)
{

  int comp;
  
  comp = strcmp(a->subdir, b->subdir);
  if(comp)
    return comp;
  else    /* subdirs are the same */  
    return strcmp(a->dirname, b->dirname);
}

    


     
static
COURSE_SEARCH_NODE *
sort_course_search_list (COURSE_SEARCH_NODE *course_head)
{
  COURSE_SEARCH_NODE *q, *tail, *p, *r;
  if (course_head != NULL)
    {
      tail = course_head;
      while (tail->next)
	{
	  q = tail->next;
	  if (course_node_cmp (q, course_head) <  0)
	    {
	      tail->next = q->next;
	      q->next = course_head;
	      course_head = q;
	    }
	  else
	    {
	      r = course_head;
	      p = r->next;
	      while (course_node_cmp (q,p) > 0)
		{
		  r = p;
		  p = r->next;
		}
	      if (q == p)
		tail = q;
	      else
		{
		  tail->next = q->next;
		  q->next = p;
		  r->next = q;
		}
	    }
	}
    }
    return  course_head;
}
             
   
     

COURSE_SEARCH_NODE *
build_course_search_list(COURSE_SEARCH_PARAMS *params)
{
 
 SUBDIR_NODE *sub_ptr;
 COURSE_SEARCH_NODE *head = 0;
 char *lowercased_search;
 char *ptr;
 
 
 /*save a lower case version of the search string for comparisons */
 lowercased_search = (char *) malloc(strlen(params->search) + 1);
 if(!lowercased_search)
    cs_critical_error(ERR_MALLOC_FAILED, "");
 strcpy(lowercased_search, params->search);
 for(ptr=lowercased_search; *ptr; ptr++);   
    *ptr = tolower(*ptr);
    
 
 for(sub_ptr = params->subdir_head; sub_ptr; sub_ptr = sub_ptr->next)
    head = process_one_subdir(head, lowercased_search, params->type, params->where, sub_ptr->subdir);
    
 free(lowercased_search);    
 head = sort_course_search_list(head);
 return head;
}
 


void
free_course_search_list(COURSE_SEARCH_NODE *head)
{
 COURSE_SEARCH_NODE *ptr;
 
 while(head)
  {
   ptr = head;
   head = head->next;
   free(ptr);
  }
}




void
write_last_search_selections(const char *username, COURSE_SEARCH_PARAMS *params)
{
 FILE *fp;
 char path[MAX_PATH + 1];
 SUBDIR_NODE *ptr;

 /* build a path to the file that holds the last search by this user 
 ** sub_dir_name() is in shared_authenticate.c 
 */
 snprintf(path, MAX_PATH + 1, "../%s/%s/%c/%s/%s",       
                  USERS_DIR, ADMIN_DIR, sub_dir_name(username), username, LAST_SEARCH_FNAME);
 
 fp = fopen(path,"w");
 if(fp)
  {
   fprintf(fp,"%s\n%d\n%d\n"  ,
              params->search,
              params->type,
              params->where);
   for(ptr = params->subdir_head; ptr; ptr=ptr->next)
     fprintf(fp,"%s\n", ptr->subdir);
   fclose(fp);
  }
}  
                




void
get_last_search_selections(const char *username, COURSE_SEARCH_PARAMS *last_search)
{
 FILE *fp;
 char path[MAX_PATH + 1];
 char line[MAX_PATH + 1];
 SUBDIR_NODE *new_node, *last_node = 0;
 
 last_search->subdir_head = 0;
 
 /* build a path to the file that holds the last search by this user 
 ** sub_dir_name() is in shared_authenticate.c 
 */
 snprintf(path, MAX_PATH + 1, "../%s/%s/%c/%s/%s",       
                  USERS_DIR, ADMIN_DIR, sub_dir_name(username), username, LAST_SEARCH_FNAME);
 
 fp = fopen(path,"r");
 if(fp)
  {
   if(fgets(line, MAX_PATH, fp))     /* first line contains search string */
    {
     strtrm(line);    /* shared_strtrm.c */
     strncpy(last_search->search, line, MAX_FILENAME + 1);
     if(fgets(line, MAX_PATH, fp))  /* second line contains type - standalone, centralized, both */
     {
      last_search->type = atoi(line);  
      if(fgets(line, MAX_PATH, fp))    /* second line contains 'where' - match anywhere, start or end of string? */
       {
        last_search->where = atoi(line);
        while(fgets(line, MAX_PATH, fp))   /* remaining lines contain directories (internal course names) */
         {
           strtrm(line);
           new_node = (SUBDIR_NODE *) malloc(sizeof(SUBDIR_NODE));
           if(!new_node)
             cs_critical_error(ERR_MALLOC_FAILED, "");
           new_node->next = (SUBDIR_NODE *) 0;
           strncpy(new_node->subdir, line, MAX_COURSE_SUBDIR + 1);
             
           if(!last_search->subdir_head)
               last_search->subdir_head = new_node;
           else
             last_node -> next = new_node;
             
           last_node = new_node;
         }
        }          
       }
     }             
    fclose(fp);
   }      
}










void 
free_subdir_list(SUBDIR_NODE *head)
{
 SUBDIR_NODE *ptr;
 
 while(head)
 {
  ptr = head;
  head = head->next;
  free (ptr);
 }
}




/*******/

static
GROUP_INFO_LIST *
sort_group_list(GROUP_INFO_LIST *head)
{
    GROUP_INFO_LIST  *q, *tail, *p, *r;
    if (head != NULL)
    {
       tail = head;
       while (tail->next)
       {
           q = tail->next;
           if (q->created_time > head->created_time) 
           {
                tail->next = q->next;
                q->next = head;
                head = q;
           }
           else
            {
             r = head;
              p = r->next;
              while (q->created_time < p->created_time) 
              {
                   r = p;
                   p = r->next;
														                      }
              if (q == p)
                tail = q;
             else
             {
               tail->next = q->next;
               q->next = p;
               r->next = q;
             }
        }
     }
    }
    return head;
}



/* the "short description" is the first line of the text file named
** GROUP_DESCRIPTION_FNAME stored within the course subdirectory 
*/
void get_short_group_description(const char *subdir, char *short_description)
{
 char full_path[MAX_PATH + 1];
 FILE *fp;
 
 snprintf(full_path, MAX_PATH + 1, "%s%s/%s", *subdir == 'T'? TEMPLATE_PATH:COURSE_PATH , subdir, GROUP_DESCRIPTION_FNAME);
 fp=fopen(full_path, "r");
 if(!fp || !fgets(short_description, MAX_SEMESTER + 1, fp))
  {
   *short_description = '\0';
  }
 else
   strtrm(short_description); 
 if(fp)
   fclose(fp);
}


    

GROUP_INFO_LIST *
build_group_list(const char *course_path)
{
    DIR *dir_p, *subdir_p;
    GROUP_INFO_LIST *head =0, *ptr, *current=0;
    struct dirent *dir_entry_p;
    struct stat buf;
    char dir_path[MAX_PATH + 1];
    char *cptr;
    char subdir[MAX_PATH +1];
        
    /* remove trailing slash in course_path, if present */
    strncpy(dir_path, course_path, MAX_PATH + 1);
    cptr = strchr(dir_path, '\0');
    if(cptr && (cptr != dir_path) && (*(cptr - 1) == '/'))
           *(cptr - 1) = '\0';

    dir_p = opendir(dir_path);
    if(!dir_p)
       cs_critical_error (ERR_OPENDIR_FAILED, "");
    while (NULL != (dir_entry_p = readdir (dir_p)))
    {
         if( (*(dir_entry_p->d_name)) != '.')
         {
	     snprintf(subdir, MAX_PATH +1, "%s/%s", dir_path, dir_entry_p->d_name);
	     subdir_p = opendir(subdir);
	     if(subdir_p)
	     {
	       stat(subdir, &buf);
	       ptr = (GROUP_INFO_LIST*)malloc(sizeof(GROUP_INFO_LIST));
               if(!ptr)
                  cs_critical_error(ERR_MALLOC_FAILED, "");
               get_short_group_description(dir_entry_p->d_name, ptr->short_description);
	       strncpy(ptr->subdir, dir_entry_p->d_name, MAX_COURSE_SUBDIR + 1);
	       ptr->created_time = buf.st_mtime;
	       ptr->next =0;

	       if(!head)
		  head = ptr;
	       else
		  current->next = ptr;
	       current = ptr;
	     }
	 }
    }
    closedir(dir_p);
    head =sort_group_list(head);
    return head;
}


void
free_course_group_list(GROUP_INFO_LIST *head)
{
    GROUP_INFO_LIST *ptr;
    while(head)
    {
	 ptr = head->next;
	 free(head);
	 head = ptr;
    }
}







int
in_last_search(const char *subdir, COURSE_SEARCH_PARAMS *last_search)
{
 SUBDIR_NODE *ptr;
 
 for(ptr = last_search->subdir_head; ptr; ptr = ptr->next)
   if(!strcmp(subdir, ptr->subdir))
      return 1;
      
 return 0;
}

      


   
