/*
**++
**  FACILITY:
**      NEWSREAD
**
**  ABSTRACT:
**      Select a news item for display on the screen.
**
**  AUTHOR:
**      Geoff Huston
**
**  COPYRIGHT:
**      Copyright © 1988,1989,1990
**
**  MODIFICATION HISTORY:
**      V5.5     7-Oct-1988     GIH
**      V5.6    11-Nov-1988     GIH
**        - Add support for BACK command set
**      V5.7     7-Jan-1989     GIH
**        - bug fix in display_new call params
**      V5.8    17-Mar-1989     Stuart McGraw
**        - READ/TOPIC command does not find correct topic article.
**      V5.9    20-Apr-1989     GIH
**        - Correct bug with xrefs()
**	V5.9D	17-Jan-1990	GIH	Dont mark items as read if not
**	  accessible (i.e. NNTP server fail or similar). Also modify the
**	  do_readident code as ids are now stored case sensitive.
**	V6.1	 8-Feb-1992	rankin@eql.caltech.edu
**	  - Lint cleanup
**	V6.1	15-May-1993	Charles Bailey  bailey@genetics.upenn.edu
**	  - fixed parent_exists() to work for nntp client
**	  - optimize item number lookup
**	V6.1b8  22-Dec-1993	mark.martinec@ijs.si
**	  - convert calls to RMS to use new sys_* macros for error handling 
**	V6.1b9   8-Jan-1995	bailey@genetics.upenn.edu
**	  - add support for grp_funread and clean up read/unread update code
**	V6.1b11	21-Feb-1997     Mark Martinec   mark.martinec@ijs.si
**	  - fp_open is now int;  pass it by ref;
**	    don't try to delete_file_versions(fp_open);
**	  - abort with NEWS$_NONNTP if NNTP client mode is requested
**	    but the client support code is not compiled-in.
**	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
**--
**/

#ifdef vaxc
#module NEWSREAD "V6.2"
#endif

#define _NEWSREAD_C
#define module_name "NEWSREAD"

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

static
int tpucall,
    dont_display = 0,
    xref_enabled = 1;

static int st_h, st_tpu, st_r13, st_unupd;

static int displayfail(g,i,unupd)
  int g,i,unupd;
{
  if (ga[g]->grp_ia && i <= ga[g]->grp_count) {
    ga[g]->grp_ia[i].itm_flags |= NEWS_M_NOACCESS;
    if (xref_enabled) xrefs(ga[g]->grp_num,ga[g]->grp_ia[i].itm_id,displayfail);
    return unupd ? markasunread(g,i,1) : 1;
    }
  return 0;
}

static int
ddci()
{
  if (!do_display(st_h,st_tpu,st_r13)) displayfail(curr_g,curr_i,st_unupd);
  return(0);
}

static int st_g, st_i;

static int rgi()
{
  if (st_g != curr_g) do_select("%",st_g,2);
  cur_set_itm(curr_g,st_i);
  st_unupd = markasread(curr_g,curr_i,1);
  if (!do_display(st_h,st_tpu,st_r13)) displayfail(curr_g,curr_i,st_unupd);
  return(0);
}

/* markasread - mark an item as read
 * markasunread - mark an item as unread
 * 
 * These routines should be called to mark items as read or unread, except
 * in special cases such as UNREAD/ALL or SKIP/ALL, which are handled in
 * place for better efficiency.  Any code which manipulates the NEWS_M_UNREAD
 * flag of an item directly is also responsible for updating grp_topreadnum,
 * grp_funread, and the group and item index screens as well!!!
 *
 * If updgrp is 0, only the item specified is marked, and the unread count in
 * the group index display isn't updated.  If it's !=0, then all items with
 * the same Message-ID are marked, and the group index display is updated.
 */

int markasread(g,i,updgrp)
  int g, i, updgrp;
{
  register int idx;

  if (ga[g]->grp_ia && i <= ga[g]->grp_count) {
    ga[g]->grp_ia[i].itm_flags &= ~NEWS_M_NOACCESS;
    if (ga[g]->grp_ia[i].itm_flags & NEWS_M_UNREAD) {
      if (ga[g]->grp_unread > 0) {
        --ga[g]->grp_unread;
        if (updgrp) screen_update_gread(g);
        }
      ga[g]->grp_ia[i].itm_flags &= ~NEWS_M_UNREAD;
      if (ga[g]->grp_topreadnum < ga[g]->grp_ia[i].itm_num)
        ga[g]->grp_topreadnum = ga[g]->grp_ia[i].itm_num;
      if (ga[g]->grp_funread == ga[g]->grp_ia[i].itm_num) {
        if (ga[g]->grp_ia[i].itm_num == ga[g]->grp_topnum)
          ga[g]->grp_funread++;
        else if (ga[g]->grp_flags & NEWS_M_ORDERBYSUBJECT) {
          ga[g]->grp_funread = ga[g]->grp_topnum + 1;
          for (idx = 1; idx <= ga[g]->grp_count; idx++) {
            if (ga[g]->grp_ia[idx].itm_num < ga[g]->grp_funread &&
                ga[g]->grp_ia[idx].itm_flags & NEWS_M_UNREAD)
              ga[g]->grp_ia[idx].itm_num = ga[g]->grp_funread;
            }
          }
        else if (i == ga[g]->grp_count) ga[g]->grp_funread++;  /* safety; shouldn't be reached */
        else {
          for (idx = i; idx <= ga[g]->grp_count &&
               !(ga[g]->grp_ia[idx].itm_flags & NEWS_M_UNREAD); idx++) /**/;
          if (idx == ga[g]->grp_count) ga[g]->grp_funread = ga[g]->grp_topnum + 1;
          else ga[g]->grp_funread = ga[g]->grp_ia[idx].itm_num;
          }
        }
      screen_update_iread(g,i);
      if (xref_enabled && updgrp) xrefs(ga[g]->grp_num,ga[g]->grp_ia[i].itm_id,markasread);
      return 1;
      }
    }
  return 0;
}

