#include <stdio.h>	

#include <string.h>
#include <dirent.h>		/* Directory information.       */
#include <sys/types.h>
#include <stdlib.h>		/* for rand() */
#include <sys/stat.h>
#include <unistd.h>

#include "global.h"

#include "manhat-lib/shared_util.h"
#include "manhat-lib/shared_authenticate.h"
#include "manhat-lib/shared_super_util.h"
#include "manhat-lib/shared_course_search.h"    /* for GROUP_DESCRIPTION_FNAME */
#include "manhat-lib/shared_time.h"
#include "manhat-lib/shared_cs_util.h"
#include "manhat-lib/shared_super_menu.h"

HDF *hdf;

int keys_deleted = 0;
int courses_processed = 0;
int courses_in_use = 0;
int sessions_deleted = 0;

typedef struct login_node
{
  char course[MAX_FILENAME + 1];
  char realname[MAX_NAME + 1];
  int minutes;
  struct login_node *next;
}
LOGIN_NODE;

LOGIN_NODE *head = 0;


static void
add_to_list (char *course, char *realname, int minutes)
{
  static LOGIN_NODE *ptr = 0;

  if (!head)
    {
      head = (LOGIN_NODE *) malloc (sizeof (LOGIN_NODE));
      ptr = head;
    }
  else
    {
      ptr->next = (LOGIN_NODE *) malloc (sizeof (LOGIN_NODE));
      ptr = ptr->next;
    }

  strncpy (ptr->course, course, MAX_FILENAME + 1);
  strncpy (ptr->realname, realname, MAX_NAME + 1);
  ptr->minutes = minutes;
  ptr->next = (LOGIN_NODE *) 0;
}

void
free_login_list()
{
  LOGIN_NODE *ptr;
  
  while(head)
  {
   ptr = head;
   head = head->next;
   free(ptr);
  }
}

   


static int
login_record_cmp (LOGIN_NODE * a, LOGIN_NODE * b)
{
  return (a->minutes > b->minutes ? 1 : a->minutes < b->minutes ? -1 : 0);
}


void
sort_login_list ()
{
  LOGIN_NODE *q, *tail, *p, *r;
  if (head != NULL)
    {
      tail = head;
      while (tail->next)
	{
	  q = tail->next;
	  if (login_record_cmp (q, head) < 0)
	    {
	      tail->next = q->next;
	      q->next = head;
	      head = q;
	    }
	  else
	    {
	      r = head;
	      p = r->next;
	      while (login_record_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;
		}
	    }
	}
    }
}


void
set_data_list (const char *kind)
{
  #define MAX_TMP_NAME 30
  LOGIN_NODE *ptr;
  char name[MAX_TMP_NAME];
  int i =0;
   
  sort_login_list ();
  for (ptr = head; ptr; ptr = ptr->next)
  {
    snprintf(name, MAX_TMP_NAME, "%s.%d.course", kind, i);
    cs_set_value(name, ptr->course);
    snprintf(name, MAX_TMP_NAME, "%s.%d.realname", kind, i);
    cs_set_value(name, ptr->realname);
    snprintf(name, MAX_TMP_NAME, "%s.%d.minutes", kind, i);
    cs_set_int_value(name, ptr->minutes);
    i++;
  }
  #undef MAX_TMP_NAME 
}






void
handle_key (char *fname, int *in_use, char *coursename, CONFIG_STRUCT *conf, time_t now)
{
  FILE *fp;
  SESSION this_key;

  *in_use = 0;

  fp = fopen (fname, "r");
  if (!fp)   /* shouldn't happen, let's consider the key 'in use' */
    {

      *in_use = 1;
      return;
    }

  if (fread (&this_key, sizeof (SESSION), 1, fp) != 1)  /* shouldn't happen, consider key 'in use' */
    {
      *in_use = 1;
      
      fclose (fp);
      return;
    }

  fclose(fp);
  
  if (this_key.expires > now)
    {
      if(this_key.group != ADMIN)   /* don't report ADMIN keys */
        {

         add_to_list (coursename, this_key.realname,
		   (conf->key_expire -
		    (this_key.expires - now)) / (time_t) 60);
          *in_use = 1;
        }  
    }
   else
    {
      keys_deleted++;
      unlink (fname);
    }
}








