//  ------------------------------------------------------------------
//  GoldED+
//  Copyright (C) 1990-1999 Odinn Sorensen
//  Copyright (C) 1999-2000 Alexander S. Aganichev
//  ------------------------------------------------------------------
//  This program is free software; you can redistribute it and/or
//  modify it under the terms of the GNU General Public License as
//  published by the Free Software Foundation; either version 2 of the
//  License, or (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 59 Temple Place, Suite 330, Boston,
//  MA 02111-1307 USA
//  ------------------------------------------------------------------
//  $Id$
//  ------------------------------------------------------------------
//  Reader secondary functions
//  ------------------------------------------------------------------

#include <golded.h>
#include <gwinput.h>
#include <gftnall.h>
#include <vector>
#define PROTOTYPES
#include <uudeview.h>


//  ------------------------------------------------------------------
//  Global data from GEREAD

extern GMsg* reader_msg;
extern bool reader_msglistfirst;


//  ------------------------------------------------------------------

void ChangeAttributes() {

  if(reader_msg->msgno) {

    // Reload if hexdumped
    if(AA->attr().hex()) {
      AA->attr().hex0();
      AA->LoadMsg(reader_msg, reader_msg->msgno, CFG->dispmargin-(int)CFG->switches.get(disppagebar));
    }

    GMsg* msg = (GMsg*)throw_malloc(sizeof(GMsg));
    memcpy(msg, reader_msg, sizeof(GMsg));
    AskAttributes(reader_msg);
    if(memcmp(msg, reader_msg, sizeof(GMsg))) {
      msg->charsetlevel = LoadCharset(CFG->xlatlocalset, msg->charset);
      DoKludges(MODE_CHANGE, reader_msg, GKLUD_FLAGS);
      reader_msg->LinesToText();
      AA->SaveMsg(GMSG_UPDATE, reader_msg);
    }
    throw_free(msg);
  }
}


//  ------------------------------------------------------------------

void DecMargin() {
  
  if(CFG->dispmargin > (MAXCOL/3))
    CFG->dispmargin--;
  else {
    SayBibi();
    reader_keyok = YES;
  }
}


//  ------------------------------------------------------------------

void IncMargin() {

  if(CFG->dispmargin < MAXCOL)
    CFG->dispmargin++;
  else {
    SayBibi();
    reader_keyok = YES;
  }
}

//  ------------------------------------------------------------------

void ToggleMark() {

  uint tmp = AA->Mark.Find(reader_msg->msgno);
  if(tmp)
    AA->Mark.DelReln(tmp);
  else
    AA->Mark.Add(reader_msg->msgno);
  HeaderView->Use(AA, reader_msg);
  HeaderView->Paint();
  reader_keyok = YES;
}


//  ------------------------------------------------------------------

void ToggleBookMark() {

  if(AA->bookmark == reader_msg->msgno)
    AA->bookmark = 0;
  else
    AA->bookmark = reader_msg->msgno;
  HeaderView->Use(AA, reader_msg);
  HeaderView->Paint();
  reader_keyok = YES;
}


//  ------------------------------------------------------------------

void ToggleHiddKlud() {

  AA->ToggleViewhidden();
  AA->ToggleViewkludge();
  MsgLineReIndex(reader_msg);
  reader_topline = 0;
  reader_keyok = YES;
}


//  ------------------------------------------------------------------

void ToggleKludge() {

  AA->ToggleViewkludge();
  MsgLineReIndex(reader_msg);
  reader_topline = 0;
  reader_keyok = YES;
}


//  ------------------------------------------------------------------

void ToggleHidden() {

  AA->ToggleViewhidden();
  MsgLineReIndex(reader_msg);
  reader_topline = 0;
  reader_keyok = YES;
}


//  ------------------------------------------------------------------

void ToggleStripHTML() {

  AA->ToggleStripHTML();
  MsgLineReIndex(reader_msg);
  reader_topline = 0;
}