int markasunread(g,i,updgrp)
  int g,i, updgrp;
{
  register int idx;

  if (ga[g]->grp_ia && i <= ga[g]->grp_count &&
      !(ga[g]->grp_ia[i].itm_flags & NEWS_M_UNREAD)) {
    ++ga[g]->grp_unread;
    if (updgrp) screen_update_gread(g);
    ga[g]->grp_ia[i].itm_flags |= NEWS_M_UNREAD;
    if (ga[g]->grp_ia[i].itm_num < ga[g]->grp_funread)
       ga[g]->grp_funread = ga[g]->grp_ia[i].itm_num;
    if (ga[g]->grp_ia[i].itm_num == ga[g]->grp_topreadnum) {
      if (ga[g]->grp_ia[i].itm_num == ga[g]->grp_firstnum)
        ga[g]->grp_topreadnum--;
      else if (ga[g]->grp_flags & NEWS_M_ORDERBYSUBJECT) {
        ga[g]->grp_topreadnum = 0;
        for (idx = 1; idx <= ga[g]->grp_count; idx++) {
          if (ga[g]->grp_ia[idx].itm_num > ga[g]->grp_topreadnum &&
              !(ga[g]->grp_ia[idx].itm_flags & NEWS_M_UNREAD))
            ga[g]->grp_topreadnum = ga[g]->grp_ia[idx].itm_num;
          }
        }
      else if (i == 1) ga[g]->grp_topreadnum--;  /* safety; shouldn't be reached */
      else {
        for (idx = i; idx && (ga[g]->grp_ia[idx].itm_flags & NEWS_M_UNREAD);
             idx--) /**/ ;
        if (!idx) ga[g]->grp_topreadnum = ga[g]->grp_firstnum - 1;
        else ga[g]->grp_topreadnum = ga[g]->grp_ia[idx].itm_num;
        }
      }
    screen_update_iread(g,i);
    if (xref_enabled && updgrp) xrefs(ga[g]->grp_num,ga[g]->grp_ia[i].itm_id,markasunread);
    return 1;
    }
  return 0;
}

/*
 *  xrefs
 *
 *  Locate all items with the same message-id as the current item
 *  and carry out the action specified.
 */

#if NNTP_CLIENT_ONLY
void xrefs(g,id,f)
  unsigned int g;
  char *id;
  int (*f)();
{ return; }
#else
static
struct stk {
    unsigned int i,g;
    struct stk *nxt;
    } *stkhd = 0;

static void
stkclr()
{
  struct stk *tmp;

  while (stkhd) {
    tmp = stkhd->nxt;
    news_free(stkhd);
    stkhd = tmp;
    }
}

static void
stkpush(ni,ng)
  unsigned int ni,ng;
{
  struct stk *tmp;

  tmp = (struct stk *) news_malloc(sizeof *tmp);
  tmp->i = ni;
  tmp->g = ng;
  tmp->nxt = stkhd;
  stkhd = tmp;
}

static int
stkpop(ni,ng)
  unsigned int *ni, *ng;
{
  struct stk *tmp;
  if (!stkhd) return(0);
  *ni = stkhd->i;
  *ng = stkhd->g;
  tmp = stkhd;
  stkhd = stkhd->nxt;
  news_free(tmp);
  return(1);
}

void xrefs(g,id,f)
  unsigned int g;
  char *id;
  int (*f)();
{
  int status;
  char loc_id[IDLEN + 4];
  unsigned int *gr,
               itmnum,
               gm,
               grpnum;

  xref_enabled = 0;
  if (nntp_client) return;

  util_idcpy(loc_id,id);
  gr = (unsigned int *) &(loc_id[IDLEN]);
  *gr = 0;
  stkclr();

  itmrab.rab$l_kbf = loc_id;
  itmrab.rab$b_ksz = IDLEN + 4;
  itmrab.rab$b_krf = 1;
  itmrab.rab$l_rop = RAB$M_KGE | RAB$M_RRL | RAB$M_NLK;
  itmrab.rab$b_rac = RAB$C_KEY;
  while (sys_get_nornf(&itmrab)) {
    if (strcmp(id,newsitm.itm_id)) break;
    itmrab.rab$b_rac = RAB$C_SEQ;
    if (newsitm.itm_grp != g) stkpush(newsitm.itm_num,newsitm.itm_grp);
    }

  while (stkpop(&itmnum,&grpnum)) {
    int i;
    if ((gm = ga_locate(grpnum)) != 0) {
      if (!(ga[gm]->grp_ia)) map_items(gm);
      if (find_itm_by_num(gm,itmnum,&i)) f(gm,i,1);
      }
    }
  xref_enabled = 1;
}
#endif /* !NNTP_CLIENT_ONLY */

#define RSTACK 20

static
struct rcontxt {
    int grp;
    int mbm;
    } rcxt[RSTACK];

static
int rnxt = 0,
    rcnt = 0;

void
rpush(g,i)
  int g,i;
{
  rcxt[rnxt].grp = g;
  rcxt[rnxt].mbm = i;
  if (rcnt < RSTACK) ++rcnt;
  rnxt++;
  rnxt %= RSTACK;
}

int
rpop(g,i)
  int *g, *i;
{
  if (!rcnt) return(0);
  rnxt = (rnxt + (RSTACK - 1)) % RSTACK;
  --rcnt;
  *g = rcxt[rnxt].grp;
  *i = rcxt[rnxt].mbm;
  return(1);
}

/*
 *  do_readfind
 *
 *  Read a selected item
 */

int
do_readfind(s,rheader,rot13)
  char *s;
  int rheader,
      rot13;
{
  int i, unupd;

  if (!curr_g) return(err_line("Error: Read - No Newsgroup selected"),0);

  if (news_context == 1) {
    set_level(2);
    if (!curr_i) return(err_line("Read: Newsgroup is empty"),0);
    }
  if (!(i = item_find(s))) return(0);
  cur_set_itm(curr_g,i);
  unupd = markasread(curr_g,curr_i,1);
  if (!do_display(rheader,tpucall,rot13)) displayfail(curr_g,curr_i,unupd);
  return(0);
}

