/*
**++
**  FACILITY:
**	NEWSCLASSES
**
**  ABSTRACT:
**      Routines to manipulate CLASSES
**
**  AUTHOR:
**      Geoff Huston
**
**  COPYRIGHT:
**      Copyright © 1989, 1990
**--
**	V6.1b10	12-Sep-1996	Mark Martinec   mark.martinec@ijs.si
**	  - add parameter avoid_duplicates to add_class_member()
**	    and new routine remove_duplicates_class. This linearizes
**	    the very slow O(n^2) loop while adding many newsgroups
**	    to large classes like $net.
**	V6.1b11 14-Apr-1997	Mark Martinec   mark.martinec@ijs.si
**	  - change %s to %.<n>s in sprintf to guard against overwriting buffer
**	  - replace several calls to strcpy/strcat with safer strnncpy/strnncat
**	  - replace pattern strcpy(news_malloc(strlen(x)+1),x) with news_strdup
**/

#ifdef vaxc
#module NEWSCLASSES "V6.2"
#endif

#define _NEWSCLASSES_C
#define module_name "NEWSCLASSES"

#include "newsinclude.h"
#include "newsextern.h"

static
char class_list_ret[256],
     class_name_ret[132];
int class_list_ret_l = 0;

int add_class_member(cname,gnum,avoid_duplicates)
  char *cname;
  unsigned int gnum;
  int avoid_duplicates; /* don't add gnum to the list if already there;
               If false it always appends gnum to the list, regardless of
               possible duplicates - but it is much faster on long lists.
               It is the caller's responsibility to remove duplicates
               afterwards by calling remove_duplicates_class()   */
{
  struct dir_class *ch, *pch = NULL, *tch;
  int i;

  lower_case(cname);
  for (ch=c_head; ch && strcmp(ch->c_name,cname)<0; ch=ch->c_next) pch = ch;
  if (!ch || strcmp(ch->c_name,cname)) {
    tch = (struct dir_class *) news_malloc(sizeof *ch);
    tch->c_next = ch;
    tch->c_name = news_strdup(cname,-1);
    tch->c_nums =  (unsigned int *) news_malloc((sizeof gnum) * (tch->c_malloc = 20));
    tch->c_nums[0] = gnum;
    tch->c_size = 1;
    if (pch) pch->c_next = tch;
    else c_head = tch;
    }
  else {
    if (avoid_duplicates) {
      /* the following loop could consume 50% of elapsed time with large number of groups,
         so it makes sense to append fast and clean it up afterwards! */
      for (i = 0; i < ch->c_size; ++i) if (ch->c_nums[i] == gnum) return 0;
    }
    if (ch->c_size >= ch->c_malloc) {
      int incr = ch->c_malloc / 5;
      if (incr < 20) incr = 20; else if (incr > 400) incr = 400;
      ch->c_nums = (unsigned int *) news_realloc(ch->c_nums,
                                          (ch->c_malloc += incr) * sizeof gnum);
      }
    ch->c_nums[ch->c_size] = gnum;
    ++(ch->c_size);
    }
  return 1;
}