//  ------------------------------------------------------------------

void ToggleQuote() {

  AA->ToggleViewquote();
  MsgLineReIndex(reader_msg);
  reader_topline = 0;
  reader_keyok = YES;
}


//  ------------------------------------------------------------------

void ToggleROT13() {

  if(AA->Msgn.Count()) {
    reader_msg->attr.rotX();
    Rot13(reader_msg);
    reader_keyok = YES;
  }
}


//  ------------------------------------------------------------------

void TogglePageBar() {

  CFG->switches.set(disppagebar, not CFG->switches.get(disppagebar));
}


//  ------------------------------------------------------------------

void ToggleRealMsgno() {

  CFG->switches.set(disprealmsgno, not CFG->switches.get(disprealmsgno));
  HeaderView->Use(AA, reader_msg);
  HeaderView->Paint();
  reader_keyok = YES;
}


//  ------------------------------------------------------------------

void ToggleTwits() {

  gkey k;

  do {
    switch(AA->Twitmode()) {
      case TWIT_SHOW:
        AA->SetTwitmode(TWIT_BLANK);
        w_info(LNG->TwitBlanked);
        break;
      case TWIT_BLANK:
        AA->SetTwitmode(TWIT_SKIP);
        w_info(LNG->TwitSkipped);
        break;
      case TWIT_SKIP:
        AA->SetTwitmode(TWIT_IGNORE);
        w_info(LNG->TwitIgnoreSkip);
        break;
      case TWIT_IGNORE:
        AA->SetTwitmode(TWIT_KILL);
        w_info(LNG->TwitKilled);
        break;
      case TWIT_KILL:
        AA->SetTwitmode(TWIT_SHOW);
        w_info(LNG->TwitDisplayed);
        break;
    }

    k = waitkeyt(3000);

  } while(k and k != Key_Ent and k != Key_Esc);

  w_info(NULL);

  if(k == Key_Esc)
    AA->SetTwitmode(CFG->twitmode);
  else
    CFG->twitmode = AA->Twitmode();
}


//  ------------------------------------------------------------------

void ToggleStyles() {

  gkey k;
  int setting = CFG->usestylies + CFG->hidestylies*2;

  do {
    switch(setting) {
      case 2:
      case 0: w_info(LNG->StylecodesYes);  setting = 1; break;
      case 1: w_info(LNG->StylecodesHide); setting = 3; break;
      case 3: w_info(LNG->StylecodesNo);   setting = 0; break;
    }
    k = waitkeyt(3000);
  } while (k and k != Key_Ent and k != Key_Esc);
  w_info(NULL);

  if(k != Key_Esc) {
    CFG->usestylies = (setting & 1) ? true : false;
    CFG->hidestylies = (setting & 2) ? true : false;
  }
}


//  ------------------------------------------------------------------

