/*----------------------------------------------------------------------

            T H E    P I N E    M A I L   S Y S T E M

   Laurence Lundblade and Mike Seibel
   Networks and Distributed Computing
   Computing and Communications
   University of Washington
   Administration Building, AG-44
   Seattle, Washington, 98195, USA
   Internet: lgl@CAC.Washington.EDU
             mikes@CAC.Washington.EDU

   Please address all bugs and comments to "pine-bugs@cac.washington.edu"

   Copyright 1989, 1990, 1991, 1992  University of Washington

    Permission to use, copy, modify, and distribute this software and its
   documentation for any purpose and without fee to the University of
   Washington is hereby granted, provided that the above copyright notice
   appears in all copies and that both the above copyright notice and this
   permission notice appear in supporting documentation, and that the name
   of the University of Washington not be used in advertising or publicity
   pertaining to distribution of the software without specific, written
   prior permission.  This software is made available "as is", and
   THE UNIVERSITY OF WASHINGTON DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED,
   WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT LIMITATION ALL IMPLIED
   WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND IN
   NO EVENT SHALL THE UNIVERSITY OF WASHINGTON BE LIABLE FOR ANY SPECIAL,
   INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
   LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, TORT
   (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF OR IN CONNECTION
   WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  

   Pine is in part based on The Elm Mail System:
    ***********************************************************************
    *  The Elm Mail System  -  $Revision: 2.13 $   $State: Exp $          *
    *                                                                     *
    * 			Copyright (c) 1986, 1987 Dave Taylor              *
    * 			Copyright (c) 1988, 1989 USENET Community Trust   *
    ***********************************************************************
 

  ----------------------------------------------------------------------*/
 

/*======================================================================
     mailview.c
     Implements the mailview screen
     Also includes scrolltool used to display help text
  ====*/


#include "headers.h"



#define TYPICAL_BIG_MESSAGE_LINES 200 

#ifdef ANSI
void  set_scroll_text(char **, char *, char *, char **,char **, int, int, int);
static int print_help(char *, char **);
void       describe_mime(BODY *, char *, int, int);
void       format_mime_size(char *, BODY *);
void       zero_atmts(struct attachment *);
char      *show_multipart(MESSAGECACHE *, int);
MimeShow   mime_show(BODY *);
char      *format_envelope(ENVELOPE *);
char      *part_desc(char *,BODY *, int);
void       copy_text(char *, char *, char *, char *, int);
char      *display_parameters(PARAMETER *);


#else
void set_scroll_text();
void       scroll_scroll_text();
static int print_help();
int        search_scroll_text();
void       describe_mime();
char      *type_desc();
void       format_mime_size();
void       zero_atmts();
char      *show_multipart();
MimeShow   mime_show();
char      *format_envelope();
char      *part_desc();
void       copy_text();
char      *display_parameters();
#endif




/*----------------------------------------------------------------------
     Format a buffer with the text of the current message for browser

    Args: The pine_state structure
  
  Result: The scrolltool is called to display the message

  Loop here viewing mail until the folder changed or a command takes
us to another screen. Inside the loop the message text is fetched and
formatted into a buffer allocated for it.  These are passed to the
scrolltool(), that displays the message and executes commands. It
returns when it's times to display a different message when a we
change folders, or when it's time for a different screen. We also
break out fo the big loop when there is no more mail to read.
  ---*/

void
mail_view_screen(pine_state)
     struct pine *pine_state;
{
    char         *m_text, *h_text, *tmp_text, last_was_full_header;
    long          last_message_viewed;
    MESSAGECACHE *mc;


    dprint(1, (debugfile, "\n\n  -----  MAIL VIEW  -----\n"));

    if(pine_state->max_msgno <= 0 || pine_state->mail_stream == NULL) {
        q_status_message(0, 1, 3, "No mail to read!");
        pine_state->next_screen = pine_state->prev_screen;
	return;
    }
    if(pine_state->current_sorted_msgno < 0L)
      pine_state->current_sorted_msgno = 1L;
   
    last_message_viewed = -1;

    /*------ Loop viewing messages ------------------*/
    do {
        clear_index_cache_ent(pine_state->current_sorted_msgno);

        /* BUG, should check this return code */
        if(!pine_state->full_header) {
            (void)mail_fetchenvelope(pine_state->mail_stream,
                        pine_state->sort[pine_state->current_sorted_msgno]);

            mc = mail_elt(pine_state->mail_stream,
                          pine_state->sort[pine_state->current_sorted_msgno]);

            m_text = format_message(mc,
                      last_message_viewed != ps_global->current_sorted_msgno ||
                                    last_was_full_header == 1, 0);
        } else {
            h_text = mail_fetchheader(pine_state->mail_stream,
                          pine_state->sort[pine_state->current_sorted_msgno]);
            h_text = cpystr(h_text);

            tmp_text = mail_fetchtext(pine_state->mail_stream,
                          pine_state->sort[pine_state->current_sorted_msgno]);

            m_text = fs_get(strlen(tmp_text) + strlen(h_text) + 10);
            strcpy(m_text, h_text);
            strcat(m_text, "\n");
            strcat(m_text, tmp_text);
            fs_give((void **)&h_text);
        }

        last_message_viewed  = ps_global->current_sorted_msgno;
        last_was_full_header = ps_global->full_header;

        pine_state->next_screen = SCREEN_FUN_NULL;

        scrolltool(NULL, m_text, NULL, NULL, NULL,
                   ps_global->nr_mode ? "VIEW NEWS" : "VIEW MAIL",
                   (int *)NULL, MessageText);

        fs_give((void **)&m_text);

        if(pine_state->max_msgno == 0) {
            q_status_message(0, 2,3, "\007No mail to read!");
            if(pine_state->next_screen == SCREEN_FUN_NULL)
              pine_state->next_screen = pine_state->prev_screen;
            pine_state->prev_screen = mail_view_screen;
            return;
        }
    }
    while(pine_state->next_screen == SCREEN_FUN_NULL);

    pine_state->prev_screen = mail_view_screen;
    return;
}

    


/*----------------------------------------------------------------------
    Add lines to the attachments structure
    
  Args: body   -- body of the part being described
        prefix -- The prefix for numbering the parts
        num    -- The number of this specific part
        should_show -- Flag indicating which of alternate parts should be shown

Result: The ps_global->attachments data structure is filled in. This
is called recursively to descend through all the parts of a message. 
The description strings filled in are malloced and should be freed.

  ----*/
void
describe_mime(body, prefix, num, should_show)
     BODY *body;
     char *prefix;
     int   num, should_show;
{
    PART              *part;
    char               numx[50], string[200];
    int                n, alt_to_show;
    struct attachment *a;
    MimeShow           alts[50]; /* BUG -- no bounds checking on this */

    if(body == NULL)
      return;

    if(body->type == TYPEMULTIPART) {
        if(strucmp(body->subtype, "alternative") == 0) {
            /*---- Figure out which alternative part to display ---*/
            for(part=body->contents.part,n=1; part!=NULL; part=part->next, n++)
              alts[n] = mime_show(&(part->body));
            n--;
            while(n > 1 && alts[n] == ShowNone)
              n--;
            alt_to_show = n;
        } else {
            /*--- Not alternative parts, display them all ----*/
            alt_to_show = 0;
        }
        n = 1;
        for(part = body->contents.part; part != NULL; part = part->next, n++) {
            sprintf(numx, "%s%d.", prefix, n);
            describe_mime(&(part->body),
                          part->body.type == TYPEMULTIPART ? numx : prefix,
                          n,
                          n == alt_to_show || alt_to_show == 0 ? 1 : 0);
        }
    } else {
        for(a = ps_global->atmts; a->description != NULL; a++);
        if(a - ps_global->atmts + 1 >= ps_global->atmts_allocated) {
            ps_global->atmts_allocated *=2;
            fs_resize((void **)&(ps_global->atmts),
                       ps_global->atmts_allocated * sizeof(struct attachment));
            a = &ps_global->atmts[ps_global->atmts_allocated/2 - 1];
        }

        format_mime_size(a->size, body);

        sprintf(string, "%s%s%.*s%s",
                type_desc(body->type, body->subtype, body->parameter, 0),
                body->description != NULL ? ", \"" : "",
                sizeof(string)-20, 
                body->description != NULL ? body->description : "",
                body->description != NULL ? "\"": "");

        a->description = cpystr(string);
        a->body        = body;
        a->can_display = mime_can_display(body->type, body->subtype,
                                          body->parameter);
        a->shown = (a->body->type==TYPETEXT || a->body->type==TYPEMESSAGE) &&
                                        a->can_display && should_show;
        sprintf(a->number, "%s%d",prefix, num);
        (a+1)->description = NULL;
        if(body->type == TYPEMESSAGE) {
            body = body->contents.msg.body;
            sprintf(numx, "%s%d.", prefix, num);
            describe_mime(body, numx, 1, should_show);
        }
    }
}