/* Discard any duplicates in the list of groups of a given class list */
int remove_duplicates_class(cname)
  char *cname;
{
  struct dir_class *ch,*pch;
  int f,t, max_group_num, ind,ofs;
  unsigned int *p;

  lower_case(cname);
  for (ch=c_head, pch=NULL;  ch;  pch=ch, ch=ch->c_next) {
    if (strcmp(ch->c_name,cname) >= 0) break;
    }
  if (ch && !strcmp(ch->c_name,cname)) {
    if (ch->c_size > 1) {
      max_group_num = ch->c_nums[0];
      for (f=1; f<ch->c_size; f++)  /* find the largest group number */
        if (ch->c_nums[f] > max_group_num) max_group_num = ch->c_nums[f];
      /* allocate zero-filled bitmap for group numbers */
      p = (unsigned int *) news_calloc(max_group_num/sizeof(unsigned int) + 1,
                                       sizeof(unsigned int) );
      /* set first group by hand */
      ind = ch->c_nums[0] / sizeof(unsigned int);
      ofs = ch->c_nums[0] - ind * sizeof(unsigned int);
      p[ind] |= 1UL<<ofs;
      /* for each group number check if already in the bitmap and skip if so */
      for (f=1,t=0; f<ch->c_size; f++) {
        ind = ch->c_nums[f] / sizeof(unsigned int);
        ofs = ch->c_nums[f] - ind * sizeof(unsigned int);
        if (p[ind] & (1UL<<ofs) == 0) {
          ch->c_nums[++t] = ch->c_nums[f];
          p[ind] |= 1UL<<ofs;
          }
        }
      ch->c_size = t+1;
      news_free(p);
      }
    if (ch->c_malloc > ch->c_size+50) {
      ch->c_nums = (unsigned int *) news_realloc(ch->c_nums,
                                      ch->c_size * sizeof(unsigned int));
      ch->c_malloc = ch->c_size;
      }
    }
  return 1;
}

char *class_list(gnum)
  unsigned int gnum;
{
  struct dir_class *ch;
  char *cn;
  int i;

  *class_list_ret = '\0'; class_list_ret_l = 0;
  for (ch=c_head; ch; ch=ch->c_next) {
    cn = ch->c_name;
    if ((*cn != '$') && (cn[strlen(cn)-1] != '.')) {
      for (i = 0; i < ch->c_size; ++i) {
        if (ch->c_nums[i] == gnum) {
          strnncat(class_list_ret,sizeof(class_list_ret),class_list_ret_l,
                   class_list_ret_l>0 ? "," : "{", 1,  &class_list_ret_l);
          strnncat(class_list_ret,sizeof(class_list_ret),class_list_ret_l,
                   ch->c_name,-1, &class_list_ret_l);
          break;
          }
        }
      }
    }
  if (class_list_ret_l > 0)
    strnncat(class_list_ret,sizeof(class_list_ret),class_list_ret_l,
             "} ",2, &class_list_ret_l);
  return(class_list_ret);
}

int do_add_entry()
{
  int g, c, refresh, wild;
  char entry[132], class[132], *cn;
  unsigned short entry_len, class_len;
  struct dir_class *ch;
  $DESCRIPTOR(entry_dsc,entry);
  $DESCRIPTOR(class_dsc,class);

  if (   (!(cli$get_value(c$dsc("ENTRY"),&entry_dsc,&entry_len) & 1))
      && (get_input_dflt(&entry_dsc,c$dsc("Newsgroup: "),&entry_len,
           ((curr_g) ? c$dsc(ga[curr_g]->grp_name) : 0),0) == RMS$_EOF))
    return(0);
  if (!entry_len) return(0);
  entry[entry_len] = '\0';
  util_cvrt(entry,entry);
  if (!(g = ga_search_name(entry))) {
    sprintf(err_oline,"Add Entry %.200s: newsgroup does not exist",entry);
    err_line(err_oline);
    return(0);
    }

  c = 0; refresh = 0;
  while (cli$get_value(c$dsc("CLASSES"),&class_dsc,&class_len) & 1) {
    ++c;
    class[class_len] = '\0';
    lower_case(class);
    wild = strchr(class,'*') || strchr(class,'%');
    for (ch=c_head; ch; ch=ch->c_next) {
      cn = ch->c_name;
      if ((*cn != '$') && (cn[strlen(cn)-1] != '.')) {
        if (( wild && wild_match(cn,class)) ||
            (!wild && !strcmp(cn,class)) ) {
          add_class_member(cn,ga[g]->grp_num,1);
          refresh += (ch == curr_class);
          }
        }
      }
    }

  if (!c) {
    if (   curr_class
        && (!(   ((*(cn = curr_class->c_name)) == '$')
              || (cn[strlen(cn)-1] == '.')))) {
      add_class_member(curr_class->c_name,ga[g]->grp_num,1);
      ++refresh;
      }
    else err_line("Add Entry: No current class selected");
    }
  if (refresh) {
    int slev = news_context;

    set_level(1);
    return(groupdir(cur_dir_type + 1, 1,slev));
    }
  return(0);
}