void GotoMsgno() {

  if(AA->Msgn.Count()) {

    // Set the active window
    int lastwh = whandle();
    int inhdr = AA->Msglistheader() or not _in_msglist;

    if(reader_keycode < KK_Macro)
      if(gkbd.kbuf == NULL)
        kbput(reader_keycode); // Put back the key

    char buf[150];
    char* ptr = buf;
    int list_max = reader_msg->link.list_max();
    ulong* replies = (ulong*)throw_calloc(list_max+1, sizeof(ulong));
    ulong replyto, replynext;
    if(CFG->switches.get(disprealmsgno)) {
      ptr += sprintf(ptr, " %-5.5s:       %s %u", LNG->MsgReal, LNG->of, AA->Msgn.Count());
      replyto = reader_msg->link.to();
      replies[0] = reader_msg->link.first();
      replynext = reader_msg->link.next();
      for(int replyn=1; replyn<list_max+1; replyn++)
        replies[replyn] = reader_msg->link.list(replyn-1);
    }
    else {
      ptr += sprintf(ptr, " %-5.5s:       %s %u", LNG->Msg, LNG->of, AA->Msgn.Count());
      replyto = AA->Msgn.ToReln(reader_msg->link.to());
      replies[0] = AA->Msgn.ToReln(reader_msg->link.first());
      replynext = AA->Msgn.ToReln(reader_msg->link.next());
      for(int replyn=1; replyn<list_max+1; replyn++)
        replies[replyn] = AA->Msgn.ToReln(reader_msg->link.list(replyn-1));
    }
    if(replyto)
      ptr += sprintf(ptr, " -%lu", replyto);
    for(int replyn=0,plus=0; replyn<list_max+1; replyn++)
      if(replies[replyn])
        ptr += sprintf(ptr, " %s%lu", (plus++?"":"+"), replies[replyn]);
    if(replynext)
      sprintf(ptr, " *%lu", replynext);

    if(inhdr) {
      HeaderView->window.activate_quick();
    }
    else {
      wopen_(0, 0, 3, strlen(buf)+3, W_BHEAD, C_HEADB, C_HEADW);
      w_shadow();
      wtitle(LNG->EnterMsgno, TCENTER, C_HEADT);
    }

    int wrow = inhdr ? 1 : 0;
    wprints(wrow, 0, C_HEADW, buf);
    *buf = NUL;

    gwindow iwindow(whandle());

    std::string fbuf = buf;
    gwinput2 iform(iwindow);
    iform.setup(C_HEADW, C_HEADW, C_HEADI, _box_table(W_BHEAD, 13), true); 
    iform.add_field(0, wrow, 8, 5, fbuf, 20, gwinput::cvt_none, gwinput::entry_new);
    vcurshow();
    iform.run(H_InputMsgno);
    vcurhide();
    if(iform.dropped)
      fbuf = "";

    long active = AA->Msgn.Count();
    long msgno = atol(fbuf.c_str());

    if(CFG->switches.get(disprealmsgno))
      if(msgno > 0)
        msgno = AA->Msgn.ToReln(msgno);

    if((msgno < 0) and ((active+msgno) > 0))
      msgno = active + msgno;

    if(RngV(msgno, 1L, active))
      AA->set_lastread((uint)msgno);

    if(inhdr)
      wactiv_(lastwh);
    else
      wclose();

    throw_free(replies);
  }
}


//  ------------------------------------------------------------------

void PrevArea() {

  AA->attr().hex0();
  AA->Close();
  int currarea = AL.AreaIdToNo(CurrArea);
  int testarea = currarea;
  while(testarea > 0) {
    testarea--;
    if(not AL[testarea]->isseparator()) {
      currarea = testarea;
      break;
    }
  }
  if(currarea == testarea) {
    AL.SetActiveAreaNo(currarea);
    OrigArea = CurrArea;
  }
  else
    HandleGEvent(EVTT_ENDOFMSGS);
  AA->Open();
  AA->RandomizeData();
  AA->SetBookmark(AA->lastread());
  if(AA->PMrk.Tags()) {
    AA->isreadpm = false;
    ToggleMarkRead();
  }
}


//  ------------------------------------------------------------------

void NextArea() {

  AA->attr().hex0();
  AA->Close();
  uint currarea = AL.AreaIdToNo(CurrArea);
  uint testarea = currarea;
  while(testarea < (AL.size()-1)) {
    testarea++;
    if(not AL[testarea]->isseparator()) {
      currarea = testarea;
      break;
    }
  }
  if(currarea == testarea) {
    AL.SetActiveAreaNo(currarea);
    OrigArea = CurrArea;
  }
  else
    HandleGEvent(EVTT_ENDOFMSGS);
  AA->Open();
  AA->RandomizeData();
  AA->SetBookmark(AA->lastread());
  if(AA->PMrk.Tags()) {
    AA->isreadpm = false;
    ToggleMarkRead();
  }

}


//  ------------------------------------------------------------------

void QuitNow() {

  AA->attr().hex0();
  reader_finished = YES;
  reader_done = YES;
}