/*----------------------------------------------------------------------
   Zero out the attachments structure and free up storage
  ----*/
void
zero_atmts(atmts)
     struct attachment *atmts;
{
    struct attachment *a;
    for(a = atmts; a->description != NULL; a++)
      fs_give((void **)&(a->description)); 
    atmts->description = NULL;
}


char *
body_type_names(t)
     int t;
{
    static char *body_types[] = {"Text", "Multipart", "Message", 
                                 "Application", "Audio", "Image",
                                 "Video", "Other"};
    return(body_types[t]);
}

/*----------------------------------------------------------------------
  Return a nicely formatted discription of the type of the part
 ----*/

char *
type_desc(type, subtype, params, full)
     int type, full;
     char *subtype;
     PARAMETER *params;
{
    static char type_d[200];
    char       *p;

    switch(type) {
      case TYPETEXT:
        while(params != NULL && strucmp(params->attribute,"charset") != 0)
          params = params->next;
        if(params != NULL) {
            if(strucmp(params->value, "iso-8859-1") == 0){
                strcpy(type_d, full ? "Latin 1 text (ISO-8859-1)" :
                                      "Latin 1 text");
            } else if(strucmp(params->value, "iso-8859-2") == 0){
                strcpy(type_d, full ? "Latin 2 text (ISO-8859-2)" :
                                      "Latin 2 text");
            } else if(strucmp(params->value, "iso-8859-3") == 0){
                strcpy(type_d, full ? "Latin 3 text (ISO-8859-3)" :
                                      "Latin 3 text");
            } else if(strucmp(params->value, "iso-8859-4") == 0){
                strcpy(type_d, full ? "Latin 4 text (ISO-8859-4)" :
                                      "Lating 4 text");
            } else if(strucmp(params->value, "iso-8859-5") == 0){
                strcpy(type_d, full ? "Latin & Cyrillic text (ISO-8859-5)" :
                                      "Latin & Cyrillic text");
            } else if(strucmp(params->value, "iso-8859-6") == 0){
                strcpy(type_d, full ? "Latin & Arabic text (ISO-8859-6)" :
                                      "Latin & Arabic text");
            } else if(strucmp(params->value, "iso-8859-7") == 0){
                strcpy(type_d, full ? "Latin & Greek text (ISO-8859-7)" :
                                      "Latin & Greek text");
            } else if(strucmp(params->value, "iso-8859-8") == 0){
                strcpy(type_d, full ? "Latin 5 text (ISO-8859-8)" :
                                      "Laint 5 text");
            } else if(strucmp(params->value, "iso-8859-9") == 0){
                strcpy(type_d, full ? "Latin & Hebrew text (ISO-8859-9)" :
                                      "Latn & Hebrew text");
            } else if(strucmp(params->value, "us-ascii") == 0){
                strcpy(type_d, "Text");
            } else {
                strcpy(type_d, "Unknown text");
                if(full)
                  sprintf(type_d+strlen(type_d), " \"%.100s\"",params->value);
            }
        } else {
            strcpy(type_d, "Text");
        }
        break;

      case TYPEAPPLICATION:
        if(subtype != NULL && strucmp(subtype, "octet-stream") == 0){
            while(params != NULL && strucmp(params->attribute,"name") != 0)
              params = params->next;
            if(full)
              sprintf(type_d, "Attached file \"%s\"",
                      params != NULL && params->value != NULL ?
                                                    params->value : "");
            else
              sprintf(type_d, "File \"%s\"",
                      params != NULL && params->value != NULL ?
                                                    params->value : "");
        } else if(subtype != NULL && strucmp(subtype, "postscript") == 0)
          strcpy(type_d, "PostScript");
        else
          sprintf(type_d, "Application/%.100s", subtype != NULL ? subtype: "");
        break;

      default:
        if(full)
          sprintf(type_d, "%s/%.100s", body_type_names(type),
                 subtype != NULL ? subtype: "");
        else
          strcpy(type_d, body_type_names(type));
        break;
    }

    return(type_d);
}
     

void
format_mime_size(string, b)
     char *string;
     BODY *b;
{
    char tmp[10], *p;
    if(b->type == TYPETEXT) {
        sprintf(string, "%s lines", comatose(b->size.lines));
    } else {
        strcpy(string, byte_string(b->size.bytes));
        for(p = string; *p && (isdigit(*p) || ispunct(*p)); p++);
        sprintf(tmp, " %-5.5s",p);
        strcpy(p, tmp);
    }
}

        

/*----------------------------------------------------------------------
   Determine if we can show all, some or none of the parts of a body

Args: body --- The message body to check

Returns: ShowAll, ShowPart or ShowNone depending on how much of the body
    can be shown 
 ----*/     
MimeShow
mime_show(body)
     BODY *body;
{
    int   sp, sn, sa;
    PART *p;

    if(body == NULL)
      return(ShowNone);

    switch(body->type) {
      default:
        return(mime_can_display(body->type,body->subtype,body->parameter)== 1 ?
               ShowAll:
               ShowNone);
        break;

      case TYPEMESSAGE:
        return(mime_show(body->contents.msg.body) == ShowAll ? 
               ShowAll:
               ShowParts);
        break;

      case TYPEMULTIPART:
        sp = sn = sa = 0;
        for(p = body->contents.part; p != NULL; p = p->next) {
            switch(mime_show(&(p->body))) {
              case ShowAll:
                sa++;
                break;
              case ShowParts:
                sp++;
                break;
              case ShowNone:
                sn++;
                break;
            }
        }
        if(sa == 0)
          if(sp == 0)
            return(ShowNone);
          else
            return(ShowParts);
        else
          if(sn == 0)
            return(ShowAll);
          else
            return(ShowParts);
        break;
    }
}
        


/*----------------------------------------------------------------------
   Format a message message for viewing

 Args: mc -- The MESSAGECACHE entry for the message to view
       new_mess -- Currently unused flag indicating a different message
                   than last time is being formatted

Result: Returns a pointer to a malloced string with text

First the envelope is formatted; next a list of all attachments is
formatted if there are more than one. Then all the body parts are
formatted, fetching them as needed. This includes headers of included
message. Richtext is also formatted. An entry is made in the text for
parts that are not displayed or can't be displayed.

 ----*/    
