
//  ------------------------------------------------------------------
//  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 <vector>
#include <algorithm>
#include <golded.h>
#include <gwinput.h>
#include <gftnall.h>
#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 = AA->adat->usestylies + AA->adat->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) {
    AA->adat->usestylies = make_bool(setting & 1);
    AA->adat->hidestylies = make_bool(setting & 2);
  }
}


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

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();
    uint32_t* replies = (uint32_t*)throw_calloc(list_max+1, sizeof(uint32_t));
    uint32_t 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, " -%u", replyto);
    for(int replyn=0,plus=0; replyn<list_max+1; replyn++)
      if(replies[replyn])
        ptr += sprintf(ptr, " %s%u", (plus++?"":"+"), replies[replyn]);
    if(replynext)
      sprintf(ptr, " *%u", 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 (CFG->disppmfirst && 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 (CFG->disppmfirst && 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) != -1))
      reader_msglistfirst = true;
    else
      reader_msglistfirst = false;
  }
}


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

void DosShell() {

  char * shell = getenv(GOLD_SHELL_ENV);
  if( shell && *shell )
    ShellToDos(shell, LNG->DOS_Shell, LGREY_|_BLACK, YES);
}


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

void TouchNetscan(int popup)
{
  gfile fp(AddPath(CFG->areapath, CFG->semaphore.netscan), "wb", CFG->sharemode);
  if (fp.isopen())
  {
    if (popup)
    {
      w_infof(" %s ", CFG->semaphore.netscan);
      HandleGEvent(EVTT_JOBDONE);
      waitkeyt(10000);
      w_info(NULL);
    }
  }
}


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

int ExternUtil(GMsg *msg, const ExtUtil &extutil)
{
  Path editorfile, tmpfile, buf;
  std::string cmdline = extutil.cmdline;
  int mode = (extutil.options & EXTUTIL_KEEPCTRL) ? MODE_SAVE : MODE_SAVENOCTRL;

  if(striinc("@tmpfile", cmdline.c_str()))
  {
    if( (*CFG->temppath == NUL ) or is_dir(CFG->temppath) )
    {
      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
    {
      w_info(LNG->TMPinvalidpath);
      SayBibi();
      waitkeyt(10000);
      w_info("");
      return NO;
    }
  }
  else
    tmpfile[0] = NUL;

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

  SaveLines(mode, editorfile, msg, 79);
  strcpy(buf, editorfile);
  strchg(buf, GOLD_WRONG_SLASH_CHR, GOLD_SLASH_CHR);
  strischg(cmdline, "@file", buf);

  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.c_str(), "",
    extutil.options & EXTUTIL_CLS ? LGREY_|_BLACK : BLACK_|_BLACK,
    extutil.options & EXTUTIL_CURSOR,
    pauseval
  );

  if(extutil.options & EXTUTIL_RELOAD) {

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

    bool 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, uint32_t utilno)
{
  std::vector<ExtUtil>::iterator it = CFG->externutil.begin();
  std::vector<ExtUtil>::iterator end = CFG->externutil.end();
  
  for (; it != end; it++)
  {
    if (it->utilno != utilno) continue;
    return ExternUtil(msg, *it);
  }

  SayBibi();
  return YES;
}


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

void ExternUtilMenu(GMsg* msg)
{
  static int startat = 0;
  static char cmdline[1024] = "";
  gstrarray Listi;

  Listi.push_back(LNG->EnterCmdLine);

  std::vector<ExtUtil>::iterator it = CFG->externutil.begin();
  std::vector<ExtUtil>::iterator end = CFG->externutil.end();
  for (; it != end; it++)
  {
    char buf[100];
    sprintf(buf, " %02d %.59s%s ", it->utilno, it->cmdline.c_str(), (it->cmdline.length() > 59) ? ">" : "" );
    Listi.push_back(buf);
  }

  size_t n = MinV(Listi.size(), (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;
        extutil.cmdline = cmdline;
        extutil.options = CFG->externoptions;
        reader_keyok = ExternUtil(msg, extutil);
      }
    }
    else
    {
      reader_keyok = ExternUtil(msg, CFG->externutil[n-1]);
    }
  }
}


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

static bool PeekURLCmp(const std::string &a, const std::string b)
{
  return stricmp(a.c_str(), b.c_str()) < 0;
}


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

