/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/*
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 * 
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 * 
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */

/*
 * Robin J. Maxwell 11-22-96
 */

#include "prstrms.h"
#include <string.h> // memmove

//
// Definition of macros _PRSTR_BP, _PRSTR_DELBUF, and _PRSTR_DELBUF_C.
//
// _PRSTR_BP is the protected member of class ios that is returned
// by the public method rdbuf().
//
// _PRSTR_DELBUF is the method or data member of class ios, if available,
// with which we can ensure that the ios destructor does not delete
// the associated streambuf.  If such a method or data member does not
// exist, define _PRSTR_DELBUF to be empty.
//
// _PRSTR_DELBUF_C is just _PRSTR_DELBUF qualified by a base class.
//

#if defined(__GNUC__)
#define _PRSTR_BP _strbuf
#define _PRSTR_DELBUF(x)    /* as nothing */
#define _PRSTR_DELBUF_C(c, x)  /* as nothing */
#elif defined(WIN32)
#define _PRSTR_BP bp
#define _PRSTR_DELBUF(x)	delbuf(x)
#define _PRSTR_DELBUF_C(c, x)	c::_PRSTR_DELBUF(x)
#elif defined(OSF1)
#define _PRSTR_BP m_psb
#define _PRSTR_DELBUF(x) /* as nothing */
#define _PRSTR_DELBUF_C(c, x)	/* as nothing */
#else
#define _PRSTR_BP bp
// Unix compilers don't believe in encapsulation
// At least on Solaris this is also ignored
#define _PRSTR_DELBUF(x)	delbuf = x
#define _PRSTR_DELBUF_C(c, x)	c::_PRSTR_DELBUF(x)
#endif

const PRIntn STRM_BUFSIZ = 8192;

#if !defined (PRFSTREAMS_BROKEN)   

PRfilebuf::PRfilebuf():
_fd(0),
_opened(PR_FALSE),
_allocated(PR_FALSE)
{
}

PRfilebuf::PRfilebuf(PRFileDesc *fd):
streambuf(),
_fd(fd),
_opened(PR_FALSE),
_allocated(PR_FALSE)
{
}

PRfilebuf::PRfilebuf(PRFileDesc *fd, char * buffptr, int bufflen):
_fd(fd),
_opened(PR_FALSE),
_allocated(PR_FALSE)
{
    PRfilebuf::setbuf(buffptr, bufflen);
}

PRfilebuf::~PRfilebuf()
{
    if (_opened){
        close();
    }else
        sync();
	if (_allocated)
		delete base();
}

PRfilebuf*	
PRfilebuf::open(const char *name, int mode, int flags)
{
     if (_fd != 0)
        return 0;    // error if already open
     PRIntn PRmode = 0;
    // translate mode argument
    if (!(mode & ios::nocreate))
        PRmode |= PR_CREATE_FILE;
    //if (mode & ios::noreplace)
    //    PRmode |= O_EXCL;
    if (mode & ios::app){
        mode |= ios::out;
        PRmode |= PR_APPEND;
    }
    if (mode & ios::trunc){
        mode |= ios::out;  // IMPLIED
        PRmode |= PR_TRUNCATE;
    }
    if (mode & ios::out){
        if (mode & ios::in)
            PRmode |= PR_RDWR;
        else
            PRmode |= PR_WRONLY;
        if (!(mode & (ios::in|ios::app|ios::ate|ios::noreplace))){
            mode |= ios::trunc; // IMPLIED
            PRmode |= PR_TRUNCATE;
        }
    }else if (mode & ios::in)
        PRmode |= PR_RDONLY;
    else
        return 0;    // error if not ios:in or ios::out


    //
    // The usual portable across unix crap...
    // NT gets a hokey piece of junk layer that prevents
    // access to the API.
#ifdef WIN32
    _fd = PR_Open(name, PRmode, PRmode);
#else
    _fd = PR_Open(name, PRmode, flags);
#endif
    if (_fd == 0)
        return 0;
    _opened = PR_TRUE;
    if ((!unbuffered()) && (!ebuf())){
        char * sbuf = new char[STRM_BUFSIZ];
        if (!sbuf)
            unbuffered(1);
        else{
			_allocated = PR_TRUE;
            streambuf::setb(sbuf,sbuf+STRM_BUFSIZ,0);
		}
    }
    if (mode & ios::ate){
        if (seekoff(0,ios::end,mode)==EOF){
            close();
            return 0;
        }
    }
    return this;
}

PRfilebuf*	
PRfilebuf::attach(PRFileDesc *fd)
{
    _opened = PR_FALSE;
    _fd = fd;
    return this;
}