//  ------------------------------------------------------------------

void ExitAsk() {

  AA->attr().hex0();
  GMenuQuit MenuQuit;
  reader_done = reader_finished = MenuQuit.Run();
}


//  ------------------------------------------------------------------

void NewArea(bool jumpnext) {
  
  AA->attr().hex0();
  if(AA->isopen())
    AA->Close();

  int newarea = CurrArea;

  if(jumpnext) {
    uint idx = AL.AreaIdToNo(CurrArea);
    uint oidx = idx;

    do {
      idx++;

      // Wrap around
      if(idx >= AL.size())
        idx = 0;

      // Exit loop if no area with new mail found
      if(idx == oidx) {
        newarea = AreaPick(LNG->NewArea, 0, &newarea);
        if(newarea != -1)
          idx = AL.AreaIdToNo(newarea);
        break;
      }

    } while(AL.AreaNoToPtr(idx)->unread == 0);

    CurrArea = AL.AreaNoToId(idx);
  }
  else {
    newarea = AreaPick(LNG->NewArea, 0, &newarea);
    if(newarea != -1)
      CurrArea = newarea;
  }

  AL.Sort();
  reader_done = YES;

  if(gkbd.quitall)
    QuitNow();
  else {
    AL.SetActiveAreaId(CurrArea);
    OrigArea = CurrArea;
    strcpy(stecho, AA->echoid());
    AA->RandomizeData();
    if(AA->Msglistfirst()) {
      kbput(KK_ReadMessageList);
      reader_msglistfirst = true;
    }
  }
}


//  ------------------------------------------------------------------

void DosShell() {

  ShellToDos(getenv(GOLD_SHELL_ENV), LNG->DOS_Shell, LGREY|_BLACK, YES);
}


//  ------------------------------------------------------------------

void TouchNetscan(int popup) {

  FILE* fp = fsopen(AddPath(CFG->areapath, CFG->semaphore.netscan), "wb", CFG->sharemode);
  if(fp) {
    fclose(fp);
    if(popup) {
      w_infof(" %s ", CFG->semaphore.netscan);
      HandleGEvent(EVTT_JOBDONE);
      waitkeyt(10000);
      w_info(NULL);
    }
  }
}


//  ------------------------------------------------------------------

