text: #if !defined(lint) && !defined(DOS)
  static char rcsid[] = "$Id: sequence.c 1012 2008-03-26 00:44:22Z hubert@u.washington.edu $";
  #endif
  
  /*
   * ========================================================================
   * Copyright 2013-2020 Eduardo Chappa
   * Copyright 2006-2007 University of Washington
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * ========================================================================
   */
  
  #include "../pith/headers.h"
  #include "../pith/sequence.h"
  #include "../pith/state.h"
  #include "../pith/conf.h"
  #include "../pith/msgno.h"
  #include "../pith/flag.h"
  #include "../pith/thread.h"
  #include "../pith/icache.h"
  
  
  /*
   * Internal prototypes
   */
  
  
  /*----------------------------------------------------------------------
    Build comma delimited list of selected messages
  
    Args: stream -- mail stream to use for flag testing
  	msgmap -- message number struct of to build selected messages in
  	count -- pointer to place to write number of comma delimited
  	mark -- mark manually undeleted so we don't refilter right away
  
    Returns: malloc'd string containing sequence, else NULL if
  	   no messages in msgmap with local "selected" flag.
    ----*/
  char *
  selected_sequence(MAILSTREAM *stream, MSGNO_S *msgmap, long int *count, int mark)
  {
      long  i;
      MESSAGECACHE *mc;
  
      if(!stream)
        return(NULL);
  
      /*
       * The plan here is to use the c-client elt's "sequence" bit
       * to work around any orderings or exclusions in pine's internal
       * mapping that might cause the sequence to be artificially
       * lengthy.  It's probably cheaper to run down the elt list
       * twice rather than call nm_raw2m() for each message as
       * we run down the elt list once...
       */
      for(i = 1L; i <= stream->nmsgs; i++)
        if((mc = mail_elt(stream, i)) != NULL)
          mc->sequence = 0;
  
      for(i = 1L; i <= mn_get_total(msgmap); i++)
        if(get_lflag(stream, msgmap, i, MN_SLCT)){
  	  long rawno;
  	  int  exbits = 0;
  
  	  /*
  	   * Forget we knew about it, and set "add to sequence"
  	   * bit...
  	   */
  	  clear_index_cache_ent(stream, i, 0);
  	  rawno = mn_m2raw(msgmap, i);
  	  if(rawno > 0L && rawno <= stream->nmsgs
  	     && (mc = mail_elt(stream, rawno)))
  	    mc->sequence = 1;
  
  	  /*
  	   * Mark this message manually flagged so we don't re-filter it
  	   * with a filter which only sets flags.
  	   */
  	  if(mark){
  	      if(msgno_exceptions(stream, rawno, "0", &exbits, FALSE))
  		exbits |= MSG_EX_MANUNDEL;
  	      else
  		exbits = MSG_EX_MANUNDEL;
  
  	      msgno_exceptions(stream, rawno, "0", &exbits, TRUE);
  	  }
        }
  
      return(build_sequence(stream, NULL, count));
  }
  
  
  /*----------------------------------------------------------------------
    Build comma delimited list of messages current in msgmap which have all
    flags matching the arguments
  
    Args: stream -- mail stream to use for flag testing
  	msgmap -- only consider messages selected in this msgmap
  	  flag -- flags to match against
  	 count -- pointer to place to return number of comma delimited
  	  mark -- mark index cache entry changed, and count state change
  	 kw_on -- if flag contains F_KEYWORD, this is
  	            the array of keywords to be checked
  	kw_off -- if flag contains F_UNKEYWORD, this is
  	            the array of keywords to be checked
  
    Returns: malloc'd string containing sequence, else NULL if
  	   no messages in msgmap with local "selected" flag (a flag
  	   of zero means all current msgs).
    ----*/
  char *
  currentf_sequence(MAILSTREAM *stream, MSGNO_S *msgmap, long int flag,
  		  long int *count, int mark, char **kw_on, char **kw_off)
  {
      char	 *seq, **t;
      long	  i, rawno;
      int           exbits;
      MESSAGECACHE *mc;
  
      if(!stream)
        return(NULL);
  
      /* First, make sure elts are valid for all the interesting messages */
      if((seq = invalid_elt_sequence(stream, msgmap)) != NULL){
  	pine_mail_fetch_flags(stream, seq, NIL);
  	fs_give((void **) &seq);
      }
  
      for(i = 1L; i <= stream->nmsgs; i++)
        if((mc = mail_elt(stream, i)) != NULL)
          mc->sequence = 0;			/* clear "sequence" bits */
  
      for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap)){
  	/* if not already set, go on... */
  	rawno = mn_m2raw(msgmap, i);
  	mc = (rawno > 0L && rawno <= stream->nmsgs)
  		? mail_elt(stream, rawno) : NULL;
  	if(!mc)
  	  continue;
  
  	if((flag == 0)
  	   || ((flag & F_DEL) && mc->deleted)
  	   || ((flag & F_UNDEL) && !mc->deleted)
  	   || ((flag & F_SEEN) && mc->seen)
  	   || ((flag & F_UNSEEN) && !mc->seen)
  	   || ((flag & F_ANS) && mc->answered)
  	   || ((flag & F_UNANS) && !mc->answered)
  	   || ((flag & F_FLAG) && mc->flagged)
  	   || ((flag & F_UNFLAG) && !mc->flagged)){
  	    mc->sequence = 1;			/* set "sequence" flag */
  	}
  
  	/* check for forwarded status */
  	if(!mc->sequence
  	   && (flag & F_FWD)
  	   && user_flag_is_set(stream, rawno, FORWARDED_FLAG)){
  	    mc->sequence = 1;
  	}
  
  	if(!mc->sequence
  	   && (flag & F_UNFWD)
  	   && !user_flag_is_set(stream, rawno, FORWARDED_FLAG)){
  	    mc->sequence = 1;
  	}
  
  	/* check for user keywords or not */
  	if(!mc->sequence && flag & F_KEYWORD && kw_on){
  	    for(t = kw_on; !mc->sequence && *t; t++)
  	      if(user_flag_is_set(stream, rawno, *t))
  		mc->sequence = 1;
  	}
  	else if(!mc->sequence && flag & F_UNKEYWORD && kw_off){
  	    for(t = kw_off; !mc->sequence && *t; t++)
  	      if(!user_flag_is_set(stream, rawno, *t))
  		mc->sequence = 1;
  	}
  	
  	if(mc->sequence){
  	    if(mark){
  		if(THRD_INDX()){
  		    PINETHRD_S *thrd;
  		    long        t;
  
  		    /* clear thread index line instead of index index line */
  		    thrd = fetch_thread(stream, mn_m2raw(msgmap, i));
  		    if(thrd && thrd->top
  		       && (thrd=fetch_thread(stream,thrd->top))
  		       && (t = mn_raw2m(msgmap, thrd->rawno)))
  		      clear_index_cache_ent(stream, t, 0);
  		}
  		else
  		  clear_index_cache_ent(stream, i, 0);/* force new index line */
  
  		/*
  		 * Mark this message manually flagged so we don't re-filter it
  		 * with a filter which only sets flags.
  		 */
  		exbits = 0;
  		if(msgno_exceptions(stream, rawno, "0", &exbits, FALSE))
  		  exbits |= MSG_EX_MANUNDEL;
  		else
  		  exbits = MSG_EX_MANUNDEL;
  
  		msgno_exceptions(stream, rawno, "0", &exbits, TRUE);
  	    }
  	}
      }
  
      return(build_sequence(stream, NULL, count));
  }
  
  
  /*----------------------------------------------------------------------
    Return sequence numbers of messages with invalid MESSAGECACHEs
  
    Args: stream -- mail stream to use for flag testing
  	msgmap -- message number struct of to build selected messages in
  
    Returns: malloc'd string containing sequence, else NULL if
  	   no messages in msgmap with local "selected" flag (a flag
  	   of zero means all current msgs).
    ----*/
  char *
  invalid_elt_sequence(MAILSTREAM *stream, MSGNO_S *msgmap)
  {
      long	  i, rawno;
      MESSAGECACHE *mc;
  
      if(!stream)
        return(NULL);
  
      for(i = 1L; i <= stream->nmsgs; i++)
        if((mc = mail_elt(stream, i)) != NULL)
          mc->sequence = 0;			/* clear "sequence" bits */
  
      for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap))
        if((rawno = mn_m2raw(msgmap, i)) > 0L && rawno <= stream->nmsgs
  	 && (mc = mail_elt(stream, rawno)) && !mc->valid)
  	mc->sequence = 1;
  
      return(build_sequence(stream, NULL, NULL));
  }
  
  
  /*----------------------------------------------------------------------
    Build comma delimited list of messages with elt "sequence" bit set
  
    Args: stream -- mail stream to use for flag testing
  	msgmap -- struct containing sort to build sequence in
  	count -- pointer to place to write number of comma delimited
  		 NOTE: if non-zero, it's a clue as to how many messages
  		       have the sequence bit lit.
  
    Returns: malloc'd string containing sequence, else NULL if
  	   no messages in msgmap with elt's "sequence" bit set
    ----*/
  char *
  build_sequence(MAILSTREAM *stream, MSGNO_S *msgmap, long int *count)
  {
  #define	SEQ_INCREMENT	128
      long    n = 0L, i, x, lastn = 0L, runstart = 0L;
      size_t  size = SEQ_INCREMENT;
      char   *seq = NULL, *p;
      MESSAGECACHE *mc;
  
      if(!stream)
        return(NULL);
  
      if(count){
  	if(*count > 0L)
  	  size = MAX(size, MIN((*count) * 4, 16384));
  
  	*count = 0L;
      }
  
      for(x = 1L; x <= stream->nmsgs; x++){
  	if(msgmap){
  	    if((i = mn_m2raw(msgmap, x)) == 0L)
  	      continue;
  	}
  	else
  	  i = x;
  
  	if(i > 0L && i <= stream->nmsgs
  	   && (mc = mail_elt(stream, i)) && mc->sequence){
  	    n++;
  	    if(!seq)				/* initialize if needed */
  	      seq = p = fs_get(size);
  
  	    /*
  	     * This code will coalesce the ascending runs of
  	     * sequence numbers, but fails to break sequences
  	     * into a reasonably sensible length for imapd's to
  	     * swallow (reasonable addtition to c-client?)...
  	     */
  	    if(lastn){				/* if may be in a run */
  		if(lastn + 1L == i){		/* and its the next raw num */
  		    lastn = i;			/* skip writing anything... */
  		    continue;
  		}
  		else if(runstart != lastn){
  		    *p++ = (runstart + 1L == lastn) ? ',' : ':';
  		    sstrncpy(&p, long2string(lastn), size-(p-seq));
  		}				/* wrote end of run */
  	    }
  
  	    runstart = lastn = i;		/* remember last raw num */
  
  	    if(n > 1L && (p-seq) < size)			/* !first num, write delim */
  	      *p++ = ',';
  
  	    if(size - (p - seq) < 16){	/* room for two more nums? */
  		size_t offset = p - seq;	/* grow the sequence array */
  		size += SEQ_INCREMENT;
  		fs_resize((void **)&seq, size);
  		p = seq + offset;
  	    }
  
  	    sstrncpy(&p, long2string(i), size-(p-seq));	/* write raw number */
  	}
      }
  
      if(lastn && runstart != lastn){		/* were in a run? */
  	if((p-seq) < size)			/* !first num, write delim */
  	  *p++ = (runstart + 1L == lastn) ? ',' : ':';
  
  	sstrncpy(&p, long2string(lastn), size-(p-seq));	/* write the trailing num */
      }
  
      if(seq && (p-seq) < size)				/* if sequence, tie it off */
        *p  = '\0';
  
      if(seq)
        seq[size-1] = '\0';
  
      if(count)
        *count = n;
  
      return(seq);
  }
  
  
  /*----------------------------------------------------------------------
    If any messages flagged "selected", fake the "currently selected" array
  
    Args: map -- message number struct of to build selected messages in
  
    OK folks, here's the tradeoff: either all the functions have to
    know if the user wants to deal with the "current" hilited message
    or the list of currently "selected" messages, *or* we just
    wrap the call to these functions with some glue that tweeks
    what these functions see as the "current" message list, and let them
    do their thing.
    ----*/
  int
  pseudo_selected(MAILSTREAM *stream, MSGNO_S *map)
  {
      long i, later = 0L;
  
      if(any_lflagged(map, MN_SLCT)){
  	map->hilited = mn_m2raw(map, mn_get_cur(map));
  
  	for(i = 1L; i <= mn_get_total(map); i++)
  	  if(get_lflag(stream, map, i, MN_SLCT)){
  	      if(!later++){
  		  mn_set_cur(map, i);
  	      }
  	      else{
  		  mn_add_cur(map, i);
  	      }
  	  }
  
  	return(1);
      }
  
      return(0);
  }
  
  
  /*----------------------------------------------------------------------
    Antidote for the monkey business committed above
  
    Args: map -- message number struct of to build selected messages in
  
    ----*/
  void
  restore_selected(MSGNO_S *map)
  {
      if(map->hilited){
  	mn_reset_cur(map, mn_raw2m(map, map->hilited));
  	map->hilited = 0L;
      }
  }
  
  
  /*
   * Return a search set which can be used to limit the search to a smaller set,
   * for performance reasons. The caller must still work correctly if we return
   * the whole set (or NULL) here, because we may not be able to send the full
   * search set over IMAP. In cases where the search set description is getting
   * too large we send a search set which contains all of the relevant messages.
   * It may contain more.
   *
   * Args    stream
   *         narrow  -- If set, we are narrowing our selection (restrict to
   *                      messages with MN_SLCT already set) or if not set,
   *                      we are broadening (so we may look only at messages
   *                      with MN_SLCT not set)
   *
   * Returns - allocated search set or NULL. Caller is responsible for freeing it.
   */
  SEARCHSET *
  limiting_searchset(MAILSTREAM *stream, int narrow)
  {
      long       n, run;
      int        cnt = 0;
      SEARCHSET *full_set = NULL, **set;
  
      /*
       * If we're talking to anything other than a server older than
       * imap 4rev1, build a searchset otherwise it'll choke.
       */
      if(!(is_imap_stream(stream) && !modern_imap_stream(stream))){
  	for(n = 1L, set = &full_set, run = 0L; n <= stream->nmsgs; n++)
  	  /* test for end of run */
  	  if(get_lflag(stream, NULL, n, MN_EXLD)
  	     || (narrow && !get_lflag(stream, NULL, n, MN_SLCT))
  	     || (!narrow && get_lflag(stream, NULL, n, MN_SLCT))){
  	      if(run){		/* previous selected? */
  		  set = &(*set)->next;
  		  run = 0L;
  	      }
  	  }
  	  else if(run++){		/* next in run */
  	      (*set)->last = n;
  	  }
  	  else{				/* start of run */
  	      *set = mail_newsearchset();
  	      (*set)->first = (*set)->last = n;
  
  	      /*
  	       * Make this last set cover the rest of the messages.
  	       * We could be fancier about this but it probably isn't
  	       * worth the trouble.
  	       */
  	      if(++cnt > 100){
  		  (*set)->last = stream->nmsgs;
  		  break;
  	      }
  	  }
      }
  
      return(full_set);
  }
  