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

#include "../global.h"
#include "shared_search_profile.h"
#include "shared_util.h"
#include "shared_super_util.h"
#include "shared_authenticate.h"
#include "shared_person_list.h"
#include "shared_lock.h"
#include "shared_super_list.h"
#include "shared_strtrm.h"
#include "shared_central_user.h"
#include "shared_escape_realname.h"
#include "shared_cs_util.h"

void
read_search_parameters(char *key, char *crs, char *group, char *oper, char *search, int *field, int *users, int *results, 
				 int *sensitive, int *where, int *sort, int *start_record, int *records_per_page)
{
   char tmp[20];
   
   cs_get_required_parameter("id", key, MAX_KEY);
   cs_get_optional_parameter("crs", crs, MAX_PATH);
   cs_get_optional_parameter("group", group, MAX_ID);
   cs_get_optional_parameter("oper", oper, 19);
   cs_get_optional_parameter("sensitive", tmp, 19);
   *sensitive = atoi(tmp);

   cs_get_optional_parameter("where", tmp, 19);
   *where = atoi(tmp);

   
   cs_get_optional_parameter ("search",search,MAX_PATH);
   strtrm(search);
        
   cs_get_optional_parameter("field", tmp, 19);
   *field = atoi(tmp);
   
   cs_get_optional_parameter("users", tmp, 19);
   *users = atoi(tmp);

   cs_get_optional_parameter("results", tmp, 19);
   *results = atoi(tmp);

   cs_get_optional_parameter("sort", tmp, 19);
   *sort = atoi(tmp);

   cs_get_optional_parameter("start", tmp, 19);
   *start_record = atoi(tmp);
   if(*start_record <= 0)
        *start_record = 1;




  /* 'step' param indicates the number of records per page
  ** if not supplied then 
  **     - for FULL_RESULT searches, limit number of records on page to MAX_FULL_RECORDS_PER_PAGE
  **        as #defined in shared_search_profile.h
  **     - otherwise set to one million, which in effect means no limit
  */
   cs_get_optional_parameter("step", tmp, 19);
   *records_per_page = atoi(tmp);
   if(*records_per_page <= 0)
      *records_per_page = (*results == FULL_RESULTS)? MAX_FULL_RECORDS_PER_PAGE : 1000000;

}







static
int parse_index_line(char *line, PROFILE_NODE *test_data)
{
 char *ptr;
 char *ptr2;
 
 ptr = strchr(line,':');
 if(!ptr)
       return 0; 
 *ptr = '\0';
 
 
 strncpy(test_data->person.username, line, MAX_USERNAME + 1);
 *(test_data->person.username + MAX_USERNAME) = '\0';
 *ptr = ':';
 
 ptr++;
 ptr2 = strchr(ptr,':');
 if(!ptr2)
      return 0;
 *ptr2 = '\0';
 strncpy(test_data->person.id, ptr, MAX_ID + 1);
 *(test_data->person.id + MAX_ID) = '\0';
 *ptr2 = ':';
 
  ptr2++;
  strncpy(test_data->person.realname, ptr2, MAX_NAME + 1);
  *(test_data->person.realname + MAX_NAME) = '\0';
  strtrm(test_data->person.realname);
  
  
 test_data -> username_match = NO_MATCH;
 test_data -> id_match = NO_MATCH;
 test_data -> realname_match = NO_MATCH;
 test_data -> course_head = (USER_COURSE_NODE *) 0;
 
  
 return 1;
 
}


/* returns 1 if 'username' has a course in the course list represented by 
** 'course_head' in which he is of 'user_type' (i.e. a student or a teacher 
**/

static
int correct_user_type(COURSE_LIST_NODE *course_head, const char *username, int user_type)
{
   char passwd_fname[MAX_PATH + 1];
   FILE *fp;
   COURSE_LIST_NODE *course_ptr;
   char line[201];
   USER_DATA person;
   int type_found = 0;
   int username_found;
     
   /* this function can only find students and faculty
   ** should never receive other values 
   */  
   if( (user_type != STUDENT_USERS)  && (user_type != FACULTY_USERS))
         return 0;
         
   for(course_ptr = course_head; course_ptr && !type_found; course_ptr = course_ptr->next)
    {
     snprintf(passwd_fname, MAX_PATH + 1, "%s%s/%s", 
                             *course_ptr->crs == 'T'? TEMPLATE_PATH: COURSE_PATH,
                              course_ptr->crs,
                              PASSWD_FNAME);
     fp = fopen(passwd_fname, "r");
     if(fp)
      {
       get_shared_lock(fileno(fp),1);
       username_found = 0;
       while (!username_found && fgets (line, 200, fp))
         {
            if (*line == '?')		/* password lines starting with a '?' are ignored */
	       continue;
            get_passwd_line_data(line, &person);   /* shared_person_list.c */
            if(!strcmp(person.username, username))
              {
               username_found = 1;
               type_found = user_type==STUDENT_USERS? (person.group == STUDENT) : (person.group == FACULTY);
              } 
         }
        release_lock(fileno(fp));   /* shared_lock.c */
        fclose(fp);
      }
    }  /* for */


   return type_found;           
}

                                       
   