int
display_new(i,rheader,tpucall,rot13)
  int i,rheader,tpucall,rot13;
{
  int unupd;

  while (   (i <= ga[curr_g]->grp_count)
	 && (   !(ga[curr_g]->grp_ia[i].itm_flags & NEWS_M_UNREAD)
             || (ga[curr_g]->grp_ia[i].itm_flags & NEWS_M_NOACCESS)))
    ++i;
  if (i <= ga[curr_g]->grp_count) {
    cur_set_itm(curr_g,i);
    unupd = markasread(curr_g,curr_i,1);
    if (kill_filter(curr_g,curr_i)) {
      sprintf(err_oline,"Skipping next unread item - %.200s:%d - KILL filter",
        ga[curr_g]->grp_name,ga[curr_g]->grp_ia[curr_i].itm_num);
      err_line(err_oline);
      return(2);
      }
    if (dont_display && dont_display != curr_g) {
      if (unupd) markasunread(curr_g,curr_i,1);
      dont_display = 0;
      return 1;
      }
    else if (!do_display(rheader,tpucall,rot13)) {
      displayfail(curr_g,curr_i,unupd);
      return 2;
      }
    else return 1;
    }
  return 0;
}

/*
 *  do_rnew
 *
 *  Pick out the next unread news item - select that group and read the
 *  first unread item from the group
 */


static int dorn_h, dorn_r13, dorn_rst;

static int do_rnew_1()
{
  int i, dnv, unupd;
  int rheader,rot13,reset;

  rheader = dorn_h; rot13 = dorn_r13; reset = dorn_rst;
  if (   curr_g
      && ga[curr_g]->grp_unread
      && (news_context != 1)) {
    set_level(2);
    if (!reset) {
      i = curr_i;
      while (   (i <= ga[curr_g]->grp_count)
            && (   !(ga[curr_g]->grp_ia[i].itm_flags & NEWS_M_UNREAD)
                 || (ga[curr_g]->grp_ia[i].itm_flags & NEWS_M_NOACCESS)))
        ++i;
      if (i > ga[curr_g]->grp_count) i = 1;
      }
    else i = 1;
    if ((dnv = display_new(i,rheader,tpucall,rot13)) != 0) return(dnv - 1);
    else {
      set_level(1);
      ga[curr_g]->grp_unread = 0;
      if (smg_active && ga[curr_g]->grp_display_indx) {
        sprintf(err_oline,"%5d",ga[curr_g]->grp_unread);
        smg_put_chars(grp_vd,err_oline,ga[curr_g]->grp_display_indx,73,0,0);
        }
      }
    }
  if (!no_more_news) {
    set_level(1);
    if (selnew(2)) {
      unupd = markasread(curr_g,curr_i,1);
      if (kill_filter(curr_g,curr_i))  {
        sprintf(err_oline,"Skipping next unread item - %.200s:%d - KILL filter",
          ga[curr_g]->grp_name,ga[curr_g]->grp_ia[curr_i].itm_num);
        err_line(err_oline);
        return(1);
        }
      if (dont_display && (dont_display != curr_g)) {
        if (unupd) markasunread(curr_g,curr_i,1);
        dont_display = 0;
        }
      else if (!do_display(rheader,tpucall,rot13)) {
        displayfail(curr_g,curr_i,unupd);
        dont_display = 0;
        }
      return(0);
      }
    }
  else err_line("No unread items in registered newsgroups");
  return(0);
}

static int do_rnew(rheader,rot13,reset)
  int rheader,rot13,reset;
{
  dorn_h = rheader;
  dorn_r13 = rot13;
  dorn_rst = reset;
  return(unwind_display(OUTER_LOOP,do_rnew_1));
}

/*
 *  do_readnew
 *
 *  pick out the next unread news item and display it
 */

int do_readnew(rheader,rot13,reset)
  int rheader, rot13, reset;
{
  if (!showdirs_val) dont_display = 0;
  else dont_display = ((news_context < 2) ? -1 : curr_g);
  while (do_rnew(rheader,rot13,reset)) reset = 0;
  dont_display = 0;
  return(0);
}

/*
 *  do_readnext
 *
 *  Use the current read context to pick out the next item to display.
 */

int do_readnext(rheader,rot13,tpucall)
    int rheader,
        rot13,
        tpucall;
{
  int unupd;

  if (!curr_g) return(err_line("Error: Read - No Newsgroup selected"),0);

  if (news_context == 1) {
    set_level(2);
    if (!curr_i) return(err_line("Read: Newsgroup is empty"),0);
    }
  else if (cur_down_itm(curr_g,1,0) != 1) {
    if (!ga[curr_g]->grp_count) err_line("Read: Newsgroup is empty");
    else err_line("Read: No more items in Newsgroup");
    return(0);
    }

  unupd = markasread(curr_g,curr_i,1);
  if (!do_display(rheader,tpucall,rot13)) displayfail(curr_g,curr_i,unupd);
  return(0);
}

/*
 *  do_readparent
 *
 *  Display the parent item of the current item - pick off the rightmost
 *  reference item
 */

static int do_readparent(rheader,rot13)
  int rheader, rot13;
{
  FILE *fp;
  char inpline[512], pref[512], *ref;
  int i, g;

  if (!((news_context > 1) && curr_g && curr_i)) {
    err_line("Error: Read/Refs - No current item selected");
    return(0);
    }
  if (!(fp = do_open_item(curr_g,curr_i,"r",&fp_open))) {
    err_line("Error: Display - Cannot access item text");
    return(0);
    }
  while (fgets(inpline,sizeof(inpline),fp)) {
    if (*inpline == '\n') break;
    if (!strncmp(inpline,"References: ",12)) break;
    }
  fclose(fp);
/*if (*fp_open > 1) delete_file_versions(fp_open); */
  fp_open = 0;
  if (strncmp(inpline,"References: ",12)) {
    err_line("Error: Read/Refs - This item has no parent reference");
    return(0);
    }
  for (;;) {
    if (!(ref = strrchr(inpline,'>'))) break;
    *(ref+1) = '\0';
    if (!(ref = strrchr(inpline,'<'))) break;
    strnncpy(pref,sizeof(pref), ref,-1, 0);
    *ref = '\0';
    if (get_itm_by_ident(pref,&g,&i,curr_g)) {
      st_h = rheader; st_tpu = tpucall; st_r13 = rot13; st_g = g; st_i = i;
      return(unwind_display(OUTER_LOOP,rgi));
      }
    }
  err_line("Error: Read/Refs - Parent reference not located");
  return(0);
}

