/***********************************************************************
 *                                                                      *
 *               This software is part of the ast package               *
 *          Copyright (c) 1985-2011 AT&T Intellectual Property          *
 *                      and is licensed under the                       *
 *                 Eclipse Public License, Version 1.0                  *
 *                    by AT&T Intellectual Property                     *
 *                                                                      *
 *                A copy of the License is available at                 *
 *          http://www.eclipse.org/org/documents/epl-v10.html           *
 *         (with md5 checksum b35adb5213ca9657e911e9befb180842)         *
 *                                                                      *
 *              Information and Software Systems Research               *
 *                            AT&T Research                             *
 *                           Florham Park NJ                            *
 *                                                                      *
 *               Glenn Fowler <glenn.s.fowler@gmail.com>                *
 *                    David Korn <dgkorn@gmail.com>                     *
 *                     Phong Vo <phongvo@gmail.com>                     *
 *                                                                      *
 ***********************************************************************/
#include "config_ast.h"  // IWYU pragma: keep

#include <string.h>
#include <sys/types.h>

#include "sfhdr.h"
#include "sfio.h"

/*      Put out a null-terminated string
**
**      Written by Kiem-Phong Vo.
*/
ssize_t sfputr(Sfio_t *f, const char *s, int rc) {
    ssize_t p, n, w, sn;
    uchar *ps;
    char *ss;
    SFMTXDECL(f)

    SFMTXENTER(f, -1)

    if (f->mode != SF_WRITE && _sfmode(f, SF_WRITE, 0) < 0) SFMTXRETURN(f, -1)

    SFLOCK(f, 0)

    f->val = sn = -1;
    ss = (char *)s;
    for (w = 0; (*s || rc >= 0);) { /* need to communicate string size to exception handler */
        if ((f->flags & SF_STRING) && f->next >= f->endb) {
            sn = sn < 0 ? strlen(s) : (sn - (s - ss));
            ss = (char *)s;                  /* save current checkpoint */
            f->val = sn + (rc >= 0 ? 1 : 0); /* space requirement */
            f->bits |= SF_PUTR;              /* tell sfflsbuf to use f->val */
        }

        SFWPEEK(f, ps, p);
        f->bits &= ~SF_PUTR; /* remove any trace of this */

        if (p < 0) { /* something not right about buffering */
            break;
        }

        if (p == 0 || (f->flags & SF_WHOLE)) {
            n = sn < 0 ? strlen(s) : sn - (s - ss);
            if (p >= (n + (rc < 0 ? 0 : 1))) { /* buffer can hold everything */
                if (n > 0) {
                    memcpy(ps, s, n);
                    ps += n;
                    w += n;
                }
                if (rc >= 0) {
                    *ps++ = rc;
                    w += 1;
                }
                f->next = ps;
            } else { /* create a reserve buffer to hold data */
                Sfrsrv_t *rsrv;

                p = n + (rc >= 0 ? 1 : 0);
                if (!(rsrv = _sfrsrv(f, p))) {
                    n = 0;
                } else {
                    if (n > 0) memcpy(rsrv->data, s, n);
                    if (rc >= 0) rsrv->data[n] = rc;
                    if ((n = SFWRITE(f, rsrv->data, p)) < 0) n = 0;
                }

                w += n;
            }
            break;
        }

        if (*s == 0) {
            *ps++ = rc;
            f->next = ps;
            w += 1;
            break;
        }

        /*
         * Do not replace the following block with `memccpy()`. The
         * `ps` and `s` buffers may overlap or even point to the same
         * buffer. See issue #42 and #78.
         */
        for (; p > 0; --p, ++ps, ++s) {
            if ((*ps = *s) == 0) break;
        }

        w += ps - f->next;
        f->next = ps;
    }

    /* sync unseekable shared streams */
    if (f->extent < 0 && (f->flags & SF_SHARE)) {
        (void)SFFLSBUF(f, -1);

        /* check for line buffering */
    } else if ((f->flags & SF_LINE) && !(f->flags & SF_STRING) && (n = f->next - f->data) > 0) {
        if (n > w) n = w;
        f->next -= n;
        (void)SFWRITE(f, (void *)f->next, n);
    }

    SFOPEN(f)
    SFMTXRETURN(f, w)
}
