//  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$
//  ------------------------------------------------------------------
//  PCBoard msgbase handling.
//  ------------------------------------------------------------------

#include <gdbgerr.h>
#include <gmemdbg.h>
#include <gdbgtrk.h>
#include <gstrall.h>
#include <gutlmisc.h>
#include <gmoprot.h>
#include <gmopcbd.h>


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

void PcbArea::data_open()
{

    wide = pcbwide;
    data = pcbdata + (pcbdatano++);
    data->fhmsg = data->fhidx = -1;
    memset(&data->base, 0x20, sizeof(PcbBase));
    data->base.highmsgno = 0;
    data->base.lowmsgno  = 0;
    data->base.active    = 0;
}


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

void PcbArea::data_close()
{

    pcbdatano--;
}


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

int PcbWideTestOpen(char* __file)
{

    GFTRK("PcbWideTestOpen");

    int _fh;
    long _tries = 0;

    do
    {

        _fh = ::sopen(__file, O_RDWR|O_BINARY|O_CREAT, WideSharemode, S_STDRW);
        if(_fh == -1)
        {

            // Tell the world
            if((errno != EACCES) or (PopupLocked(++_tries, false, __file) == false))
            {
                WideLog->ErrOpen();
                PcbWideClose();
                WideLog->printf("! A PCBoard msgbase file could not be opened.");
                WideLog->printf(": %s.", __file);
                WideLog->ErrOSInfo();
                OpenErrorExit();
            }
        }
    }
    while(_fh == -1);

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

    GFTRK(0);

    return _fh;
}


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

void PcbWideOpen()
{

    GFTRK("PcbWideOpen");

    if(not pcbwide->isopen)
    {

        pcbwide->fhusr = PcbWideTestOpen(pcbwide->users);
        pcbwide->fhinf = PcbWideTestOpen(pcbwide->usersinf);
        pcbwide->user->gufh = pcbwide->fhusr;
        pcbwide->user->fhinf = pcbwide->fhinf;

        // Read lastreads from USERS file
        lseekset(pcbwide->fhusr, pcbwide->userno*sizeof(PcbUsers));
        read(pcbwide->fhusr, &pcbwide->usersrec, sizeof(PcbUsers));
        int _maxlr = MinV(pcbwide->numareas, 40);
        for(int n=0; n<_maxlr; n++)
            pcbwide->lastread[n] = B2L(pcbwide->usersrec.lastmsgread[n]);

        // Read lastreads from USERS.INF file
        if(pcbwide->extconflen)
        {
            int32_t _offset = (pcbwide->usersrec.usersinfrec-1)*pcbwide->usershdr.totalrecsize;
            _offset +=     pcbwide->usershdrsize;
            _offset +=     pcbwide->usershdr.sizeofrec;
            _offset += 2 * pcbwide->confbytelen;
            _offset += 3 * pcbwide->extconflen;
            lseekset(pcbwide->fhinf, _offset);
            read(pcbwide->fhinf, pcbwide->lastread+40, (pcbwide->numareas-40)*sizeof(int32_t));
        }

        pcbwide->isopen = true;
    }

    GFTRK(0);
}


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

void PcbWideClose()
{

    GFTRK("PcbWideClose");

    if(pcbwide->isopen)
    {
        if(pcbwide->fhinf != -1)  ::close(pcbwide->fhinf);
        pcbwide->fhinf = -1;
        if(pcbwide->fhusr != -1)  ::close(pcbwide->fhusr);
        pcbwide->fhusr = -1;
        pcbwide->isopen = false;
    }

    GFTRK(0);
}


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

void PcbArea::raw_close()
{

    GFTRK("PcbRawClose");

    if(data->fhidx != -1)  ::close(data->fhidx);
    data->fhidx = -1;
    if(data->fhmsg != -1)  ::close(data->fhmsg);
    data->fhmsg = -1;

    GFTRK(0);
}


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

int PcbArea::test_open(const char* __file)
{

    GFTRK("PcbArea::test_open");

    int _fh;
    long _tries = 0;

    do
    {

        _fh = ::sopen(__file, O_RDWR|O_BINARY|O_CREAT, WideSharemode, S_STDRW);
        if(_fh == -1)
        {

            // Tell the world
            if((errno != EACCES) or (PopupLocked(++_tries, false, __file) == false))
            {
                WideLog->ErrOpen();
                raw_close();
                PcbWideClose();
                WideLog->printf("! A PCBoard msgbase file could not be opened.");
                WideLog->printf(": %s.", __file);
                WideLog->ErrOSInfo();
                OpenErrorExit();
            }
        }
    }
    while(_fh == -1);

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

    GFTRK(0);

    return _fh;
}


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