char *
format_message(mc, new_mess, do_print)
     MESSAGECACHE *mc;
     int new_mess, do_print;
{
    char              *t, *text, *part_contents, *decoded_part, temp[200];
    struct attachment *a;
    int                parts_len, max_num_l, max_size_l, parts_in_header;
    long               part_bytes, decoded_len, shown_len;
    extern char       *rfc822_qprint(), *rfc822_base64();
    PARAMETER         *param;

    parts_in_header = !do_print;

    if(mc->body == NULL) {
        /*--- Sever is not an IMAP2bis, It can't parse MIME
              so we just show the text here. Hopefully the 
              message isn't a MIME message 
          ---*/
        shown_len = strlen(format_envelope(mc->env));
        t         = mail_fetchtext(ps_global->mail_stream, mc->msgno);
        if(t == NULL) {
            t = "\n    [ERROR fetching text of message]\n\n";
        }

        text = fs_get(shown_len + strlen(t) + 10);
        strcpy(text, format_envelope(mc->env));
        strcat(text, "\n");
        strcat(text, t);
        return(text);
    }
        
    if(new_mess) {
        zero_atmts(ps_global->atmts);
        describe_mime(mc->body, "", 1, 1);
    }

    /*---- Calculate the approximate length of what we've got to show  ---*/
    shown_len   = strlen(format_envelope(mc->env)); 
    parts_len   = 0;
    max_num_l   = max_size_l = 0;
        dprint(5, (debugfile, "adding up env %d\n", shown_len));
    for(a = ps_global->atmts; a->description != NULL; a++) {
        if(a->shown) {
            if(a->body->type == TYPEMESSAGE) {
                shown_len += MAX_SCREEN_COLS +
                            strlen(format_envelope(a->body->contents.msg.env));
                if(a->body->subtype != NULL &&
                            strucmp(a->body->subtype, "external-body") == 0) {
                    shown_len +=strlen(display_parameters(a->body->parameter));
                    shown_len += 100; /* note about external-bodies */
                }
                
            } else {
                shown_len += a->body->size.bytes;

                if(a->body->type == TYPETEXT) {
                   for(param = a->body->parameter; param != NULL &&
                       strucmp(param->attribute,"charset") != 0;
                       param = param->next);
                   if(match_charset(param->value, ps_global->VAR_CHAR_SET)==1)
                     /* The note that says characters are greeked */
                     shown_len += 400;
               }
            }
        } else {
            shown_len += strlen(part_desc(a->number, a->body,
                                   do_print ? 3 : a->can_display ? 1 : 2));
        }
        shown_len += 19; /* For good measure */
        dprint(5, (debugfile, "adding up %s %d\n", a->number, shown_len));

        /*--- The parts list display ---*/
        if(strlen(a->number) > max_num_l)
          max_num_l = strlen(a->number);
        if(strlen(a->size) > max_size_l)
          max_size_l = strlen(a->size);
    }

    if(parts_in_header && ps_global->atmts[1].description != NULL)  {
        shown_len += (a - ps_global->atmts) * (MAX_SCREEN_COLS);
        dprint(5, (debugfile, "adding up misc %d\n", shown_len));
        shown_len += 200; /* Heading and trailing line */
        dprint(5, (debugfile, "adding up heading line %d\n", shown_len));
    }
    text     = fs_get(shown_len);
    text[0]  = '\0';
    t        = text;

    /*=========== Format the header into the buffer =========*/
    /*----- The mail envelope ----*/
    strcpy(t, format_envelope(mc->env));
    t += strlen(t);
    dprint(5,(debugfile, "format_message lengths  env Allocated:%d  Used:%d\n",
               shown_len, t - text));

    /*----- First do the list of parts/attachments if needed ----*/
    if(parts_in_header && ps_global->atmts[1].description != NULL) {
        strcpy(t,"Parts/attachments:\n");
        t += strlen(t);

    dprint(5,(debugfile, "format_message lengths head Allocated:%d  Used:%d\n",
               shown_len, t - text));
        /*----- Format the list of attachments -----*/
        for(a = ps_global->atmts; a->description != NULL; a++) {
            sprintf(t, "   %-*.*s %s  %*.*s  %-*.*s\n",
                    max_num_l, max_num_l, a->number,
                    (a->shown ? "Shown" :
                       a->can_display ? "  OK " : "     "),
                    max_size_l, max_size_l, a->size,
                    ps_global->ttyo->screen_cols - max_num_l - max_size_l - 14,
                    ps_global->ttyo->screen_cols - max_num_l - max_size_l - 14,
                    a->description);
            t += strlen(t);
        }
        strcpy(t, "----------------------------------------\n");
        t += strlen(t);
    }
    *t++ = '\n';

    dprint(5,(debugfile, "format_message lengths  eop Allocated:%d  Used:%d\n",
               shown_len, t - text));

    /*======== Now loop through formatting all the parts =======*/
    for(a = ps_global->atmts; a->description != NULL; a++) {
        if(!a->shown) {
            strcpy(t, part_desc(a->number, a->body,
                                do_print ? 3 : a->can_display ? 1 : 2));
            t += strlen(t);
            *t++ = '\n';
            continue;
        } 

        switch(a->body->type) {
          case TYPETEXT:
            part_contents = mail_fetchbody(ps_global->mail_stream, mc->msgno,
                                       a->number, &part_bytes);

            if(part_contents == NULL) {
                sprintf(t,"\n    [ERROR fetching part %s \"%.100s\"]\n\n",
                        a->number, 
                        a->body->description==NULL ?"" :a->body->description);
                t += strlen(t);
                continue;
            }
            if(part_bytes == 0)
              continue;
    
            switch(a->body->encoding) {
              case ENC7BIT:
              case ENC8BIT:
              case ENCBINARY:
                decoded_part = part_contents;
                decoded_len  = part_bytes;
                break;
    
              case ENCBASE64:
                decoded_part = rfc822_base64(part_contents, part_bytes,
                                                    &decoded_len);
                break;
    
              case ENCQUOTEDPRINTABLE:
                decoded_part = rfc822_qprint(part_contents, part_bytes,
                                                    &decoded_len);
                break;
    
              default:
              case ENCOTHER:
                decoded_part=cpystr("Unknown message encoding; can't display");
                break;
            }
            if(decoded_part == NULL) {
                sprintf(t, "\n\n    [Error decoding \"%s\" format of text]\n",
                        a->body->encoding == ENCBASE64 ? "base 64" :
                        "quoted-printable");
                t += strlen(t);
                break;
            }

            if(a->body->subtype != NULL &&
               strucmp(a->body->subtype, "richtext") == 0) {
                /*--- Do richtext if necessary ----*/
                rich2plain(decoded_part, ps_global->ttyo->screen_cols -2,
                           do_print ? 1 : 0);
            }
            for(param = a->body->parameter; param != NULL &&
                strucmp(param->attribute,"charset") != 0; param = param->next);

            copy_text(t, decoded_part, param != NULL ? param->value : NULL,
                      ps_global->VAR_CHAR_SET, ps_global->show_all_characters);
            t += strlen(t);
            if(decoded_part != part_contents)
              fs_give((void **)&decoded_part);
            break;

          case TYPEMESSAGE:
            sprintf(t, "\n\n----- Part %s \"%s\" -----\n",
                    a->number, 
                    a->body->description== NULL ? "Included Message"
                                                : a->body->description);
            t += strlen(t);
            strcpy(t, format_envelope(a->body->contents.msg.env));
            t += strlen(t);
            if(a->body->subtype != NULL &&
               strucmp(a->body->subtype, "external-body") == 0) {
                strcpy(t, "This part is not included and can be fetched as follows:\n");
                t += strlen(t);
                strcpy(t, display_parameters(a->body->parameter));
                t += strlen(t);
            }
            *t++ = '\n';
            break;

          default:
            strcpy(t, part_desc(a->number, a->body, do_print ? 3: 1));
            t += strlen(t);
        }
    }
    *t = '\0';
        
    dprint(5,(debugfile, "format_message lengths  %s Allocated:%d  Used:%d\n",
               a->number, shown_len, t - text));

    return(text);
}


/*----------------------------------------------------------------------
  Copy the text, filtering character that don't match the character 
 set of the display. This is the place to do translation if it is 
 to be done.
  ----*/
