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

//  ------------------------------------------------------------------
//  The Goldware Library
//  Copyright (C) 1990-1999 Odinn Sorensen
//  Copyright (C) 1999-2000 Alex. S. Aganichev
//  ------------------------------------------------------------------
//  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$
//  ------------------------------------------------------------------
//  Hudson / Goldbase msgbase handling
//  ------------------------------------------------------------------


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

template <class msgn_t, class rec_t, class attr_t, class board_t, class last_t, bool __HUDSON>
void _HudsWide<msgn_t, rec_t, attr_t, board_t, last_t, __HUDSON>::raw_close()
{
    GFTRK("HudsRawClose");

    fhtxt.Close();
    fhhdr.Close();
    fhidx.Close();
    fhinf.Close();
    fhlrd.Close();
    fhtoi.Close();
    fhusr.Close();

    GFTRK(0);
}


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

template <class msgn_t, class rec_t, class attr_t, class board_t, class last_t, bool __HUDSON>
void _HudsWide<msgn_t, rec_t, attr_t, board_t, last_t, __HUDSON>::test_open(gfile &__file, const char* __fname, int __oaccess)
{
    GFTRK("HudsTestOpen");

    long _tries = 0;
    __oaccess |= O_RDWR|O_BINARY;

    do
    {
        Path _testfn;
        strcpy(_testfn, AddPath(path, __fname));
        int _omode = (__oaccess & O_CREAT) ? S_STDRW : S_STDRD;

        __file.Open(_testfn, __oaccess, WideSharemode, _omode);
        if (!__file.isopen())
        {
            if (errno == ENOENT)
            {
                if (_tries == 0)
                {
                    __oaccess |= O_CREAT;
                    _tries++;
                    continue;
                }
            }

            // Request the other program to unlock
            if (errno != ENOENT)
                TouchFile(AddPath(path, "mbunlock.now"));

            // Tell the world
            if (PopupLocked(++_tries, false, _testfn) == false)
            {
                // User requested to exit
                WideLog->ErrOpen();
                raw_close();
                WideLog->printf("! A %s msgbase file could not be opened.", __HUDSON ? HUDS_NAME : GOLD_NAME);
                WideLog->printf(": %s.", _testfn);
                WideLog->ErrOSInfo();
                OpenErrorExit();
            }
        }
    }
    while (!__file.isopen());

    // Remove the popup window
    if (_tries)
        PopupLocked(0, 0, NULL);

    GFTRK(0);
}


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

template <class msgn_t, class rec_t, class attr_t, class board_t, class last_t, bool __HUDSON>
void _HudsWide<msgn_t, rec_t, attr_t, board_t, last_t, __HUDSON>::raw_open(int __oaccess, int __all)
{

    GFTRK("HudsRawOpen");

    test_open(fhidx, __HUDSON ? "msgidx" HUDS_EXT : "msgidx" GOLD_EXT, __oaccess);
    test_open(fhinf, __HUDSON ? "msginfo" HUDS_EXT : "msginfo" GOLD_EXT, __oaccess);
    test_open(fhlrd, __HUDSON ? "lastread" HUDS_EXT : "lastread" GOLD_EXT, __oaccess);
    test_open(fhtoi, __HUDSON ? "msgtoidx" HUDS_EXT : "msgtoidx" GOLD_EXT, __oaccess);
    if (__all)
    {
        test_open(fhhdr, __HUDSON ? "msghdr" HUDS_EXT : "msghdr" GOLD_EXT, __oaccess);
        test_open(fhtxt, __HUDSON ? "msgtxt" HUDS_EXT : "msgtxt" GOLD_EXT, __oaccess);
        test_open(fhusr, __HUDSON ? "users" HUDS_EXT : "users" GOLD_EXT, __oaccess);
    }

    GFTRK(0);
}


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

template <class msgn_t, class rec_t, class attr_t, class board_t, class last_t, bool __HUDSON>
void _HudsWide<msgn_t, rec_t, attr_t, board_t, last_t, __HUDSON>::raw_open_scan()
{

    GFTRK("HudsRawOpenScan");

    raw_open(0, false);

    GFTRK(0);
}


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

