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

#include "global.h"
#include "custom.h"		/* for date FORMAT strings */

#include "manhat-lib/shared_util.h"
#include "manhat-lib/shared_cs_util.h"
#include "manhat-lib/shared_news_util.h"
#include "manhat-lib/shared_translate_url.h"
#include "manhat-lib/shared_access.h"



typedef struct chat_msg_node
{
  long offset;
  time_t msg_time;
  char sender[40];
  char *msg;
  struct chat_msg_node *next;
}
CHAT_MSG_NODE;


typedef struct day_node
{
  struct day_node *prev;
  time_t day;
  CHAT_MSG_NODE *msg_head;
  struct day_node *next;
}
DAY_NODE;


DAY_NODE *head = 0;
DAY_NODE *tail = 0;





static void
read_parameters (char *course, char *key, time_t * display_day)
{
 char value[25];

  cs_get_required_parameter ("crs", course, MAX_PATH);	/* shared_cgi_util.c */
  cs_get_required_parameter ("id", key, MAX_KEY);
  cs_get_optional_parameter ("day", value, 24);
  if(strlen(value))
    *display_day = atol (value);
  else
    *display_day = 0;

}



static void
read_news_filter (NEWS_FILTER * filter)
{
  FILE *fp;
  char filter_path[MAX_PATH + 1];

  snprintf (filter_path, MAX_PATH + 1, "%s%s", discussion_path,
	    NEWS_FILTER_FNAME);
	    
  fp = fopen (filter_path, "r");
  if (fp)
    {
      if(fread (filter, sizeof (NEWS_FILTER), 1, fp) != 1) /* if read failed */
        filter->oldest_first = 1;
      fclose (fp);
    }
  else
    {
      filter->oldest_first = 1;
    }
}





static void
set_chat_commands (char *course, char *key, int can_write)
{

  char url[MAX_PATH +1];

  snprintf (url, MAX_PATH +1, "%s?crs=%s&amp;id=%s", "main_menu", course, key);
  cs_set_value("main_menu_url", url);
  if(can_write)
  {
    snprintf (url, MAX_PATH +1, "%s?crs=%s&amp;id=%s&amp;join=yes", "chat_enter", course, key);
    cs_set_value("enter_chat_url", url);
  }

}




/* returns a time_t value which is 01:01.01 on the same date as 'a' */
static
time_t normalized_time (time_t a)
{
  struct tm a_tm;

  a_tm = *localtime (&a);

  a_tm.tm_hour = 1;
  a_tm.tm_min = 1;
  a_tm.tm_sec = 1;

  return (mktime (&a_tm));
}


static void
memory_error ()
{
  cs_critical_error (ERR_MALLOC_FAILED, "");
}


static
void add_to_list (time_t msg_time, char *sender, char *msg, long offset)
{
  time_t day;
  DAY_NODE *day_ptr;
  CHAT_MSG_NODE *msg_ptr;
  int len;

  day = normalized_time (msg_time);
  /* search for node with correct day */
  for (day_ptr = head; day_ptr && ((day_ptr->day) != day);
       day_ptr = day_ptr->next);
  if (!day_ptr)			/* node not found, so add a new node */
    {
      if (!head)
	{
	  head = (DAY_NODE *) malloc (sizeof (DAY_NODE));
	  if (!head)
	    memory_error ();
	  head->next = (DAY_NODE *) 0;
	  head->prev = (DAY_NODE *) 0;
	  head->day = day;
	  head->msg_head = (CHAT_MSG_NODE *) 0;
	  day_ptr = head;
	}
      else
	{
	  for (day_ptr = head; day_ptr->next; day_ptr = day_ptr->next);
	  day_ptr->next = (DAY_NODE *) malloc (sizeof (DAY_NODE));
	  if (!day_ptr->next)
	    memory_error ();
	  day_ptr->next->prev = day_ptr;
	  day_ptr = day_ptr->next;
	  day_ptr->next = (DAY_NODE *) 0;
	  day_ptr->msg_head = (CHAT_MSG_NODE *) 0;
	  day_ptr->day = day;
	}
    }


  /* now add this message */

  if (!(day_ptr->msg_head))	/* this is a newly created DAY_NODE */
    {
      day_ptr->msg_head = (CHAT_MSG_NODE *) malloc (sizeof (CHAT_MSG_NODE));
      if (!day_ptr->msg_head)
	memory_error ();
      msg_ptr = day_ptr->msg_head;
    }
  else
    {
      for (msg_ptr = day_ptr->msg_head; msg_ptr->next;
	   msg_ptr = msg_ptr->next);
      msg_ptr->next = (CHAT_MSG_NODE *) malloc (sizeof (CHAT_MSG_NODE));
      if (!msg_ptr->next)
	memory_error ();
      msg_ptr = msg_ptr->next;
    }

  msg_ptr->offset = offset;
  msg_ptr->msg_time = msg_time;
  strncpy (msg_ptr->sender, sender, 40);
  len = strlen (msg);
  msg_ptr->msg = (char *) malloc (len + 1);
  if (!msg_ptr->msg)
    memory_error ();
  strncpy (msg_ptr->msg, msg, len + 1);
  msg_ptr->next = (CHAT_MSG_NODE *) 0;
}