/*
 *  do_readancestor
 *
 *  Display the top parent item of the current item - pick off the leftmost
 *  reference item and follow the chain back until exhausted
 */

#if NNTP_CLIENT
static char remid[IDLEN], nntp_reply[2048];
static unsigned char server_returns_inums = 1;

static int get_nntp_headline(ibuf)
  char *ibuf;
{
  char *s;

  if (news_strncasecmp(ibuf,remid,strlen(remid))) return 1;
  chop_str(ibuf,'\n');
  if ( !(s = strchr(ibuf,' ')) ) return 1;
  strnncpy(nntp_reply,sizeof(nntp_reply), ++s,-1, 0);
  return 0;
}
#endif

int parent_exists(gp,ip)
  int *gp, *ip;
{
  int status;

  if (nntp_client) {
#if !NNTP_CLIENT
    exit(NEWS$_NONNTP);
#else
    int tmp, i, itmnum = 0;
    char schar, *idstrt, *idend, *gstrt, *gend, *nntp_line, *refhdr;
    
    nntp_reply[0] = '\0';
    if ( !map_ids(*gp) || !*(ga[*gp]->grp_ia[*ip].itm_id) ) {
      sprintf(err_oline,"Error retrieving message ID for item %d",
                        ga[*gp]->grp_ia[*ip].itm_num);
      err_line(err_oline);
      return(0);
      }
    strcpy(err_oline,"XHDR references ");
    err_oline_cat(ga[*gp]->grp_ia[*ip].itm_id);
    strnncpy(remid,sizeof(remid), ga[*gp]->grp_ia[*ip].itm_id,-1, 0);
    if (!nntp_get_info(nntp_node,nntp_proto,err_oline,221,0,get_nntp_headline)
        || !nntp_reply) {
      strcpy(err_oline,"\tNNTP XHDR error - item "); err_oline_cat(remid);
      err_line(err_oline);
      return(0);
      }
    
    refhdr = news_strdup(nntp_reply,-1);
    idend = refhdr;
    schar = *idend;
    while ( (idstrt = strchr(idend,'<')) ) {
      *idend = schar;
      if ( !(idend = strchr(idstrt,'>')) ) break;
      schar = *(++idend);
      *idend = '\0';
      nntp_reply[0] = '\0';
      strcpy(err_oline,"XHDR newsgroups "); err_oline_cat(idstrt);
      strnncpy(remid,sizeof(remid), idstrt,-1, 0);
      if (!nntp_get_info(nntp_node,nntp_proto,err_oline,221,0,
                         get_nntp_headline) || !nntp_reply) {
        strcpy(err_oline,"\tNNTP XHDR error - item "); err_oline_cat(remid);
        err_line(err_oline);
        news_free(refhdr);
        return(0);
        }
      strip_compress(nntp_reply);
      if (!strstr(nntp_reply,ga[*gp]->grp_name)) {
        gstrt = nntp_reply;
        tmp = 0;
        do {
          gend = strchr(gstrt,',');
          if (gend) *gend = '\0';
          if ( (tmp = ga_exact_name(gstrt)) ) break;
          gstrt = gend + 1;
          } while (gend);
        if (!tmp) {
          strcpy(err_oline,"No valid newsgroups in item ");
          err_oline_cat(idstrt);
          err_line(err_oline);
          continue;
          }
        *gp = tmp;
        }
      if (!ga[*gp]->grp_ia) map_items(*gp);
      tmp = 0;
      /* if the server includes the item number in the status line like it's
         supposed to, we can do a quick search . . . */
      if (server_returns_inums) {
        strcpy(err_oline,"STAT "); err_oline_cat(idstrt);
        if (nntp_one_call(nntp_node,nntp_proto,err_oline,&nntp_line) != 223)
          continue;
        if ( (sscanf(nntp_line,"%d %d",&i,&itmnum) == 2) && itmnum )
          tmp = find_itm_by_num(*gp,itmnum,&tmp) ? tmp : 0;
        else server_returns_inums = 0;
        }
      /* . . . but not all servers are well-behaved, or we may not have
         retrieved the item yet, so we may have to hunt it up by ID */
      if (!tmp && !get_itm_by_ident(idstrt,gp,&tmp,*gp)) {
        sprintf(err_oline,"Item %.200s not found in group ",idstrt);
        err_oline_cat(ga[*gp]->grp_name);
        err_line(err_oline);
        continue;
        }
      *ip = tmp;
      news_free(refhdr);
      return(1);
      }
    news_free(refhdr);
    return(0);
#endif /* NNTP_CLIENT */
    }
  else {
    char inpline[512], pref[IDLEN+4];
    int g, i, ni;
    unsigned int *gr;
    FILE *fp;
    
    if (!(fp = do_open_item(*gp,*ip,"r",&fp_open))) return(0);
    while (fgets(inpline,sizeof(inpline),fp)) {
      if (*inpline == '\n') {
        fclose(fp);
     /* if (*fp_open > 1) delete_file_versions(fp_open); */
        fp_open = 0;
        return(0);
        }
      if (!strncmp(inpline,"References: ",12)) {
        char *ref, *eref, schar;
    
        fclose(fp);
     /* if (*fp_open > 1) delete_file_versions(fp_open); */
        fp_open = 0;
        ref = inpline;
    
        while ((ref = strchr(ref,'<')) != 0) {
          if ((eref = strchr(ref,'>')) == 0) break;
          eref++;
          schar = *eref;
          *eref = '\0';
          strnncpy(pref,sizeof(pref), ref,-1, 0);
          util_idcpy(pref,pref);
          gr = (unsigned int *) &(pref[IDLEN]);
          *gr = ga[*gp]->grp_num;
    
          itmrab.rab$l_kbf = pref;
          itmrab.rab$b_ksz = IDLEN + 4;
          itmrab.rab$b_krf = 1;
          itmrab.rab$l_rop = RAB$M_RRL | RAB$M_NLK;
          itmrab.rab$b_rac = RAB$C_KEY;
    
          if (!sys_get_nornf(&itmrab)) {
            *gr = 0;
            itmrab.rab$l_rop = RAB$M_KGE | RAB$M_RRL | RAB$M_NLK;
            if (sys_get_nornf(&itmrab) && !strcmp(pref,newsitm.itm_id)) {
              if (*gp != newsitm.itm_grp) {
                if (!(g = ga_locate(newsitm.itm_grp))) {
                  *eref = schar;
                  ref = eref;
                  continue;
                  }
                }
              else g = *gp;
              }
            else {
              *eref = schar;
              ref = eref;
              continue;
              }
            }
          else g = *gp;
          ni = newsitm.itm_num;
          map_items(g);
          if (!find_itm_by_num(g,ni,&i)) {
            *eref = schar;
            ref = eref;
            continue;
            }
          if ((fp = do_open_item(g,i,"r",&fp_open)) != 0) {
            fclose(fp);
         /* if (*fp_open > 1) delete_file_versions(fp_open); */
            fp_open = 0;
            *gp = g;
            *ip = i;
            return(1);
            }
          *eref = schar;
          ref = eref;
          }
        return(0);
        }
      }
    fclose(fp);
 /* if (*fp_open > 1) delete_file_versions(fp_open); */
    fp_open = 0;
    }
  return(0);
}