template <class msgn_t, class rec_t, class attr_t, class board_t, class last_t, bool __HUDSON>
void _HudsWide<msgn_t, rec_t, attr_t, board_t, last_t, __HUDSON>::refresh()
{

    GFTRK("HudsRefresh");

    // (Re)Allocate memory to hold the complete MSGIDX.BBS/DAT
    msgidxsize = fhidx.FileLength();
    msgidxptr = (HudsIdx*)throw_realloc(msgidxptr, (uint)(msgidxsize+sizeof(HudsIdx)));

    // Load MSGIDX.BBS/DAT
    fhidx.LseekSet(0);
    fhidx.Read(msgidxptr, (uint)msgidxsize);

    // Load MSGINFO.BBS/DAT
    fhinf.LseekSet(0);
    fhinf.Read(&msginfo, sizeof(HudsInfo));

    // Load LASTREAD.BBS/DAT
    fhlrd.LseekSet(userno*sizeof(HudsLast));
    fhlrd.Read(lastrec, sizeof(HudsLast));

    GFTRK(0);
}


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

template <class msgn_t, class rec_t, class attr_t, class board_t, class last_t, bool __HUDSON>
void _HudsWide<msgn_t, rec_t, attr_t, board_t, last_t, __HUDSON>::open()
{

    GFTRK("HudsWideOpen");

    isopen = 1;
    iswideopen = true;
    ispmscanned = false;
    iswidescanned = false;
    raw_open_scan();
    lock();

    GFTRK(0);
}


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

template <class msgn_t, class rec_t, class attr_t, class board_t, class last_t, bool __HUDSON>
void _HudsWide<msgn_t, rec_t, attr_t, board_t, last_t, __HUDSON>::close()
{

    GFTRK("HudsWideClose");

    unlock();
    raw_close();
    throw_release(msgidxptr);
    throw_release(pmscan);
    throw_release(scn);
    iswideopen = false;
    isopen = 0;

    GFTRK(0);
}


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

template <class msgn_t, class rec_t, class attr_t, class board_t, class last_t, bool __HUDSON>
void _HudsWide<msgn_t, rec_t, attr_t, board_t, last_t, __HUDSON>::lock()
{

    GFTRK("HudsLock");

    if(not islocked and WideCanLock)
    {

        long _tries = 0;

        // Try to get the lock
        while (fhinf.Lock(sizeof(HudsInfo)+1, 1) == -1)
        {
            // Tell the world
            if(PopupLocked(++_tries, true, AddPath(path, __HUDSON ? "msginfo" HUDS_EXT : "msginfo" GOLD_EXT)) == false)
            {

                // User requested to exit
                WideLog->ErrLock();
                raw_close();
                WideLog->printf("! A %s msgbase file could not be locked.", __HUDSON ? HUDS_NAME : GOLD_NAME);
                WideLog->printf(": %smsginfo%s.", path, __HUDSON ? HUDS_EXT : GOLD_EXT);
                WideLog->ErrOSInfo();
                LockErrorExit();
            }

            // Request the other program to unlock
            TouchFile(AddPath(path, "mbunlock.now"));
        }

        // Remove the popup window
        if(_tries)
            PopupLocked(0, 0, NULL);

        // We got the lock
        islocked = true;
    }

    // Refresh msgbase data
    refresh();

    GFTRK(0);
}


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

template <class msgn_t, class rec_t, class attr_t, class board_t, class last_t, bool __HUDSON>
void _HudsWide<msgn_t, rec_t, attr_t, board_t, last_t, __HUDSON>::unlock()
{

    GFTRK("HudsUnlock");

    if (islocked and WideCanLock)
    {
        fhinf.Unlock(sizeof(HudsInfo)+1, 1);
        islocked = false;
    }

    GFTRK(0);
}


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