static void
build_transcript_list (CONFIG_STRUCT *conf)
{
  FILE *fp;
  char path[MAX_PATH + 1];
  long offset = 0;		/* track where we are in the file */

  time_t msg_time;		/* the CHAT_MSG_LOG has these data items in this order */
  int deleted;
  char sender[40];
  int msg_len;
  char *msg = (char *) 0;
  char *escaped_msg = (char *) 0;

  snprintf (path, MAX_PATH + 1, "%s/%s", conf->course_path, CHAT_MSG_LOG);
  fp = fopen (path, "r");
  if (!fp)
    cs_critical_error (ERR_FOPEN_READ_FAILED, path);

  while (fread (&msg_time, sizeof (time_t), 1, fp) == 1)
    {
      /* was this message deleted by the teacher ? */
      if (fread (&deleted, sizeof (int), 1, fp) != 1)
        cs_critical_error (ERR_READING_TRANSCRIPT_FILE, path);

      /* who typed the message (realname) **/
      if (fread (sender, 40 * sizeof (char), 1, fp) != 1)
          cs_critical_error (ERR_READING_TRANSCRIPT_FILE, path);


      /* how many chars were typed - what is the length of the message? */ 
      if (fread (&msg_len, sizeof (int), 1, fp) != 1)
             cs_critical_error (ERR_READING_TRANSCRIPT_FILE, path);

     /** allocate space for the 'raw' message **/
      msg = (char *) malloc ((msg_len + 1) * sizeof (char));

      /* read the raw message */
      if ((int)fread (msg, sizeof (char), msg_len, fp) != msg_len)
                cs_critical_error (ERR_READING_TRANSCRIPT_FILE, path);
      msg[msg_len] = '\0';

      /* need to escape html chars */
      if(!deleted)
       {
        if(cgi_html_escape_strfunc(msg,&escaped_msg) == STATUS_OK)
           add_to_list (msg_time, sender, escaped_msg, offset);
        else
           add_to_list (msg_time, sender, msg, offset);   /* should never happen ? */
           
       }

      if(msg)
       {
         free (msg);
         msg = (char *) 0;
       }
      if(escaped_msg)
       {
        free(escaped_msg);
        escaped_msg = (char *) 0;
       }
           
      offset = ftell (fp);
    }

  fclose (fp);
}



static void
free_transcript_list ()
{
  DAY_NODE *day_ptr;
  CHAT_MSG_NODE *msg_ptr;

  while (head)
    {
      day_ptr = head;
      while (day_ptr->msg_head)
	{
	  msg_ptr = day_ptr->msg_head;
	  day_ptr->msg_head = day_ptr->msg_head->next;
	  free (msg_ptr);
	}

      free (day_ptr);
      head = head->next;
    }
}


static void
set_one_day (DAY_NODE * day_ptr, char *course, char *key)
{
#define MAX_TMP_NAME 30
  CHAT_MSG_NODE *msg_ptr;
  char timestring[MAX_TIMESTRING + 1];
  static int day_count = 0;
  int count;
  char name[MAX_TMP_NAME];
  char url[MAX_PATH +1];

  strftime (timestring, MAX_TIMESTRING, LONG_DOW_DATE_FORMAT,
	    localtime (&(day_ptr->day)));
  
  snprintf(name, MAX_TMP_NAME, "day.%d.date", day_count);
  cs_set_value(name, timestring); 

  for (msg_ptr = day_ptr->msg_head, count = 1; msg_ptr;
       msg_ptr = msg_ptr->next, count++);
  count--;
  snprintf(name, MAX_TMP_NAME, "day.%d.msg_count", day_count);
  cs_set_int_value(name, count); 

  snprintf(url, MAX_PATH +1, "%s?crs=%s&amp;id=%s&amp;day=%ld","chat_transcript", course, key, (long)day_ptr->day);
  snprintf(name, MAX_TMP_NAME, "day.%d.read_url", day_count);
  cs_set_value(name, url); 
  day_count++;
#undef MAX_TMP_NAME 
}


static void
set_list_data (NEWS_FILTER * filter, char *course, char *key, CONFIG_STRUCT *conf)
{
  DAY_NODE *day_ptr;

  build_transcript_list (conf);
  if (!head)
    return;

  if (filter->oldest_first)
    {
      for (day_ptr = head; day_ptr; day_ptr = day_ptr->next)
	set_one_day (day_ptr, course, key);
    }
  else
    {
      for (day_ptr = head; day_ptr->next; day_ptr = day_ptr->next);
      for (; day_ptr; day_ptr = day_ptr->prev)
	set_one_day (day_ptr, course, key);
    }

  free_transcript_list ();
}