static int
do_readancestor(rheader,rot13,tpucall)
  int rheader, rot13, tpucall;
{
  int i = curr_i, g = curr_g, unupd;

  if (!((news_context > 1) && curr_g && curr_i))
    return(err_line("Display: No current item selected"),0);

  while (parent_exists(&g,&i)) {
    if ((g == curr_g) && (i == curr_i))
      return(err_line("Display: This item has no parent references"),0);
    }
  if ((g == curr_g) && (i == curr_i))
    return(err_line("Display: This item has no parent references"),0);

  do_select("%",g,2);
  cur_set_itm(curr_g,i);
  unupd = markasread(curr_g,curr_i,1);
  if (!do_display(rheader,tpucall,rot13)) displayfail(curr_g,curr_i,unupd);
  return(0);
}

/*
 *  do_readident
 *
 *  Display the item selected by the supplied identifier
 */

static int do_readident(rheader,rot13)
  int rheader, rot13;
{
  char id[152];
  unsigned short id_len = 0;
  int g, i;
  $DESCRIPTOR(id_dsc,id);

  if (   (!(cli$get_value(c$dsc("IDENTIFIER"),&id_dsc,&id_len) & 1))
      && (!(get_input(&id_dsc,c$dsc("Message-ID: "),&id_len) & 1))) {
    return(0);
    }
  c$free_tmp();
  if (!id_len) return 0;
  id[id_len] = '\0';
  util_idcpy(id,id);

  if (get_itm_by_ident(id,&g,&i,0)) {
    st_h = rheader; st_tpu = tpucall; st_r13 = rot13; st_g = g; st_i = i;
    return(unwind_display(OUTER_LOOP,rgi));
    }
  err_line("Error: Read/Identifier - Message Identifier reference not located");
  return(0);
}

/*
 *  do_readmark
 *
 *  Read the next Marked item
 */

static char savtg[80] = "*";
static unsigned int     fg = 0;
static unsigned int     fm = 0;

static int do_readmark(rheader,rot13)
  int rheader, rot13;
{
  int i, unupd;
  unsigned int gm, g, m;
  unsigned short tag_len;
  char stg[80], *tag = 0;
  $DESCRIPTOR(tag_dsc,stg);

  if (   (cli$get_value(c$dsc("MARKER"),&tag_dsc,&tag_len) & 1)
      || (cli$get_value(c$dsc("NEWSITEM"),&tag_dsc,&tag_len) & 1)) {
    stg[tag_len] = '\0';
    lower_case(stg);
    if (strcmp(savtg,stg)) {
      strnncpy(savtg,sizeof(savtg), stg,tag_len, 0);
      fg = fm = 0;
      }
    tag = savtg;
    }
  else tag = savtg;

  c$free_tmp();
  if (!curr_g) g = m = 0;
  else g = ga[curr_g]->grp_num;
  if ((g) && ((curr_i <= 0) || (news_context < 2))) m = 0;
  else if (g) m = ga[curr_g]->grp_ia[curr_i].itm_num;

  for (;;) {
    if (!mark_find(g,m,tag,&g, &m)) {
      err_line("Info: Read/Mark - No more items with this tag");
      return(0);
      }
    if ((fg == g) && (fm == m)) {
      err_line("Info: Read/Mark - No more items with this tag");
      return(0);
      }
    if (!(gm = ga_locate(g)) || !ga[gm]->grp_count) continue;
    if (!ga[gm]->grp_ia) map_items(gm);
    if (!find_itm_by_num(gm,m,&i)) continue;
    break;
    }
  if (!fg) {
    fg = g;
    fm = m;
    }
  do_select("%",gm,2);
  cur_set_itm(curr_g,i);
  unupd = markasread(curr_g,curr_i,1);
  if (!do_display(rheader,tpucall,rot13)) displayfail(curr_g,curr_i,unupd);
  return(0);
}

/*
 *  do_readfollow
 *
 *  Read a followup posting (if any)
 */

