//  This may look like C code, but it is really -*- C++ -*-

//  ------------------------------------------------------------------
//  The Goldware Library
//  Copyright (C) 1990-1999 Odinn Sorensen
//  ------------------------------------------------------------------
//  This library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Library General Public
//  License as published by the Free Software Foundation; either
//  version 2 of the License, or (at your option) any later version.
//
//  This library 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
//  Library General Public License for more details.
//
//  You should have received a copy of the GNU Library 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$
//  ------------------------------------------------------------------
//  Squish msgbase handling and Maximus user functions.
//  ------------------------------------------------------------------


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

#include <gdbgerr.h>
#include <gmemdbg.h>
#include <gdbgtrk.h>
#include <gstrall.h>
#include <gcrcall.h>

#include <gmosqsh.h>


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

void SquishArea::lock()
{

    GFTRK("SquishLock");

    if(not data->islocked)
    {
        if(WideCanLock)
        {
            long _tries = 0;
            while(::lock(data->fhsqd, 0, 1) == -1)
            {
                if(PopupLocked(++_tries, true, real_path()) == false)
                {
                    WideLog->ErrLock();
                    raw_close();
                    WideLog->printf("! A Squish msgbase file could not be locked.");
                    WideLog->printf(": %s.sqd.", real_path());
                    WideLog->ErrOSInfo();
                    LockErrorExit();
                }
            }
            if(_tries)
                PopupLocked(0, 0, NULL);
        }
        refresh();
        data->islocked = true;
    }

    GFTRK(0);
}


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

void SquishArea::unlock()
{

    GFTRK("SquishUnlock");

    if(WideCanLock and data->islocked)
        ::unlock(data->fhsqd, 0, 1);
    lseekset(data->fhsqd, 0);
    write(data->fhsqd, &data->base, sizeof(SqshBase));
    lseekset(data->fhsqi, 0);
    write(data->fhsqi, data->idx, (uint)(data->base.totalmsgs*sizeof(SqshIdx)));
    chsize(data->fhsqi, data->base.totalmsgs*sizeof(SqshIdx));
    data->islocked = false;

    GFTRK(0);
}


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

void SquishArea::read_frm(dword __offset, SqshFrm* __frm)
{

    lseekset(data->fhsqd, __offset);
    read(data->fhsqd, __frm, sizeof(SqshFrm));
}


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

void SquishArea::write_frm(dword __offset, SqshFrm* __frm)
{

    lseekset(data->fhsqd, __offset);
    write(data->fhsqd, __frm, sizeof(SqshFrm));
}


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

void SquishArea::upd_frm_next(dword __offset, SqshFrm* __frm, dword __next)
{

    if(__offset != SQFRAME_NULL)
    {
        read_frm(__offset, __frm);
        __frm->next = __next;
        write_frm(__offset, __frm);
    }
}


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

void SquishArea::upd_frm_prev(dword __offset, SqshFrm* __frm, dword __prev)
{

    if(__offset != SQFRAME_NULL)
    {
        read_frm(__offset, __frm);
        __frm->prev = __prev;
        write_frm(__offset, __frm);
    }
}


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

void SquishArea::add_to_free_chain(dword __delframe, SqshFrm* __delfrm)
{

    SqshBase& _base = data->base;

    // Update free frame chain
    if((_base.firstfreeframe == SQFRAME_NULL) or (_base.lastfreeframe == SQFRAME_NULL))
    {

        // No other free frames, so create new chain
        _base.firstfreeframe = _base.lastfreeframe = __delframe;
        __delfrm->prev = __delfrm->next = SQFRAME_NULL;
    }
    else
    {

        // Insert this frame into the chain
        __delfrm->next = SQFRAME_NULL;
        __delfrm->prev = _base.lastfreeframe;
        SqshFrm _lastfrm;
        upd_frm_next(_base.lastfreeframe, &_lastfrm, __delframe);
        _base.lastfreeframe = __delframe;
    }

    // Write the deleted frame
    __delfrm->type = SQFRAME_FREE;
    //WideLog->printf("- Deleted frame 0x%08X of length %u.", __delframe, __delfrm->length);
    write_frm(__delframe, __delfrm);
}


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