template <class msgn_t, class rec_t, class attr_t, class board_t, class last_t, bool __HUDSON>
void _HudsArea<msgn_t, rec_t, attr_t, board_t, last_t, __HUDSON>::lock()
{

    wide->lock();
}


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

template <class msgn_t, class rec_t, class attr_t, class board_t, class last_t, bool __HUDSON>
void _HudsArea<msgn_t, rec_t, attr_t, board_t, last_t, __HUDSON>::unlock()
{

    wide->unlock();
}


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

template <class msgn_t, class rec_t, class attr_t, class board_t, class last_t, bool __HUDSON>
void _HudsWide<msgn_t, rec_t, attr_t, board_t, last_t, __HUDSON>::scan()
{

    GFTRK("HudsWideScan");

    iswidescanned = true;

    // Alloc scan array
    throw_free(scn);
    scn = (HudsScan*)throw_calloc((__HUDSON ? HUDS_MAXBOARD : GOLD_MAXBOARD), sizeof(HudsScan));

    // Board and scan array pointer
    int _board;
    HudsScan* _scan = scn;

    // Init wide scan array
    _board = 0;
    while(_board < (__HUDSON ? HUDS_MAXBOARD : GOLD_MAXBOARD))
    {
        _scan->active = msginfo.active[_board];
        _scan->lastread = lastrec[_board];
        _board++;
        _scan++;
    }

    // Setup MSGIDX pointer and counters
    HudsIdx* _msgidx_ptr = msgidxptr;
    uint    _msgidx_count = 0;
    uint    _msgidx_total = (uint)(msgidxsize/sizeof(HudsIdx));
    int         _invalidboards = 0;

    // Scan the index
    while(_msgidx_count < _msgidx_total)
    {

        // Is the msg not deleted?
        if(_msgidx_ptr->msgno != (__HUDSON ? HUDS_DELETEDMSGNO : GOLD_DELETEDMSGNO))
        {

            int _idxboard = _msgidx_ptr->board;
            if(_idxboard and (_idxboard <= (__HUDSON ? HUDS_MAXBOARD : GOLD_MAXBOARD)))
            {

                _scan = scn + (_idxboard - 1);

                // Get message number
                _scan->lastmsgno = _msgidx_ptr->msgno;
                _scan->count++;

                // Set first message number
                if(not _scan->firstmsgno)
                    _scan->firstmsgno = _scan->lastmsgno;

                // Set lastread pointer
                if((_scan->lastmsgno >= _scan->lastread) and (_scan->lastreadreln == 0))
                {
                    _scan->lastreadfound = _scan->lastmsgno;
                    _scan->lastreadreln = _scan->count - (_scan->lastmsgno != _scan->lastread ? 1 : 0);
                }
            }
            else
            {
                _invalidboards++;
            }
        }

        // Go to next record
        _msgidx_count++;
        _msgidx_ptr++;
    }

    if(_invalidboards)
    {
        WideLog->printf("! Found %u msgs with an invalid board number (0 or >%u).", _invalidboards, (__HUDSON ? HUDS_MAXBOARD : GOLD_MAXBOARD));
        WideLog->printf("! In the %s msgbase at %s.", __HUDSON ? HUDS_NAME : GOLD_NAME, path);
        WideLog->printf(": Info: Your msgbase may be partially corrupted.");
        WideLog->printf("+ Advice: Run a msgbase index rebuild/recover utility.");
    }

    // Check/fix boards
    _board = 0;
    _scan = scn;
    while(_board < (__HUDSON ? HUDS_MAXBOARD : GOLD_MAXBOARD))
    {

        // Check/fix lastreads
        if(_scan->count and (_scan->lastreadfound != _scan->lastread))
        {
            if(_scan->lastread > _scan->lastmsgno)
                _scan->lastreadreln = _scan->count;
            else if(_scan->lastread < _scan->firstmsgno)
                _scan->lastreadreln = 0;
        }

        if(WideDebug)
        {
            WideLog->printf("- b:%u: t:%u, l:%u, fm:%u, hm:%u, lr:%u, u:%u",
                            _board,
                            _scan->count,
                            _scan->lastreadreln,
                            _scan->firstmsgno,
                            _scan->lastmsgno,
                            _scan->lastread,
                            userno
                           );
        }

        // Check active message count and log it if different
        if(_scan->active != _scan->count)
        {
            WideLog->printf("! Counted %u active msgs in %s board %u.", _scan->count, __HUDSON ? HUDS_NAME : GOLD_NAME, _board+1);
            WideLog->printf("! According to msginfo%s there should be %u active msgs.", __HUDSON ? HUDS_EXT : GOLD_EXT, _scan->active);
            WideLog->printf(": Info: A program did not update msginfo%s correctly.", __HUDSON ? HUDS_EXT : GOLD_EXT);
            WideLog->printf("+ Advice: Run a msgbase index rebuild utility.");
        }

        _board++;
        _scan++;
    }

    GFTRK(0);
}


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