int do_readfollow(rheader,rot13,new_qual)
  int rheader, rot13, new_qual;
{
  char title[SUBJLEN], tmp_title[SUBJLEN], *cp;
  int i, s_i;
  ITM_PTR iap;

  c$free_tmp();
  if (new_qual) dont_display = ((showdirs_val) ? curr_g : 0);
  if ((!curr_g) || (news_context < 2) || (curr_i < 1)) {
    if (new_qual) {
      dont_display = ((showdirs_val) ? -1 : 0);
      do_readnew(rheader,rot13,1);
      dont_display = 0;
      return(0);
      }
    else {
      err_line("Error: READ/Followup - No current item selected");
      return(0);
      }
    }
  iap = ga[curr_g]->grp_ia;
  if (new_qual && (iap[curr_i].itm_flags & NEWS_M_UNREAD)) {
    st_unupd = markasread(curr_g,curr_i,1);
    if (kill_filter(curr_g,curr_i)) {
      sprintf(err_oline,"Skipping next unread item - %.200s:%d - KILL filter",
          ga[curr_g]->grp_name,ga[curr_g]->grp_ia[curr_i].itm_num);
      err_line(err_oline);
      }
    else {
      st_h = rheader; st_tpu=tpucall; st_r13 = rot13;
      return(unwind_display(OUTER_LOOP,ddci));
      }
    }
  cp = iap[curr_i].itm_title;
  strnncpy(title,sizeof(title), cp,-1, 0);
  lower_case(title);
  cp = title;
  while (!strncmp(cp,"re:",3)) {
    cp += 3;
    while (*cp && isspace(*cp)) cp++;
    }
  if (!*cp) {
    if (new_qual) {
      do_readnew(rheader,rot13,1);
      return(0);
      }
    else {
      err_line("Error: READ/Followup - No current item selected");
      return(0);
      }
    }
  strip(cp,strlen(cp));
  if (strlen(cp) > 20) cp[20] = '\0';
  s_i = curr_i;
  for (i = curr_i + 1; i <= ga[curr_g]->grp_count; ++i) {
    strnncpy(tmp_title,sizeof(tmp_title), iap[i].itm_title,-1, 0);
    lower_case(tmp_title);
    if (   (   !new_qual
            || (   (iap[i].itm_flags & NEWS_M_UNREAD)
                && !(iap[i].itm_flags & NEWS_M_NOACCESS)))
        && substrcmp(tmp_title,cp)) {
      cur_set_itm(curr_g,i);
      st_unupd = markasread(curr_g,curr_i,1);
      if (new_qual && kill_filter(curr_g,curr_i)) {
        sprintf(err_oline,"Skipping next unread item - %.200s:%d - KILL filter",
          ga[curr_g]->grp_name,ga[curr_g]->grp_ia[curr_i].itm_num);
        err_line(err_oline);
        continue;
        }
      st_h = rheader; st_tpu=tpucall; st_r13 = rot13;
      return(unwind_display(OUTER_LOOP,ddci));
      }
    }
  if (new_qual) {
    do_readnew(rheader,rot13,1);
    return(0);
    }
  err_line("READ/Followup: No followup items posted");
  return(0);
}

int do_readprevstream(rheader,rot13)
  int rheader, rot13;
{
  char title[SUBJLEN], tmp_title[SUBJLEN], *cp;
  int i, s_i;
  ITM_PTR iap;

  c$free_tmp();
  if ((!curr_g) || (news_context < 2) || (curr_i < 1)) {
    err_line("Error: READ/Parent - No current item selected");
    return(0);
    }
  iap = ga[curr_g]->grp_ia;
  strnncpy(title,sizeof(title), iap[curr_i].itm_title,-1, 0);
  lower_case(title);
  cp = title;
  while (!strncmp(cp,"re:",3)) {
    cp += 3;
    while (*cp && isspace(*cp)) cp++;
    }
  if (!*cp) {
    err_line("Error: READ/Parent - No current item selected");
    return(0);
    }
  strip(cp,strlen(cp));
  if (strlen(cp) > 20) cp[20] = '\0';
  s_i = curr_i;
  for (i = s_i - 1; i > 0; --i) {
    strnncpy(tmp_title,sizeof(tmp_title), iap[i].itm_title,-1, 0);
    lower_case(tmp_title);
    if (!(iap[i].itm_flags & NEWS_M_NOACCESS) && substrcmp(tmp_title,cp)) {
      cur_set_itm(curr_g,i);
      st_unupd = markasread(curr_g,curr_i,1);
      st_h = rheader; st_tpu=tpucall; st_r13 = rot13;
      return(unwind_display(OUTER_LOOP,ddci));
      }
    }
  do_readparent(rheader,rot13);
  return(0);
}

/*
 *  do_readprev
 *
 *  Pop the read stack
 */

int do_readprev(rheader,rot13)
    int rheader,
        rot13;
{
    int ng, ni, unupd;

    do {
      if (!rpop(&ng,&ni)) {
        err_line("Error: Read/Prev - Prev stack is empty");
        return 0;
        }
      } while ((ng == curr_g) && (ni == curr_i));
    do_select("%",ng,2);
    cur_set_itm(curr_g,ni);
    unupd = markasread(curr_g,curr_i,1);
    if (!do_display(rheader,tpucall,rot13)) displayfail(curr_g,curr_i,unupd);
    return 0;
}

/*
 *  do_read
 *
 *  read command processing
 */

int readtitle(h,r13,str,new_qual)
  int h, r13, new_qual;
  char *str;
{
  char *w_str,
       c_str[SUBJLEN];
  int i, unupd;
  ITM_PTR iap;

  c$free_tmp();
  if ((!curr_g) || (news_context < 2) || (curr_i < 1)) {
    err_line("Error: Read - No Newsgroup selected");
    return(0);
    }

  iap = ga[curr_g]->grp_ia;
  lower_case(str);
  w_str = (char *) news_malloc(strlen(str)+3);
  strcpy(w_str,"*");
  strcat(w_str,str);
  strcat(w_str,"*");
  for (i = curr_i+1; i <= ga[curr_g]->grp_count; ++i) {
    if (   !new_qual
        || (   (iap[i].itm_flags & NEWS_M_UNREAD)
            && !(iap[i].itm_flags & NEWS_M_NOACCESS))) {
      strnncpy(c_str,sizeof(c_str), iap[i].itm_title,-1, 0);
      lower_case(c_str);
      if (wild_match(c_str,w_str)) break;
      }
    }
  news_free(w_str);
  if (i <= ga[curr_g]->grp_count) {
    cur_set_itm(curr_g,i);
    unupd = markasread(curr_g,curr_i,1);
    if (!do_display(h,tpucall,r13)) displayfail(curr_g,curr_i,unupd);
    }
  else {
    sprintf(err_oline,"Subject: %.200s - not located",str);
    err_line(err_oline);
    }
  return 0;
}

