/*
 * MultiMail offline mail reader
 * Packet base class -- common methods

 Copyright 1999-2017 William McBrine <wmcbrine@gmail.com>
 Distributed under the GNU General Public License, version 3 or later. */

#include "compress.h"
#include "pktbase.h"

/* Not all these methods are used in all derived classes. The formats I
   think of as "QWK-like" -- QWK, OMEN and OPX -- have the most in common:
   message headers and bodies concatenated in a single file, a separate
   file for the area list, and replies matched by conference number.
*/

// -----------------------------------------------------------------
// The packet methods
// -----------------------------------------------------------------

pktbase::pktbase(mmail *mmA)
{
    mm = mmA;
    ID = 0;
    bodyString = 0;
    bulletins = 0;
    infile = 0;
    LoginName = AliasName = BBSName = SysOpName = BBSProg = DoorProg = 0;
    hello = goodbye = 0;
}

pktbase::~pktbase()
{
    if (infile)
        fclose(infile);

    delete[] body;
    delete bodyString;
    delete[] bulletins;
    delete[] goodbye;
    delete[] hello;
    delete[] DoorProg;
    delete[] BBSProg;
    delete[] SysOpName;
    delete[] BBSName;
    delete[] AliasName;
    delete[] LoginName;
}

// Clean up for the QWK-like packets
void pktbase::cleanup()
{
    if (!hasPers) {
        areas--;
        maxConf++;
    }
    while (maxConf--) {
        delete[] body[maxConf];
        delete[] areas[maxConf].name;
    }
    delete[] areas;
}

// Final index build for QWK-like packets
void pktbase::initBody(ndx_fake *tmpndx, int personal)
{
    int x, cMsgNum;

    hasPers = !(!personal);
    if (hasPers) {
        body[0] = new bodytype[personal];
        areas[0].nummsgs = personal;
        personal = 0;
    } else {
        areas++;
        maxConf--;
    }

    for (x = hasPers; x < maxConf; x++) {
        cMsgNum = areas[x].nummsgs;
        if (cMsgNum) {
            body[x] = new bodytype[cMsgNum];
            areas[x].nummsgs = 0;
        }
    }

    for (x = 0; x < numMsgs; x++) {
        int current = tmpndx->confnum - !hasPers;
        body[current][areas[current].nummsgs].pointer = tmpndx->pointer;
        body[current][areas[current].nummsgs++].msgLength = tmpndx->length;

        if (tmpndx->pers) {
            body[0][personal].pointer = tmpndx->pointer;
            body[0][personal++].msgLength = tmpndx->length;
        }
        ndx_fake *oldndx = tmpndx;
        tmpndx = tmpndx->next;
        delete oldndx;
    }
}

// Match the conference number to the internal number
int pktbase::getXNum(int area)
{
    static int lastres = 0;

    if ((lastres >= maxConf) || (areas[lastres].num > area))
        lastres = 0;

    for (int x = 0; x < maxConf; x++)
        if (areas[lastres].num == area)
            break;
        else {
            lastres++;
            lastres %= maxConf;
        }

    return (areas[lastres].num == area) ? lastres : -1;
}

// Find the original number of a message in a collection area
int pktbase::getYNum(int area, unsigned long rawpos)
{
    static int lastarea = -1;
    static int lastres = 0;

    if (lastarea != area) {
        lastarea = area;
        lastres = 0;
    }

    if (-1 == area)
        return -1;

    int x, limit = areas[area].nummsgs;
    for (x = 0; x < limit; x++)
        if ((unsigned long) body[area][lastres].pointer == rawpos)
            break;
        else {
            lastres++;
            lastres %= limit;
        }

    return (x < limit) ? lastres : -1;
}

int pktbase::getNoOfLetters()
{
    return areas[currentArea].nummsgs;
}

bool pktbase::hasPersArea()
{
    return hasPers;
}

bool pktbase::hasPersonal()
{
    return false;
}

bool pktbase::isLatin()
{
    return false;
}

const char *pktbase::oldFlagsName()
{
    return 0;
}

bool pktbase::readOldFlags()
{
    return false;
}

bool pktbase::saveOldFlags()
{
    return false;
}

int pktbase::getNoOfAreas()
{
    return maxConf;
}

void pktbase::selectArea(int area)
{
    currentArea = area;
    resetLetters();
}

void pktbase::resetLetters()
{
    currentLetter = 0;
}