void
copy_text(dest, source, text_char_set, device_char_set, no_filter)
     char *dest, *source, *text_char_set, *device_char_set;
     int   no_filter;
{
    int match;
 
    match =  match_charset(text_char_set, device_char_set);

    if(!no_filter && match == 1) {
        sprintf(dest, "    [The following text is in the \"%s\" character set]\n",
                text_char_set);
        dest += strlen(dest);
        sprintf(dest,
                "    [Your display is set for the \"%s\" character set]\n",
                device_char_set == NULL ? "US-ASCII" : device_char_set);
        dest += strlen(dest);
        sprintf(dest,
                "    [Some characters are not displayable and shown as \"_\"]\n\n");
        dest += strlen(dest);
    }

    while(*source != '\0') {
        if(no_filter || match == 0 || (unsigned char)*source <= 0x80) {
            if(*source == '\033' && !no_filter) {
                if(match_iso2022(source)){
                    *dest++ = *source++;
                } else {
                    *dest++ = '_';
                    source++;
                }
            } else {
                *dest++ = *source++;
            }
        } else {
            *dest++ = '_';
            source++;
        }
    }
    *dest = '\0';
}



/*----------------------------------------------------------------------
   
  Returns:  0 - can display all characters
            1 - can display most of the characters
            2 - can't display any characters or don't know
  ----*/
match_charset(text_char_set, device_char_set)
     char *text_char_set, *device_char_set;
{
    enum {IsUsAscii, IsIso8859, IsOther}  text, dev;
    
    if(text_char_set == NULL && device_char_set == NULL)
      return(0); /* Both us-ascii */

    if(text_char_set != NULL && device_char_set != NULL &&
       strucmp(text_char_set, device_char_set) == 0) {
        return(0);
    }
    text = text_char_set == NULL ||
                 strucmp(text_char_set, "us-ascii") == 0 ? IsUsAscii:
            struncmp(text_char_set, "iso-8859-", 9) == 0 ? IsIso8859:
                                                           IsOther;
    dev  = device_char_set == NULL ||
                 strucmp(device_char_set, "us-ascii") == 0 ? IsUsAscii:
           struncmp(device_char_set, "iso-8859-", 9) == 0 ?  IsIso8859:
                                                             IsOther;
    if(text == IsUsAscii && (dev == IsIso8859 || dev == IsUsAscii))
      return(0);
    if(text == IsIso8859 && (dev == IsIso8859 || dev == IsUsAscii))
      return(1);

    return(2);
}



 
/*------------------------------------------------------------------
   This list of iso 2022 escapes is taken from the X11R5 source with only
  a remote understanding of what this all means
  ----*/
static char *iso2022_escapes[] = {
    "\033(B",   "\033(J",   "\033)I",   "\033-A",   "\033-B",
    "\033-C",   "\033-D",   "\033-F",   "\033-G",   "\033-H",
    "\033-L",   "\033-M",   "\033-$(A", "\033$)A",  "\033$(B",
    "\033$)B",  "\033$(C",  "\033$)C",  "\033$(D",  "\033$)D",
    "\033$B",   "\033$@",
    NULL};

match_iso2022(esc_seq)
     char *esc_seq;
{
    char **p;

    for(p = iso2022_escapes;
        *p != NULL && struncmp(esc_seq, *p, strlen(*p));
        p++);
    if(*p == NULL)
      return(0);
    else
      return(strlen(*p));
}



/*

Returns: pointer to static buffer with formatted envelope
 ----*/
char *
format_envelope(e)
     ENVELOPE *e;
{
    char *t, *s;

    t = tmp_20k_buf;
    *t = '\0';

    if(e == NULL)
      return(tmp_20k_buf);

    if(e->date != NULL) {
        sprintf(t,"Date: %s\n", e->date);
        t += strlen(t);
    }

    if(e->from != NULL) {
        s = pretty_addr_string("From: ", e->from, "");
        strcpy(t, s);
        t += strlen(t);
    }

    if(e->reply_to != NULL && (e->from == NULL || 
                           strucmp(e->from->mailbox, e->reply_to->mailbox) ||
                           strucmp(e->from->host, e->reply_to->host))) {
        s = pretty_addr_string("Reply to: ", e->reply_to, "");
        strcpy(t, s);
        t += strlen(t);
    }

    if(e->to != NULL) {
        s = pretty_addr_string("To: ", e->to, "");
        strcpy(t, s);
        t += strlen(t);
    }

    if(e->cc != NULL) {
        s = pretty_addr_string("Cc: ", e->cc, "");
        strcpy(t, s);
        t += strlen(t);
    }

    if(e->bcc != NULL) {
        s = pretty_addr_string("Bcc: ", e->bcc, "");
        strcpy(t, s);
        t += strlen(t);
    }

    if(e->subject != NULL)
      sprintf(t,"Subject: %s\n", e->subject);

    return(tmp_20k_buf);
}



/*----------------------------------------------------------------------
    Format a strings describing one unshown part of a Mime message

Args: number -- A string with the part number i.e. "3.2.1"
      body   -- The body part
      type   -- 1 - Not shown, but can be
                2 - Not shown, cannot be shown
                3 - Can't print


Result: pointer to formatted string in static buffer

Note that size of the strings are carefully calculated never to overflow 
the static buffer:
    number  < 20,  description limited to 100, type_desc < 200,
    size    < 20,  second line < 100           other stuff < 60
 ----*/
char *
part_desc(number, body, type)
     BODY *body;
     int type;
     char *number;
{
    static char line[500];

    sprintf(line, "\n  [Part %s, %s%.100s%s%s  %s%s]\n",
            number,
            body->description == NULL ? "" : "\"",
            body->description == NULL ? "" : body->description,
            body->description == NULL ? "" : "\"  ",
            type_desc(body->type, body->subtype, body->parameter, 1),
            body->type == TYPETEXT ? comatose(body->size.lines) :
                                     byte_string(body->size.bytes),
            body->type == TYPETEXT ? " lines" : "");

    switch(type) {
      case 1:
        strcat(line, "  [Not Shown. Use the \"A\" command to view or save this part]\n");
        break;

      case 2:
        sprintf(line + strlen(line),
                "  [Can not %splay this part. Use the \"A\" command to save in a file]\n",
                body->type==TYPEAUDIO || body->type==TYPEVIDEO ? "" : "dis" );
        break;

      case 3:
        strcat(line, "  [Unable to print this part]\n");
        break;
    }
    return(line);
}



static struct key_menu  help_keymenu =
  {0,{
        {"M","Main Menu",0},  {NULL,NULL,0},            {"E","Exit Help",0},
        {NULL,NULL,0},        {NULL,NULL,0},            {"L","Print",0},
        {"-","Prev Page",0},  {"SPACE","Next Page",0},  {NULL,NULL,0},        
        {NULL,NULL,0},        {NULL,NULL,0},            {"W", "Where is",0}}};

static struct key_menu  composer_help_keymenu =
  {0,{
        {NULL,NULL,0},        {NULL,NULL,0},            {"E","Exit Help",0},
        {NULL,NULL,0},        {NULL,NULL,0},            {"L","Print",0},
        {"-","Prev Page",0},  {"SPACE","Next Page",0},  {NULL,NULL,0},        
        {NULL,NULL,0},        {NULL,NULL,0},            {"W", "Where is",0}}};

static struct key_menu view_keymenu0 = 
   {  0,{
        {"?","Help",0},       {"O","OTHER CMDS",0},    {"M","Main Menu",0},
        {"I","Mail Index",0}, {"P","Prev Msg",0},      {"N","Next Msg",0},
        {"-","Prev Page",0},  {"SPACE","Next Page",0}, {"F","Forward",0},
        {"R","Reply",0},      {"D","Delete",0},        {"S","Save",0}}}; 