static int dread()
{
  static char subj[128];
  char item[128];
  $DESCRIPTOR(item_dsc,item);
  unsigned short item_len;
  int h = 0,
      rot13 = 0,
      unupd;

  tpucall = 0;
  if (cli$present(c$dsc("HEADER")) & 1) h = 1;
  if (cli$present(c$dsc("TPU")) & 1) tpucall = 1;
	/* JLH Stop READ EDITOR working within /CAPTIVE */
  if (!news_captive && (cli$present(c$dsc("EDITOR")) & 1)) tpucall = 1;
  else if (cli$present(c$dsc("ROT13")) & 1) rot13 = 1;
  c$free_tmp();
  if (cli$present(c$dsc("FOLLOWUP")) & 1) {
    do_readfollow(h,rot13,(cli$present(c$dsc("NEW")) & 1));
    return(0);
    }
  c$free_tmp();

  if (cli$present(c$dsc("SUBJECT")) & 1) {
    if (   (cli$get_value(c$dsc("SUBJECT"),&item_dsc,&item_len) & 1)
        || (get_input_dflt(&item_dsc,c$dsc("Read/Subject="),&item_len,
			   c$dsc(subj),0) & 1)) {
      c$free_tmp();
      strnncpy(subj,sizeof(subj), item,item_len, 0);
      if (item_len) readtitle(h,rot13,subj,(cli$present(c$dsc("NEW")) & 1));
      }
    return(0);
    }
  c$free_tmp();

  if (cli$present(c$dsc("TITLE")) & 1) {
    if (   (cli$get_value(c$dsc("TITLE"),&item_dsc,&item_len) & 1)
        && (get_input_dflt(&item_dsc,c$dsc("Read/Title="),&item_len,
			   c$dsc(subj),0) & 1)) {
      c$free_tmp();
      strnncpy(subj,sizeof(subj), item,item_len, 0);
      if (item_len) readtitle(h,rot13,subj,(cli$present(c$dsc("NEW")) & 1));
      }
    return(0);
    }
  c$free_tmp();

  if (cli$present(c$dsc("NEW")) & 1) {
    c$free_tmp();
    do_readnew(h,rot13,0);
    return(0);
    }
  c$free_tmp();

  if (cli$present(c$dsc("NEXT")) & 1) {
    c$free_tmp();
    do_readnext(h,rot13,tpucall);
    return(0);
    }
  c$free_tmp();

  if (cli$present(c$dsc("REFS")) & 1) {
    c$free_tmp();
    do_readparent(h,rot13);
    return(0);
    }
  c$free_tmp();

  if (cli$present(c$dsc("PARENT")) & 1) {
    c$free_tmp();
    do_readprevstream(h,rot13);
    return(0);
    }
  c$free_tmp();

  if (cli$present(c$dsc("TOPIC")) & 1) {
    c$free_tmp();
    do_readancestor(h,rot13,tpucall);
    return(0);
    }
  c$free_tmp();

  if (cli$present(c$dsc("IDENTIFIER")) & 1) {
    c$free_tmp();
    do_readident(h,rot13);
    return(0);
    }
  c$free_tmp();

  if (cli$present(c$dsc("MARKER")) & 1) {
    c$free_tmp();
    do_readmark(h,rot13);
    return(0);
    }
  c$free_tmp();

  if (cli$present(c$dsc("PREV")) & 1) {
    c$free_tmp();
    do_readprev(h,rot13);
    return(0);
    }
  c$free_tmp();

  if ((cli$present(c$dsc("LAST")) & 1)
      || (cli$present(c$dsc("BACK")) & 1)) {
    c$free_tmp();
    if ((news_context > 1) && curr_g && (curr_i > 0)) {
      if (cur_up_itm(curr_g,1,0) == 1) {
        unupd = markasread(curr_g,curr_i,1);
        if (!do_display(h,tpucall,rot13)) displayfail(curr_g,curr_i,unupd);
        }
      }
    return(0);
    }
  c$free_tmp();

  if (cli$get_value(c$dsc("NEWSITEM"),&item_dsc,&item_len) != CLI$_ABSENT) {
    c$free_tmp();
    if (curr_g) {
      item[item_len] = '\0';
      do_readfind(item,h,rot13);
      }
    }
  else if ((curr_g) && (curr_i > 0) && (news_context > 1)) {
    unupd = markasread(curr_g,curr_i,1);
    if (!do_display(h,tpucall,rot13)) displayfail(curr_g,curr_i,unupd);
    }
  c$free_tmp();

  return(0);
}

int do_read()
{
  return(unwind_display(OUTER_LOOP,dread));
}

/*
 *  do_unread
 *
 *  Mark an item as unread
 */

static int dunread()
{
  int i;

  if (cli$present(c$dsc("ALL")) & 1) {
    if (!curr_g) return(err_line("Error: Unread/All - No current newsgroup selected"),0);
    if (!ga[curr_g]->grp_count) return(err_line("Error: Unread/All - Newsgroup is empty"),0);
    if (!ga[curr_g]->grp_ia) map_items(curr_g);
    for (i = 1; i <= ga[curr_g]->grp_count; ++i)
      if (!(ga[curr_g]->grp_ia[i].itm_flags & NEWS_M_UNREAD)) {
        ++ga[curr_g]->grp_unread;
        ga[curr_g]->grp_ia[i].itm_flags |= NEWS_M_UNREAD;
        screen_update_iread(curr_g,i);
        }
    ga[curr_g]->grp_funread = ga[curr_g]->grp_firstnum;
    ga[curr_g]->grp_topreadnum = ga[curr_g]->grp_funread - 1;
    screen_update_gread(curr_g);
    return(0);
    }

  if ((!curr_g) || (news_context < 2) || (curr_i < 1))
    return(err_line("Error: Unread - No current item selected"),0);
  markasunread(curr_g,curr_i,1);
  return(0);
}

