//  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$
//  ------------------------------------------------------------------
//  Fido/Opus/FTSC (*.MSG) type handling
//  ------------------------------------------------------------------

#include <sys/types.h>
#include <sys/stat.h>
#if defined(__WATCOMC__) || defined(__MINGW32__) || defined(_MSC_VER)
    #include <sys/utime.h>
#else
    #include <utime.h>
#endif
#include <gdbgtrk.h>
#include <gstrall.h>
#include <gmofido.h>


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

void FidoArea::lock()
{

    GFTRK("FidoLock");

    GFTRK(0);
}


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

void FidoArea::unlock()
{

    GFTRK("FidoUnlock");

    GFTRK(0);
}


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

void FidoArea::save_message(int __mode, gmsg* __msg, FidoHdr& __hdr)
{
    GFTRK("FidoSaveMessage");

    // Build message filename
    Path _msgfile;
    if(__mode & GMSG_NEW)
    {

        // Get a new message number
        uint _cnt = 0;
        do
        {
            __msg->msgno = Msgn->CvtReln(Msgn->Count()) + (++_cnt);
        }
        while(fexist(build_msgname(_msgfile, __msg->msgno)));

        // Never create a new 1.MSG in echo or local areas.
        if(__msg->msgno == 1)
        {
            if(not isnet())
            {
                __msg->msgno = 2;
                build_msgname(_msgfile, __msg->msgno);
            }
        }

        // Avoid collision with an existing msg
        while(fexist(_msgfile))
        {
            build_msgname(_msgfile, ++__msg->msgno);
            // here we should signal that a rescan is needed
        }
    }
    else
    {
        build_msgname(_msgfile, __msg->msgno);
    }

    // Delete message, if so ordered
    if(__mode & GMSG_DELETE)
    {
        if(remove(_msgfile))
        {
            chmod(_msgfile, S_STDRW);
            remove(_msgfile);
        }
        GFTRK(0);
        return;
    }

    // Get message file attributes
    struct stat st;
    if(stat(_msgfile, &st) == 0)
    {
        bool readonly = not (st.st_mode & S_IWUSR);

        // If the file is read-only, make it read-write-able
        if(readonly)
            chmod(_msgfile, S_STDRW);
    }

    // Determine file open mode
    int _omode = O_RDWR|O_CREAT|O_BINARY;
    if(__mode & GMSG_TXT)
    {

        // Add truncate, in case a changed msg is shorter
        _omode |= O_TRUNC;
    }

    // Open the message file
    int _fh = test_open(_msgfile, _omode, WideSharemode, YES);

    // Get date/time of message file
    if(not (__mode & GMSG_NEW) and not __msg->attr.upd())
    {
        fstat(_fh, &st);
    }

    memset(&__hdr, 0, sizeof(FidoHdr));

    // Transfer attributes
    __hdr.attr |= (word)(__msg->attr.pvt() ? FIDO_PVT       : 0);
    __hdr.attr |= (word)(__msg->attr.cra() ? FIDO_CRASH     : 0);
    __hdr.attr |= (word)(__msg->attr.rcv() ? FIDO_RECEIVED  : 0);
    __hdr.attr |= (word)(__msg->attr.snt() ? FIDO_SENT      : 0);
    __hdr.attr |= (word)(__msg->attr.att() ? FIDO_ATTACH    : 0);
    __hdr.attr |= (word)(__msg->attr.trs() ? FIDO_TRANSIT   : 0);
    __hdr.attr |= (word)(__msg->attr.orp() ? FIDO_ORPHAN    : 0);
    __hdr.attr |= (word)(__msg->attr.k_s() ? FIDO_KILLSENT  : 0);
    __hdr.attr |= (word)(__msg->attr.loc() ? FIDO_LOCAL     : 0);
    __hdr.attr |= (word)(__msg->attr.hld() ? FIDO_HOLD      : 0);
    __hdr.attr |= (word)(__msg->attr.rsv() ? FIDO_RESERVED  : 0);
    __hdr.attr |= (word)(__msg->attr.frq() ? FIDO_FREQ      : 0);
    __hdr.attr |= (word)(__msg->attr.rrq() ? FIDO_RETRECREQ : 0);
    __hdr.attr |= (word)(__msg->attr.rrc() ? FIDO_RETREC    : 0);
    __hdr.attr |= (word)(__msg->attr.arq() ? FIDO_AUDITREQ  : 0);
    __hdr.attr |= (word)(__msg->attr.urq() ? FIDO_UPDREQ    : 0);

    strxcpy(__hdr.to, __msg->to, 36);
    strxcpy(__hdr.by, __msg->by, 36);
    strxcpy(__hdr.re, __msg->re, 72);

    __hdr.ftsc.origzone  = __msg->oorig.zone;
    __hdr.orignet        = __msg->oorig.net;
    __hdr.orignode       = __msg->oorig.node;
    __hdr.ftsc.origpoint = __msg->oorig.point;

    __hdr.ftsc.destzone  = __msg->odest.zone;
    __hdr.destnet        = __msg->odest.net;
    __hdr.destnode       = __msg->odest.node;
    __hdr.ftsc.destpoint = __msg->odest.point;

    __hdr.replyto   = (word)__msg->link.to();
    __hdr.reply1st  = (word)__msg->link.first();

    __hdr.cost      = (word)__msg->cost;
    __hdr.timesread = (word)__msg->timesread;

    if(isopus())
    {
        __hdr.opus.written = TimeToFTime(__msg->written);
        __hdr.opus.arrived = TimeToFTime(__msg->arrived);
    }

    struct tm _tm;
    ggmtime(&_tm, &__msg->written);
    sprintf(__hdr.datetime, "%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
           );

    // Write message header
    write(_fh, &__hdr, sizeof(FidoHdr));

    // If message text is used
    if(__mode & GMSG_TXT)
    {

        // Write the message text
        write(_fh, __msg->txt, strlen(__msg->txt)+1);
    }

    // Close the message file
    ::close(_fh);

    // Reset date/time of message file
    if(not (__mode & GMSG_NEW) and not __msg->attr.upd())
    {
        struct utimbuf t;
        t.actime = st.st_atime;
        t.modtime = st.st_mtime;
        utime(_msgfile, &t);
    }

    // If the message has locked status, make it read-only
    if(__msg->attr.lok())
        chmod(_msgfile, S_STDRD);

    // Update internal array if the msg is new
    if(__mode & GMSG_NEW)
    {

        // Add the msgno to the index
        Msgn->Append(__msg->msgno);
    }

    GFTRK(0);
}


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

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

    GFTRK("FidoSaveHdr");

    FidoHdr _hdr;
    save_message(__mode|GMSG_HDR, __msg, _hdr);
}


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

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

    GFTRK("FidoSaveMsg");

    FidoHdr _hdr;
    save_message(__mode|GMSG_HDRTXT, __msg, _hdr);
}


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