void SquishArea::delete_msg(uint __reln)
{

    GFTRK("SquishDeleteMsg");

    int _was_locked = data->islocked;
    if(not _was_locked)
        lock();

    // Setup some local variables for speed
    SqshIdx* _idx = data->idx;
    SqshBase& _base = data->base;

    // Load the frame to be deleted
    SqshFrm _delfrm;
    dword _delframe = _idx[__reln].offset;
    read_frm(_delframe, &_delfrm);

    // Chain the previous and next frames together
    SqshFrm _tmpfrm;
    upd_frm_next(_delfrm.prev, &_tmpfrm, _delfrm.next);
    upd_frm_prev(_delfrm.next, &_tmpfrm, _delfrm.prev);

    // Update base data if the first or last frame was deleted
    if(_delframe == _base.firstframe)
        _base.firstframe = _delfrm.next;
    if(_delframe == _base.lastframe)
        _base.lastframe = _delfrm.prev;

    // Add the deleted msg to the free frame chain
    add_to_free_chain(_delframe, &_delfrm);

    // Remove deleted message number from the indexes
    Msgn->DelReln(__reln+1);
    dword _tomove = _base.totalmsgs - __reln - 1;
    memmove(_idx+__reln, _idx+__reln+1, (uint)(_tomove*sizeof(SqshIdx)));

    // Update base data
    _base.highestmsg--;
    _base.totalmsgs--;

    // Update area data
    if(lastread)
        lastread--;

    if(not _was_locked)
        unlock();

    GFTRK(0);
}


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

void SquishArea::init_frm(SqshFrm* __frm)
{

    memset(__frm, 0, sizeof(SqshFrm));
    __frm->type = SQFRAME_NORMAL;
    __frm->id = SQFRAMEID;
}


//  ------------------------------------------------------------------
//  Copy the text itself to a buffer, or count its length if out==NULL

uint CopyToBuf(char* p, char* out, char** end)
{

    if(out)
        *out++ = CTRL_A;

    uint len = 1;

    while((*p==CR) or (*p==LF) or issoftcr(*p))
        p++;

    while((*p==CTRL_A) or (strncmp(p, "AREA:", 5)==0))
    {

        // Skip over the first ^A
        if(*p == CTRL_A)
            p++;

        while(*p and (*p != CR) and (*p != LF) and !issoftcr(*p))
        {
            if(out)
                *out++ = *p;
            len++;
            p++;
        }

        if(out)
            *out++ = CTRL_A;

        len++;

        while((*p==LF) or issoftcr(*p))
            p++;
        if(*p == CR)
            p++;
        while((*p==LF) or issoftcr(*p))
            p++;
    }

    // Nul-term the string
    if(out)
        *out = NUL;

    len++;

    // Make sure to leave no trailing CTRL_A's.
    if(out and (out[-1]==CTRL_A))
        out[-1] = NUL;


    // Now store the new end location of the kludge lines
    if(end)
        *end = p;

    return len;
}


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

char* CopyToControlBuf(char* txt, char** newtext, uint* length)
{

    // Figure out how int32_t the control info is
    uint ctlsize = CopyToBuf(txt, NULL, NULL);

    // Allocate memory for it
    char* cbuf = (char*)throw_calloc(1, ctlsize+20);

    // Now copy the text itself
    char* end;
    CopyToBuf(txt, cbuf, &end);

    if(length)
        *length -= (uint)(end-txt);
    if(newtext)
        *newtext = end;

    return cbuf;
}


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