int ExternUtil(GMsg *msg, ExtUtil *extutil) {

  Path editorfile, tmpfile, buf;
  strxcpy(editorfile, AddPath(CFG->goldpath, EDIT->File()), sizeof(Path));

  char cmdline[1024];
  strxcpy(cmdline, extutil->cmdline, sizeof(cmdline));

  int mode = (extutil->options & EXTUTIL_KEEPCTRL) ? MODE_SAVE : MODE_SAVENOCTRL;
  SaveLines(mode, editorfile, msg, 79);
  strcpy(buf, editorfile);
  strchg(buf, GOLD_WRONG_SLASH_CHR, GOLD_SLASH_CHR);
  strischg(cmdline, "@file", buf);
  if(striinc("@tmpfile", cmdline)) {
    mktemp(strcpy(tmpfile, AddPath(CFG->temppath, "GDXXXXXX")));
    SaveLines(mode, tmpfile, msg, 79);
    strcpy(buf, tmpfile);
    strchg(buf, GOLD_WRONG_SLASH_CHR, GOLD_SLASH_CHR);
    strischg(cmdline, "@tmpfile", buf);
  }
  else
    tmpfile[0] = NUL;
  strcpy(buf, CFG->goldpath);
  strchg(buf, GOLD_WRONG_SLASH_CHR, GOLD_SLASH_CHR);
  strischg(cmdline, "@path", buf);
  TokenXlat(MODE_NEW, cmdline, msg, msg, CurrArea);

  int pauseval = 0;
  if(extutil->options & EXTUTIL_PAUSEONERROR)
    pauseval = -1;
  if(extutil->options & EXTUTIL_PAUSE)
    pauseval = 1;

  ShellToDos(cmdline, "",
    extutil->options & EXTUTIL_CLS ? LGREY|_BLACK : 0,
    extutil->options & EXTUTIL_CURSOR,
    pauseval
  );

  if(extutil->options & EXTUTIL_RELOAD) {

    if(not (extutil->options & EXTUTIL_KEEPCTRL)) {
      if(*msg->tearline or *msg->origin) {
        gfile fp;
        fp.fopen(editorfile, "at");
        if(fp.isopen()) {
          if(*msg->tearline)
            fp.printf("--- %s\n", msg->tearline);
          if(*msg->origin)
            fp.printf(" * Origin: %s\n", msg->origin);
          fp.fclose();
        }
      }
    }

    int hardlines = EDIT->HardLines();
    EDIT->HardLines(false);
    LoadText(msg, editorfile);
    EDIT->HardLines(hardlines);

    if(not strstr(msg->txt, "\001CHRS:") and not strstr(msg->txt, "\001CHARSET:")) {
      // Prepend "@CHRS: XLATLOCALSET 2\n" to force proper encoding
      const char *charset = AA->Viewkludge() ? msg->charset : CFG->xlatlocalset;
      char *msg_txt_chrs = (char *)throw_malloc(strlen(msg->txt)+strlen(charset)+sizeof("\001CHRS:  2\r")+16);
      sprintf(msg_txt_chrs, "\001CHRS: %s 2\r%s", charset, msg->txt);
      throw_free(msg->txt);
      msg->txt = msg_txt_chrs;
    }

    // Ignore any kludge address found
    msg->TextToLines(CFG->dispmargin-(int)CFG->switches.get(disppagebar), false);

    if(AA->Viewkludge()) {
      // Try to convert to the original codepage
      char *inpcharset = msg->charset;
      int level = LoadCharset(CFG->xlatlocalset, IsQuotedPrintable(inpcharset) ? ExtractPlainCharset(inpcharset) : inpcharset);
      if(level) {
        // do recode
        char *msg_txt_chrs = (char *)throw_malloc(strlen(msg->txt)*3+16);
        XlatStr(msg_txt_chrs, msg->txt, level, CharTable);
        throw_free(msg->txt);
        msg->txt = (char *)throw_realloc(msg_txt_chrs, strlen(msg_txt_chrs)+16);
      }
    }
    msg->charsetlevel = LoadCharset(msg->charset, CFG->xlatlocalset);
  }

  if(extutil->options & EXTUTIL_WIPE)
    WipeFile(editorfile, 0);
  if(tmpfile[0] != NUL)
    WipeFile(tmpfile, 0);

  return YES;
}


//  ------------------------------------------------------------------

int ExternUtil(GMsg *msg, int utilno) {

  std::vector<ExtUtil>::iterator extutil = CFG->externutil.begin();

  for(int utlno=0; extutil != CFG->externutil.end(); utlno++, extutil++) {

    if(extutil->utilno == utilno) {
      return ExternUtil(msg, &(*extutil));
    }
  }

  SayBibi();
  return YES;
}


//  ------------------------------------------------------------------