template <class msgn_t, class rec_t, class attr_t, class board_t, class last_t, bool __HUDSON>
void _HudsArea<msgn_t, rec_t, attr_t, board_t, last_t, __HUDSON>::raw_scan(int __keep_index)
{

    GFTRK("HudsRawScan");

    if(!wide)
        wide = (HudsWide*) (__HUDSON ? (void *)hudsonwide : (void *)goldbasewide);

    if(wide->iswideopen and not wide->iswidescanned)
        wide->scan();

    // Get wide scan data if any
    if(wide->scn)
    {

        // Update area data
        HudsScan* _scan = wide->scn + (board() - 1);
        Msgn->SetCount(_scan->count);
        lastread = _scan->lastreadreln;
        lastreadentry = _scan->lastreadfound;
    }
    else
    {

        // Open the msgbase if it wasn't already
        int _was_open = wide->isopen;
        if(not _was_open)
            wide->open();

        // Get the number of active msgs in the area
        board_t _board = (board_t)board();
        uint _active = wide->msginfo.active[_board-1];
        msgn_t _lastread = wide->lastrec[_board-1];

        // Setup pointers and counts
        uint    _msg_count = 0;
        HudsIdx* _msgidx_ptr = wide->msgidxptr;
        uint    _msgidx_count = 0;
        uint    _msgidx_total = (uint)(wide->msgidxsize/sizeof(HudsIdx));
        uint    _lastread_reln = 0;

        // (Re)Allocate index
        if(__keep_index)
            Msgn->Resize(_active);

        // Index pointers
        uint32_t* _msgno_ptr = Msgn->tag;

        // Fill index
        uint _firstmsgno = 0;
        uint _lastmsgno = 0;
        uint _lastreadfound = 0;
        while(_msgidx_count < _msgidx_total)
        {

            // Is it our board and is the msg not deleted?
            if((_msgidx_ptr->board == _board) and (_msgidx_ptr->msgno != (__HUDSON ? HUDS_DELETEDMSGNO : GOLD_DELETEDMSGNO)))
            {

                // Get message number
                _lastmsgno = _msgidx_ptr->msgno;
                _msg_count++;

                // Set first message number
                if(not _firstmsgno)
                    _firstmsgno = _lastmsgno;

                // Transfer data to the index
                if(__keep_index)
                    *_msgno_ptr++ = _lastmsgno;

                // Set lastread pointer
                if((_lastmsgno >= _lastread) and (_lastread_reln == 0))
                {
                    _lastreadfound = _lastmsgno;
                    _lastread_reln = _msg_count - (_lastmsgno != _lastread ? 1 : 0);
                }

                // Break loop as soon as we have all we need
                if(_msg_count == _active)
                    break;
            }

            // Go to next record
            _msgidx_count++;
            _msgidx_ptr++;
        }

        // If the exact lastread was not found
        if(_msg_count and (_lastreadfound != _lastread))
        {

            // Higher than highest or lower than lowest?
            if(_lastread > _lastmsgno)
                _lastread_reln = _msg_count;
            else if(_lastread < _firstmsgno)
                _lastread_reln = 0;
        }

        // Check active message count and log it if different
        if(_active != _msg_count)
        {
            WideLog->printf("! Counted %u active msgs in %s board %u (%s).", _msg_count, __HUDSON ? HUDS_NAME : GOLD_NAME, board(), echoid());
            WideLog->printf("! According to msginfo%s there should be %u active msgs.", __HUDSON ? HUDS_EXT : GOLD_EXT, _active);
            WideLog->printf(": Info: A program did not update msginfo%s correctly.", __HUDSON ? HUDS_EXT : GOLD_EXT);
            WideLog->printf("+ Advice: Run a msgbase index rebuild utility.");
        }

        // Update area data
        Msgn->SetCount(_msg_count);
        lastread = _lastread_reln;
        lastreadentry = _lastreadfound;

        if(WideDebug)
        {
            WideLog->printf("- b:%u: t:%u, l:%u, fm:%u, hm:%u, lr:%u, u:%u",
                            board(),
                            Msgn->Count(),
                            lastread,
                            _firstmsgno,
                            _lastmsgno,
                            _lastread,
                            wide->userno
                           );
        }

        // Close the msgbase again if we opened it in here
        if(not _was_open)
            wide->close();
    }

    GFTRK(0);
}


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