int class_check(g)
  int g;
{
  unsigned int gn;
  int i;

  if (!curr_class) return(1);
  gn = ga[g]->grp_num;
  for (i = 0; i < curr_class->c_size; ++i)
    if (curr_class->c_nums[i] == gn) return(1);
  return(0);
}

static int dsc_1()
{
  int g;
  char class[132], clp[132];
  unsigned short class_len;
  $DESCRIPTOR(class_dsc,class);

  if (   (!(cli$get_value(c$dsc("CLASSNAME"),&class_dsc,&class_len) & 1))
      && (get_input_dflt(&class_dsc,c$dsc("Class: "),&class_len,
           ((curr_class) ? c$dsc(curr_class->c_name) : 0),0) == RMS$_EOF))
    return(0);
  if (!class_len) return(0);
  class[class_len] = '\0';
  lower_case(class);
  if (!strcmp(class,"*")) curr_class = 0;
  else if (!strcmp(class,"all")) curr_class = 0;
  else {
    struct dir_class *ch;
    for (ch=c_head; ch; ch=ch->c_next) {
      if (!strcmp(class,ch->c_name)) break;
      }
    if (!ch && (*class != '$')) {
      strcpy(clp,"$");
      strnncat(clp,sizeof(clp),1, class,class_len, 0);
      for (ch=c_head; ch; ch=ch->c_next) {
        if (!strcmp(clp,ch->c_name)) break;
        }
      }
    if (!ch) {
      int memb_added = 0;

      if (strchr(class,'*') || (class[class_len - 1] == '.')) {
        char cmpclass[132], cmpclass2[132];
        int cmpclass_l;

        strnncpy(cmpclass,sizeof(cmpclass), class,class_len, &cmpclass_l);
        if (class[class_len-1] != '.') *cmpclass2 = '\0';
        else {
          strnncat(cmpclass,sizeof(cmpclass),cmpclass_l, "*",1, &cmpclass_l);
          strnncpy(cmpclass2,sizeof(cmpclass2), class,class_len-1, 0);
          }       
        for (g = 1; g <= ga_size; ++g) {
          if (wild_match(ga[g]->grp_name,cmpclass)
              || (*cmpclass2 && wild_match(ga[g]->grp_name,cmpclass2))) {
            add_class_member(class,ga[g]->grp_num,1);
            ++memb_added;
            }
          }
        }
      if (memb_added) set_class(class);
      else err_line("Set Class: No such class name defined");
      }
    else curr_class = ch;
    }
  set_level(1);
  do_dir(cur_dir_type + 1,1);
  set_level(1);
  return(0);
}

int do_set_class()
{
  return(unwind_display(OUTER_LOOP,dsc_1));
}

int set_class(class)
  char *class;
{
  char clp[132];
  int g;
  int class_len = strlen(class);

  lower_case(class);
  if (!strcmp(class,"*")) curr_class = 0;
  else if (!strcmp(class,"all")) curr_class = 0;
  else {
    struct dir_class *ch;
    for (ch=c_head; ch; ch=ch->c_next) {
      if (!strcmp(class,ch->c_name)) break;
      }
    if (!ch && (*class != '$')) {
      strcpy(clp,"$");
      strnncat(clp,sizeof(clp),1, class,class_len, 0);
      for (ch=c_head; ch; ch=ch->c_next) {
        if (!strcmp(clp,ch->c_name)) break;
        }
      }
    if (!ch) {
      int memb_added = 0;

      if (class[class_len-1] == '.') {
        char cmpclass[132];
        int cmpclass_l;

        strnncpy(cmpclass,sizeof(cmpclass), class,class_len, &cmpclass_l);
        strnncat(cmpclass,sizeof(cmpclass),cmpclass_l, "*",1, &cmpclass_l);
        for (g = 1; g <= ga_size; ++g) {
          if (   wild_match(ga[g]->grp_name,cmpclass)
              || !strncmp(class,ga[g]->grp_name,class_len-1)) {
            add_class_member(class,ga[g]->grp_num,1);
            ++memb_added;
            }
          }
        }
      if (memb_added) return(set_class(class));
      else return(err_line("Set Class: No such class name defined"),0);
      }
    else curr_class = ch;
    }
  return(1);
}