int	
PRfilebuf::overflow(int c)
{
    if (allocate()==EOF)        // make sure there is a reserve area
        return EOF;
    if (PRfilebuf::sync()==EOF) // sync before new buffer created below
        return EOF;

    if (!unbuffered())
        setp(base(),ebuf());

    if (c!=EOF){
        if ((!unbuffered()) && (pptr() < epptr())) // guard against recursion
            sputc(c);
        else{
            if (PR_Write(_fd, &c, 1)!=1)
                return(EOF);
        }
    }
    return(1);  // return something other than EOF if successful
}

int	
PRfilebuf::underflow()
{
    int count;
    unsigned char tbuf;

    if (in_avail())
        return (int)(unsigned char) *gptr();

    if (allocate()==EOF)        // make sure there is a reserve area
        return EOF;
    if (PRfilebuf::sync()==EOF)
        return EOF;

    if (unbuffered())
        {
        if (PR_Read(_fd,(void *)&tbuf,1)<=0)
            return EOF;
        return (int)tbuf;
        }

    if ((count=PR_Read(_fd,(void *)base(),blen())) <= 0)
        return EOF;     // reached EOF
    setg(base(),base(),base()+count);
    return (int)(unsigned char) *gptr();
}

streambuf*	
PRfilebuf::setbuf(char *buffptr, int bufflen)
{
    if (is_open() && (ebuf()))
        return 0;
    if ((!buffptr) || (bufflen <= 0))
        unbuffered(1);
    else
        setb(buffptr, buffptr+bufflen, 0);
    return this;
}

streampos	
PRfilebuf::seekoff(streamoff offset, ios::seek_dir dir, int /* mode */)
{
    if (PR_GetDescType(_fd) == PR_DESC_FILE){
        PRSeekWhence fdir;
        PRInt32 retpos;
        switch (dir) {
            case ios::beg :
                fdir = PR_SEEK_SET;
                break;
            case ios::cur :
                fdir = PR_SEEK_CUR;
                break;
            case ios::end :
                fdir = PR_SEEK_END;
                break;
            default:
            // error
                return(EOF);
            }

        if (PRfilebuf::sync()==EOF)
            return EOF;
        if ((retpos=PR_Seek(_fd, offset, fdir))==-1L)
            return (EOF);
        return((streampos)retpos);
    }else
        return (EOF);
}


int 
PRfilebuf::sync()
{
    PRInt32 count; 

    if (_fd==0)
        return(EOF);

    if (!unbuffered()){
        // Sync write area
        if ((count=out_waiting())!=0){
            PRInt32 nout;
            if ((nout =PR_Write(_fd,
                               (void *) pbase(),
                               (unsigned int)count)) != count){
                if (nout > 0) {
                    // should set _pptr -= nout
                    pbump(-(int)nout);
                    memmove(pbase(), pbase()+nout, (int)(count-nout));
                }
                return(EOF);
            }
        }
        setp(0,0); // empty put area

        if (PR_GetDescType(_fd) == PR_DESC_FILE){
            // Sockets can't seek; don't need this
            if ((count=in_avail()) > 0){
                if (PR_Seek(_fd, -count, PR_SEEK_CUR)!=-1L)
                {
                    return (EOF);
                }
            }
        }
        setg(0,0,0); // empty get area
    }
    return(0);
}

PRfilebuf * 
PRfilebuf::close()
{
    int retval;
    if (_fd==0)
        return 0;

    retval = sync();

    if ((PR_Close(_fd)==0) || (retval==EOF))
        return 0;
    _fd = 0;
    return this;
}

PRifstream::PRifstream():
istream(new PRfilebuf)
{
    _PRSTR_DELBUF(0);
}

PRifstream::PRifstream(PRFileDesc *fd):
istream(new PRfilebuf(fd))
{
    _PRSTR_DELBUF(0);
}

PRifstream::PRifstream(PRFileDesc *fd, char *buff, int bufflen):
istream(new PRfilebuf(fd, buff, bufflen))
{
    _PRSTR_DELBUF(0);
}

PRifstream::PRifstream(const char * name, int mode, int flags):
istream(new PRfilebuf)
{
    _PRSTR_DELBUF(0);
    if (!rdbuf()->open(name, (mode|ios::in), flags))
        clear(rdstate() | ios::failbit);
}

PRifstream::~PRifstream()
{
	sync();

	delete rdbuf();
#ifdef _PRSTR_BP
	_PRSTR_BP = 0;
#endif
}