// returns the body of the requested letter
letter_body *pktbase::getBody(letter_header &mhead)
{
    int AreaID, LetterID;
    long length, offset;
    letter_body head(0, 0), *currblk = &head;

    AreaID = mhead.getAreaID() - 1;
    LetterID = mhead.getLetterID();

    delete bodyString;

    length = limitmem(body[AreaID][LetterID].msgLength);
    offset = body[AreaID][LetterID].pointer;

    bool firstblk = true;

    if (!length)
        head.next = new letter_body(strdupplus("\n"), 1);
    else
        while (length) {
            unsigned char *p, *src, *begin;
            long count, blklen, oldoffs;

            fseek(infile, offset, SEEK_SET);
            oldoffs = offset;

            if (firstblk)
                prefirstblk();

            blklen = (length > MAXBLOCK) ? MAXBLOCK : length;
            src = begin = p = new unsigned char[blklen + 1];

            getblk(AreaID, offset, blklen, p, begin);

            if (length > MAXBLOCK) {
                if (begin > src)
                    count = begin - src;
                else {
                    offset = ftell(infile);
                    count = p - src;
                }
                length -= (offset - oldoffs);
            } else {
                // Strip blank lines
                do
                    p--;
                while ((*p == ' ') || (*p == '\n'));

                length = 0;
                count = p - src + 1;
            }
            src[count] = '\0';

            p = src;
            if (firstblk)
                postfirstblk(p, mhead);

            currblk->next = new letter_body((char *) src, count, p - src);
            currblk = currblk->next;

            firstblk = false;
        }

    bodyString = head.next;
    head.next = 0;             // Prevent deletion of chain

    endproc(mhead);

    return bodyString;
}

void pktbase::getblk(int, long &offset, long blklen,
                     unsigned char *&p, unsigned char *&begin)
{
    for (long count = 0; count < blklen; count++) {
        int kar = fgetc(infile);

        if (!kar)
            kar = ' ';

        if (kar != '\r')
            *p++ = kar;

        if (kar == '\n') {
            begin = p;
            offset = ftell(infile);
        }
    }
}

// The new getBody() framework uses 4 subprocedures, only one of which
// must be defined in all derived classes. Here are dummy procedures for
// the others.

void pktbase::prefirstblk()
{
}

void pktbase::postfirstblk(unsigned char *&, letter_header &)
{
}

void pktbase::endproc(letter_header &)
{
}

// Check the character set kludge lines
void pktbase::checkLatin(letter_header &mhead)
{
    const char *s = strstr(bodyString->getText(), "\001CHRS: L");
    if (!s)
        s = strstr(bodyString->getText(), "\001CHARSET: L");
    if (s)
        mhead.setLatin(true);
}

// Find a hidden line and return its contents
const char *pktbase::getHidden(const char *pattern, char *&end)
{
    char *s = strstr(bodyString->getText(), pattern);

    if (s) {
        s += strlen(pattern);
        end = strchr(s, '\n');
        if (end)
            *end = '\0';
    }
    return s;
}

// Check FMPT, MSGID and character set
void pktbase::fidocheck(letter_header &mhead)
{
    const char *s;
    char *end;

    net_address &na = mhead.getNetAddr();

    // Add point to netmail address, if possible/necessary:
    if (na.isSet)
        if (!na.point) {
            s = strstr(bodyString->getText(), "\001FMPT");
            if (s)
                sscanf(s, "\001FMPT%u\n", &na.point);
        }

    // Get MSGID:
    if (!mhead.getMsgID()) {
        s = getHidden("\001MSGID: ", end);
        if (s) {
            mhead.changeMsgID(s);
            if (end)
                *end = '\n';
        }
    }

    // Change to Latin character set, if necessary:
    checkLatin(mhead);
}

// Build a list of bulletin files
void pktbase::listBulletins(const char x[][13], int d, int generic)
{
    file_list *wl = mm->workList;
    int filecount = 0;

    bulletins = new file_header *[wl->getNoOfFiles() + 1];

    for (int c = 0; c < d; c++)
        if (x[c][0]) {
            if (!hello && (!strncasecmp("hello", x[c], 5) ||
                           !strncasecmp("welcome", x[c], 7)))
                hello = strdupplus(x[c]);
            else
                if (!goodbye && !strncasecmp("goodbye", x[c], 7))
                    goodbye = strdupplus(x[c]);
                else
                    wl->addItem(bulletins, x[c], filecount);
        }

    if (generic) {
        wl->addItem(bulletins, "blt", filecount);
        if (generic == 2)
            wl->addItem(bulletins, ".txt", filecount);
    }

    wl->addItem(bulletins, "newfiles.", filecount);
    wl->addItem(bulletins, "nfile", filecount);

    if (filecount)
        bulletins[filecount] = 0;
    else {
        delete[] bulletins;
        bulletins = 0;
    }
}