void reset_class()
{
  curr_class = 0;
}

char *class_name()
{
  if (curr_class) sprintf(class_name_ret,"CLASS: %.100s",curr_class->c_name);
  else *class_name_ret = '\0';
  return(class_name_ret);
}

static int dsclasses()
{
  struct dir_class *ch;
  int i, gn, g;
  char *cn;

  start_header(T_DISPLAY_LOOP,SHOW_CLASSES);
  put_line("CLASS DIRECTORY",0,T_DISPLAY_LOOP);
  put_line("",0,T_DISPLAY_LOOP);
  end_header(T_DISPLAY_LOOP);
  for (ch=c_head; ch; ch=ch->c_next) {
    cn = ch->c_name;
    if (cn[strlen(cn)-1] != '.') {
      sprintf(err_oline,"  %.200s (%d)",ch->c_name,ch->c_size);
      if (ch == curr_class) *err_oline = '*';
      put_line(err_oline,0,T_DISPLAY_LOOP);
      for (i = 0; i < ch->c_size; ++i) {
        gn = ch->c_nums[i];
	if ((g = ga_locate(gn)) != 0) {
          strcpy(err_oline,"\t"); err_oline_cat(ga[g]->grp_name);
          put_line(err_oline,0,T_DISPLAY_LOOP);
          }
        }
      }
    }
  put_line("",1,T_DISPLAY_LOOP);
  return(0);
}

int do_show_classes()
{
  if (!c_head) return(err_line("Show Classes: No classes defined"),0);
  return(unwind_display(I_DISPLAY_LOOP,dsclasses));
}

void list_classes(g)
  unsigned int g;
{
  struct dir_class *ch;
  int op = 0, i;
  char *cn;

  for (ch=c_head; ch; ch=ch->c_next) {
    cn = ch->c_name;
    if (cn[strlen(cn)-1] != '.') {
      for (i = 0; i < ch->c_size; ++i) {
        if (ch->c_nums[i] == g) {
          if (!op++)
	    put_line("Newsgroup is entered in the following Classes:",0,T_DISPLAY_LOOP);
          strcpy(err_oline,"  "); err_oline_cat(ch->c_name);
          put_line(err_oline,0,T_DISPLAY_LOOP);
          break;
          }
        }
      }
    }
}

int do_modify_entry()
{
  int g, i;
  unsigned int gn;
  char entry[132], class[132], *cn;
  struct dir_class *ch;
  unsigned short entry_len,
        class_len;
  $DESCRIPTOR(entry_dsc,entry);
  $DESCRIPTOR(class_dsc,class);

  if (   (!(cli$get_value(c$dsc("NEWSGROUP"),&entry_dsc,&entry_len) & 1))
      && (get_input_dflt(&entry_dsc,c$dsc("Newsgroup: "),&entry_len,
              ((curr_g) ? c$dsc(ga[curr_g]->grp_name) : 0),0) == RMS$_EOF))
    return(0);
  if (!entry_len) return(0);
  entry[entry_len] = '\0';
  util_cvrt(entry,entry);
  if (!(g = ga_search_name(entry))) {
    sprintf(err_oline,"Modify Entry %.200s: newsgroup does not exist",entry);
    err_line(err_oline);
    return(0);
    }
  gn = ga[g]->grp_num;

  if (!(cli$present(c$dsc("CLASSES")) & 1)) return(0);
  for (ch=c_head; ch; ch=ch->c_next) {
    cn = ch->c_name;
    if ((*cn != '$') && (cn[strlen(cn)-1] != '.')) {
      for (i = 0; i < ch->c_size; ++i)
        if (ch->c_nums[i] == gn) ch->c_nums[i] = 0;
      }
    }

  while (cli$get_value(c$dsc("CLASSES"),&class_dsc,&class_len) & 1) {
    class[class_len] = '\0';
    lower_case(class);
    if (strchr(class,'*') || strchr(class,'%')) {
      for (ch=c_head; ch; ch=ch->c_next) {
        cn = ch->c_name;
        if ((*cn != '$') && (cn[strlen(cn)-1] != '.')) {
          if (wild_match(cn,class))
            add_class_member(cn,ga[g]->grp_num,1);
          }
        }
      }
    else if ((*class != '$') && (class[class_len-1] != '.'))
      add_class_member(class,ga[g]->grp_num,1);
    }
  return(0);
}