void ExternUtilMenu(GMsg* msg) {
  int n;
  std::vector<ExtUtil>::iterator i;
  static int startat = 0;
  char** Listi;
  char buf[100];
  static char cmdline[1024] = "";

  Listi = (char**)throw_calloc(CFG->externutil.size()+2, sizeof(char*));
  Listi[0] = throw_strdup(LNG->EnterCmdLine);
  for(i = CFG->externutil.begin(), n=1; i != CFG->externutil.end(); n++, i++) {
    sprintf(buf, " %02d %.59s%s ", i->utilno, i->cmdline, strlen(i->cmdline) > 59 ? ">" : "" );
    Listi[n] = throw_strdup(buf);
  }
  n = MinV(n, (MAXROW-10));
  set_title(LNG->ExternUtil, TCENTER, C_ASKT);
  update_statusline(LNG->SelectExternUtil);
  whelppcat(H_ReadExternUtil);
  n = wpickstr(6, 0, 6+n+1, -1, W_BASK, C_ASKB, C_ASKW, C_ASKS, Listi, startat, title_shadow);
  whelpop();
  if(n != -1) {
    if(n == 0) {
      if(edit_string(cmdline, sizeof(cmdline), LNG->ExecCmdLine, H_ReadExternUtil)) {
        ExtUtil extutil;
        extutil.utilno = 0;
        strxcpy(extutil.cmdline, cmdline, sizeof(extutil.cmdline));
        extutil.options = CFG->externoptions;
        reader_keyok = ExternUtil(msg, &extutil);
      }
    }
    else {
      reader_keyok = ExternUtil(msg, &CFG->externutil[n-1]);
    }
  }
  for(n=CFG->externutil.size()+1; n; n--)
    throw_free(Listi[n-1]);
  throw_free(Listi);
}


//  ------------------------------------------------------------------

static int PeekURLCmp(const char** a, const char** b) {

  return stricmp(*a, *b);
}


//  ------------------------------------------------------------------

void ReadPeekURLs(GMsg* msg) {

  Line **lin = msg->line;
  const char *ptr, *end, *begin;
  char buf[256];
  std::vector<char *> urls;
  std::vector<char *>::iterator i;
  int n;

  w_info(LNG->Wait);

  // Scan the current msg for urls
  int tline = reader_topline;
  if(CFG->peekurloptions & PEEK_FROMTOP)
    tline = 0;
  for(n=tline; n<msg->lines; n++) {

    ptr = lin[n]->txt.c_str();

    while(*ptr) {
      if(((begin = url_begin(ptr)) != NULL) and not strneql(ptr, "mailto:", 7)) {
        end = begin+strcspn(begin, " \t\"\'<>()[]");
        if(ispunct(end[-1]) and (end[-1] != '/'))
          --end;
        if(begin < end) {
          buf[0] = ' ';
          strxcpy(buf+1, ptr, MinV((long)((end-ptr)+1), (long)(MAXCOL-2-2-2)));
          strcat(buf, " ");
          for(i = urls.begin(); i != urls.end(); i++)
            if(strieql(*i, buf))
              break;
          if(i == urls.end())
            urls.push_back(throw_strdup(buf));
          ptr = end-1;
        }
      }
      ptr++;
    }
  }

  w_info(NULL);

  if(not urls.empty()) {

    char** Listi = (char**)throw_calloc(urls.size()+1, sizeof(char*));

    for(n = 0, i = urls.begin(); i != urls.end(); i++, n++)
      Listi[n] = *i;

    // Sort list if requested
    if(CFG->peekurloptions & FREQ_SORT)
      qsort(Listi, urls.size(), sizeof(char*), (StdCmpCP)PeekURLCmp);

    // Run the picker
    n = MinV((int)urls.size(), (MAXROW-10));
    set_title(LNG->PeekURLMenuTitle, TCENTER, C_ASKT);
    update_statusline(LNG->PeekURLStat);
    whelppcat(H_PeekURL);
    n = wpickstr(6, 0, 6+n+1, -1, W_BASK, C_ASKB, C_ASKW, C_ASKS, Listi, 0, title_shadow);
    whelpop();

    if(n != -1) {
      char cmdline[1024];
      strxcpy(cmdline, CFG->urlhandler.cmdline, sizeof(cmdline));
      strxmerge(buf, sizeof(buf), "\"", strtrim(strltrim(Listi[n])), "\"", NULL);
      strischg(cmdline, "@url", buf);
      strxcpy(buf, CFG->goldpath, sizeof(buf));
      strchg(buf, GOLD_WRONG_SLASH_CHR, GOLD_SLASH_CHR);
      strischg(cmdline, "@path", buf);
      TokenXlat(MODE_NEW, cmdline, msg, msg, CurrArea);

      int pauseval = 0;
      if(CFG->urlhandler.options & EXTUTIL_PAUSEONERROR)
        pauseval = -1;
      if(CFG->urlhandler.options & EXTUTIL_PAUSE)
        pauseval = 1;

      ShellToDos(cmdline, "", 
        CFG->urlhandler.options & EXTUTIL_CLS ? LGREY|_BLACK : 0,
        CFG->urlhandler.options & EXTUTIL_CURSOR,
        pauseval
      );
    }

    for(n = 0, i = urls.begin(); i != urls.end(); i++, n++)
      throw_free(Listi[n]);
    throw_free(Listi);
  }
  else {
    w_info(LNG->PeekInfoNoURLs);
    waitkeyt(10000);
    w_info(NULL);
  }
}


