
//  ------------------------------------------------------------------
//  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$
//  ------------------------------------------------------------------
//  Mega-functions to handle creation of msgs :-(
//  ------------------------------------------------------------------

#include <golded.h>


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

static std::vector<int> post_xparea;
bool akamatchreply = false;


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

std::string &strtrimline(std::string &p)
{

    if(not p.empty())
    {
        std::string::iterator trail = p.end();
        while(trail != p.begin())
        {
            --trail;
            if(not strchr(" \t\r\n", *trail) and not issoftcr(*trail))
            {
                ++trail;
                break;
            }
        }
        p.erase(trail, p.end());
    }
    return p;
}


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

const char* get_subject_re_info(const char* s, uint32_t& n)
{

    if(g_toupper(*s) == 'R' and g_tolower(s[1]) == 'e')
    {

        if(s[2] == ':')
        {
            n = 1;
            return s + 3;
        }
        else if(s[2] == '^' and isdigit(s[3]))
        {
            const char* d = s + 4;
            while(isdigit(*d))
                d++;
            if(*d == ':')
            {
                n = (uint32_t)atol(s + 3);
                return d + 1;
            }
        }
    }

    n = 0;
    return NULL;
}


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

void CheckSubject(GMsg* msg, char* subj)
{

    for(int n=0; n<fspecs; n++)
        throw_release(fspec[n].fblk);
    fspecs = specfiles = 0;
    fspec = (FileSpec*)throw_realloc(fspec, sizeof(FileSpec));

    if(AA->isnet())
    {
        if(EDIT->AutoAttach())
        {
            // If the subject was changed
            if(not strieql(subj, msg->re))
            {
                // Check if the subj begins with drive spec
                if(not (msg->attr.frq() or msg->attr.att() or msg->attr.urq()))
                {
                    char* ptr = subj;
                    if(*ptr == '^')
                        ptr++;
#ifdef __UNIX__
                    if((*ptr == '/') and not strchr(ptr, ':'))   /* } */
                    {
#else
                    if((in_range((char)g_tolower(*ptr), (char)'c', (char)'z') and (ptr[1] == ':')) or ((*ptr == '\\') and (ptr[1] == '\\')))
                    {
#endif
                        // Autoattach
                        msg->attr.att1();
                        AttrAdd(&msg->attr, &CFG->attribsattach);
                        DispHeadAttrs(msg);
                    }
                }
            }
        }

        if(msg->attr.frq())
        {
            // Handle File Requests
            if(*subj)
            {
                ISub buf;
                strcpy(buf, subj);
                char* ptr = strtok(buf, " ");
                while(ptr)
                {
                    if(*ptr == '!')     // Store password
                    {
                        if(fspecs)
                            strcpy(fspec[fspecs-1].password, ptr);
                    }
                    else
                    {
                        fspec = (FileSpec*)throw_realloc(fspec, (fspecs+1)*sizeof(FileSpec));
                        memset(fspec+fspecs, 0, sizeof(FileSpec));
                        fspec[fspecs].files = 1;
                        strxcpy(fspec[fspecs].path, ptr, sizeof(Path));
                        specfiles++;
                        fspecs++;
                    }
                    ptr = strtok(NULL, " ");
                }
            }
        }
        else if(msg->attr.att())
        {
            // Handle File Attaching
            ISub buf;
            if(strblank(subj))
            {
                if(*CFG->attachpath)
                    strxcpy(subj, CFG->attachpath, sizeof(ISub));
                else
                    strxcpy(subj, CFG->goldpath, sizeof(ISub));
            }
            strcpy(buf, subj);
            char* ptr = strtok(buf, " ");
            while(ptr)
            {
                fspec = (FileSpec*)throw_realloc(fspec, (fspecs+1)*sizeof(FileSpec));
                memset(fspec+fspecs, 0, sizeof(FileSpec));
                if(*ptr == '^')
                {
                    fspec[fspecs].delsent = YES;
                    ptr++;
                }
                if((ptr == CleanFilename(ptr)) and *CFG->attachpath)
                    strxmerge(fspec[fspecs].path, sizeof(Path), CFG->attachpath, ptr, NULL);
                else
                    strxcpy(fspec[fspecs].path, ptr, sizeof(Path));
                FileSelect(msg, LNG->AttachFiles, &fspec[fspecs]);
                specfiles += fspec[fspecs].files;
                if(fspec[fspecs].files)
                    fspecs++;
                ptr = strtok(NULL, " ");
            }
        }
        else if(msg->attr.urq())
        {
            // Handle Update Requests
            ISub buf;
            if(not *subj)
                strcpy(subj, ".");
            strcpy(buf, subj);
            char* ptr = strtok(buf, " ");
            while(ptr)
            {
                if(*ptr == '!')     // Store password
                {
                    if(fspecs)
                        strcpy(fspec[fspecs-1].password, ptr);
                }
                else
                {
                    fspec = (FileSpec*)throw_realloc(fspec, (fspecs+1)*sizeof(FileSpec));
                    memset(fspec+fspecs, 0, sizeof(FileSpec));
                    strxcpy(fspec[fspecs].path, ptr, sizeof(Path));
                    FileSelect(msg, LNG->UpdreqFiles, &fspec[fspecs]);
                    specfiles += fspec[fspecs].files;
                    if(fspec[fspecs].files)
                        fspecs++;
                }
                ptr = strtok(NULL, " ");
            }
        }

        // Generate the (first) processed subject line
        if (specfiles)
        {
            ISub buf;
            *buf = NUL;
            for (int x = 0,n = 0; x < fspecs; x++)
            {
                for (int m = 0; m<fspec[x].files; m++)
                {
                    ISub subject;
                    gsprintf(PRINTF_DECLARE_BUFFER(subject), "%s%s%s%s%s ", fspec[x].delsent ? "^" : "", ReMapPath(fspec[x].path), fspec[x].fblk ? (fspec[x].fblk[m].name ? fspec[x].fblk[m].name : "") : "", *fspec[x].password ? " " : "", fspec[x].password);

                    if ((strlen(buf) + strlen(subject)) > 71)
                    {
                        n++;
                        break;
                    }
                    else
                        strcat(buf, subject);
                }

                if (n) break;
            }
            strtrim(strcpy(subj, buf));
        }
    }
}


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

static bool have_origin(GMsg *msg)
{

    Line* line = msg->lin;

    while(line)
        if(line->type & GLINE_ORIG)
            return true;
        else
            line = line->next;
    return false;
}

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

static void MakeMsg3(int& mode, GMsg* msg)
{

    int n;
    std::vector<gaka>::iterator u;

    msg->charsetlevel = 0;
    if (*AA->Xlatexport())
    {
        msg->charsetlevel = LoadCharset(CFG->xlatlocalset, AA->Xlatexport());
        if (msg->charsetlevel)
            gsprintf(PRINTF_DECLARE_BUFFER(msg->charset), "%s %d", AA->Xlatexport(),
                CharTable ? CharTable->displaylevel : msg->charsetlevel);
        else
            gsprintf(PRINTF_DECLARE_BUFFER(msg->charset), "%s 2", CFG->xlatlocalset);
    }

    // Do Timefields
    if(msg->attr.fmu())
    {
        time32_t a  = gtime(NULL);
        struct tm tp;
        ggmtime(&tp, &a);
        tp.tm_isdst = -1;
        time32_t b  = gmktime(&tp);
        a += a - b;
        msg->written = a;
        if(AA->havereceivedstamp())
            msg->received = a;
        if(AA->havearrivedstamp())
            msg->arrived = a;
    }

    std::vector<GMsg> carbon;
    int cc = DoCarboncopy(*msg, carbon);

    if(AA->isecho() or have_origin(msg))
        DoTearorig(mode, msg);

    // Do FakeNets
    msg->oorig = msg->orig;
    if(AA->isnet() and msg->orig.point)
    {
        for(u = CFG->aka.begin(); u != CFG->aka.end(); u++)
        {
            if(not memcmp(&(*u), &msg->orig, sizeof(Addr)))
            {
                // Use fakenet to everybody
                if(u->pointnet)
                {
                    msg->oorig.net   = u->pointnet;  // Create fake address
                    msg->oorig.node  = msg->orig.point;
                    msg->oorig.point = 0;
                }
                break;
            }
        }
    }

    DoKludges(mode, msg);

    if(cc)
    {
        std::string temp;
        update_statuslinef(LNG->StatusCC, "ST_STATUSCC", msg->to, msg->dest.make_string(temp).c_str());
    }

    msg->LinesToText();

    if(AA->isnet() and (msg->attr.frq() or msg->attr.att() or msg->attr.urq()) and specfiles >= 1)
        CreateFileMsgs(mode, msg);
    else
    {
        HeaderView->Use(AA, msg);
        HeaderView->Paint();

        // Do zoneGating
        if(AA->isnet() and CFG->zonegating and (msg->dest.zone != msg->orig.zone))
        {
            if(not (msg->attr.dir() or msg->attr.cra() or msg->attr.hld()))
            {
                GMenuZonegate MenuZonegate;
                if(CFG->zonegating == YES or MenuZonegate.Run())
                    ZonegateIt(msg->odest, msg->orig, msg->dest);
            }
        }

        if(msg->odest.net == 0)
            msg->odest = msg->dest;

        HeaderView->Use(AA, msg);
        HeaderView->Paint();
        w_info(LNG->Wait);
        AA->SaveMsg((mode == MODE_CHANGE) ? GMSG_UPDATE : GMSG_NEW, msg);
        if(not (AA->isecho() and mode == MODE_REPLYCOMMENT))
            update_addressbook(msg);
        w_info(NULL);
    }

    if(AL.AreaEchoToNo(AA->Areayouwroteto())>=0)
    {
        Area* A = AL.AreaEchoToPtr(AA->Areayouwroteto());
        if(AA != A)
        {
            char* back = NULL;
            uint mlen = 0;

            msg->link.reset();
            char* ptr = msg->txt + (*msg->txt == CTRL_A);
            if(not strnieql(ptr, "AREA:", 5))
            {
                uint elen = 6 + strlen(AA->echoid()) + 1;
                mlen = strlen(msg->txt)+1;
                msg->txt = (char*)throw_realloc(msg->txt, elen+mlen);
                back = msg->txt+elen;
                memmove(back, msg->txt, mlen);
                strcpy(msg->txt, "\001AREA:");
                strcpy(msg->txt+6, AA->echoid());
                msg->txt[elen-1] = CR;
            }
            AA->Close();
            A->Open();
            uint32_t oldmsgno = msg->msgno;
            A->NewMsgno(msg);
            A->SaveMsg(GMSG_NEW|GMSG_NOLSTUPD, msg);
            A->Close();
            AA->Open();
            AA->RandomizeData(mode);
            if(back)
                memmove(msg->txt, back, mlen);
            msg->msgno = oldmsgno;
        }
    }

    if(cc)
    {

        Area* A = AA;

        if(AA->isecho())
        {
            AA = AL.AreaEchoToPtr(AA->Areareplyto());
            if(AA and (AA != A))
            {
                AA->Open();
                AA->RandomizeData(mode);
            }
            else
                AA = A;
        }

        for(n=0; n<cc; n++)
        {
            GMsg* cmsg = &carbon[n];
            cmsg->txt   = msg->txt;
            cmsg->lin   = msg->lin;
            cmsg->line  = msg->line;
            cmsg->lines = msg->lines;
            cmsg->messageid = msg->messageid;
            cmsg->inreplyto = msg->inreplyto;
            cmsg->references = msg->references;
            AA->NewMsgno(cmsg);
            {
                std::string temp;
                update_statuslinef(LNG->StatusCC, "ST_STATUSCC", cmsg->to, cmsg->dest.make_string(temp).c_str());
            }

            // Do aka matching
            if(AA->Akamatching())
            {
                int newaka = AkaMatch(&cmsg->orig, &cmsg->dest);
                cmsg->orig = CFG->aka[newaka].addr;
            }

            // Do FakeNets
            cmsg->oorig = cmsg->orig;
            if(AA->isnet() and cmsg->orig.point)
            {
                for(u = CFG->aka.begin(); u != CFG->aka.end(); u++)
                {
                    if(not memcmp(&(*u), &cmsg->orig, sizeof(Addr)))
                    {
                        // Use fakenet to everybody
                        if(u->pointnet)
                        {
                            cmsg->oorig.net   = u->pointnet;  // Create fake address
                            cmsg->oorig.node  = cmsg->orig.point;
                            cmsg->oorig.point = 0;
                        }
                        break;
                    }
                }
            }

            // remove unnessesary To: line from message if present
            for(Line* line = cmsg->lin; line; line = line->next)
                if(strnieql(line->txt.c_str(), "To:", 3))
                    line = DeleteLine(line);
                else if(line->type & GLINE_KLUDGE)
                    continue;
                else
                    break;

            DoKludges(mode, cmsg);

            if(have_origin(cmsg))
                DoTearorig(mode, cmsg);

            cmsg->LinesToText();
            msg->txt   = cmsg->txt;
            msg->lin   = cmsg->lin;
            msg->line  = cmsg->line;
            msg->lines = cmsg->lines;
            msg->messageid  = cmsg->messageid;
            msg->inreplyto  = cmsg->inreplyto;
            msg->references = cmsg->references;
            if(AA->isnet() and (cmsg->attr.frq() or cmsg->attr.att() or cmsg->attr.urq()))
            {
                CreateFileMsgs(MODE_NEW, cmsg);
                msg->txt   = cmsg->txt;
                msg->lin   = cmsg->lin;
                msg->line  = cmsg->line;
                msg->lines = cmsg->lines;
                msg->messageid  = cmsg->messageid;
                msg->inreplyto  = cmsg->inreplyto;
                msg->references = cmsg->references;
            }
            else
            {
                HeaderView->Use(AA, cmsg);
                HeaderView->Paint();

                // Do zoneGating
                if(AA->isnet() and CFG->zonegating and (cmsg->dest.zone != cmsg->orig.zone))
                {
                    if(not cmsg->attr.dir())
                    {
                        GMenuZonegate MenuZonegate;
                        if(CFG->zonegating == YES or MenuZonegate.Run())
                            ZonegateIt(cmsg->odest, cmsg->orig, cmsg->dest);
                    }
                }

                if(cmsg->odest.net == 0)
                    cmsg->odest = cmsg->dest;

                HeaderView->Use(AA, cmsg);
                HeaderView->Paint();
                w_info(LNG->Wait);
                AA->SaveMsg(GMSG_NEW, cmsg);
                w_info(NULL);
                update_addressbook(cmsg);
                msg->txt   = cmsg->txt;
                msg->lin   = cmsg->lin;
                msg->line  = cmsg->line;
                msg->lines = cmsg->lines;
                msg->messageid  = cmsg->messageid;
                msg->inreplyto  = cmsg->inreplyto;
                msg->references = cmsg->references;
            }
        }

        if(A != AA)
        {
            AA->Close();
            AA = A;
            AA->RandomizeData();
        }
        AA->UpdateAreadata();
    }
}


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

static void MakeMsg2(int& mode, int& status, int& forwstat, int& topline, GMsg* msg, GMsg* oldmsg, GMsg* cmpmsg)
{

    uint n;
    char buf[256], buf2[256];
    Line* line;
    Line* newline;

    if(status == MODE_CHANGE or (mode == MODE_FORWARD and status == MODE_SAVE))
    {
        if(mode == MODE_CHANGE)
        {
            if(stricmp(msg->By(), AA->Username().name))
            {
                std::vector<Node>::iterator x;
                for(n = 0, x = CFG->username.begin(); x != CFG->username.end(); n++, x++)
                {
                    if(strieql(x->name, msg->By()))
                    {
                        n = 0;
                        break;
                    }
                }
                if(n)
                    status = MODE_SAVE;
            }
        }
        w_info(LNG->Wait);
        if(mode != MODE_CHANGE)
        {
            if(not CFG->switches.get(emptytearline))
                strcpy(msg->tearline, AA->Tearline());

            if(AA->Taglinesupport())
                strcpy(msg->tagline, AA->Tagline());

            DoTearorig(mode, msg);

            strcpy(msg->organization, AA->Organization());
        }
        if(mode != MODE_CHANGE or status == MODE_SAVE)
        {
            if(TemplateToText(mode, msg, oldmsg, AA->Tpl(), OrigArea))
            {
                HeaderView->Use(AA, msg);
                HeaderView->Paint();
            }
            msg->attr.pos1();
            msg->TextToLines(CFG->dispmargin-1, false); // Ignore any kludge address found
            msg->attr.pos0();
        }
        uint lineno, position = reader_topline+1;
        for(lineno = 0; lineno < msg->lines; lineno++)
        {
            if(not (msg->line[lineno]->type & GLINE_QUOT))
                if(msg->line[lineno]->type & GLINE_POSI)
                    position = lineno+reader_topline+1;
        }
        if(*EDIT->External() and not EDIT->Internal())
        {
            SaveLines(MODE_NEW, AddPath(CFG->goldpath, EDIT->File()), msg, 79);
        }
        int loop = 0;
        w_info(NULL);
        do
        {
            if(loop)
            {
                status = MODE_SAVE;
                forwstat = NO;
            }
            if(mode == MODE_FORWARD)
                status = MODE_SAVE;
            if (*EDIT->External() and forwstat == NO and not EDIT->Internal())
            {
                strcpy(buf, EDIT->External());
                gsprintf(PRINTF_DECLARE_BUFFER(buf2), "%u", position);
                strischg(buf, "@line", buf2);
                strcpy(buf2, AddPath(CFG->goldpath, EDIT->File()));
                strchg(buf2, GOLD_WRONG_SLASH_CHR, GOLD_SLASH_CHR);
                strischg(buf, "@file", buf2);
                long ftbefore = GetFiletime(AddPath(CFG->goldpath, EDIT->File()));
                gsprintf(PRINTF_DECLARE_BUFFER(buf2), LNG->EditCmd, buf);
                ShellToDos(buf, buf2, LGREY_|_BLACK, YES);
                long ftafter = GetFiletime(AddPath(CFG->goldpath, EDIT->File()));
                if(status != MODE_SAVE)
                {
                    status = MODE_SAVE;
                    if(ftbefore == ftafter)
                    {
                        if(streql(msg->by, cmpmsg->by))
                            if(streql(msg->to, cmpmsg->to))
                                if(streql(msg->re, cmpmsg->re))
                                    if(not memcmp(&msg->attr, &cmpmsg->attr, sizeof(Attr)))
                                        status = MODE_QUIT;
                    }
                }
                if(status != MODE_QUIT)
                {
                    LoadCharset(-1);
                    strcpy(stpcpy(msg->charset, CFG->xlatlocalset), " 2");
                    msg->charsetlevel = 2;
                }
            }
            else if(forwstat == NO and (EDIT->Internal() or not *EDIT->External()))
            {
                w_info(LNG->Wait);
                status = MODE_SAVE;
                newline = line = msg->lin;
                while(line)
                {
                    newline = line;
                    if((line->type & GLINE_KLUD) and not AA->Viewkludge() and not (line->kludge & GKLUD_RFC))
                        newline = line = DeleteLine(line);
                    else
                    {
                        strtrimline(line->txt);
                        if(line->type & GLINE_HARD)
                            line->txt += "\n";
                        else
                            line->txt += " ";
                        line = line->next;
                    }
                }
                msg->lin = FirstLine(newline);

                w_info(NULL);
                if(position)
                    position--;
                if(not savedirect)
                    status = EditMsg(mode, &position, msg);
                _in_editor = NO;
                w_info(LNG->Wait);

                if(status != MODE_QUIT)
                {
                    line = msg->lin;
                    while(line)
                    {
                        if(line->txt.find('\n') != line->txt.npos)
                            line->type = GLINE_HARD;
                        else if(line->next and (line->next->type & GLINE_QUOT))
                            line->type = GLINE_HARD;
                        else
                            line->type = 0;
                        strtrimline(line->txt);
                        if(AA->isinternet() or *msg->ito or *msg->ifrom)
                        {
                            // Check for signature indicator
                            if(streql(line->txt.c_str(), "--"))
                            {
                                line->txt = "-- ";
                                line->type = GLINE_HARD;
                            }
                        }
                        line = line->next;
                    }
                    LoadCharset(-1);
                    strcpy(stpcpy(msg->charset, CFG->xlatlocalset), " 2");
                    msg->charsetlevel = 2;
                    msg->LinesToText();
                }
                w_info(NULL);
            }
            if(status == MODE_SAVE)
            {

                if(*EDIT->External() and not EDIT->Internal())
                    LoadText(msg, AddPath(CFG->goldpath, EDIT->File()));
                if(mode == MODE_FORWARD)
                    msg->attr.pos1();
                bool adat_viewhidden = AA->Viewhidden();
                bool adat_viewkludge = AA->Viewkludge();
                bool adat_viewquote  = AA->Viewquote();
                bool adat_striphtml = AA->StripHTML();
                AA->adat->viewhidden = true;
                AA->adat->viewkludge = true;
                AA->adat->viewquote = true;
                AA->adat->striphtml = false;
                msg->TextToLines(CFG->dispmargin-1, false); // Ignore any kludge address found
                AA->adat->viewhidden = adat_viewhidden;
                AA->adat->viewkludge = adat_viewkludge;
                AA->adat->viewquote = adat_viewquote;
                AA->adat->striphtml = adat_striphtml;
                msg->attr.pos0();

                InvalidateControlInfo(msg);

                if(not savedirect)
                {
                    HeaderView->Use(AA, msg);
                    HeaderView->Paint();
                    BodyView->Use(AA, msg, topline);
                    BodyView->Paint();
                    do
                    {
                        if(gkbd.quitall)
                            status = MODE_SAVE;
                        else
                        {
                            GMenuEditfile MenuEditfile;
                            status = MenuEditfile.Run(msg);
                        }
                        if(status == MODE_VIEW)
                        {
                            BodyView->Use(AA, msg, topline);
                            BodyView->Paint();
                            ViewMessage();
                        }
                    }
                    while(status == MODE_VIEW);
                }

                if(status == MODE_SAVE)
                    DoCrosspost(msg, post_xparea);
            }
            loop++;
            if(status == MODE_CHANGE)
            {
                _in_editor = YES;
                HeaderView->Use(AA, msg);
                HeaderView->Paint();
            }
        }
        while(status == MODE_CHANGE);
    }
}


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

static void GetLastLink(GMsg* msg, uint32_t& msgno)
{

    GMsg* uplink = new GMsg();
    throw_new(uplink);

    uplink->msgno = msg->msgno;
    uplink->link.first_set(msg->link.first());

    if(uplink->link.first())
    {
        while(AA->Msgn.ToReln(uplink->link.first()))
        {
            if(not AA->LoadHdr(uplink, uplink->link.first(), false))
                uplink->msgno = 0;
        }
    }

    // Return msgno of the last link
    msgno = uplink->msgno;

    uplink->Reset();
    throw_delete(uplink);
}


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

void MakeMsg(int mode, GMsg* omsg, bool ignore_replyto)
{

    GFTRK("MakeMsg");

    // Tell GMsgHeaderView not to show msg size
    _in_editor = YES;

    // Allocate some msgs
    GMsg* msg = new GMsg();
    throw_new(msg);
    GMsg* reply = new GMsg();
    throw_new(reply);
    GMsg* cmpmsg = new GMsg();
    throw_new(cmpmsg);
    uint32_t reply_msgno = 0;

    // Keep copy of original aka for later restoration
    Addr origaka = AL.AreaIdToPtr(OrigArea)->aka();

    // Force template choice if necessary
    if(AA->Forcetemplate() and (mode != MODE_CHANGE))
        ChangeTemplate();

    // Random System
    AA->RandomizeData();

    // Prepare confirmation receipts
    int confirm = NO;
    if(mode == MODE_CONFIRM)
    {
        confirm = YES;
        mode = MODE_REPLY;
    }

    // Prepare crossposting
    int crosspost = NO;
    post_xparea.clear();

    do
    {
        if(post_xparea.size())
        {
            mode = MODE_COPY;
            AL.SetActiveAreaNo(post_xparea.back());
            if(CurrArea != OrigArea)
            {
                AA->Open();
                AA->RandomizeData(mode);
            }

            msg->TextToLines(CFG->dispmargin-1);
            msg->orig = AA->Aka().addr;
            msg->charsetlevel = LoadCharset(CFG->xlatlocalset, AA->Xlatexport());
            if (msg->charsetlevel)
                gsprintf(PRINTF_DECLARE_BUFFER(msg->charset), "%s %d", AA->Xlatexport(),
                    CharTable ? CharTable->displaylevel : msg->charsetlevel);
            else
                gsprintf(PRINTF_DECLARE_BUFFER(msg->charset), "%s 2", CFG->xlatlocalset);
            strcpy(msg->odom, CFG->aka[AkaMatch(&msg->orig, &AA->Aka().addr)].domain);
            if(AA->isecho() or have_origin(msg))
                DoTearorig(mode, msg);
            DoKludges(mode, msg);
            msg->LinesToText();
            post_xparea.pop_back();
        }

        int  status = MODE_SAVE;
        bool hardlines = EDIT->HardLines();
        int  topline = 0;
        fspecs = specfiles = 0;
        fspec = (FileSpec*)throw_realloc(fspec, sizeof(FileSpec));
        memset(fspec, 0, sizeof(FileSpec));

        // Get msg number and reset/copy msg data
        if(mode != MODE_COPY)
            msg->Reset();
        switch(mode)
        {
        case MODE_FORWARD:
            *msg = *omsg;
            msg->txt = (char*)throw_strdup(msg->txt);
            msg->lin = NULL;
            msg->line = NULL;
            msg->messageid = NULL;
            msg->inreplyto = NULL;
            msg->references = NULL;
            if(CurrArea != OrigArea)
                AA->SetXlatimport(AL.AreaIdToPtr(OrigArea)->Xlatimport());
            msg->TextToLines(CFG->dispmargin-1, true, false);
            msg->msgid.reset();
            *msg->iorig = NUL;
            *msg->idest = NUL;
            *msg->ireplyto = NUL;
            *msg->iaddr = NUL;
            *msg->igate = NUL;
            *msg->ifrom = NUL;
            *msg->ito = NUL;
            *msg->organization = NUL;
            *msg->realby = NUL;
            *msg->realto = NUL;
            *msg->origin = NUL;
        // Drop through ...
        case MODE_COPY:
            AA->NewMsgno(msg);
            msg->link.reset();
            break;
        case MODE_CHANGE:
            *msg = *omsg;
            msg->txt = (char*)throw_strdup(msg->txt);
            msg->lin = NULL;
            msg->line = NULL;
            msg->messageid = NULL;
            msg->inreplyto = NULL;
            msg->references = NULL;
            msg->TextToLines(CFG->dispmargin-1, true, false);
            break;
        case MODE_NEW:
            wfill(MINROW, 0, MAXROW-2, MAXCOL-1, ' ', C_READW);
        case MODE_QUOTE:
        case MODE_REPLY:
        case MODE_REPLYCOMMENT:
            AA->NewMsgno(msg);
            msg->timesread = 1;
        }

        msg->oorig = msg->orig;
        msg->board = AA->board();
        msg->odest = msg->dest;

        if(mode != MODE_COPY)
        {

            // Set msg date and time
            bool dochgdate = true;
            if(mode == MODE_CHANGE)
            {
                dochgdate = make_bool(EDIT->ChangeDate());
                if((EDIT->ChangeDate() == YES) and not msg->attr.fmu())
                    dochgdate = false;
            }
            if(dochgdate)
            {
                time32_t a  = gtime(NULL);
                struct tm tp;
                ggmtime(&tp, &a);
                tp.tm_isdst = -1;
                time32_t b  = gmktime(&tp);
                a += a - b;
                msg->received = msg->arrived = msg->written = a;
            }

            // Prepare msg header

            if(mode != MODE_CHANGE)
                msg->attr = AA->Attributes();   // Set default attributes

            switch(mode)
            {
            case MODE_QUOTE:
            case MODE_REPLYCOMMENT:
                if(CurrArea != OrigArea)
                    AA->SetXlatimport(AL.AreaIdToPtr(OrigArea)->Xlatimport());
                omsg->TextToLines(-CFG->quotemargin, true, false);
                if(ignore_replyto)
                    *omsg->ireplyto = NUL;
                if(omsg->attr.rot())
                    Rot13(omsg);
            // Drop through
            case MODE_REPLY:
                if(confirm)
                    msg->attr = CFG->attribscfm;
                if(omsg->attr.pvt() & !AA->isecho())
                    msg->attr.pvt1();    // Set Pvt attr in response to Pvt in orig
                if(omsg->attr.frq() and (mode == MODE_REPLY) and (not confirm))
                {
                    msg->attr = CFG->attribsfrq;
                    msg->attr.frq1();   // Special case for the file request function
                }

                // Change header data if replying to a forwarded message
                if((mode == MODE_REPLYCOMMENT) and omsg->fwdorig.net)
                {
                    strcpy(omsg->by, omsg->fwdfrom);
                    strcpy(omsg->realby, omsg->fwdfrom);
                    omsg->oorig = omsg->orig = omsg->fwdorig;
                    strcpy(omsg->to, omsg->fwdto);
                    strcpy(omsg->realto, omsg->fwdto);
                    omsg->odest = omsg->dest = omsg->fwddest;
                    strcpy(omsg->re, omsg->fwdsubj);
                    *omsg->ireplyto = *omsg->iorig = NUL;
                }

                // Do aka matching
                if(AA->Akamatching())
                {
                    // ... but only if we did NOT change aka manually
                    if(AA->Aka().addr.equals(AA->aka()))
                    {

                        Addr aka_addr = AA->Aka().addr;

                        if (CFG->akamatchfromto)
                        {
                            bool useto = true;
                            std::vector<gaka>::iterator a;

                            for (a = CFG->aka.begin(); useto && (a != CFG->aka.end()); a++)
                            {
                                if(omsg->orig.equals(a->addr))
                                    useto = false;
                            }

                            if (useto)
                            {
                                for (a = CFG->aka.begin(); a != CFG->aka.end(); a++)
                                    if(omsg->dest.equals(a->addr))
                                    {
                                        akamatchreply = true;
                                        break;
                                    }
                            }
                        }

                        if (CFG->akamatchfromto && akamatchreply)
                            aka_addr = omsg->dest;
                        else
                            aka_addr = AA->Aka().addr;

                        AkaMatch(&aka_addr, &omsg->orig);
                        AA->SetAka(aka_addr);
                    }
                }

                if((mode == MODE_REPLYCOMMENT) and not omsg->fwdorig.net)
                {
                    strcpy(msg->to, omsg->to);
                    strcpy(msg->realto, omsg->realto);
                    msg->dest = omsg->dest;
                    strcpy(msg->idest, omsg->idest);
                }
                else
                {
                    strcpy(msg->to, omsg->By());
                    strcpy(msg->realto, omsg->realby);
                    msg->dest = omsg->orig;
                    strcpy(msg->idest, *omsg->ireplyto ? omsg->ireplyto : *omsg->iorig ? omsg->iorig : omsg->iaddr);
                }
                if(not *msg->iaddr)
                    strcpy(msg->iaddr, msg->idest);
                strcpy(msg->re, omsg->re);

                if(AA->Replyre() or AA->isinternet())
                {

                    uint32_t number;
                    const char* r = get_subject_re_info(msg->re, number);
                    if (r)
                    {
                        if (AA->Replyre() == REPLYRE_NUMERIC and not AA->isinternet())
                        {
                            ISub subj;
                            gsprintf(PRINTF_DECLARE_BUFFER(subj), "Re^%u:%s", (((number+1) > number) ? (number+1) : number), r);
                            strcpy(msg->re, subj);
                        }
                    }
                    else if(not msg->attr.frq() and not msg->attr.urq())
                    {
                        memmove(msg->re+4, msg->re, sizeof(ISub)-5);
                        memmove(msg->re, "Re: ", 4);
                    }
                }
                else
                {
                    const char* r;
                    uint32_t number;
                    const char* ptr = msg->re;
                    do
                    {
                        r = get_subject_re_info(ptr, number);
                        if(r)
                            ptr = strskip_wht(r);
                    }
                    while(r);
                    if (ptr != msg->re)
                    {
                        memmove(msg->re, ptr, strlen(ptr) + 1);
                    }
                }
                strcpy(msg->ddom, omsg->odom);
                if((mode == MODE_REPLYCOMMENT) and omsg->fwdorig.net)
                    strcpy(msg->replys, omsg->fwdmsgid);
                else
                    strcpy(msg->replys, omsg->msgids);
                if(AA->isnet())
                {
                    if(omsg->messageid)
                    {
                        throw_free(msg->inreplyto);
                        msg->inreplyto = throw_strdup(omsg->messageid);
                    }
                    strcpy(msg->igate, omsg->igate);
                    if(*msg->igate)
                    {
                        msg->dest.set(msg->igate);
                        char* ptr = strchr(msg->igate, ' ');
                        if(ptr)
                        {
                            if(not isuucp(msg->to))
                                strcpy(msg->realto, msg->to);
                            strcpy(msg->to, strskip_wht(ptr));
                        }
                    }
                }
                else
                {
                    int reflen = omsg->references ? strlen(omsg->references) : 0;
                    int midlen = omsg->messageid ? strlen(omsg->messageid) : 0;
                    msg->references = (char*)throw_calloc(1, reflen+1+midlen+1);
                    if(omsg->references)
                        strcpy(msg->references, omsg->references);
                    if(omsg->messageid)
                    {
                        if(reflen)
                            strcat(msg->references, " ");
                        strcat(msg->references, omsg->messageid);
                    }
                    if(*msg->references == NUL)
                        throw_release(msg->references);
                }

                if (CurrArea == OrigArea)
                {
                    if (CFG->replylink != REPLYLINK_NONE)
                    {
                        if ((CFG->replylink == REPLYLINK_DIRECT) or (AA->basetype() == "JAM"))
                            reply_msgno = omsg->msgno;
                        else if (CFG->replylink == REPLYLINK_CHAIN)
                            GetLastLink(omsg, reply_msgno);
                    }

                    msg->link.to_set(reply_msgno);
                    if(msg->link.to() == omsg->msgno)
                        omsg->link.first_set(msg->msgno);
                }

            ///////////
            /////////// Drop through
            ///////////

            case MODE_FORWARD:
                if(mode == MODE_FORWARD)
                {
                    if(msg->fwdorig.net == 0)
                    {
                        strcpy(msg->fwdfrom, msg->By());
                        msg->fwdorig = msg->orig;
                        strcpy(msg->fwdto, msg->To());
                        msg->fwddest = msg->dest;
                        strxcpy(msg->fwdsubj, msg->re, sizeof(Subj));
                        Area* fwdarea = AL.AreaIdToPtr(OrigArea);
                        strcpy(msg->fwdarea, fwdarea->isecho() ? fwdarea->echoid() : "");
                        strcpy(msg->fwdmsgid, msg->msgids);
                    }
                }

            ///////////
            /////////// Drop through
            ///////////

            case MODE_NEW:
                strcpy(msg->by, AA->Username().name);
                msg->attr.nwm1();
                if(not AA->islocal())
                    msg->attr.uns1();
                strcpy(msg->odom, CFG->aka[AkaMatch(&msg->orig, &AA->Aka().addr)].domain);
                break;

            case MODE_CHANGE:
                if(not AA->islocal())
                    msg->attr.uns1();
                msg->attr.snt0();
                msg->attr.rcv0();
                break;
            }

            // Get random items

            switch(mode)
            {
            case MODE_NEW:
            case MODE_FORWARD:
                *msg->replys = NUL;
                throw_release(msg->references);
                throw_release(msg->inreplyto);
                if(AA->isnet())
                {
                    strcpy(msg->to, "");
                    msg->dest.reset_fast();
                    msg->odest.reset_fast();
                }
                if(not AA->isnet() or (AA->isemail() and strchr(AA->Whoto(), '@')))
                    strcpy(msg->to, AA->Whoto());

            ///////
            /////// Drop through
            ///////

            case MODE_REPLYCOMMENT:
            case MODE_QUOTE:
            case MODE_REPLY:
            case MODE_CHANGE:
                AA->RandomizeData(mode);
                if(mode != MODE_CHANGE)
                {
                    msg->orig = AA->Aka().addr;
                    if(AA->isinternet())
                        strcpy(msg->iorig, AA->Internetaddress());
                    if(not strblank(AA->Username().name))
                        strcpy(msg->by, AA->Username().name);
                    if(CFG->switches.get(emptytearline))
                        *msg->tearline = NUL;
                }
                break;
            }

            int forwstat = NO;
            if(msg->odest.net == 0)
                msg->odest = msg->dest;
            status = NO;
            if(not confirm)
            {
                strcpy(cmpmsg->by, msg->by);
                strcpy(cmpmsg->to, msg->to);
                strcpy(cmpmsg->re, msg->re);
                cmpmsg->attr = msg->attr;
                if(savedirect)
                {
                    CheckSubject(msg, msg->re);
                }
                else
                {
                    GMenuEditHeader MenuEditHeader;
                    while((status = MenuEditHeader.Run(mode, msg)) == YES);
                }
            }

            if(status == NO)
            {
                if(mode == MODE_CHANGE or mode == MODE_FORWARD)
                {
                    status = MODE_SAVE;
                    if(mode == MODE_FORWARD)
                        forwstat = YES;
                }
                else
                {
                    if (confirm)
                    {
                        throw_release(msg->txt);
                        gfile fp(AddPath(CFG->goldpath, CFG->confirmfile), "rt", CFG->sharemode);

                        if (fp.isopen())
                            LoadText(msg, AddPath(CFG->goldpath, CFG->confirmfile));

                        if (msg->txt == NULL)
                            msg->txt = throw_strdup("\r\rConfirmation Receipt\r\r");

                        TokenXlat(mode, msg->txt, strlen(msg->txt)+1, true, msg, omsg, CurrArea);
                    }
                    else
                        CreateFileAddr(msg);
                    msg->TextToLines(CFG->dispmargin-1);
                    status = MODE_SAVE;
                }
            }
            MakeMsg2(mode, status, forwstat, topline, msg, omsg, cmpmsg);
        }

        if(status != MODE_SAVE)
        {
            post_xparea.clear();
            crosspost = NO;
        }

        if(status == MODE_SAVE)
        {

            if(*msg->iorig and (strlen(msg->iorig) < sizeof(Name)))
            {
                strcpy(msg->realby, msg->by);
                strcpy(msg->by, msg->iorig);
            }

            if(mode == MODE_COPY)
                AA->SaveMsg(GMSG_NEW, msg);
            else
                MakeMsg3(mode, msg);

            // If message is a reply, update the links on the original
            if(CurrArea == OrigArea and (mode == MODE_QUOTE or mode == MODE_REPLYCOMMENT or mode == MODE_REPLY))
            {
                if(AA->Msgn.ToReln(reply_msgno))
                {
                    if(AA->LoadHdr(reply, reply_msgno, false))
                    {
                        uint32_t replynext;
                        bool ok2save = false;

                        if (AA->basetype() == "SQUISH")
                        {
                            if (reply->link.first())
                            {
                                for (size_t r = 0; !ok2save; r++)
                                {
                                    if (reply->link.list(r) == 0)
                                    {
                                        reply->link.list_set(r, msg->msgno);
                                        ok2save = true;
                                    }
                                }
                            }
                            else
                            {
                                reply->link.first_set(msg->msgno);
                                ok2save = true;
                            }
                        }
                        else if (AA->basetype() == "JAM")
                        {
                            if(reply->link.first())
                            {
                                replynext = reply->link.first();
                                do
                                {
                                    reply_msgno = replynext;
                                    if(not AA->LoadHdr(reply, reply_msgno, false))
                                        break;
                                    replynext = reply->link.next();
                                }
                                while(reply->link.next());
                                if(reply->link.next() == 0)
                                {
                                    reply->link.next_set(msg->msgno);
                                    ok2save = true;
                                }
                            }
                            else
                            {
                                reply->link.first_set(msg->msgno);
                                ok2save = true;
                            }
                        }
                        else
                        {
                            reply->link.first_set(msg->msgno);
                            ok2save = true;
                        }
                        if(ok2save)
                        {
                            reply->pcboard.reply_written = msg->written;
                            AA->SaveHdr(GMSG_UPDATE, reply);
                        }
                    }
                }
            }

            // Ask if the original msg should be deleted while we're at it
            if(CurrArea == OrigArea and CFG->switches.get(askdelorig) and not confirm)
            {
                switch(mode)
                {
                case MODE_REPLY:
                case MODE_QUOTE:
                case MODE_REPLYCOMMENT:
                    if(not AA->isecho())
                    {
                        topline = 0;
                        omsg->TextToLines(CFG->dispmargin-1);
                        if(omsg->attr.rot())
                            Rot13(omsg);
                        HeaderView->Use(AA, omsg);
                        HeaderView->Paint();
                        BodyView->Use(AA, omsg, topline);
                        BodyView->Paint();
                        GMenuDelorig MenuDelorig;
                        if(MenuDelorig.Run())
                        {
                            AA->Mark.Del(omsg->msgno);
                            AA->PMrk.Del(omsg->msgno);
                            AA->DeleteMsg(omsg, DIR_PREV);
                        }
                    }
                    break;
                }
            }
        }

        if(CurrArea != OrigArea)
        {
            AA->UpdateAreadata();
            AA->Close();
            AL.SetActiveAreaId(OrigArea);
            AA->RandomizeData();
        }
        AA->UpdateAreadata();
        EDIT->HardLines(hardlines);
        for(int n=0; n<fspecs; n++)
            throw_release(fspec[n].fblk);
        fspecs = specfiles = 0;
        throw_release(fspec);

        if(crosspost and post_xparea.size())
            update_statuslinef(LNG->Crossposting, "ST_CROSSPOSTING", AL[post_xparea.back()]->echoid());
    }
    while(post_xparea.size());

    _in_editor = NO;

    // Restore original aka
    AA->SetAka(origaka);
    akamatchreply = false;

    omsg->Reset();
    cmpmsg->Reset();
    reply->Reset();
    msg->Reset();

    throw_delete(cmpmsg);
    throw_delete(reply);
    throw_delete(msg);

    LoadLanguage(AA->Loadlanguage());

    GFTRK(0);
}


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