const char *pktbase::getLoginName()
{
    return LoginName;
}

const char *pktbase::getAliasName()
{
    return AliasName;
}

const char *pktbase::getBBSName()
{
    return BBSName;
}

const char *pktbase::getSysOpName()
{
    return SysOpName;
}

const char *pktbase::getBBSProg()
{
    return BBSProg;
}

const char *pktbase::getDoorProg()
{
    return DoorProg;
}

file_header *pktbase::getHello()
{
    return (hello && *hello) ? mm->workList->existsF(hello) : 0;
}

file_header *pktbase::getGoodbye()
{
    return (goodbye && *goodbye) ? mm->workList->existsF(goodbye) : 0;
}

file_header **pktbase::getBulletins()
{
    return bulletins;
}

const char *pktbase::getTear(int)
{
    static char tear[80];

    sprintf(tear, "--- " MM_NAME "/%.58s v" MM_VERNUM, sysname());

    return tear;
}

const char *pktbase::getBaseName()
{
    return packetBaseName;
}

char *pktbase::nextLine()
{
    static char line[128];

    char *end = myfgets(line, sizeof line, infile);
    if (end) {
        while ((*end == '\n') || (*end == '\r'))
            *end-- = '\0';
    } else
        line[0] = '\0';

    return line;
}

// -----------------------------------------------------------------
// The reply methods
// -----------------------------------------------------------------

pktreply::upl_base::upl_base(const char *name)
{
    if (name)
        fname = strdupplus(name);
    else
        fname = mytmpnam();

    nextRecord = 0;
    msglen = 0;
}

pktreply::upl_base::~upl_base()
{
    delete[] fname;
}

pktreply::pktreply(mmail *mmA, specific_driver *baseClassA)
{
    mm = mmA;
    baseClass = (pktbase *) baseClassA;
    replyText = 0;
    uplListHead = 0;
    replyExists = false;
}

pktreply::~pktreply()
{
    if (replyExists) {
        upl_base *next, *curr = uplListHead;

        while (noOfLetters--) {
            remove(curr->fname);
            next = curr->nextRecord;
            delete curr;
            curr = next;
        }
        delete replyText;
    }
}

bool pktreply::checkForReplies()
{
    repFileName();
    mychdir(mm->resourceObject->get(ReplyDir));

    mystat st(replyPacketName);
    replyExists = st.writeable();

    return replyExists;
}

void pktreply::init()
{
    if (replyExists) {
        uncompress();
        readRep();
        currentLetter = 1;
    } else
        noOfLetters = currentLetter = 0;
}

void pktreply::uncompress()
{
    resource *ro = mm->resourceObject;
    char *tmppath = fullpath(ro->get(ReplyDir), replyPacketName);

    uncompressFile(ro, tmppath, ro->get(UpWorkDir));

    delete[] tmppath;
}

int pktreply::getNoOfAreas()
{
    return 1;
}

void pktreply::selectArea(int ID)
{
    if (ID == 0)
        resetLetters();
}

int pktreply::getNoOfLetters()
{
    return noOfLetters;
}

void pktreply::resetLetters()
{
    currentLetter = 1;
    uplListCurrent = uplListHead;
}

letter_body *pktreply::getBody(letter_header &mhead)
{
    FILE *replyFile;
    upl_base *actUplList;
    long length, offset = 0;
    letter_body head(0,0), *currblk = &head;

    int ID = mhead.getLetterID();

    delete replyText;

    actUplList = uplListHead;
    for (int c = 1; c < ID; c++)
        actUplList = actUplList->nextRecord;

    length = limitmem(actUplList->msglen);
    replyFile = fopen(actUplList->fname, "rt");

    while (length) {
        if (replyFile) {
            unsigned char *p, *src, *begin;
            long blklen, count, oldoffs;

            fseek(replyFile, offset, SEEK_SET);
            oldoffs = offset;

            blklen = (length > MAXBLOCK) ? MAXBLOCK : length;
            src = begin = p = new unsigned char[blklen + 1];

            for (count = 0; count < blklen; count++) {
                int kar = fgetc(replyFile);

                if (!kar)
                    kar = ' ';

                *p++ = kar;

                if (kar == '\n') {
                    begin = p;
                    offset = ftell(replyFile);
                }
            }

            if (length > MAXBLOCK) {
                if (begin > src)
                    p = begin;
                else
                    offset = ftell(replyFile);

                length -= (offset - oldoffs);
            } else
                length = 0;

            *p = '\0';
            count = p - src;

            currblk->next = new letter_body((char *) src, count);
            currblk = currblk->next;
        } else {
            head.next = new letter_body(strdupplus("\n"), 1);
            length = 0;
        }
    }
    if (replyFile)
        fclose(replyFile);

    replyText = head.next;
    head.next = 0;

    return replyText;
}