static struct key_menu view_keymenu1 =
   {  0,{
        {"?","Help",0},      {"O","OTHER CMDS",0}, {"E","Export Msg",0},
        {"Q","Quit",0},      {"C","Compose",0},    {"L","Print",0},
        {"U","Undelete",0},  {"A","Attachments",0},{"T","Take Addr",0},
        {"J","Jump",0},      {"G","Go to Fldr",0}, {"W","Where is",0}}};

static struct key_menu view_keymenu2 =
   {  0,{
        {"?","Help",0},      {"O","OTHER CMDS",0},    {"H",  "Full Headers",0},
        {"|","Pipe to cmd",0},{NULL, NULL,0},         {NULL, NULL, 0}, 
        {NULL, NULL,0},      {NULL, NULL,0},          {NULL, NULL, 0},
        {NULL, NULL,0},      {NULL, NULL,0},          {NULL, NULL, 0}}}; 

static struct key_menu nr_view_keymenu0 = 
   {  0,{
        {"?","Help",0},       {"W","Where is",0},      {"I", "News Index", 0},
        {"Q","Quit",0},       {"P","Prev Article",0},  {"N","Next Article",0},
        {"-","Prev Page",0},  {"SPACE","Next Page",0}, {"F","Forward",0},
        {"J","Jump",0},       {NULL, NULL,0},          {NULL, NULL, 0}}}; 



/*----------------------------------------------------------------------
   routine for displaying help and message text on the screen.

  Args: text_array    possible array of lines to display
        text          possible buffer to display
        header_text   possible buffer containing message header
        header_filter list of message header fields to display
        header_addr_fields  header fields that get special address formatting
        title         string with title of text being displayed
        pages         page numbering if text is to has page numbers
        style         whether we are display a message, help text ...
 

   This displays in three different kinds of text. One is an array of
lines passed in in text_array. The other is a simple long string of
characters passed in in text. The simple string of characters may have
another string, the header which is prepended to the text with some
special processing. The two header.... args specify how format and
filter the header.

    It can scroll by line or by page. The pages array passed in is a
list of line numbers that are assumed to correspond to pages one
through n. When scrolling down it will go to the next page, whatever
line number that is.

  The style determines what some of the error messages will be, and
what commands are available as different things are appropriate for
help text than for message text etc.

 ---*/