static void
set_filter (char *course, char *key, int oldestfirst, int access)
{

  char fromstring[MAX_PATH + 1];
  char url[MAX_PATH + 1];

 /* print "reverse order" button only if we're not capturing */
 if(access != ACCESS_CAPTURE)
  {
   /* AMP_SUB, et. al. are #defined in shared_translate_url.h */
    snprintf (fromstring, MAX_PATH + 1, "%s?crs%c%s%cid%c%s",
	    "chat_transcript", EQUAL_SUB, course, AMP_SUB, EQUAL_SUB, key);

    snprintf (url, MAX_PATH +1, "%s?crs=%s&amp;id=%s&amp;grp=%d&amp;cmd=%d&amp;from=%s",
	    "news_filter_set", course, key, CHATTER, oldestfirst? 1 : 0, fromstring);
    cs_set_value("sort_order_url", url);

  }
}




static void
set_day_list (char *course, char *key, CONFIG_STRUCT *conf,int can_write)
{
  NEWS_FILTER filter;

  if(conf->access == ACCESS_CAPTURE)  /* oldest first if capturing */
     filter.oldest_first = 1;
  else   
    read_news_filter (&filter);
    
  set_chat_commands (course, key, can_write);
  set_filter (course, key, filter.oldest_first, conf->access);
  set_list_data (&filter, course, key, conf);

}


static void
set_detailed_day (DAY_NODE * day_ptr, char *course, char *key,
		    time_t display_day, SESSION *user, int can_write)
{
  #define MAX_TMP_NAME 30

  CHAT_MSG_NODE *msg_ptr;
  char timestring[MAX_TIMESTRING + 1];
  int i =0;
  char name[MAX_TMP_NAME];
  char url[MAX_PATH +1];

  strftime (timestring, MAX_TIMESTRING, LONG_DOW_DATE_FORMAT,
	                                       localtime (&(day_ptr->day)));

  cs_set_value("day_string", timestring);

  for (msg_ptr = day_ptr->msg_head, i = 0; msg_ptr; msg_ptr = msg_ptr->next, i++)
    {
      strftime (timestring, MAX_TIMESTRING, TIME_FORMAT, localtime (&(msg_ptr->msg_time)));
      snprintf(name, MAX_TMP_NAME, "msg.%d.time", i);
      cs_set_value(name, timestring);
      
      if ( (user->group == FACULTY) && can_write)
        {
            snprintf(name, MAX_TMP_NAME, "msg.%d.delete_url", i);
	    snprintf(url, MAX_PATH +1,  "%s?crs=%s&amp;id=%s&amp;loc=%ld&amp;day=%ld",
		  "chat_delete", course, key, msg_ptr->offset, (long)display_day);
	    cs_set_value(name, url);
	}

       snprintf(name, MAX_TMP_NAME, "msg.%d.sender", i);
       cs_set_value(name, msg_ptr->sender);
       
       snprintf(name, MAX_TMP_NAME, "msg.%d.msg", i);
       cs_set_value(name, msg_ptr->msg);
    }
  #undef MAX_TMP_NAME 
}






static void
set_single_day (char *course, char *key, time_t display_day,
                  CONFIG_STRUCT *conf, SESSION *user, int can_write)
{
  DAY_NODE *day_ptr;
  char url[MAX_PATH +1];

  build_transcript_list(conf);
     
  snprintf (url, MAX_PATH +1, "%s?crs=%s&amp;id=%s","chat_transcript", course, key);
  cs_set_value("back_url", url);

  if ( (user->group == FACULTY) && can_write)
      cs_set_int_value("allow_deletions", 1);

  for (day_ptr = head; day_ptr; day_ptr = day_ptr->next)
    if (day_ptr->day == display_day)
        set_detailed_day (day_ptr, course, key, display_day, user, can_write);

  free_transcript_list ();
}




static void
display_transcript (char *course, char *key, time_t display_day,
                    CONFIG_STRUCT *conf, SESSION *user, int can_write)
{
  cs_set_course_info(conf);
  cs_set_current_time();
  
  if (display_day)
  {
    cs_set_int_value("single_day", 1);
    set_single_day (course, key, display_day, conf, user, can_write);
    
  }
  else
  {
    cs_set_int_value("single_day", 0);
    set_day_list (course, key, conf, can_write);
  }
}






int
main (void)
{
  char course[MAX_PATH + 1];	/* from crs=? command line */
  char key[MAX_KEY + 1];	/* from id=? command line */
  time_t display_day;		/* from optional day=? command line */
  SESSION user;
  CONFIG_STRUCT conf;		/* the configuration read from config file */
  int can_write; 


  cs_cgi_init();

  read_parameters (course, key, &display_day);
  read_configuration_file (course, &conf);	/* shared_util.c */
  validate_key (key, &user, &conf);	/* shared_util.c */

  can_write = has_write_permission(&user, &conf);
  set_discussion_names (conf.course_path, user.username, CHATTER);	/* shared_news_util.c */

  display_transcript (course, key, display_day, &conf, &user, can_write);

  cs_cgi_display("chat_transcript", 1);
  cs_cgi_destroy();

  return 0;
}