streambuf * 
PRifstream::setbuf(char * ptr, int len)
{
    if ((is_open()) || (!(rdbuf()->setbuf(ptr, len)))){
        clear(rdstate() | ios::failbit);
        return 0;
    }
    return rdbuf();
}

void 
PRifstream::attach(PRFileDesc *fd)
{
    if (!(rdbuf()->attach(fd)))
        clear(rdstate() | ios::failbit);
}

void 
PRifstream::open(const char * name, int mode, int flags)
{
    if (is_open() || !(rdbuf()->open(name, (mode|ios::in), flags)))
        clear(rdstate() | ios::failbit);
}

void 
PRifstream::close()
{
    clear((rdbuf()->close()) ? 0 : (rdstate() | ios::failbit));
}

PRofstream::PRofstream():
ostream(new PRfilebuf)
{
    _PRSTR_DELBUF(0);
}

PRofstream::PRofstream(PRFileDesc *fd):
ostream(new PRfilebuf(fd))
{
    _PRSTR_DELBUF(0);
}

PRofstream::PRofstream(PRFileDesc *fd, char *buff, int bufflen):
ostream(new PRfilebuf(fd, buff, bufflen))
{
    _PRSTR_DELBUF(0);
}

PRofstream::PRofstream(const char *name, int mode, int flags):
ostream(new PRfilebuf)
{
    _PRSTR_DELBUF(0);
    if (!rdbuf()->open(name, (mode|ios::out), flags))
        clear(rdstate() | ios::failbit);
}

PRofstream::~PRofstream()
{
	flush();

	delete rdbuf();
#ifdef _PRSTR_BP
	_PRSTR_BP = 0;
#endif
}

streambuf * 
PRofstream::setbuf(char * ptr, int len)
{
    if ((is_open()) || (!(rdbuf()->setbuf(ptr, len)))){
        clear(rdstate() | ios::failbit);
        return 0;
    }
    return rdbuf();
}

void 
PRofstream::attach(PRFileDesc *fd)
{
    if (!(rdbuf()->attach(fd)))
        clear(rdstate() | ios::failbit);
}

void 
PRofstream::open(const char * name, int mode, int flags)
{
    if (is_open() || !(rdbuf()->open(name, (mode|ios::out), flags)))
        clear(rdstate() | ios::failbit);
}

void 
PRofstream::close()
{
    clear((rdbuf()->close()) ? 0 : (rdstate() | ios::failbit));
}

PRfstream::PRfstream():
iostream(new PRfilebuf)
{
	_PRSTR_DELBUF_C(istream, 0);
	_PRSTR_DELBUF_C(ostream, 0);
}

PRfstream::PRfstream(PRFileDesc *fd):
iostream(new PRfilebuf(fd))
{
	_PRSTR_DELBUF_C(istream, 0);
	_PRSTR_DELBUF_C(ostream, 0);
}

PRfstream::PRfstream(PRFileDesc *fd, char *buff, int bufflen):
iostream(new PRfilebuf(fd, buff, bufflen))
{
	_PRSTR_DELBUF_C(istream, 0);
	_PRSTR_DELBUF_C(ostream, 0);
}

PRfstream::PRfstream(const char *name, int mode, int flags):
iostream(new PRfilebuf)
{
	_PRSTR_DELBUF_C(istream, 0);
	_PRSTR_DELBUF_C(ostream, 0);
    if (!rdbuf()->open(name, (mode|(ios::in|ios::out)), flags))
        clear(rdstate() | ios::failbit);
}

PRfstream::~PRfstream()
{
	sync();
	flush();

	delete rdbuf();
#ifdef _PRSTR_BP
	istream::_PRSTR_BP = 0;
	ostream::_PRSTR_BP = 0;
#endif
}

streambuf * 
PRfstream::setbuf(char * ptr, int len)
{
    if ((is_open()) || (!(rdbuf()->setbuf(ptr, len)))){
        clear(rdstate() | ios::failbit);
        return 0;
    }
    return rdbuf();
}

void 
PRfstream::attach(PRFileDesc *fd)
{
    if (!(rdbuf()->attach(fd)))
        clear(rdstate() | ios::failbit);
}

void 
PRfstream::open(const char * name, int mode, int flags)
{
    if (is_open() || !(rdbuf()->open(name, (mode|(ios::in|ios::out)), flags)))
        clear(rdstate() | ios::failbit);
}

void 
PRfstream::close()
{
    clear((rdbuf()->close()) ? 0 : (rdstate() | ios::failbit));
}

#else

// fix it sometime

int fix_prfstreams ()	{	return 0; }

#endif
