#include <stdio.h>
#include <dirent.h>		/* Directory information */

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>    /* for unlink(), etc. */
#include <string.h>
#include <stdlib.h>   /* for malloc(), free(), etc. */

#include "../global.h"

#include "shared_cs_util.h"
#include "shared_strtrm.h"
#include "shared_file_util.h"
#include "shared_user_index.h"   /* for typedefs */

static void
unlock_user_index (const char *users_path)
{
  char lkfile[MAX_PATH + 1];

  snprintf (lkfile, MAX_PATH + 1, "%s%s", TMP_PATH,
					    USER_INDEX_LOCKFILE);
  unlink (lkfile);
}



/* the user index is locked using a lockfile
** Can't use flock() since some functions 
** will need to entirely re-create the index file
**/

static void
lock_user_index(const char *users_path)
{
#define STALE_SECS 120    /* a lock file older than this #seconds has been abandoned */

  char lkfile[MAX_PATH + 1];
  int i;
  int fd;
  extern int errno;
  struct stat buf;
  time_t now;
  
  snprintf (lkfile, MAX_PATH + 1, "%s%s", TMP_PATH,
					    USER_INDEX_LOCKFILE);
					    
  /* check for an abandoned lock file
  ** if it exists, and was modified more than STALE_SECS ago,
  ** then the file is probably abandoned and can be deleted 
  */					    
  if(!stat(lkfile, &buf))
   { 
    now = time(NULL);
    if( (now - buf.st_mtime) >  STALE_SECS)
        unlink(lkfile);
   }
    
  /* now create the lock file */      
  for (i = 1; i <= MAX_LOCK_ATTEMPTS; i++)
    {
      fd = open (lkfile, O_WRONLY | O_CREAT | O_EXCL, 0644);
      if ((fd < 0) && (errno == EEXIST))
	sleep (3);
      else if (fd < 0)
	cs_critical_error (ERR_ERROR_CHECKING_INDEX_LOCKFILE, 
					"");
      else
	{
	  close (fd);
	  change_mkstemp_permission(lkfile);   /* shared_file_util.c */
	  return;
	}
    }
  cs_critical_error (ERR_CANT_LOCK_USER_INDEX, "");

#undef STALE_SECS
}






static void
add_index_data(const char *users_directory, 
                        const char *letter_directory, 
                            const char *username, 
				FILE *index_fp)
{
 char id_path[MAX_PATH + 1];
 FILE *id_fp;
 char id[100];
 char realname[100];
 
 snprintf(id_path, MAX_PATH, "%s/%s/%s/%s", 
                                users_directory, 
                                     letter_directory, 
	 				 username,ID_FNAME);
	 				
 id_fp = fopen(id_path, "r");
 if(!id_fp)
  {
    return;
  }  
 if(!fgets(id,100,id_fp) ||
    !fgets(realname,100,id_fp))
 {
 
   unlock_user_index (users_directory);
   cs_critical_error(ERR_ERROR_READING_ID_FILE_X, id_path);
 }
fclose(id_fp);
strtrm(id);  /* shared_strtrm.c */
strtrm(realname);
fprintf(index_fp,"%s:%s:%s\n",username,id,realname);
}



static void 
process_letter_directory(const char *users_directory, 
                            const char *letter_directory, FILE *fp)
{
  char dir_path[MAX_PATH + 1];
  DIR *dir_p;
  struct dirent *dir_entry_p;
  
  snprintf(dir_path, MAX_PATH + 1, "%s/%s" , 
  			users_directory, letter_directory);

  dir_p = opendir (dir_path);
  if (!dir_p)
    return;

  while (NULL != (dir_entry_p = readdir (dir_p)))
    {
      if(*(dir_entry_p->d_name) != '.')
        add_index_data(users_directory, letter_directory, 
        				dir_entry_p->d_name, fp);      
    }
  closedir (dir_p);
}