template <class msgn_t, class rec_t, class attr_t, class board_t, class last_t, bool __HUDSON>
void _HudsArea<msgn_t, rec_t, attr_t, board_t, last_t, __HUDSON>::scan()
{

    GFTRK("HudsScan");

    raw_scan(true);

    GFTRK(0);
}


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

template <class msgn_t, class rec_t, class attr_t, class board_t, class last_t, bool __HUDSON>
void _HudsArea<msgn_t, rec_t, attr_t, board_t, last_t, __HUDSON>::scan_area()
{

    GFTRK("HudsScanArea");

    raw_scan(false);

    GFTRK(0);
}


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

#define HudsIdxCmp(a,b) ((a.board-b.board != 0) ? a.board-b.board : ((int)(a.msgno-b.msgno)))


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

#define TOIDXBUFSZ 100u
#define PMSCANBUFSZ 50u

template <class msgn_t, class rec_t, class attr_t, class board_t, class last_t, bool __HUDSON>
void _HudsWide<msgn_t, rec_t, attr_t, board_t, last_t, __HUDSON>::realloc_pm_scan()
{

    pmscan = (HudsIdx*)throw_realloc(pmscan, (pmscantotal+PMSCANBUFSZ)*sizeof(HudsIdx));
}


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

template <class msgn_t, class rec_t, class attr_t, class board_t, class last_t, bool __HUDSON>
void _HudsWide<msgn_t, rec_t, attr_t, board_t, last_t, __HUDSON>::scan_pm()
{

    GFTRK("HudsWideScanPM");

    if(not iswidescanned)
        scan();

    ispmscanned = true;

    HudsToIdx* toidxbuf = (HudsToIdx*)throw_calloc(TOIDXBUFSZ, sizeof(HudsToIdx));
    uint totrecs = (uint)(fhtoi.FileLength() / sizeof(HudsToIdx));
    HudsIdx* idxptr = msgidxptr;
    throw_release(pmscan);
    pmscantotal = 0;
    int invalidboards = 0;
    int gotpm, idxboard, umax, u;
    uint getrecs, rec;
    HudsToIdx* toidx;

    do
    {

        rec = 0;
        gotpm = false;
        getrecs = MinV(TOIDXBUFSZ, totrecs);
        fhtoi.Read(toidxbuf, getrecs*sizeof(HudsToIdx));

        for(toidx=toidxbuf; rec<getrecs; idxptr++,toidx++,rec++)
        {

            // Skip deleted msgs
            if(idxptr->msgno == (__HUDSON ? HUDS_DELETEDMSGNO : GOLD_DELETEDMSGNO))
                continue;

            // Skip msgs in invalid boards
            idxboard = idxptr->board;
            if(not (idxboard and (idxboard <= (__HUDSON ? HUDS_MAXBOARD : GOLD_MAXBOARD))))
            {
                invalidboards++;
                continue;
            }

            // If after lastread
            if(idxptr->msgno <= scn[idxboard-1].lastreadfound)
                continue;

            // Convert name string
            strnp2c(toidx->name, sizeof(HudsToIdx)-1);

            // Is it a PM?
            umax = (WidePersonalmail & PM_ALLNAMES) ? WideUsernames : 1;
            for(u=0; u<umax; u++)
            {
                if(strieql(toidx->name, WideUsername[u]))
                {
                    gotpm = true;
                    break;
                }
            }

            if(gotpm)
            {
                if((pmscantotal % PMSCANBUFSZ) == 0)
                    realloc_pm_scan();
                pmscan[pmscantotal++] = *idxptr;
                scn[idxboard-1].pmcount++;
                gotpm = false;
            }
        }
        totrecs -= getrecs;
    }
    while(totrecs);

    throw_free(toidxbuf);
    pmscan = (HudsIdx*)throw_realloc(pmscan, (pmscantotal+1)*sizeof(HudsIdx));

    // Sort all pm records in board/msgno order
    for(uint k=pmscantotal >> 1; k; k >>= 1)
        for(uint i=k; i < pmscantotal; i++)
            for(uint j=i-k; (j >= 0) and HudsIdxCmp(pmscan[j], pmscan[j+k]) > 0; j-=k)
            {
                HudsIdx e = pmscan[j];
                pmscan[j] = pmscan[j+k];
                pmscan[j+k] = e;
            }

    if(invalidboards)
    {
        WideLog->printf("! Found %u msgs with an invalid board number (0 or >%u).", invalidboards, (__HUDSON ? HUDS_MAXBOARD : GOLD_MAXBOARD));
        WideLog->printf("! In the %s msgbase at %s.", __HUDSON ? HUDS_NAME : GOLD_NAME, path);
        WideLog->printf(": Info: Your msgbase may be partially corrupted.");
        WideLog->printf("+ Advice: Run a msgbase index rebuild/recover utility.");
    }

    GFTRK(0);
}


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