static 
int user_type_matches(const char *username, int user_type)
{
 char course_list[MAX_PATH + 1];
 FILE *fp;
 COURSE_LIST_NODE *course_head;
 int result;
 
 if(user_type == ALL_USERS)
    return 1;
 
 snprintf(course_list, MAX_PATH +1, "../%s/%c/%s/%s", USERS_DIR, 
           sub_dir_name(username), username, COURSE_LIST_FNAME);
 fp = fopen(course_list, "r");
 if(!fp)
   {
    if((user_type == STUDENT_USERS) || (user_type == CLASSLESS_USERS))
       return 1;
    else   /* if looking for teachers */
       return 0;
   }       
     
 get_shared_lock(fileno(fp), 1);        /* shared_lock.c */
 course_head = build_course_list(fp);   /*shared_super_list.c */
 release_lock(fileno(fp));              /*shared_lock.c */
 fclose(fp);

 if(!course_head)
     {
      if((user_type == STUDENT_USERS) || (user_type == CLASSLESS_USERS))
         return 1;
      else   /* if looking for teachers */
         return 0;
     }       
 result = correct_user_type(course_head, username, user_type);
 super_free_course_list(course_head);    /* shared_super_list.c */
 return result;  
}




static
int string_matches(const char *needle, int sensitive, int where, const char *haystack, int *matchpoint)
{
 char *tmp;
 char *ptr;
 int haystack_len;
 int match = 0;
 int needle_len;
 
 
 *matchpoint = NO_MATCH;
 
 /* work with a copy of the string to be searched 
 */
 haystack_len = strlen(haystack);
 tmp = (char *) malloc((haystack_len + 1) * sizeof(char));
 if(!tmp)
    cs_critical_error(ERR_MALLOC_FAILED, "");
    
 strncpy(tmp,haystack, haystack_len + 1);
 
 /* if this search is not case sensitive,
 ** convert the working copy to lowercase
 ** The 'needle' string should already be
 ** in lowercase 
 */
 if(!sensitive)
  {
   for(ptr=tmp; *ptr; ptr++)
      *ptr = tolower(*ptr);
  }
 
 /* deal with searching from the end of the 
 ** string - this is a special case
 */   
 if(where == END_WITH)
 {
   needle_len = strlen(needle);
   if(needle_len > haystack_len)
     {
      free(tmp);
      return 0;
     } 
   else
    {
     ptr = strchr(tmp, '\0');
     ptr -= needle_len;
     match = !strcmp(ptr, needle);
     if(match)
      {
        *matchpoint = ptr - tmp;
        free(tmp);
        return 1;
      }
     else
       {
        free(tmp);
        return 0;
       }
    }           
}    
    


/* now deal with 'contains' and 'starts with' searches
*/    
    
 ptr = strstr(tmp, needle);
 if(ptr)
    match = (where==CONTAINS)? 1 : (ptr == tmp);  /* else is 'starts with'  condition */

*matchpoint = match? ptr - tmp : NO_MATCH;
 free(tmp);
 return match;
}    

      


static 
int search_string_matches(const char *search, int sensitive, int where, int field, PROFILE_NODE *record) 
{
 int username_matches;
 int realname_matches;
 int id_matches;
 
 switch(field)
  {
   case ALL_FIELDS:  username_matches = string_matches(search, sensitive, where, record->person.username, &record->username_match);
                     realname_matches = string_matches(search, sensitive, where, record->person.realname, &record->realname_match);
                     id_matches = string_matches(search, sensitive, where, record->person.id, &record->id_match); 
                     return (username_matches || realname_matches || id_matches);
                     break;

   case USERNAME_FIELD: return string_matches(search, sensitive, where, record->person.username, &record->username_match);                      
                        break;
                     
   case ID_FIELD:       return  string_matches(search, sensitive, where, record->person.id, &record->id_match);  
   			break;
   			
   case NAME_FIELD:     return string_matches(search, sensitive, where, record->person.realname, &record->realname_match);   	
                        break;
                        
   default:             return 0;   /*should not happen */
  }                           
}  