void PcbArea::raw_open()
{

    GFTRK("PcbRawOpen");

    if(not just_scanning)
        data->fhmsg = test_open(real_path());
    data->fhidx = test_open(AddPath(real_path(), ".idx"));

    GFTRK(0);
}


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

void PcbArea::raw_scan(int __keep_index, int __scanpm)
{

    GFTRK("PcbRawScan");

    // Open the msgbase if it wasn't already
    int _was_open = isopen;
    if(not _was_open)
    {
        if(not __keep_index)
            just_scanning = true;
        if(ispacked())
        {
            const char* newpath = Unpack(path());
            if(newpath == NULL)
                packed(false);
            set_real_path(newpath ? newpath : path());
        }
        isopen++;
        data_open();
        raw_open();
        just_scanning = false;
    }

    int _was_wideopen = wide->isopen;
    if(not _was_wideopen)
        PcbWideOpen();

    // Load the base header
    if(__keep_index)
    {
        lseekset(data->fhmsg, 0);
        read(data->fhmsg, &data->base, sizeof(PcbBase));
        data->base.highmsgno = B2L(data->base.highmsgno);
        data->base.lowmsgno  = B2L(data->base.lowmsgno);
        data->base.active    = B2L(data->base.active);
    }

    // Get some sizes
    uint _idxlen = filelength(data->fhidx);
    uint _idxsize  = (uint)_idxlen;
    uint _idxtotal = _idxsize / sizeof(PcbIdx);

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

    // Allocate buffer to hold .IDX data
    PcbIdx* _idxbuf = (PcbIdx*)throw_malloc(_idxsize+1);

    // Read the entire .IDX file into memory
    lseekset(data->fhidx, 0);
    read(data->fhidx, _idxbuf, _idxsize);

    // Variables for the loop
    uint _active = 0;
    uint _firstmsgno = 0;
    uint _lastmsgno = 0;
    uint _lastreadfound = 0;
    uint _totalmsgs = _idxtotal;
    uint _lastread = wide->lastread[board()];
    uint _lastread_reln = 0;
    uint32_t* _msgndxptr = Msgn->tag;
    PcbIdx* _idxptr = _idxbuf;

    // Fill message index
    uint n = 0;
    while((n++) < _totalmsgs)
    {
        if(_idxptr->offset > 0)
        {
            _active++;
            _lastmsgno = _idxptr->num;
            if(not _firstmsgno)
                _firstmsgno = _lastmsgno;
            if(__keep_index)
                *_msgndxptr++ = _lastmsgno;
            if((_lastmsgno >= _lastread) and (_lastread_reln == 0))
            {
                _lastreadfound = _lastmsgno;
                _lastread_reln = (uint)(_active - (_lastmsgno != _lastread ? 1 : 0));
            }
        }
        _idxptr++;
    }

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

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

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

    // Scan for personal mail
    if(__scanpm)
    {
        int umax = (WidePersonalmail & PM_ALLNAMES) ? WideUsernames : 1;
        PcbName* uname = (PcbName*)throw_calloc(umax, sizeof(PcbName));
        for(int un=0; un<umax; un++)
            strsetsz(strxcpy(uname[un], WideUsername[un], sizeof(PcbName)), 25);
        PMrk->ResetAll();
        uint n = lastread + 1;
        uint cnt = Msgn->Count();
        int gotpm = false;
        while(n <= cnt)
        {
            PcbIdx* idx = _idxbuf + (uint)(Msgn->at(n-1) - data->base.lowmsgno);
            for(int u=0; u<umax; u++)
            {
                if(strnieql(idx->to, uname[u], 25))
                {
                    gotpm = true;
                    break;
                }
            }
            if(gotpm)
            {
                switch(idx->status)
                {
                case '+':
                case '-':
                case '`':
                case '^':
                case '#':
                    break;
                default:
                    PMrk->Append(idx->num);
                }
                gotpm = false;
            }
            n++;
        }
        throw_free(uname);
    }

    // Free the .IDX buffer
    throw_free(_idxbuf);

    if(not _was_wideopen)
        PcbWideClose();

    // Close the msgbase again if we opened it in here
    if(not _was_open)
    {
        raw_close();
        data_close();
        if(ispacked())
        {
            CleanUnpacked(real_path());
        }
        isopen--;
    }

    GFTRK(0);
}


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

void PcbArea::scan()
{

    GFTRK("PcbScan");

    raw_scan(true);

    GFTRK(0);
}


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

void PcbArea::scan_area()
{

    GFTRK("PcbScanArea");

    raw_scan(false);

    GFTRK(0);
}


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

void PcbArea::scan_area_pm()
{

    GFTRK("PcbScanArea*M");

    raw_scan(true, true);
    Msgn->Reset();

    GFTRK(0);
}


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