//  ------------------------------------------------------------------

void UUDecode(GMsg* msg) {

  Path infile, outfile;
  bool ok = false;

  if((*CFG->uudecodepath == NUL) or is_dir(CFG->uudecodepath)) {

    mktemp(strcpy(infile, AddPath(CFG->temppath, "GDXXXXXX")));
    strcpy(outfile, CFG->uudecodepath);

    GMenuDomarks MenuDomarks;

    int source = AA->Mark.Count() ? MenuDomarks.Run(LNG->Decode) : WRITE_CURRENT;
    bool overwrite = true;

    if(source == WRITE_QUIT)
      return;

    bool old_quotespacing = CFG->switches.get(quotespacing);
    CFG->switches.set(quotespacing, false);

    if(source == WRITE_MARKED) {
      for(uint n=0; n<AA->Mark.Count(); n++) {
        if(overwrite and n)
          overwrite = false;  // Overwrite only the first time
        w_progress(MODE_UPDATE, C_INFOW, n+1, AA->Mark.Count(), LNG->Preparing);
        AA->LoadMsg(msg, AA->Mark[n], 79);
        SaveLines(overwrite ? MODE_WRITE : MODE_APPEND, infile, msg, 79);
      }
      if(AA->Mark.Count())
        w_progress(MODE_QUIT, 0, 0, 0, NULL);
    }
    else if(source == WRITE_CURRENT) {
      AA->LoadMsg(msg, msg->msgno, 79);
      SaveLines(MODE_WRITE, infile, msg, 79);
    }

    CFG->switches.set(quotespacing, old_quotespacing);

    uulist* item;
    int i, res;

    w_info(LNG->WaitUUdecoding);

    UUInitialize();
    UUSetOption(UUOPT_SAVEPATH, 0, outfile);
    if((res = UULoadFile(infile, NULL, 0)) == UURET_OK) {

      for (i=0; (item=UUGetFileListItem(i)) != NULL; i++) {
        if ((item->state & UUFILE_OK) == 0)
          continue;
        if ((res = UUDecodeFile(item, NULL)) == UURET_OK) {
          ok = true;
          w_infof(LNG->CompletedUUdecode, item->filename);
        }
      }
    }
    UUCleanUp ();

    remove(infile);

    if(not ok)
      w_info(LNG->NotUUencoded);
  }
  else
    w_info(LNG->UUEinvalidpath);

  if(not ok)
    waitkeyt(10000);

  w_info(NULL);
}


//  ------------------------------------------------------------------