static
int user_meets_criteria(PROFILE_NODE *test_data, const char *search, int sensitive, int where, int field, int users)
{
 return  search_string_matches(search, sensitive, where, field, test_data)  && user_type_matches(test_data->person.username, users);
}






static
int person_cmp(USER_DATA *a, USER_DATA *b, int sort)
{
 switch(sort)
  {
   case SORT_LAST:  return(username_cmp (a, b));   /* shared_authenticate.c */
                    break;
                    
   case SORT_USERNAME: return(strcmp(a->username, b->username));
                       break;
                       
   case SORT_ID:       return(strcmp(a->id, b->id));
                       break;
                       
   case SORT_FIRST:    return(strcmp(a->realname, b->realname));
                       break;                                                                  
  
   default        :  return(username_cmp (a, b));   /* shared_authenticate.c */
                     break;
  }
}  



PROFILE_NODE *
sort_profile_list (PROFILE_NODE *pers_head, int sort)
{
   
  PROFILE_NODE *q, *tail, *p, *r;
  if (pers_head != NULL)
    {
      tail = pers_head;
      while (tail->next)
	{
	  q = tail->next;
	  if (person_cmp (&(q->person), &(pers_head->person), sort) < 0)
	    {
	      tail->next = q->next;
	      q->next = pers_head;
	      pers_head = q;
	    }
	  else
	    {
	      r = pers_head;
	      p = r->next;
	      while (person_cmp (&(q->person), &(p->person), sort) > 0) /* shared_authenticate.c */
		{
		  r = p;
		  p = r->next;
		}
	      if (q == p)
		tail = q;
	      else
		{
		  tail->next = q->next;
		  q->next = p;
		  r->next = q;
		}
	    }
	}
    }
    return  pers_head;
}



void free_profile_list(PROFILE_NODE *head)
{
 PROFILE_NODE *ptr;
 USER_COURSE_NODE *cptr;
 
 while(head)
  {
   ptr = head;
   head = head->next;
   while(ptr->course_head)
    {
     cptr = ptr->course_head;
     ptr->course_head = ptr->course_head -> next;
     free(cptr);
    } 
   free(ptr);
  }
}







static PROFILE_NODE *
shrink_profile_list(PROFILE_NODE *head, int count, int start_record, int records_per_page)
{
 PROFILE_NODE *ptr, *new_head;
 int i;
 
 
 new_head = head;
 if((start_record <= count) && (count > records_per_page))  /* then some trimming is necessary */
  {
   if(start_record != 1)   /* then trim the start of the list */
    {
        for(ptr = head, i = 1; 
                     ptr && (i < (start_record -  1));
                                      ptr=ptr->next, i++);   /* skip to the start_record record */
        if(ptr)   /* this should always be true */
          {
            new_head = ptr->next;
            ptr->next = (PROFILE_NODE *) 0;
            free_profile_list(head);
          }
         else
             new_head = (PROFILE_NODE *) 0; 
    }
    
    
          
      /* we've trimmed the start of the list, now trim the end */
      for(ptr = new_head, i=1; ptr && i <= (records_per_page - 1); ptr=ptr->next, i++);  /* skip records_per_page records */
      if(ptr)
        {
         free_profile_list(ptr->next);
         ptr->next = (PROFILE_NODE *) 0;
        }       
   }
 return new_head;
}

 