#define LINES_ABOVE 2  /* Title Bar and blank line */
#define LINES_BELOW 3  /* Status line and key menu */
void
scrolltool(text_array, text, header_text, header_filter,
               header_addr_fields, title, pages, style)
     char   **text_array, *text, *title;
     char    *header_text, **header_filter, **header_addr_fields;
     TextType style;
     int     *pages;
{
    register int     cur_top_line,  num_display_lines;
    int              result, done, command_char, which_keys, num_text_lines,
                     current_page, page_count, found_on,
                     orig_command, count_by_lines, first_view;
    struct key_menu *km;

    page_count        = 0; 
    command_char      = 'x'; /* For display_messaage first time through */
    num_display_lines = ps_global->ttyo->screen_rows -LINES_ABOVE- LINES_BELOW;
    ps_global->mangled_header = 1;
    ps_global->mangled_footer = 1;
    ps_global->mangled_screen = 0;

    cur_top_line      =  0;
    done              =  0;
    which_keys        =  0; /* which key menu to display, 0 or 1 or 2*/
    current_page      =  0;
    found_on          =  0;
    first_view        =  1;

    count_by_lines    =  pages == NULL;
    if(pages != NULL)
      for(page_count = 0; pages[page_count] != -1; page_count++);

    set_scroll_text(text_array, text, header_text, header_filter,
                    header_addr_fields, cur_top_line,
                    LINES_ABOVE, LINES_ABOVE + LINES_BELOW);
    ps_global->scroll_text_changed = 1; 
    num_text_lines          = get_scroll_text_lines();
    ps_global->redrawer     = redraw_scroll_text;
    ps_global->mangled_body = 0;

    while(!done) {
	if(ps_global->mangled_screen) {
	    ps_global->mangled_header = 1;
	    ps_global->mangled_footer = 1;
            ps_global->mangled_body   = 1;
            ps_global->mangled_screen = 0;
	}

	/*=================  Check for New Mail ==================*/
        if(new_mail(NULL, 0, command_char==NO_OP_IDLE ? 0 :
                             command_char==NO_OP_COMMAND ? 1 : 2) >= 0){
            if(ps_global->new_current_sorted_msgno > 0L) {
                ps_global->current_sorted_msgno =
                  ps_global->new_current_sorted_msgno;
                ps_global->new_current_sorted_msgno = -1L;
            }

            ps_global->mangled_header = 1;
        }
        if(streams_died())
          ps_global->mangled_header = 1;

        dprint(9, (debugfile, "@@@@ new: %ld  current:%ld\n",
                   ps_global->new_current_sorted_msgno,
                   ps_global->current_sorted_msgno));


        /*==================== All Screen painting ====================
        /*-------- The title bar ---------------*/
	if(count_by_lines) {
	    if(ps_global->mangled_header) {
		set_titlebar(title, 1, TextPercent,
                             ps_global->current_sorted_msgno,
		             cur_top_line +num_display_lines > num_text_lines ?
		  	     num_text_lines :
                             cur_top_line + num_display_lines,
		             num_text_lines);

		ps_global->mangled_header = 0;
	    } else {
		update_titlebar_percent(cur_top_line + num_display_lines >
				      num_text_lines ?
			num_text_lines : cur_top_line + num_display_lines);
	    } 
	} else {
	    if(ps_global->mangled_header) {
	        set_titlebar(title,1, PageNumber,0,current_page+1,page_count);
	        ps_global->mangled_header = 0;
	    } else {
		update_titlebar_page(current_page + 1);
	    }

	}


        /*---- Scroll or update the body of the text on the screen -------*/
        scroll_scroll_text(cur_top_line, ps_global->mangled_body);
        ps_global->mangled_body = 0;
        num_text_lines          = get_scroll_text_lines();


        /*------------- The key menu footer --------------------*/
	if(ps_global->mangled_footer) {
            if(ps_global->nr_mode) {
                km = &nr_view_keymenu0;
            } else if(style == MessageText) {
                km = which_keys == 0 ? (&view_keymenu0) :
                       which_keys == 1 ? (&view_keymenu1) :
                                           (&view_keymenu2) ;
            } else if(style == ComposerHelpText) {
                km = &composer_help_keymenu;
            } else {
                km = &help_keymenu;
            }
               
            format_keymenu(km, ps_global->ttyo->screen_cols);
            output_keymenu(km, -2, 0);
	    ps_global->mangled_footer = 0;
	}


	/*==================== Output the status message ==============*/
        display_message(command_char);
	MoveCursor(min(LINES_ABOVE + num_display_lines,
                       ps_global->ttyo->screen_rows), 0);
	fflush(stdout);
        check_point(0, command_char == NO_OP_IDLE || first_view ? GoodTime :
                                                                  BadTime);
        first_view = 0;


	/*================ Get command and validate =====================*/
        command_char = read_command();
        orig_command = command_char;

        if(command_char < 0x0100)
	    if(isupper(command_char))
	       command_char = tolower(command_char);

        if(which_keys == 1)
          if(command_char >= PF1 && command_char <= PF12)
            command_char = PF2OPF(command_char);
        else if(which_keys == 2)
          if(command_char >= PF1 && command_char <= PF12)
            command_char = PF2OOPF(command_char);

	command_char = validatekeys(command_char);


	/*============= Execute command =======================*/
        switch(command_char){


            /*---------- display other key bindings ------*/
          case OPF2:
          case PF2:
	  case 'o' :
	    if(style != MessageText)
	      goto df;
            which_keys = (which_keys + 1) %
               (ps_global->feature_level == Seasoned ? 3 : 2);
	    ps_global->mangled_footer = 1;
	    break;

            

            /* -------- scroll back one page -----------*/
          case  '-' :   
          case ctrl('Y'): 
          case  PF7  :
	    if(cur_top_line == 0) {
	        q_status_message(0, 0,1,
                      style == MessageText  ?
                       (ps_global->nr_mode ?  "Already at start of article" :
                                              "Already at start of message" ) :
	              style == HelpText         ? "Already at start of help" :
	              style == ComposerHelpText ? "Already at start of help" :
                                                  "Already at start of text");

	    } else {
	        if(pages == NULL) {
                    cur_top_line -= (num_display_lines-OVERLAP);
	            if(cur_top_line < 0)
	              cur_top_line = 0;
	        } else {
	    	current_page--;
	    	cur_top_line = pages[current_page];
	        }
            }
            break;


            /*---- scroll down one page -------*/
          case  '+' :     
          case  PF8  :
          case ctrl('V'): 
          case  ' ':
            if(cur_top_line + num_display_lines < num_text_lines){
	        if(pages == NULL) {
                    cur_top_line += (num_display_lines - OVERLAP);
	        } else {
                    if(current_page + 1 <  page_count) {
                            current_page++;
                        cur_top_line = pages[current_page];
                    } else {
                        q_status_message(0, 0,1,
                         style == MessageText ? 
                         (ps_global->nr_mode ?  "Already at end of article" :
                                                "Already at end of message" ):
                         style == ComposerHelpText ? "Already at end of help" :
                         style == HelpText ?    "Already at end of help" :
                                                "Already at end of text");
                    }   
	        }
            } else {
	        q_status_message(0, 0, 1,
                  style == MessageText ? 
                    (ps_global->nr_mode ?     "Already at end of article" :
                                              "Already at end of message" ) :
	    	  style == HelpText ?         "Already at end of help" :
	    	  style == ComposerHelpText ? "Already at end of help" :
                                              "Already at end of text");
	    }
            break;


            /*------ Scroll down one line -----*/
	  case KEY_DOWN:
	  case ctrl('N'):
	  case ctrl('M'):
	  case ctrl('J'):
            if(cur_top_line + num_display_lines > num_text_lines)
	        q_status_message(0, 0,1,
                 style == MessageText ?  
                  (ps_global->nr_mode ?      "Already at end of article" :
                                             "Already at end of message") :
	    	 style == HelpText ?         "Already at end of help" :
	    	 style == ComposerHelpText ? "Already at end of help" :
                                             "Already at end of text");
	    else {
	        cur_top_line++;
	        if(pages != NULL&& cur_top_line >= pages[current_page+1] &&
	           pages[current_page+1] != -1)
	          current_page++;
	    }
	    break;


            /* ------ Scroll back up one line -------*/
          case KEY_UP:
	  case ctrl('P'):
	    if(cur_top_line == 0) {
	       q_status_message(0, 0,1,
                      style == MessageText ?
                       (ps_global->nr_mode ?  "Already at start of article" :
                                              "Already at start of message" ) :
                      style == HelpText ?         "Already at start of help":
                      style == ComposerHelpText ? "Already at start of help":
                                                  "Already at start of text");
	    } else {
	        cur_top_line--;
	        if(pages != NULL && cur_top_line < pages[current_page])
	          current_page--;
	    }
	    break;

          case '?':
          case ctrl('G'):
            if(ps_global->nr_mode) {
                q_status_message(1, 3,5,"No help text currently available");
                break;
            }
            if(style == HelpText || style == ComposerHelpText)
              goto df;
            helper(h_mail_view, "HELP FOR VIEW MAIL", 0);
            dprint(2, (debugfile,"MAIL_CMD: did help command\n"));
            /* Have to reset because helper uses sroll_text */
            set_scroll_text(text_array, text, header_text, header_filter,
                    header_addr_fields, cur_top_line,
                    LINES_ABOVE, LINES_ABOVE + LINES_BELOW);
            num_text_lines = get_scroll_text_lines();
            ps_global->mangled_header = 1;
            ps_global->mangled_footer = 1;
            break; 


            /*---------- Search text (where is) ----------*/
          case 'w':
          case OPF12:
            ps_global->mangled_footer = 1;
            found_on = search_text( -3,
                    found_on <= 0 || found_on < cur_top_line ||
                      found_on >= cur_top_line + num_display_lines
                             ? cur_top_line : found_on + 1);
            if(found_on >= 0) {
                if(found_on > cur_top_line + num_display_lines ||
                   found_on < cur_top_line)  {
                    cur_top_line = (found_on/(num_display_lines-OVERLAP)) *
                                            (num_display_lines -OVERLAP);
                }
                if(pages != NULL) {
                    int *pg;
                    for(pg = pages; pg < &pages[page_count]; pg++)
                      if(cur_top_line <= *pg)
                        break;
                    if(pg != pages)
                      pg--;
                    current_page = pg - pages;
                }
                
                q_status_message2(0, 1,3, "%sFound on line %s on screen",
                                  found_on >= cur_top_line ? "" :
                                  "Search wrapped to start. ",
                                  int2string(found_on - cur_top_line + 1));
            } else if(found_on == -1) {
                q_status_message(0, 0,2, "\007Search aborted");
            } else {
                q_status_message(0, 1,3, "\007Word not found");
            }
            break; 


            /*---------- Suspend Pine ( ^Z ) ----------*/
          case ctrl('Z'):
            if(!have_job_control())
              goto df;
            if(!ps_global->can_suspend) {
                q_status_message(1, 1,3,
                        "\007Pine suspension not enabled - see help text");
                break;
            } else {
                do_suspend(ps_global);
            }
            /*-- Fall through to redraw --*/

            

            /*-------------- refresh -------------*/
          case KEY_RESIZE:
          case  ctrl('L') :
	    ClearScreen();
            num_display_lines = ps_global->ttyo->screen_rows - LINES_ABOVE -
                         LINES_BELOW;
            ps_global->mangled_screen = 1;
            break;


            /*------- no op timeout to check for new mail ------*/
          case NO_OP_IDLE:
          case NO_OP_COMMAND :
            break;


	    /*------- Other commands of error ------*/
          default :
	  df:
	    if(style == MessageText){
                ps_global->scroll_text_changed = 0;
	        result = process_cmd(command_char, 0, orig_command); 
	        dprint(7, (debugfile, "PROCESS_CMD return: %d\n", result));

                if(ps_global->next_screen != SCREEN_FUN_NULL || result == 1)
                  return; /* New screen, new message or new attachments */
                if(ps_global->scroll_text_changed) {
                    set_scroll_text(text_array, text, header_text,
                                    header_filter, header_addr_fields,
                                    cur_top_line, LINES_ABOVE,
                                    LINES_ABOVE + LINES_BELOW);
                    ps_global->scroll_text_changed = 0;
                }
            } else {
                if(command_char == 'm' && style != ComposerHelpText) {
    	            /*---------- Main menu -----------*/
                    ps_global->next_screen = main_menu_screen;
                    done = 1;
    	        } else if(command_char == PF3 || command_char == 'e'){
    	            /*----------- Done with help -----------*/
                    done = 1;
                } else if(command_char == PF6 || command_char == 'l') {
    	            /*----------- Print ------------*/
    	            print_help(text, text_array);
                } else {
    	            /*----------- Unknown command -------*/
    	            q_status_message(0, 0,1, "\007Unknown Command.");
    	        }
            }
            break;

        } /* End of switch() */

    } /* End of while() -- loop executing commands */

    return;
}




/*----------------------------------------------------------------------
      Print out the help text on paper

    Args:  text -- The help text to print out

  ----*/
static int
print_help(text, textarray)
     char	 **textarray, *text;	
{
    register char **t;

    if(open_printer("help text ") != 0)
      return(-1);

    if(text != NULL) {
        print_text(text);
    } else {
        for(t = textarray; *t != NULL; t++) {
            print_text(*t);
            print_text("\n");
        }
    }

    close_printer();
    return(0);
}