int
do_unread()
{
  return(unwind_display(I_DISPLAY_LOOP,dunread));
}

static int dnn()
{
  if (cur_down_itm(curr_g,1,0) == 1) {
    st_unupd = markasread(curr_g,curr_i,1);
    ddci();
    return(0);
    }
  else err_line("NEXT: No next item");
  return 0;
}

int do_next_note()
{
  int h = 0, rot13 = 0;

  tpucall = 0;
  if (cli$present(c$dsc("HEADER")) & 1) h = 1;
  if (cli$present(c$dsc("TPU")) & 1) tpucall = 1;
	/* JLH Stop READ EDITOR working within /CAPTIVE */
  if (!news_captive && (cli$present(c$dsc("EDITOR")) & 1)) tpucall = 1;
  else if (cli$present(c$dsc("ROT13")) & 1) rot13 = 1;
  if ((news_context > 1) && curr_g && (curr_i > 0)) {
    if (curr_i < ga[curr_g]->grp_count) {
      st_h = h; st_tpu = tpucall; st_r13 = rot13;
      return(unwind_display(OUTER_LOOP,dnn));
      }
    err_line("NEXT: No next item");
    }
  else err_line("NEXT: No newsgroup open");
  return(0);
}

int do_next_reply()
{
  int h = 0, rot13 = 0;

  tpucall = 0;
  if (cli$present(c$dsc("HEADER")) & 1) h = 1;
  if (cli$present(c$dsc("TPU")) & 1) tpucall = 1;
	/* JLH Stop READ EDITOR working within /CAPTIVE */
  if (!news_captive && (cli$present(c$dsc("EDITOR")) & 1)) tpucall = 1;
  else if (cli$present(c$dsc("ROT13")) & 1) rot13 = 1;
  if ((news_context > 1) && curr_g && (curr_i > 0))
    return(do_readfollow(h,rot13,0),0);
  else err_line("NEXT: No newsgroup open");
  return 0;
}

int
do_next_unseen()
{
  int h = 0, rot13 = 0;

  tpucall = 0;
  if (cli$present(c$dsc("HEADER")) & 1) h = 1;
  if (cli$present(c$dsc("TPU")) & 1) tpucall = 1;
	/* JLH Stop READ EDITOR working within /CAPTIVE */
  if (!news_captive && (cli$present(c$dsc("EDITOR")) & 1)) tpucall = 1;
  else if (cli$present(c$dsc("ROT13")) & 1) rot13 = 1;
  return(do_readnew(h,rot13,0),0);
}

static int dbn()
{
  if (cur_up_itm(curr_g,1,0) == 1) {
    st_unupd = markasread(curr_g,curr_i,1);
    ddci();
    return(0);
    }
  err_line("BACK: No previous item");
  return 0;
}

int do_back_note()
{
  int h = 0, rot13 = 0;

  tpucall = 0;
  if (cli$present(c$dsc("HEADER")) & 1) h = 1;
  if (cli$present(c$dsc("TPU")) & 1) tpucall = 1;
	/* JLH Stop READ EDITOR working within /CAPTIVE */
  if (!news_captive && (cli$present(c$dsc("EDITOR")) & 1)) tpucall = 1;
  else if (cli$present(c$dsc("ROT13")) & 1) rot13 = 1;
  if ((news_context > 1) && curr_g && (curr_i > 0)) {
    if (curr_i > 1) {
      st_h = h; st_tpu = tpucall; st_r13 = rot13;
      return(unwind_display(OUTER_LOOP,dbn));
      }
    err_line("BACK: No previous item");
    }
  else err_line("BACK: No newsgroup open");
  return(0);
}

int do_back_reply()
{
  int h = 0, rot13 = 0;

  tpucall = 0;
  if (cli$present(c$dsc("HEADER")) & 1) h = 1;
  if (cli$present(c$dsc("TPU")) & 1) tpucall = 1;
	/* JLH Stop READ EDITOR working within /CAPTIVE */
  if (!news_captive && (cli$present(c$dsc("EDITOR")) & 1)) tpucall = 1;
  else if (cli$present(c$dsc("ROT13")) & 1) rot13 = 1;
  if ((news_context > 1) && curr_g && (curr_i > 0))
    return(do_readparent(h,rot13),0);
  else err_line("BACK: No newsgroup open");
  return(0);
}

static int dtopic()
{
  int h = 0, rot13 = 0;

  tpucall = 0;
  if (cli$present(c$dsc("HEADER")) & 1) h = 1;
  if (cli$present(c$dsc("TPU")) & 1) tpucall = 1;
	/* JLH Stop READ EDITOR working within /CAPTIVE */
  if (!news_captive && (cli$present(c$dsc("EDITOR")) & 1)) tpucall = 1;
  else if (cli$present(c$dsc("ROT13")) & 1) rot13 = 1;
  if ((news_context > 1) && curr_g && (curr_i > 0))
    return(do_readancestor(h,rot13,tpucall),0);
  else err_line("TOPIC: No newsgroup open");
  return(0);
}

int do_topic()
{
  return(unwind_display(OUTER_LOOP,dtopic));
}

static int dlst()
{
  int h = 0, rot13 = 0, unupd;

  do_bottom();
  if (curr_g && (news_context == 1)) {
    set_level(2);
    return(0);
    }
  if (!curr_g || (curr_i <= 0)) return(0);
  tpucall = 0;
  if (cli$present(c$dsc("HEADER")) & 1) h = 1;
  if (cli$present(c$dsc("TPU")) & 1) tpucall = 1;
	/* JLH Stop READ EDITOR working within /CAPTIVE */
  if (!news_captive && (cli$present(c$dsc("EDITOR")) & 1)) tpucall = 1;
  else if (cli$present(c$dsc("ROT13")) & 1) rot13 = 1;
  unupd = markasread(curr_g,curr_i,1);
  if (!do_display(h,tpucall,rot13)) displayfail(curr_g,curr_i,unupd);
  return(0);
}

int do_last()
{
  return(unwind_display(OUTER_LOOP,dlst));
}