void
delete_stale_keys (int *in_use, char *coursename, CONFIG_STRUCT *conf, time_t now)
{
  DIR *dir_p;
  struct dirent *dir_entry_p;
  char key_fname[MAX_PATH + 1];
  char dirname[MAX_PATH + 1];

  int key_used;

  *in_use = 0;
  snprintf (dirname, MAX_PATH + 1, "%skeys", conf->course_path);
  
  dir_p = opendir (dirname);
  if (!dir_p)
    {
      *in_use = 1;
      return;
    }

  while (NULL != (dir_entry_p = readdir (dir_p)))
    {
      if (strcmp (dir_entry_p->d_name, ".")
	  && strcmp (dir_entry_p->d_name, ".."))
	{
	  snprintf (key_fname, MAX_PATH + 1, "%s/%s", dirname,
		    dir_entry_p->d_name);
	  handle_key (key_fname, &key_used, coursename, conf, now);
	  if (key_used)
	    *in_use = 1;
	}
    }

  closedir (dir_p);
}







void
visit_classroom ( char *coursename,  time_t now)
{
  int in_use;
  CONFIG_STRUCT conf;

  if (!read_configuration_file_ok(coursename, &conf))   /* shared_util.c */
    return;
  if(conf.standalone)
  {  
    delete_stale_keys (&in_use, coursename, &conf, now);
    if (in_use)
       courses_in_use++;
    courses_processed++;
  }     
}





void
do_standalone_courses(time_t now)
{
  DIR *dir_p;                   /* used to traverse ../courses/ directory */
  struct dirent *dir_entry_p;
  
  DIR *course_dir_p;    /* used to traverse a directories within the ../courses directory */
  struct dirent *course_dir_entry_p;  
  
  char course_dir[MAX_PATH + 1];
  char course_name[MAX_PATH + 1];

  /* reset globals */
   keys_deleted = 0;
   courses_processed = 0;
   courses_in_use = 0;
   free_login_list();
  
  dir_p = opendir (COURSE_PATH);
  if (!dir_p)
    cs_critical_error(ERR_UNABLE_TO_OPEN_COURSES, "");

  
  while (NULL != (dir_entry_p = readdir (dir_p)))   /*for each directory within the ../courses directory */
    {
      if (strcmp (dir_entry_p->d_name, ".")
	  && strcmp (dir_entry_p->d_name, ".."))

	{
           snprintf(course_dir, MAX_PATH + 1, "%s%s", COURSE_PATH, dir_entry_p->d_name);
           
           course_dir_p = opendir(course_dir);
           if(course_dir_p)
            {
               while (NULL != (course_dir_entry_p = readdir (course_dir_p)))  /* for each course dir within ../courses/xx_subdir_xx.. */
                  {
                   if (strcmp (course_dir_entry_p->d_name, ".")
	               && strcmp (course_dir_entry_p->d_name, "..")
                       && strcmp (course_dir_entry_p->d_name, GROUP_DESCRIPTION_FNAME) )    /* GROUP_DESCRIPTION_FNAME is #defined in shared_course_search.h */	             {
  
                        snprintf(course_name, MAX_PATH + 1, "%s/%s", dir_entry_p->d_name, course_dir_entry_p->d_name);
                        visit_classroom (course_name, now);
                     }
                  }
            closedir(course_dir_p);           
	    }
        }
    }
  closedir(dir_p);
  set_data_list ("std");
  cs_set_int_value("std_course_num", courses_processed);
  cs_set_int_value("std_course_inuse", courses_in_use);
  cs_set_int_value("std_delete_key", keys_deleted);
}




static
int count_sessions(const char *username)
{
 char user_dir[MAX_PATH + 1];
 DIR *dir_p;
 struct dirent *dir_entry_p;
 char *ptr;
 int count = 0;
  
  /* sub_dir_name() is in shared_authenticate.c */
  snprintf(user_dir,MAX_PATH + 1, "../%s/%c/%s", USERS_DIR, sub_dir_name(username), username);
  dir_p = opendir (user_dir);
  if (!dir_p)   /* shouldn't happen, but lets not complain */
      return 0;

  while (NULL != (dir_entry_p = readdir (dir_p)))
    {
      if (strcmp (dir_entry_p->d_name, ".")
	  && strcmp (dir_entry_p->d_name, ".."))
	{ 
          ptr = strstr(dir_entry_p->d_name, KEY_FILE_EXTENSION);
          if(ptr && (strlen(ptr) == strlen(KEY_FILE_EXTENSION)))  /* if file ends with KEY_FILE_EXTENSION */
            count++;
	} 
    }
  closedir(dir_p);  
  return count;
}