/*----------------------------------------------------------------------
   Search text being viewed (help or message)

      Args: q_line      -- The screen line to prompt for search string on
            start_line  -- Line number in text to begin search on

    Result: returns line number string was found on
            -1 for abort
            -2 if not found

 ---*/

search_text(q_line, start_line)
     int            q_line, start_line;
{
    static char search_string[MAX_SEARCH+1] = { '\0' };
    char        prompt[MAX_SEARCH+50], nsearch_string[MAX_SEARCH+1], **help;
    int         rc;

    sprintf(prompt, "Word to search for [%s] : ", search_string);
    help = (char **)NULL;
    nsearch_string[0] = '\0';

    while(1) {
        rc = optionally_enter(nsearch_string, q_line, 0, MAX_SEARCH, 1, 0,
                              prompt, NULL, help, 0);
        if(rc == 3) {
            help = help == NULL ? h_oe_searchview : NULL;
            continue;
        }
        if(rc != 4)
          break;
    }

    if(rc == 1 || (search_string[0] == '\0' && nsearch_string[0] == '\0'))
      return(-1);

    if(nsearch_string[0] != '\0')
      strcpy(search_string, nsearch_string);


    rc = search_scroll_text(start_line, search_string);
    return(rc);
}



/*----------------------------------------------------------------------
    Saved state for scrolling text 
 ----*/
static struct scroll_text {
    char *text;          /* Original text */
    char *header_text;   /* Original header text */
    char **text_array;   /* Original text if passed in as array of lines */
    char **text_lines;   /* Lines to diapley. Malloced once for life of Pine */
    int top_text_line;   /* index into text array of line on top of screen */
    int num_lines;       /* Calculated number lines of text to display */
    int lines_allocated; /* size of array text_lines */
    int num_header_lines;/* Count header lines so they can be freed */
    int screen_width;    /* screen width of current formatting */
    int screen_start_line; /* First line on screen that we scroll text on */
    int screen_other_lines;/* Line ons screen not used for scroll text */
    short *line_lengths;   /* Lengths of lines for display, not \0 terminatd*/
    char **header_filter, **header_addr_fields;
    enum {PlainText, TextArray} mode;
} st;



/*----------------------------------------------------------------------
      Save all the data for scrolling text and paint the screen


  ----*/
void
set_scroll_text(text_array, text, header_text, header_filter,
                header_addr_fields, current_line, screen_top, screen_other)
     char *text, **text_array, *header_text, **header_filter,
       **header_addr_fields;
     int current_line;
     int screen_top, screen_other;
{
    static int first_time = 1;

    if(first_time) {
        first_time = 0;
        st.lines_allocated = 0;
        st.num_header_lines = 0;
    }
    /* save all the stuff for possible asynchronos redraws */
    st.text_array         = text_array;
    st.text               = text;
    st.header_text        = header_text;
    st.top_text_line      = current_line;
    st.header_filter      = header_filter;
    st.header_addr_fields = header_addr_fields;
    st.screen_start_line  = screen_top;
    st.screen_other_lines = screen_other;
    
    if(text_array != NULL)
      st.mode = TextArray;
    else
      st.mode = PlainText;

    st.screen_width = -1; /* Force text formatting calculation */

    redraw_scroll_text();
}



/*----------------------------------------------------------------------
     Redraw the text on the screen, possibly reformatting if necessary

   Args None

 ----*/
void
redraw_scroll_text()
{
    int i, len;

    if(st.screen_width != ps_global->ttyo->screen_cols) {
        format_scroll_text();
    }

    /*---- Actually display the text on the screen ------*/
    for(i = 0; i < ps_global->ttyo->screen_rows - st.screen_other_lines; i++){
        MoveCursor((i + st.screen_start_line), 0);
        CleartoEOLN();
        if((st.top_text_line + i) < st.num_lines) {
            len = min(st.line_lengths[st.top_text_line + i], st.screen_width);
            PutLine0n8b(0, i + st.screen_start_line, 0,
                      st.text_lines[st.top_text_line+i], len);
        }
    }
}



/*----------------------------------------------------------------------
  Free residual memory used by scroll text functions
  ----*/
void
end_scroll_text()
{
    register char **tl;

    if(st.lines_allocated != 0) {
        if(st.num_header_lines) 
          for(tl = st.text_lines; st.num_header_lines >= 0; st.num_header_lines--)
            fs_give((void **)tl++);  /* Free old malloced header lines */
        fs_give((void **)&st.text_lines);
        fs_give((void **)&st.line_lengths);
    }
}
    



/*----------------------------------------------------------------------

Always format at least 20 chars wide. Wrapping lines would be crazy for
screen widths of 1-20 characters 
  ----*/
void
format_scroll_text()
{
    int             last_match, indent_len, is_addr, line_len;
    char           *p, *p1, *line_start, **pp, *max_line, *indent, *last_space;
    register short  *ll;
    register char  **tl, **tl_end;

    is_addr = 0; /* Avoid ref before set warnings */
    st.screen_width = max(20, ps_global->ttyo->screen_cols);

    if(st.lines_allocated == 0) {
        st.lines_allocated = TYPICAL_BIG_MESSAGE_LINES;
        st.text_lines   = (char **)fs_get(st.lines_allocated *sizeof(char *));
        st.line_lengths = (short *)fs_get(st.lines_allocated *sizeof(short));
    }
    if(st.num_header_lines) 
      for(tl = st.text_lines; st.num_header_lines >= 0; st.num_header_lines--)
        fs_give((void **)tl++);  /* Free old malloced header lines */

    tl     = st.text_lines;
    ll     = st.line_lengths;
    tl_end = &st.text_lines[st.lines_allocated];
    st.num_header_lines = 0;

    if(st.mode == TextArray) {
        /*---- original text is already list of lines -----*/
        /*   The text could be wrapped nicely for narrow screens; for now
             it will get truncated as it is displayed */
        for(pp = st.text_array; *pp != NULL;) {
            *tl++ = *pp++;
            *ll++ = st.screen_width;
            if(tl >= tl_end) {
                int index = tl - st.text_lines;
                st.lines_allocated *= 2;
                fs_resize((void **)&st.text_lines,
                          st.lines_allocated * sizeof(char *));
                fs_resize((void **)&st.line_lengths,
                          st.lines_allocated*sizeof(short));
                tl     = &st.text_lines[index];
                ll     = &st.line_lengths[index];
                tl_end = &st.text_lines[st.lines_allocated];
            }
        }
    } else  {
        /*------ Format the plain text ------*/
        for(p = st.text; *p; ) {
            *tl = p;
#ifdef X_NEW
            max_line = p + st.screen_width;
            while(*p != '\r' && *p != '\n' && p < max_line && *p) p++;
#else
            line_len = 0;            
            last_space = NULL;

            while(*p) {
                if(*p == '\r' || *p == '\n') {
                    break;
                } else if(*p == '\033') {
                    int x;
                    if(x = match_iso2022(p))  {
                        /* Don't count 2022 escape in length */
                        while(x--)
                          p++;
                    } else {
                        /* A plain escape, not sure what it means in the 
                           text, but count it's length anyway to be safe 
                         */
                        p++;
                        line_len++;
                    }
                } else if(*p == '\t') {
                    int nt;
                    nt = next_tab(line_len + 1, 8);
                    if(nt > prev_tab(st.screen_width, 8)) 
                      line_len = st.screen_width - 1;
                    else
                      line_len = nt - 1;
                    p++;
                    last_space = p;
                } else if(*p == ' ') {
                    p++;
                    last_space = p;
                    line_len++;
                } else {
                    p++;
                    line_len++;
                }
                if(line_len >= st.screen_width) {
                    if(last_space == NULL) {
                        break;
                    } else {
                        p = last_space;
                        break;
                    }
                }
            }
        
#endif
            *ll = p - *tl;
            ll++; tl++;
            if(tl >= tl_end) {
                int index = tl - st.text_lines;
                st.lines_allocated *= 2;
                fs_resize((void **)&st.text_lines,
                          st.lines_allocated * sizeof(char *));
                fs_resize((void **)&st.line_lengths,
                         st.lines_allocated*sizeof(short));
                tl     = &st.text_lines[index];
                ll     = &st.line_lengths[index];
                tl_end = &st.text_lines[st.lines_allocated];
            }      
            if(*p == '\r' && *(p+1) == '\n') 
              p += 2;
            else if(*p == '\n' || *p == '\r')
              p++;
             continue;
        }
    }
    *tl = NULL;
    st.num_lines = tl - st.text_lines;

}