PROFILE_NODE *build_profile_list(const char *search, int sensitive, int where, int field, 
								int users, int sort, 
				                                int start_record, int records_per_page,
		                                                 int *count)
{
 #define MAX_LINE 200
 
     FILE *fp;
     int fd;
     char filename[MAX_PATH + 1];
     char line[MAX_LINE + 1];
     PROFILE_NODE *head = (PROFILE_NODE *) 0;
     PROFILE_NODE *last_ptr = (PROFILE_NODE *) 0;
     
     char *search_string;
     int search_len;
     char *ptr;
     
     PROFILE_NODE test_data;
     
      *count = 0;
     
     /* work with a copy of the original search string
     ** if the search is not case sensitive, convert the 
     ** working copy to lowercase
     */
      search_len = strlen(search);
      search_string = (char *) malloc((search_len + 1) * sizeof(char));
      if(!search_string)
         cs_critical_error(ERR_MALLOC_FAILED, "");
      strcpy(search_string, search);
      if(!sensitive)
        for(ptr=search_string; *ptr; ptr++)
             *ptr = tolower(*ptr);
              
          
      snprintf(filename, MAX_PATH + 1, "../%s/%s", USERS_DIR, USER_INDEX_FNAME);
      fp = fopen(filename, "r");
      if(!fp)
         return (PROFILE_NODE *) 0;    /* no users */
              
      fd = fileno(fp);
      get_shared_lock(fd, 1);                 /* shared_lock.c */
      while(fgets(line, MAX_LINE, fp))
      {
         strtrm(line); 			      /* shared_strtrm.c */
         if( parse_index_line(line, &test_data) )
           {
            
            if( user_meets_criteria(&test_data, search_string, sensitive, where, field, users))
              {

                *(test_data.person.password) = '\0';   /* blank out password */
                
               /* get_lastname is in shared_authenticate.c 
               ** the value is needed later when we sort the list
               */
               strncpy(test_data.person.lastname, get_lastname(test_data.person.realname),MAX_NAME + 1);
               if(!head)
                 {
                   head =  (PROFILE_NODE *) malloc(sizeof(PROFILE_NODE));
                   if(!head)
                      cs_critical_error(ERR_MALLOC_FAILED, "");
                   last_ptr = head;
                 }
                else
                 {
                  last_ptr->next = (PROFILE_NODE *)malloc(sizeof(PROFILE_NODE));
                  if(!last_ptr->next)
                       cs_critical_error(ERR_MALLOC_FAILED, "");
                  last_ptr = last_ptr->next;
                 }      
                *last_ptr = test_data;
                last_ptr->next = (PROFILE_NODE *) 0;
                (*count)++;
              }
            }
       }                                
       release_lock(fd);
       fclose(fp);

       head = sort_profile_list(head, sort);
       head = shrink_profile_list(head, *count, start_record, records_per_page);
            
       return head;
#undef MAX_LINE
}         
         
         
         
         
         
         

void set_highlighted(char *string, int match, int search_len, const char *name_string, int num)
{
   #define MAX_TMP_NAME 50
   char *ptr;
   int count;
   int end_highlight,  j=0, flag =0;
   char name[MAX_TMP_NAME];
   char temp[MAX_TMP_NAME +1];
   char str[MAX_PATH +1];
   str[0] = '\0';
    temp[0] = '\0';

    if( (match == NO_MATCH) || !search_len)
    {
	 snprintf(name, MAX_TMP_NAME, "user.%d.%s", num, name_string);
	 cs_set_value(name, string);
    }
     else
     {
          end_highlight = match + search_len;
         for(ptr=string, count=0; *ptr; ptr++, count++)
	  {
	      if(count == match)
	      {
		 if(match != 0)
		 {
		  strcat(str, temp);
	          memset(temp, 0, MAX_TMP_NAME);	  
		  j =0;
		 }
		 flag =1;
	      }

	      if(count == end_highlight)
	      {
		  strcat(str, "<span class=\"search_match\">");
		  strcat(str, temp);
		  strcat(str, "</span>");
	          memset(temp, 0, MAX_TMP_NAME);	  
		  flag =0;
		  j=0;
	      }
	      
	      temp[j] = *ptr;
	      temp[j+1] = '\0';
	      j++;
	      	
	  }
	 if(strlen(temp))
	 {
	     if(flag)
	     {
		  strcat(str, "<span class=\"search_match\">");
		  strcat(str, temp);
		  strcat(str, "</span>");

	     }
	     else
		  strcat(str, temp);
	 }
        snprintf(name, MAX_TMP_NAME, "user.%d.%s", num, name_string);
	cs_set_value(name, str);
   }
   #undef MAX_TMP_NAME 
}

int
find_user_in_course(P_NODE *person_crs, const char *username)
{
    P_NODE *ptr;
    for(ptr = person_crs; ptr; ptr = ptr->next)
    {
        if(strcmp(ptr->data.username, username) ==0)
           return 1;
    }
    return 0;
}


int parse_line(const char *str, P_NODE *user_data)
{
   char *ptr;
   char *ptr2;
   char line[strlen(str) +1];
   
   strncpy(line, str, strlen(str) +1);
   ptr = strchr(line, ':');
  if(!ptr)
       return 0;
   *ptr = '\0';
    if(!strlen(line))
	 return 0;
    strncpy(user_data->data.username, line, MAX_USERNAME +1);
    *(user_data->data.username + MAX_USERNAME) = '\0';
    *ptr = ':';

    ptr++;
    ptr2 = strchr(ptr, ':');
    if(!ptr2)
       return 0;
    *ptr2 = '\0';
    if(!strlen(ptr))
	  return 0;
    strncpy(user_data->data.realname, ptr, MAX_NAME +1);
    *(user_data->data.realname + MAX_NAME) = '\0';
    *ptr2 = ':';

    ptr2++;
    if(!strlen(ptr2))
	 return 0;
    strncpy(user_data->data.id, ptr2, MAX_ID+1);
      *(user_data->data.id + MAX_ID)='\0';
   return 1;
}