void SquishArea::excess_frm(dword __lastframe, dword __newframe, SqshFrm* __newfrm, dword __totsize)
{

    // Is the excess length large enough for a frame and message header?
    dword _excesslength = __newfrm->length - __totsize;
    if(_excesslength >= sizeof(SqshFrm))
    {

        // Calculate frame offset of the excess frame
        dword _exframe = __newframe + __totsize + sizeof(SqshFrm);

        // Adjust base data if this becomes the last free frame
        if(__lastframe == data->base.lastfreeframe)
            data->base.lastfreeframe = _exframe;

        // Setup the excess frame and write it
        SqshFrm _exfrm;
        init_frm(&_exfrm);
        _exfrm.type = SQFRAME_FREE;
        _exfrm.next = __newfrm->next;
        _exfrm.prev = __newframe;
        __newfrm->next = _exframe;
        __newfrm->length = __totsize;
        _exfrm.length = _excesslength - sizeof(SqshFrm);
        write_frm(_exframe, &_exfrm);
        SqshFrm _tmpfrm;
        upd_frm_prev(_exfrm.next, &_tmpfrm, _exframe);
        //WideLog->printf("- Created excess free frame 0x%08X of length %u.", _exframe, _exfrm.length);
    }
}


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

uint SquishArea::find_msgn(uint32_t __tagn)
{

    if(data->idx)
    {

        SqshIdx* tag = data->idx;
        uint tags = (uint)data->base.totalmsgs;

        if(__tagn and tags and (__tagn > tag[tags-1].msgno))
            return 0;

        if(tags and __tagn)
        {

            int32_t _mid;
            int32_t _left = 0;
            int32_t _right = tags;

            do
            {
                _mid = (_left+_right)/2;
                if(__tagn < tag[(uint)_mid].msgno)
                    _right = _mid - 1;
                else if(__tagn > tag[(uint)_mid].msgno)
                    _left = _mid + 1;
                else
                    return (uint)(_mid + 1);
            }
            while(_left < _right);

            if(__tagn == tag[(uint)_left].msgno)
                return (uint)(_left + 1);
        }
    }

    return 0;
}


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