bool pktreply::hasPersArea()
{
    return false;
}

bool pktreply::hasPersonal()
{
    return false;
}

bool pktreply::isLatin()
{
    return false;
}

const char *pktreply::oldFlagsName()
{
    return 0;
}

bool pktreply::readOldFlags()
{
    return false;
}

bool pktreply::saveOldFlags()
{
    return false;
}

const char *pktreply::getLoginName()
{
    return 0;
}

const char *pktreply::getAliasName()
{
    return 0;
}

const char *pktreply::getBBSName()
{
    return 0;
}

const char *pktreply::getSysOpName()
{
    return 0;
}

const char *pktreply::getBBSProg()
{
    return 0;
}

const char *pktreply::getDoorProg()
{
    return 0;
}

file_header *pktreply::getHello()
{
    return 0;
}

file_header *pktreply::getGoodbye()
{
    return 0;
}

file_header *pktreply::getFileList()
{
    return 0;
}

file_header **pktreply::getBulletins()
{
    return 0;
}

const char *pktreply::getTear(int)
{
    return 0;
}

void pktreply::readRep()
{
    upWorkList = new file_list(mm->resourceObject->get(UpWorkDir));

    FILE *repFile = upWorkList->ftryopen(replyInnerName);

    if (repFile) {
        getReplies(repFile);
        fclose(repFile);
        remove(upWorkList->exists(replyInnerName));
    } else
        fatalError("Error opening reply packet");

    delete upWorkList;
}

void pktreply::addUpl(upl_base *newList)
{
    if (!noOfLetters)
        uplListHead = newList;
    else {
        upl_base *workList = uplListHead;
        for (int c = 1; c < noOfLetters; c++)   //go to last elem
            workList = workList->nextRecord;
        workList->nextRecord = newList;
    }
    noOfLetters++;
    replyExists = true;
}

void pktreply::killLetter(int letterNo)
{
    upl_base *actUplList, *tmpUplList;

    if (!noOfLetters || (letterNo < 1) || (letterNo > noOfLetters))
        fatalError("Internal error in pktreply::killLetter");

    if (letterNo == 1) {
        tmpUplList = uplListHead;
        uplListHead = uplListHead->nextRecord;
    } else {
        actUplList = uplListHead;
        for (int c = 1; c < letterNo - 1; c++)
            actUplList = actUplList->nextRecord;
        tmpUplList = actUplList->nextRecord;
        actUplList->nextRecord = (letterNo == noOfLetters) ? 0 :
                                 actUplList->nextRecord->nextRecord;
    }
    noOfLetters--;
    remove(tmpUplList->fname);
    delete tmpUplList;
    resetLetters();
}

area_header *pktreply::refreshArea()
{
    return getNextArea();
}

bool pktreply::makeReply()
{
    if (mychdir(mm->resourceObject->get(UpWorkDir)))
        fatalError("Could not cd to upworkdir in pktreply::makeReply");

    bool offres = mm->areaList->anyChanged();
    if (offres)
        offres = makeOffConfig();
    if (!noOfLetters && !offres) {
        deleteReplies();
        return true;
    }

    FILE *repFile;

    repFile = fopen(replyInnerName, "wb");    //!! no check yet

    addHeader(repFile);

    upl_base *actUplList = uplListHead;
    for (int c = 0; c < noOfLetters; c++) {
        addRep1(repFile, actUplList, c);
        actUplList = actUplList->nextRecord;
    }

    fclose(repFile);

    // delete old packet
    deleteReplies();

    // pack the files
    int result = compressAddFile(mm->resourceObject,
                 mm->resourceObject->get(ReplyDir), replyPacketName,
                 repTemplate(offres));

    // clean up the work area
    clearDirectory(mm->resourceObject->get(UpWorkDir));

    return !result && checkForReplies();
}

void pktreply::deleteReplies()
{
    char *tmppath = fullpath(mm->resourceObject->get(ReplyDir),
                             replyPacketName);

    remove(tmppath);

    delete[] tmppath;
}