void ReadPeekURLs(GMsg* msg)
{
  if( CFG->urlhandler.empty() )
  {
    w_info(LNG->PeekInfoNoHandler);
    waitkeyt(10000);
    w_info(NULL);
    return;
  }
  w_info(LNG->Wait);

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

  Line **lin = msg->line;
  std::vector<char *> urls;

  for (int n = tline; n < msg->lines; n++)
  {
    const char *ptr = lin[n]->txt.c_str();

    while (*ptr)
    {
      const char *urlBegin = url_begin(ptr);
      if (urlBegin and not strneql(ptr, "mailto:", 7))
      {
        const char *urlEnd = urlBegin + strcspn(urlBegin, " \t\"\'<>()[]");
        if (ispunct(urlEnd[-1]) and (urlEnd[-1] != '/'))
          --urlEnd;

        if (urlBegin < urlEnd)
        {
          char* bufurl=(char*)throw_malloc(urlEnd - ptr + 3);
          bufurl[0] = ' ';
          strxcpy(bufurl + 1, ptr, (urlEnd - ptr) + 1);
          strcat(bufurl, " ");

          std::vector<char *>::iterator it = urls.begin();
          std::vector<char *>::iterator end = urls.end();
          for (; it != end; it++)
          {
            if (strieql(*it, bufurl))
              break;
          }

          if (it == end)
            urls.push_back(bufurl);
          else
            throw_free(bufurl);

          ptr = urlEnd - 1;
        }
      }

      ptr++;
    }
  }

  w_info(NULL);

  if (not urls.empty())
  {
    gstrarray Listi;

    std::vector<char *>::iterator it = urls.begin();
    std::vector<char *>::iterator end = urls.end();
    for (; it != end; it++)
    {
      Listi.push_back(*it);
    }

    // Sort list if requested
    if (CFG->peekurloptions & FREQ_SORT)
      std::sort(Listi.begin(), Listi.end(), PeekURLCmp);

    // Run the picker
    size_t n = MinV(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)
    {
      std::vector<UrlHandler>::iterator it = CFG->urlhandler.begin();
      std::vector<UrlHandler>::iterator end = CFG->urlhandler.end();
#if 0   // will be implemented later
      for (; it != end; it++)
      {
        if (it->scheme.match(Listi[n].c_str(), gregex::extended | gregex::icase))
          break;
      }
#else
      it = end;
#endif

      if (it == end) it = CFG->urlhandler.begin();

      std::string cmdline = it->handler.cmdline;
      std::string buf = "\"" + strtrim(strltrim(Listi[n])) + "\"";
      strischg(cmdline, "@url", buf.c_str());

      buf = CFG->goldpath;
      strchg(buf, GOLD_WRONG_SLASH_CHR, GOLD_SLASH_CHR);
      strischg(cmdline, "@path", buf.c_str());

      TokenXlat(MODE_NEW, cmdline, msg, msg, CurrArea);

      int pauseval = 0;
      if (it->handler.options & EXTUTIL_PAUSEONERROR)
        pauseval = -1;

      if (it->handler.options & EXTUTIL_PAUSE)
        pauseval = 1;

      ShellToDos(cmdline.c_str(), "",
        it->handler.options & EXTUTIL_CLS ? LGREY_|_BLACK : BLACK_|_BLACK,
        it->handler.options & EXTUTIL_CURSOR,
        pauseval
      );
    }
  }
  else
  {
    w_info(LNG->PeekInfoNoURLs);
    waitkeyt(10000);
    w_info(NULL);
  }
  while( !urls.empty() )
  {
    throw_free(urls.back());
    urls.pop_back();
  }
}


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

void UUDecode(GMsg* msg) {

  Path infile, outfile;
  bool ok = false;

  if( not ((*CFG->temppath == NUL ) or is_dir(CFG->temppath)) )
  {
    w_info(LNG->TMPinvalidpath);
    SayBibi();
    waitkeyt(10000);
    w_info("");
    return;
  }

  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, BLACK_|_BLACK, 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)
{
  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));
  gfile fp(userlist, "ab", CFG->sharemode);
  if (fp.isopen())
  {
    w_progress(MODE_NEW, C_INFOW, 0, AA->Msgn.Count(), LNG->GenUserlist);
    for(n=AA->Msgn.Count(); n; n--) {
      update_statuslinef(LNG->ReadingMsg, "ST_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);
        fp.Printf("%-36.36s%24.24s\r\n", userline, adrs);
      }
    }
    throw_free(crclist);
    w_progress(MODE_QUIT, BLACK_|_BLACK, 0, 0, NULL);
  }

  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(reportfile, "wt");
  if (fp.isopen())
  {
    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, "ST_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, BLACK_|_BLACK, 0, 0, NULL);
    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);
  }
}


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