void SquishArea::save_message(int __mode, gmsg* __msg)
{

    SqshHdr __hdr;
    int _was_locked = data->islocked;
    if(not _was_locked)
        lock();

    // If not new, does the message still exist?
    if((__mode & GMSG_NEW) or find_msgn(__msg->msgno))
    {

        uint _reln = (__mode & GMSG_NEW) ? 0 : (Msgn->ToReln(__msg->msgno) - 1);

        // Reset header
        __hdr = SqshHdr();

        // Convert attributes
        __hdr.attr |= MSGUID;
        __hdr.attr |= __msg->attr.pvt() ? MSGPRIVATE : 0;
        __hdr.attr |= __msg->attr.cra() ? MSGCRASH   : 0;
        __hdr.attr |= __msg->attr.rcv() ? MSGREAD    : 0;
        __hdr.attr |= __msg->attr.snt() ? MSGSENT    : 0;
        __hdr.attr |= __msg->attr.att() ? MSGFILE    : 0;
        __hdr.attr |= __msg->attr.trs() ? MSGFWD     : 0;
        __hdr.attr |= __msg->attr.orp() ? MSGORPHAN  : 0;
        __hdr.attr |= __msg->attr.k_s() ? MSGKILL    : 0;
        __hdr.attr |= __msg->attr.loc() ? MSGLOCAL   : 0;
        __hdr.attr |= __msg->attr.hld() ? MSGHOLD    : 0;
        __hdr.attr |= __msg->attr.rsv() ? MSGXX2     : 0;
        __hdr.attr |= __msg->attr.frq() ? MSGFRQ     : 0;
        __hdr.attr |= __msg->attr.rrq() ? MSGRRQ     : 0;
        __hdr.attr |= __msg->attr.rrc() ? MSGCPT     : 0;
        __hdr.attr |= __msg->attr.arq() ? MSGARQ     : 0;
        __hdr.attr |= __msg->attr.urq() ? MSGURQ     : 0;
        __hdr.attr |= __msg->attr.prn() ? MSGPRINTED : 0;
        __hdr.attr |= __msg->attr.lok() ? MSGLOK     : 0;
        __hdr.attr |= __msg->timesread  ? MSGSEEN    : 0;

        if(__msg->attr.scn() and not __msg->attr.uns())
            __hdr.attr |= MSGSCANNED;

        if(__msg->attr.dir() and wide->direct)
            __hdr.attr |= MSGCRASH | MSGHOLD;

        memcpy(__hdr.from, __msg->by, 36);
        memcpy(__hdr.to,   __msg->to, 36);
        memcpy(__hdr.subj, __msg->re, 72);

        __hdr.orig.zone  = __msg->oorig.zone;
        __hdr.orig.net   = __msg->oorig.net;
        __hdr.orig.node  = __msg->oorig.node;
        __hdr.orig.point = __msg->oorig.point;

        __hdr.dest.zone  = __msg->odest.zone;
        __hdr.dest.net   = __msg->odest.net;
        __hdr.dest.node  = __msg->odest.node;
        __hdr.dest.point = __msg->odest.point;

        __hdr.replyto    = __msg->link.to();
        __hdr.replies[0] = __msg->link.first();
        for(int r=1; r<=8; r++)
            __hdr.replies[r] = __msg->link.list(r-1);
        __hdr.umsgid = (__mode & GMSG_NEW) ? data->base.nextmsgno : __msg->msgno;

        __hdr.date_written = TimeToFTime(__msg->written);
        __hdr.date_arrived = TimeToFTime(__msg->arrived);

        struct tm _tm;
        ggmtime(&_tm, &__msg->written);
        sprintf(__hdr.ftsc_date, "%02d %3s %02d  %02d:%02d:%02d",
                _tm.tm_mday, gmonths[_tm.tm_mon + 1], _tm.tm_year % 100,
                _tm.tm_hour, _tm.tm_min, _tm.tm_sec
               );

        // Setup some local variables for speed
        int _fhsqd = data->fhsqd;
        SqshIdx* _idx = data->idx;
        SqshBase& _base = data->base;
        dword _hash = strHash32(__hdr.to) | ((__hdr.attr & MSGREAD) ? 0x80000000LU : 0);

        // Writing msg text?
        if(__mode & GMSG_TXT)
        {

            char* _txt = __msg->txt;
            uint _usize = strlen(_txt) + 1;
            char* _ctl = CopyToControlBuf(_txt, &_txt, &_usize);
            dword _txtsize = strlen(_txt) + 1;
            dword _ctlsize = strlen(_ctl) + 1;
            dword _totsize = sizeof(SqshHdr) + _ctlsize + _txtsize;

            SqshFrm _oldfrm, _newfrm;
            dword _newframe = SQFRAME_NULL;
            dword _oldframe = _idx ? _idx[_reln].offset : _base.endframe;
            if(not (__mode & GMSG_NEW))
            {

                // Get the original frame and see if there is still room for the msg
                read_frm(_oldframe, &_oldfrm);
                if(_oldfrm.length >= _totsize)
                {
                    _newframe = _oldframe;
                    _newfrm = _oldfrm;
                }
            }

            // It's a new message or the changed message doesn't fit
            if(_newframe == SQFRAME_NULL)
            {

                // If there is a max msgs limit and are we writing a new
                // msg, delete msgs to (hopefully) make room for this msg
                if(_base.maxmsgs and (__mode & GMSG_NEW))
                    while(_base.maxmsgs <= _base.totalmsgs)
                        delete_msg((uint)_base.protmsgs);

                // Locate a free frame, if possible
                _newframe = _base.firstfreeframe;
                //WideLog->printf("- Looking for a frame of at least length %u.", _totsize);
                while(1)
                {

                    // At end of free frames?
                    if(_newframe == SQFRAME_NULL)
                    {
                        _newframe = _base.endframe;
                        init_frm(&_newfrm);
                        //WideLog->printf("- Allocated new frame 0x%08X of length %u.", _newframe, _totsize);
                        break;
                    }

                    // Is this frame large enough in itself?
                    read_frm(_newframe, &_newfrm);
                    //WideLog->printf("- Found free frame 0x%08X of length %u.", _newframe, _newfrm.length);
                    if(_newfrm.length >= _totsize)
                    {

                        // Create excess frame if possible
                        if(wide->recycle == SQUISHRECYCLE_YES)
                        {
                            excess_frm(_newframe, _newframe, &_newfrm, _totsize);
                            //WideLog->printf("- Frame was large enough (%u bytes wasted).", _newfrm.length - _totsize);
                        }
                        break;
                    }

                    // If two frames are adjacent, try to merge them to make more room
                    if(wide->recycle and (wide->recycle != SQUISHRECYCLE_MSGAPI2))
                    {
                        dword _lastframe = SQFRAME_NULL;
                        while((_newfrm.next == (_newframe+_newfrm.length+sizeof(SqshFrm))) and (_newfrm.length < _totsize))
                        {
                            SqshFrm _lastfrm;
                            read_frm(_newfrm.next, &_lastfrm);
                            _newfrm.length += _lastfrm.length + sizeof(SqshFrm);
                            //WideLog->printf("- Merged frames 0x%08X and 0x%08X. New length: %u.", _newframe, _newfrm.next, _newfrm.length);
                            _lastframe = _newfrm.next;
                            _newfrm.next = _lastfrm.next;
                        }

                        // Did we get a large enough frame?
                        if(_newfrm.length >= _totsize)
                        {

                            // Create excess frame if possible
                            if(wide->recycle == SQUISHRECYCLE_YES)
                            {
                                excess_frm(_lastframe, _newframe, &_newfrm, _totsize);
                                //WideLog->printf("- Merged frame was large enough (%u bytes wasted).", _newfrm.length - _totsize);
                            }

                            // If one of the frames in our chain was the last free frame,
                            // set the last free frame to the one we've merged it into,
                            // for later a clean up effort.
                            if(_lastframe == _base.lastfreeframe)
                                _base.lastfreeframe = _newframe;

                            // Got a free frame
                            break;
                        }
                    }

                    // Go to next free frame and try again
                    _newframe = _newfrm.next;
                }

                // If this was the first frame (ie. the first one pointed to by
                // firstfreeframe, which means that the first frame found was int32_t
                // enough to hold the message), then set the free pointer to the
                // start of the new free chain.
                if(_newframe == _base.firstfreeframe)
                    _base.firstfreeframe = _newfrm.next;
                if(_newframe == _base.lastfreeframe)
                    _base.lastfreeframe = _newfrm.prev;

                // Now update the linked list of free frames, to remove the current
                // frame from the free-frame list, if necessary.  We only need to do
                // this if the current frame wasn't just being appended to the .SQD
                // file, since there would be no links to update in that case.
                if(_newframe != _base.endframe)
                {
                    SqshFrm _tmpfrm;
                    upd_frm_next(_newfrm.prev, &_tmpfrm, _newfrm.next);
                    upd_frm_prev(_newfrm.next, &_tmpfrm, _newfrm.prev);
                }

                if(__mode & GMSG_NEW)
                {

                    // Link the frame to the last frame
                    _newfrm.prev = _base.lastframe;
                    _newfrm.next = SQFRAME_NULL;
                    SqshFrm _tmpfrm;
                    upd_frm_next(_newfrm.prev, &_tmpfrm, _newframe);
                    if(_base.firstframe == SQFRAME_NULL)
                        _base.firstframe = _newframe;
                    _base.lastframe = _newframe;
                }
                else
                {

                    // Rewriting old message
                    _newfrm.next = _oldfrm.next;
                    _newfrm.prev = _oldfrm.prev;
                    add_to_free_chain(_oldframe, &_oldfrm);
                    SqshFrm _tmpfrm;
                    upd_frm_next(_newfrm.prev, &_tmpfrm, _newframe);
                    upd_frm_prev(_newfrm.next, &_tmpfrm, _newframe);
                    if(_base.firstframe == _oldframe)
                        _base.firstframe = _newframe;
                    if(_base.lastframe == _oldframe)
                        _base.lastframe = _newframe;
                }

                // Set the frame length only if this is a brand new frame
                if(_newframe == _base.endframe)
                {
                    _newfrm.length = _totsize;
                    _base.endframe += sizeof(SqshFrm) + _totsize;
                }
            }

            // Set sizes in the frame
            _newfrm.totsize = _totsize;
            _newfrm.ctlsize = _ctlsize;
            _newfrm.type = SQFRAME_NORMAL;

            // Write frame, header, control info and message text
            write_frm(_newframe, &_newfrm);
            write(_fhsqd, &__hdr, sizeof(SqshHdr));
            write(_fhsqd, _ctl, (uint)_ctlsize);
            write(_fhsqd, _txt, (uint)_txtsize);
            throw_free(_ctl);

            // Update internal arrays if new
            if(__mode & GMSG_NEW)
            {
                _base.highestmsg++;
                _reln = (uint)(_base.totalmsgs++);
                __msg->msgno = _base.nextmsgno++;
                Msgn->Append(__msg->msgno);
                data->idx = _idx = (SqshIdx*)throw_realloc(data->idx, (uint)(_base.totalmsgs*sizeof(SqshIdx)));
            }

            // Update index
            SqshIdx* _idxp = _idx + _reln;
            _idxp->offset = _newframe;
            _idxp->msgno = __msg->msgno;
            _idxp->hash = _hash;
        }
        else
        {

            // Just update the header
            _idx[_reln].hash = _hash;
            lseekset(_fhsqd, _idx[_reln].offset+sizeof(SqshFrm));
            write(_fhsqd, &__hdr, sizeof(SqshHdr));
        }

        // Adjust the highwatermark if required
        if(__msg->attr.uns())
            if(_base.highwatermark >= __msg->msgno)
                _base.highwatermark = __msg->msgno - 1;
    }
    else
    {
        scan();
    }

    if(not _was_locked)
        unlock();

    GFTRK(0);
}


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