/*----------------------------------------------------------------------
     Scroll the text on the screen

   Args:  new_top_line -- The line to be displayed on top of the screen
          redraw -- Flag to force a redraw even in nothing changed 

 ----*/
void
scroll_scroll_text(new_top_line, redraw)
     int new_top_line, redraw;
{
    int num_display_lines, len;
    num_display_lines = ps_global->ttyo->screen_rows - st.screen_other_lines;

    if(st.top_text_line == new_top_line && !redraw)
      return;

    /* --- 
       Check out the scrolling situation. If we want to scroll, but BeginScroll
       says we can't then repaint,  + 10 is so we repaint most of the time.
      ----*/
    if(redraw ||
       (st.top_text_line - new_top_line + 10 >= num_display_lines ||
        new_top_line - st.top_text_line + 10 >= num_display_lines) ||
	BeginScroll(st.screen_start_line,
                    st.screen_start_line + num_display_lines - 1) != 0) {
        /* Too much text to scroll, or can't scroll -- just repaint */
        st.top_text_line = new_top_line;
        redraw_scroll_text();
        return;
    }

    if(new_top_line > st.top_text_line  ) {
        /*------ scroll down ------*/
        while(new_top_line > st.top_text_line) {
            int l;
            ScrollRegion(1);
            l = st.top_text_line + num_display_lines;
            if(l < st.num_lines) {
                len = min(st.line_lengths[l], st.screen_width);
                PutLine0n8b(0, st.screen_start_line + num_display_lines - 1, 0,
                          st.text_lines[l], len);
            }
            st.top_text_line++;
        }
    } else {
        /*------ scroll up -----*/
        while(new_top_line < st.top_text_line) {
            ScrollRegion(-1);
            st.top_text_line--;
            len = min(st.line_lengths[st.top_text_line], st.screen_width);
            PutLine0n8b(0, st.screen_start_line, 0,
                      st.text_lines[st.top_text_line], len);
        }
    }
    EndScroll();
}



/*----------------------------------------------------------------------
     Return the number of lines of text with the current formatting
  ----*/
int
get_scroll_text_lines()
{
    return(st.num_lines);
}



/*----------------------------------------------------------------------
      Search the set scrolling text

   Args:   start_line -- line to start searching on
           word       -- string to search for

   Returns: the line the word was found on or -2 if it wasn't found

 ----*/
int
search_scroll_text(start_line, word)
     char *word;
     int   start_line;
{
    char tmp[MAX_SCREEN_COLS+1];
    int  l;

    for(l = start_line+1; l < st.num_lines; l++) {
        strncpy(tmp, st.text_lines[l], st.line_lengths[l]);
        tmp[st.line_lengths[l]] = '\0';
        if(srchstr(tmp, word)!= NULL)
          break;
    }

    if(l < st.num_lines) {
        return(l);
    }

    for(l = 0; l < start_line; l++) {
        strncpy(tmp, st.text_lines[l], st.line_lengths[l]);
        tmp[st.line_lengths[l]] = '\0';
        if(srchstr(tmp, word)!= NULL)
          break;
    }

    return(l == start_line ? -2 : l);
}
     


/*----------------------------------------------------------------------
    Convert a buffer from rich text to plain text in place
    
 Args: text -- NULL terminated buffer to convert

Buffer is converted in place. This basically removes all richtext
formatting. A cute hack is used to get bold and underlining to work.
Further work could be done to handle things like centering and right
and left flush, but then it could no longer be done in place. This
operates on text *with* CRLF's.
 ----*/
void
rich2plain(text, width, plain)
     char *text;
     int width, plain;
{
    char           token[50];
    register char *pnew, *porig, *ptok, *tok_end, *p_last_space;
    int            w, w_space;

    width = min(width, 70);
    width = max(width, 25); /* Must be between 25 and 70 */

    tok_end = &token[49];
    w       = 0;
    w_space = 0;
    p_last_space = NULL;
    for(porig = pnew = text; *porig; porig++) {
        if(*porig == '<') {
            porig++;
            for(ptok = token; *porig != '>' && *porig; porig++)
              if(ptok < tok_end)
                *ptok++ = isupper(*porig) ? tolower(*porig) : *porig;
            if(*porig == '\0')
              break;
            *ptok = '\0';
            if(token[0] == 'l' && token[1] == 't') {
                *pnew++ = '<';
            } else if(token[0] == 'n' && token[1] == 'l') {
                *pnew++ = '\r';
                *pnew++ = '\n';
                w = 0;
            } else if(strcmp(token, "comment") == 0) {
                while(strcmp(token, "/comment")) {
                    while(*porig != '<' && *porig)
                      porig++;
                    if(*porig == '\0')
                      break;
                    for(ptok = token; *porig != '>' && *porig; porig++)
                      if(ptok < tok_end)
                        *ptok++ = isupper(*porig) ? tolower(*porig) : *porig;
                    if(*porig == '\0')
                      break;
                    *ptok = '\0';
                }
            /* Following are a cute hacks to get bold and underline
               on the screen. See Putline0n() where these codes are
               interpreted */
            } else if(!plain && strcmp(token, "bold") == 0) {
                *pnew++ = '\01';
            } else if(!plain && strcmp(token, "/bold") == 0) {
                *pnew++ = '\02';
            } else if(!plain && strcmp(token, "italic") == 0) {
                *pnew++ = '\03';
            } else if(!plain && strcmp(token, "/italic") == 0) {
                *pnew++ = '\04';
            } else if(!plain && strcmp(token, "underline") == 0) {
                *pnew++ = '\03';
            } else if(!plain && strcmp(token, "/underline") == 0) {
                *pnew++ = '\04';
            } 
        } else if(*porig == '\r' && *(porig+1) == '\n') {
            porig++;
            *pnew++ = ' ';
        } else {
            *pnew = *porig; /* Copy basic text */
            if(isspace(*pnew)) { /* remember last space to for wrapping */
                w_space = w;
                p_last_space = pnew;
            }
            if(w > width) { /* Time to wrap */
                if(p_last_space != NULL) {
                    /* Was a space recently */
                    *p_last_space = '\n'; 
                    w -= w_space;
                } else {
                    /* No spaces at all, just break it here */
                    pnew++;
                    *pnew = '\n';
                    w = 0;
                    p_last_space = NULL;
                }
            }
            w++;
            pnew++;
        }
    }
    *pnew = '\0';
}



char *    
display_parameters(parameter_list)
     PARAMETER *parameter_list;
{
    int        longest_attribute;
    PARAMETER *p;
    char      *d;

    if(parameter_list == NULL) {
        tmp_20k_buf[0] = '\0';

    } else {
        longest_attribute = 0;
    
        for(p = parameter_list; p != NULL; p = p->next)
          longest_attribute = max(longest_attribute, (p->attribute == NULL ?
                                                      0 :
                                                      strlen(p->attribute)));
    
        longest_attribute = min(longest_attribute, 11);
    
        d = tmp_20k_buf;
        for(p = parameter_list; p != NULL; p = p->next) {
            sprintf(d, "%-*s: %s\n", longest_attribute,
                    p->attribute != NULL ? p->attribute : "",
                    p->value     != NULL ? p->value     : "");
            d += strlen(d);
        }
    }
    return(tmp_20k_buf);
}