static
int delete_sessions(const char *username)
{
 char user_dir[MAX_PATH + 1];
 char session_fname[MAX_PATH + 1];
 
 DIR *dir_p;
 struct dirent *dir_entry_p;
 char *ptr;
 int count = 0;
  
  /* sub_dir_name() is in shared_authenticate.c */
  snprintf(user_dir,MAX_PATH + 1, "../%s/%c/%s", USERS_DIR, sub_dir_name(username), username);
  dir_p = opendir (user_dir);
  if (!dir_p)   /* shouldn't happen, but lets not complain */
      return 0;

  while (NULL != (dir_entry_p = readdir (dir_p)))
    {
      if (strcmp (dir_entry_p->d_name, ".")
	  && strcmp (dir_entry_p->d_name, ".."))
	{
          ptr = strstr(dir_entry_p->d_name, KEY_FILE_EXTENSION);
          if(ptr && (strlen(ptr) == strlen(KEY_FILE_EXTENSION)))  /* if file ends with KEY_FILE_EXTENSION */
           {
            snprintf(session_fname, MAX_PATH + 1, "%s/%s", user_dir, dir_entry_p->d_name);
            unlink(session_fname);
            sessions_deleted++;
           } 
	}
    }
  closedir(dir_p);  
  return count;
}



  

void
handle_central_key (const char *fname, time_t now)
{
  FILE *fp;
  SESSION this_key;
  char count_string[50];
  
  fp = fopen (fname, "r");
  if(!fp)
    {
      return;
    }

  if (fread (&this_key, sizeof (SESSION), 1, fp) != 1)  /* shouldn't happen, consider key 'in use' */
    {
      fclose (fp);
      return;
    }

  fclose(fp);

  /* some keys might belong to an administrator entering a course's configuration via
  ** the super user menu.  Handle these silently
  */  
  if (this_key.expires > now)
    {
      if(this_key.group != ADMIN)   /* don't report ADMIN keys */
        {
         snprintf(count_string, 50, "%d %s", count_sessions(this_key.username), hdf_get_value(hdf, "manwho_cgi", "course(s)"));
          
         add_to_list (count_string, this_key.realname,
		   (MAX_KEY_EXPIRE - (this_key.expires - now)) / (time_t) 60);
        }
         courses_in_use++;
    }
   else
    {
     keys_deleted++;
     unlink (fname);
     if(this_key.group != ADMIN)
          delete_sessions(this_key.username);
    }
}


void
do_central_courses(time_t now)
{
  DIR *dir_p;
  char fname[MAX_PATH + 1];
  struct dirent *dir_entry_p;
  char keydir[MAX_PATH + 1];
  
  
  snprintf(keydir,MAX_PATH + 1, "../%s/%s", USERS_DIR, KEY_DIR);
  dir_p = opendir (keydir);
  if (!dir_p)
    cs_critical_error(ERR_UNABLE_TO_OPEN_KEYS, "");
  
    /* reset globals */
   keys_deleted = 0;
   courses_processed = 0;
   courses_in_use = 0;
   sessions_deleted = 0;
   free_login_list();
  
  get_lang_string(&hdf); 
  
  while (NULL != (dir_entry_p = readdir (dir_p)))
    {
      if (strcmp (dir_entry_p->d_name, ".")
	  && strcmp (dir_entry_p->d_name, ".."))
	{
	  snprintf (fname, MAX_PATH + 1, "%s/%s", keydir, dir_entry_p->d_name);
	  handle_central_key (fname, now);
	  courses_processed++;
	}
    }

  closedir(dir_p);
  set_data_list ("ctr");
  cs_set_int_value("ctr_course_num", courses_processed);
  cs_set_int_value("ctr_course_inuse", courses_in_use);
  cs_set_int_value("ctr_delete_key", keys_deleted);
  cs_set_int_value("ctr_session_key", sessions_deleted);
  hdf_destroy(&hdf);
}






int
main ()
{
  char key[MAX_KEY +1];
  SESSION user;
  time_t now;
  
  
  cs_cgi_init();
  read_param( key);
  validate_super_key(key, &user);   /*shared_util.c */
  set_admin_page(&user, key, 0, "super_manwho_cgi");  /* shared_super_menu.c */

  cs_set_current_time();         /* shared_cs_util.c */
  now = time(NULL);
  do_central_courses(now);
  do_standalone_courses(now);
  cs_cgi_display("super_manwho", 1);
  cs_cgi_destroy();
  return 0;
}