static void 
do_reindex(const char *users_directory, FILE *fp)
{
  DIR *dir_p;
  struct dirent *dir_entry_p;
  
  dir_p = opendir (users_directory);
  if (!dir_p)
   {
    unlock_user_index(users_directory);
    cs_critical_error (ERR_CANT_OPEN_USERS_DIR_X, users_directory);
   }
   
  while (NULL != (dir_entry_p = readdir (dir_p)))
    {
      if( (*(dir_entry_p->d_name) != '.') &&
          strcmp(dir_entry_p->d_name, "keys") &&
          strcmp(dir_entry_p->d_name,USER_INDEX_FNAME) &&
          strcmp(dir_entry_p->d_name, USER_INDEX_LOCKFILE) )
      {
        process_letter_directory(users_directory, dir_entry_p->d_name,fp);      
      }                  		    
    }
  closedir (dir_p);
}


void
reindex_users(const char *users_path)
{
 FILE *fp = 0;
 char index_path[MAX_PATH + 1];
 
 lock_user_index(users_path);    

 snprintf(index_path, MAX_PATH + 1, "%s/%s",users_path,USER_INDEX_FNAME);
 fp = fopen(index_path, "w");
 if(!fp)
  {
   unlock_user_index(users_path);
   cs_critical_error(ERR_FOPEN_WRITE_FAILED, "");
  }

 do_reindex(users_path,fp);
 fclose(fp);
 unlock_user_index(users_path);
} 



int
add_to_user_index(const char *users_path, const char *username, 
			const char *realname, const char *id)
{
 char index_path[MAX_PATH + 1];
 FILE *fp;
 
 lock_user_index(users_path);    

 snprintf(index_path, MAX_PATH + 1, "%s/%s",users_path,USER_INDEX_FNAME);
 fp = fopen(index_path, "a");
 if(!fp)
  {
   unlock_user_index(users_path);
   return 0;
  }

 fprintf(fp,"%s:%s:%s\n",username,id,realname);
 fclose(fp);
 unlock_user_index(users_path);
 return 1;
} 





/* reads entire index.txt file into buffer */
void
buffer_index ( char **buffer )
{
 FILE *fp;
 struct stat statbuf;
 char users_path[MAX_PATH + 1];
 char index_path[MAX_PATH + 1];
 
 *buffer = (char *) 0;
 
 snprintf(users_path, MAX_PATH + 1, "../%s",USERS_DIR);
 snprintf(index_path, MAX_PATH + 1, "%s/%s", users_path, USER_INDEX_FNAME);
 
 lock_user_index(users_path);

  
 if (stat (index_path, &statbuf)) /* then index file doesn't exist - return a null string */
 {
   unlock_user_index(users_path);
   *buffer = (char *) malloc(1  * sizeof(char));
   **buffer = '\0';
   return;
 }  

 *buffer = (char *) malloc((statbuf.st_size + 1)  * sizeof(char));
 if(!*buffer)
  {
   unlock_user_index(users_path);
   cs_critical_error (ERR_MALLOC_FAILED, "");
  }  
  
 fp = fopen(index_path, "r");
 if(!fp)
  {
   unlock_user_index(users_path);
   cs_critical_error(ERR_FOPEN_READ_FAILED , "");
  } 
 
 if(fread(*buffer,statbuf.st_size,1,fp) != 1)
  {
   unlock_user_index(users_path); /* empty index.txt */
   *buffer = (char *) malloc(1  * sizeof(char));
   **buffer = '\0';
   return;
  }

 unlock_user_index(users_path);
 fclose(fp);
 *(*buffer + statbuf.st_size) = '\0';
}
   
   
/*** searches buffer for a record represented by id
***  if found, returns username and realname from the same record
***  upon return an empty username means id wasn't found or an error occurred
**/
void
index_get_names(char *buffer, const char *id, char *username, char *realname)
{
 char *ptr, *search_start;
 int id_len;
 int found = 0;
 char *tmp;
    
 *username = '\0';
 *realname = '\0';
 id_len = strlen(id);
 if(!id_len)
    return;

 if(!buffer || !*buffer)
     return;
         
 search_start = buffer;   
 while(!found)
 {
   ptr = strstr(search_start, id);
   if(ptr)
    {
     found =  (ptr != buffer)  && (*(ptr - 1) == ':')
                   && (*(ptr + id_len) == ':');
     if(!found)
      search_start = ptr + 1;
    }                 
   else
      break;
 }     
 
 
 if(found)
  {

   /* copy the username field */
   tmp = ptr - 1;
   *tmp = '\0';
   ptr = strrchr(buffer,'\n');
   if(ptr)
      ptr++;
   else
      ptr = buffer;

   strncpy(username, ptr, MAX_USERNAME + 1);
     
   *tmp = ':';
   
   
   /* copy the realname field */
   
   ptr = strchr(ptr, ':');
   if(!ptr)
     {
       *username = '\0';
       return;   
     }
   ptr++;
   ptr = strchr(ptr, ':');
   if(!ptr)
     {
       *username = '\0';
       return;   
     }    
   ptr++;
   tmp = strchr(ptr,'\n');
   if(!tmp)
     {
       *username = '\0';
       return;   
     }
    *tmp = '\0';
    strncpy(realname, ptr, MAX_NAME + 1);
    *(realname + MAX_NAME) = '\0';
    *tmp = '\n'; 
  }  
}  			