int do_delete_entry()
{
  int g = 0, c, delete_class = 0, i;

  unsigned int gn;
  char entry[132], class[132], *cn;
  unsigned short entry_len,
		 class_len;
  struct dir_class *ch, *pch;
  $DESCRIPTOR(entry_dsc,entry);
  $DESCRIPTOR(class_dsc,class);

  if (   (!(cli$get_value(c$dsc("ENTRY"),&entry_dsc,&entry_len) & 1))
      && (get_input_dflt(&entry_dsc,c$dsc("Newsgroup: "),&entry_len,
           ((curr_g) ? c$dsc(ga[curr_g]->grp_name) : 0),0) == RMS$_EOF))
    return(0);
  if (!entry_len) return(0);
  entry[entry_len] = '\0';
  util_cvrt(entry,entry);

  if (!strcmp(entry,"*")) delete_class = 1;
  else if (!(g = ga_search_name(entry))) {
    sprintf(err_oline,"Delete Entry %.200s: newsgroup does not exist",entry);
    err_line(err_oline);
    return(0);
    }
  gn = g ? ga[g]->grp_num : 0;

  c = 0;
  while (cli$get_value(c$dsc("CLASSES"),&class_dsc,&class_len) & 1) {
    ++c;
    class[class_len] = '\0';
    lower_case(class);
    for (ch=c_head; ch; ch=ch->c_next) {
      cn = ch->c_name;
      if ((*cn != '$') && (cn[strlen(cn)-1] != '.')) {
        if (wild_match(cn,class)) {
          if (delete_class) {
            pch = 0;
            if (ch != c_head) {
              for (pch=c_head; pch->c_next != ch; pch=pch->c_next) {}
              }
            news_free(ch->c_name);
            news_free(ch->c_nums);
            if (ch == curr_class) curr_class = 0;
            if (pch) pch->c_next = ch->c_next;
            else c_head = ch->c_next;
            news_free(ch);
            }
          else {
            for (i = 0; i < ch->c_size; ++i)
              if (ch->c_nums[i] == gn) ch->c_nums[i] = 0;
            }
          }
        }
      }
    }
  if (!c && curr_class) {
    ch = curr_class;
    cn = ch->c_name;
    if ((*cn != '$') && (cn[strlen(cn)-1] != '.')) {
      if (delete_class) {
        pch = 0;
        if (ch != c_head) {
          for (pch=c_head; pch->c_next != ch; pch=pch->c_next) {}
          }
        news_free(ch->c_name);
        news_free(ch->c_nums);
        curr_class = 0;
        if (pch) pch->c_next = ch->c_next;
        else c_head = ch->c_next;
        news_free(ch);
        }
      else {
        for (i = 0; i < ch->c_size; ++i)
          if (ch->c_nums[i] == gn) ch->c_nums[i] = 0;
        }
      }
    }
  return(0);
}

void set_start_classname(class)
  char *class;
{
  strnncpy(n_class_name,n_class_name_m, class,-1, 0);
  initial_classname = 1;
}