void SquishArea::save_hdr(int __mode, gmsg* __msg)
{

    GFTRK("SquishSaveHdr");

    save_message(__mode|GMSG_HDR, __msg);
}


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

void SquishArea::save_msg(int __mode, gmsg* __msg)
{

    GFTRK("SquishSaveMsg");

    save_message(__mode|GMSG_HDRTXT, __msg);
}


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

void SquishArea::del_msg(gmsg* __msg)
{

    GFTRK("SquishDelMsg");

    delete_msg(Msgn->ToReln(__msg->msgno)-1);

    GFTRK(0);
}


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

void SquishArea::new_msgno(gmsg* __msg)
{

    __msg->msgno = data->base.nextmsgno;
}


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

void SquishArea::update_timesread(gmsg* msg)
{

    GFTRK("SquishArea::update_timesread");

    lock();

    uint reln = Msgn->ToReln(msg->msgno) - 1;

    dword frame = data->idx[reln].offset;

    SqshHdr hdr;
    ::lseekset(data->fhsqd, frame+sizeof(SqshFrm));
    ::read(data->fhsqd, &hdr, sizeof(SqshHdr));

    hdr.attr |= msg->timesread ? MSGSEEN : 0;

    ::lseekset(data->fhsqd, frame+sizeof(SqshFrm));
    ::write(data->fhsqd, &hdr, sizeof(SqshHdr));

    unlock();

    GFTRK(0);
}


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