static
void
parse_index_line(char *line, char *username, char *id, char *realname)
{
     char temp[200];
     char *ptr, *ptr2;

     strncpy(temp, line, 200);
     ptr = strchr(temp, ':');
     if(!ptr)
	cs_critical_error(ERR_INDEX_FILE_FORMAT_ERROR, "");
     *ptr = '\0';
     ptr++;
     strncpy(username, temp, MAX_USERNAME +1);

    ptr2 = ptr;
    ptr = strchr(ptr2, ':');
    if(!ptr)
	cs_critical_error(ERR_INDEX_FILE_FORMAT_ERROR, "");
    *ptr = '\0';
    ptr++;
    strncpy(id, ptr2, MAX_ID +1);
    strncpy(realname, ptr, MAX_NAME +1);
}



INDEX_USER_LIST *
add_to_index_list(INDEX_USER_LIST *list,
		const char *username, const char *id, const char *realname)
{
   if(!list->head)
   {
       list->head = (INDEX_USER*)malloc(sizeof(INDEX_USER));
       list->head->next =0;
       list->current = list->head;
       strncpy(list->head->username, username, MAX_USERNAME+1);
       strncpy(list->head->id, id, MAX_ID+1);
       strncpy(list->head->realname, realname, MAX_NAME+1);
    }
   else
   {
      list->current->next =(INDEX_USER*)malloc(sizeof(INDEX_USER));
      list->current = list->current->next;
      list->current->next =0;
      strncpy(list->current->username, username, MAX_USERNAME+1);
      strncpy(list->current->id, id, MAX_ID+1);
      strncpy(list->current->realname, realname, MAX_NAME+1);
   }
   return list;
}



INDEX_USER_LIST *
build_index_user_list(INDEX_USER_LIST *list)
{
    FILE *fp;
    char line[200];
    char username[MAX_USERNAME +1];
    char id[MAX_ID +1];
    char realname[MAX_NAME +1];

    char users_path[MAX_PATH + 1];
    char index_path[MAX_PATH + 1];
 
    snprintf(users_path, MAX_PATH + 1, "../%s",USERS_DIR);
    snprintf(index_path, MAX_PATH + 1, "%s/%s", users_path, USER_INDEX_FNAME);

    lock_user_index(users_path);

    fp = fopen(index_path, "r");
    if(!fp)
     {
      unlock_user_index(users_path);
      return list; 
     }
      
    while(fgets(line, 200, fp))
    {
      strtrm(line);
      parse_index_line(line, username, id, realname);
      list = add_to_index_list(list,username, id, realname);
    }
    
    fclose(fp);
    unlock_user_index(users_path);
    return list;
}


void
free_index_user_list(INDEX_USER_LIST *list)
{
      INDEX_USER *ptr;
      while(list->head)
      {
	      ptr = list->head->next;
	      free(list->head);
	      list->head = ptr;
      }
}