template <class msgn_t, class rec_t, class attr_t, class board_t, class last_t, bool __HUDSON>
void _HudsArea<msgn_t, rec_t, attr_t, board_t, last_t, __HUDSON>::scan_area_pm()
{

    GFTRK("HudsScanAreaPM");

    PMrk->ResetAll();

    if(!wide)
        wide = (HudsWide*) (__HUDSON ? (void *)hudsonwide : (void *)goldbasewide);

    if(wide->iswideopen and not wide->ispmscanned)
        wide->scan_pm();

    // Get wide scan data if any
    if(wide->scn)
    {

        board_t _board = (board_t)board();
        HudsScan* _scan = wide->scn + (_board - 1);

        if(wide->pmscan)
        {
            if(_scan->pmcount)
            {

                HudsIdx* pmscanptr = wide->pmscan;
                while(pmscanptr->board != _board)
                    pmscanptr++;

                uint n = 0;
                uint cnt = _scan->pmcount;
                while(n < cnt)
                {
                    PMrk->Append(pmscanptr->msgno);
                    pmscanptr++;
                    n++;
                }
            }
        }

        // Update area data
        Msgn->SetCount(_scan->count);
        lastread = _scan->lastreadreln;
        lastreadentry = _scan->lastreadfound;

        if(WideDebug)
        {
            WideLog->printf("- b:%u: t:%u, l:%u, fm:%u, hm:%u, lr:%u, u:%u, pm:%i",
                            _board,
                            _scan->count,
                            _scan->lastreadreln,
                            _scan->firstmsgno,
                            _scan->lastmsgno,
                            _scan->lastread,
                            wide->userno,
                            PMrk->Count()
                           );
        }

    }
    else
    {
        if(WideDebug)
            WideLog->printf("- Oops!  Fell into empty bracket.");
    }

    GFTRK(0);
}


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