void Make_Userlist(const char* userlist) {

  FILE* fp;
  word* crclist;
  word crc, crcs=0;
  uint n, x;
  char userline[80], adrs[40];

  GMsg* msg = (GMsg*)throw_calloc(1, sizeof(GMsg));

  crclist = (word*)throw_calloc(AA->Msgn.Count()+1, sizeof(word));
  fp = fsopen(userlist, "ab", CFG->sharemode);
  if(fp) {
    w_progress(MODE_NEW, C_INFOW, 0, AA->Msgn.Count(), LNG->GenUserlist);
    for(n=AA->Msgn.Count(); n; n--) {
      update_statuslinef(LNG->ReadingMsg, n, AA->Msgn.Count());
      w_progress(MODE_UPDATE, C_INFOW, (AA->Msgn.Count()-n)+1, AA->Msgn.Count(), NULL);
      AA->LoadMsg(msg, AA->Msgn.CvtReln(n), CFG->dispmargin);
      crc = strCrc16(msg->by, false);
      for(x=0; x<crcs; x++)
        if(crclist[x] == crc)
          break;  // We have already used it
      if(x >= crcs) {
        crclist[crcs++] = crc;
        strrevname(userline, msg->by);
        msg->orig.make_string(adrs);
        fprintf(fp, "%-36.36s%24.24s\r\n", userline, adrs);
      }
    }
    throw_free(crclist);
    w_progress(MODE_QUIT, 0, 0, 0, NULL);
    fclose(fp);
  }

  ResetMsg(msg);
  throw_free(msg);
}


//  ------------------------------------------------------------------

void MakeUserList() {

  if(AA->Msgn.Count()) {
    uint temp = AA->lastread();
    if(not edit_pathname(CFG->userlistfile, sizeof(Path), LNG->UserlistName, 0))
      return;
    Make_Userlist(AddPath(CFG->goldpath, CFG->userlistfile));
    AA->set_lastread(temp);
  }
}


//  ------------------------------------------------------------------

void TouchSemaphore() {

  Path file;
  *file = NUL;
  if(edit_string(file, sizeof(Path), LNG->TouchSemaphore, 0))
    TouchFile(file);
}


//  ------------------------------------------------------------------

void make_pathreport(const char* reportfile) {

  gfile fp;
  fp.fopen(reportfile, "wt");
  if(fp) {
    std::string path;
    ftn_addr address;
    std::vector<ftn_addr> alist;
    GMsg* msg = (GMsg*)throw_calloc(1, sizeof(GMsg));
    w_progress(MODE_NEW, C_INFOW, 0, AA->Msgn.Count(), "Generating PATH report");
    for(int n=AA->Msgn.Count(); n>=AA->lastread(); n--) {
      update_statuslinef(LNG->ReadingMsg, n, AA->Msgn.Count());
      w_progress(MODE_UPDATE, C_INFOW, n, AA->Msgn.Count(), NULL);
      AA->LoadMsg(msg, AA->Msgn.CvtReln(n), CFG->dispmargin-(int)CFG->switches.get(disppagebar));
      address = msg->orig;
      int x;
      for(x=0; x<alist.size(); x++) {
        if(alist[x] == address)
          break;  // We have already used it
      }
      if(x >= alist.size()) {
        alist.push_back(address);
        INam buf;
        strcpy(buf, msg->By());
        strchg(buf, ' ', '_');
        std::string temp;
        fp.printf("%s  %s  ", buf, address.make_string(temp).c_str());
        path = "";
        Line* line = msg->lin;
        while(line) {
          if(line->kludge == GKLUD_PATH)
            path += line->txt.c_str() + 6;
          line = line->next;
        }
        if(path.length()) {
          gstrarray links;
          tokenize(links, path.c_str(), " ");
          for(int i=0; i<links.size(); i++)
            address = links[i];
        }
        fp.printf("%s\n", path.c_str());
      }
    }
    w_progress(MODE_QUIT, 0, 0, 0, NULL);
    fp.fclose();
    ResetMsg(msg);
    throw_free(msg);
  }
}


//  ------------------------------------------------------------------

void MakePathreport() {

  if(AA->Msgn.Count()) {
    uint temp = AA->lastread();
    if(not edit_pathname(CFG->pathreportfile, sizeof(Path), LNG->Pathreport, 0))
      return;
    make_pathreport(AddPath(CFG->goldpath, CFG->pathreportfile));
    AA->set_lastread(temp);
  }
}


//  ------------------------------------------------------------------