void FidoArea::del_msg(gmsg* __msg)
{

    GFTRK("FidoDelMsg");

    FidoHdr _hdr;
    save_message(GMSG_HDR | GMSG_DELETE, __msg, _hdr);

    // Determine the highest remaining message number and the highest
    // remaining message number that is lower than the deleted message
    uint32_t _deleted_msgno = __msg->msgno;
    word _max_msgno = 0;
    word _prev_msgno = 0;
    uint _count = Msgn->Count();

    if (_count > 0) {
        _max_msgno = (word)Msgn->at(_count - 1);
        if (_max_msgno == _deleted_msgno) {
            _max_msgno = (word)((_count > 1) ? Msgn->at(_count - 2) : 0);
        }

        // Binary search for the largest message number less than _deleted_msgno
        int left = 0, right = _count - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            uint32_t val = Msgn->at(mid);
            if (val < _deleted_msgno) {
                _prev_msgno = (word)val;
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
    }

    // Update lastread pointers for all users in the lastread file.
    int _fh = ::sopen(AddPath(real_path(), wide->fidolastread), O_RDWR|O_BINARY, WideSharemode, S_STDRW);
    if(_fh != -1)
    {
        // Get the file size to determine how many user entries exist
        long _filesize = filelength(_fh);
        long _numusers = _filesize / (long)sizeof(word);

        if(_numusers > 0)
        {
            word* _lr_array = new word[_numusers];

            // Read the entire lastread file into memory
            lseekset(_fh, 0);
            if(read(_fh, _lr_array, _numusers * sizeof(word)) == (int)(_numusers * sizeof(word)))
            {
                bool _changed = false;

                // Scan through all user entries
                for(long _u = 0; _u < _numusers; _u++)
                {
                    // If this user's lastread points to the deleted message,
                    // update it to the next lower existing message number.
                    if(_lr_array[_u] == (word)_deleted_msgno)
                    {
                        _lr_array[_u] = _prev_msgno;
                        _changed = true;
                    }

                    // If a user's lastread pointer is higher than the maximum
                    // available message (e.g., due to corruption), set it to max.
                    if(_lr_array[_u] > _max_msgno)
                    {
                        _lr_array[_u] = _max_msgno;
                        _changed = true;
                    }
                }

                // If any pointers were updated, write the array back to disk
                if(_changed)
                {
                    lseekset(_fh, 0);
                    write(_fh, _lr_array, _numusers * sizeof(word));
                }
            }

            delete[] _lr_array;
        }

        ::close(_fh);
    }

    GFTRK(0);
}
//  ------------------------------------------------------------------

void FidoArea::new_msgno(gmsg* __msg)
{

    GFTRK("FidoNewMsgno");

    uint n = 0;
    Path _buf;
    do
    {
        __msg->msgno = Msgn->CvtReln(Msgn->Count()) + (++n);
    }
    while(fexist(build_msgname(_buf, __msg->msgno)));

    GFTRK(0);
}


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

void FidoArea::update_timesread(gmsg* msg)
{

    GFTRK("FidoArea::update_timesread");

    Path filename;
    build_msgname(filename, msg->msgno);

    // If it has Lok attribute set, remove READ_ONLY attribute
    // and later add it again
    struct stat st;
    bool readonly = false;

    if(stat(filename, &st) == 0)
    {
        readonly = not (st.st_mode & S_IWUSR);

        if(readonly)
            chmod(filename, S_STDRW);
    }

    int fh = test_open(filename, O_RDWR|O_BINARY, WideSharemode);
    if(fh != -1)
    {
        FidoHdr hdr;
        ::read(fh, &hdr, sizeof(FidoHdr));
        hdr.timesread = (word)msg->timesread;
        ::lseekset(fh, 0);
        ::write(fh, &hdr, sizeof(FidoHdr));
        ::close(fh);
    }

    // If file was read_only before, make it again read_only
    if(readonly)
        chmod(filename,S_STDRD);

    GFTRK(0);
}


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