The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "perlio.h"
#include "perliol.h"

#include "eol.h"
#include "fill.h"
#include "write.h"

IV
PerlIOEOL_pushed(pTHX_ PerlIO *f, const char *mode, SV *arg, PerlIO_funcs *tab)
{
    PerlIOEOL *s = PerlIOSelf(f, PerlIOEOL);
    STDCHAR *p, *eol_w = NULL, *eol_r = NULL;
    STRLEN len;
    
    if (PerlIOBase(PerlIONext(f))->flags & PERLIO_F_UTF8) {
        PerlIOBase(f)->flags |= PERLIO_F_UTF8;
    }
    else {
        PerlIOBase(f)->flags &= ~PERLIO_F_UTF8;
    }

    s->name = NULL;
    s->read.cr = s->write.cr = 0;
    s->read.seen = s->write.seen = 0;

    p = SvPV(arg, len);
    if (len) {
        STDCHAR *end = p + len;
        Newz('e', eol_r, len + 1, char);
        Copy(p, eol_r, len, char);

        p = eol_r; end = p + len;
        for (; p < end; p++) {
            *p = toLOWER(*p);
            if ((*p == '-') && (eol_w == NULL)) {
                *p = '\0';
                eol_w = p+1;
            }
        }
    }
    else {
        Perl_die(aTHX_ "Must pass CRLF, CR, LF or Native to :eol().");
    }

    if (eol_w == NULL) { eol_w = eol_r; }

    EOL_AssignEOL( eol_r, s->read );
    EOL_AssignEOL( eol_w, s->write );

    Safefree( eol_r );

    return PerlIOBuf_pushed(aTHX_ f, mode, arg, tab);
}

STDCHAR *
PerlIOEOL_get_base(pTHX_ PerlIO *f)
{
    PerlIOBuf *b = PerlIOSelf(f, PerlIOBuf);
    if (!b->buf) {
        PerlIOEOL *s = PerlIOSelf(f, PerlIOEOL);

	if (!b->bufsiz)
	    b->bufsiz = 4096;

	b->buf = Newz( 'B', b->buf, b->bufsiz * ( (s->read.eol == EOL_CRLF) ? 2 : 1 ), STDCHAR );

	if (!b->buf) {
	    b->buf = (STDCHAR *) & b->oneword;
	    b->bufsiz = sizeof(b->oneword);
	}
	b->ptr = b->buf;
	b->end = b->ptr;
    }
    return b->buf;
}

void
PerlIOEOL_clearerr(pTHX_ PerlIO *f)
{
    PerlIOEOL *s;
    
    if (PerlIOValid(f)) {
        s = PerlIOSelf(f, PerlIOEOL);
        if (PerlIOBase(f)->flags & PERLIO_F_EOF) {
            s->read.cr = s->write.cr = 0;
            s->read.seen = s->write.seen = 0;
        }
    }

    PerlIOBase_clearerr(aTHX_ f);
}

SSize_t
PerlIOEOL_write(pTHX_ PerlIO *f, const void *vbuf, Size_t count)
{
    PerlIOEOL *s = PerlIOSelf(f, PerlIOEOL);
    const STDCHAR *i, *start = vbuf, *end = vbuf;

    end += (unsigned int)count;

    EOL_StartUpdate( s->write );
    
    if (!(PerlIOBase(f)->flags & PERLIO_F_CANWRITE)) { return 0; }

    EOL_Dispatch( s->write, WriteWithCR, WriteWithLF, WriteWithCRLF );

    if (start >= end) { return count; }

    return ( (start + PerlIOBuf_write(aTHX_ f, start, end - start)) - (STDCHAR*)vbuf );
}

IV
PerlIOEOL_fill(pTHX_ PerlIO * f)
{
    IV code = PerlIOBuf_fill(aTHX_ f);
    PerlIOEOL *s = PerlIOSelf(f, PerlIOEOL);
    PerlIOBuf *b = PerlIOSelf(f, PerlIOBuf);
    const STDCHAR *i, *start = b->ptr, *end = b->end;
    STDCHAR *buf = NULL, *ptr = NULL;

    if (code != 0) { return code; }

    EOL_StartUpdate( s->read );
    EOL_Dispatch( s->read, FillWithCR, FillWithLF, FillWithCRLF );

    if (buf == NULL) { return 0; }

    if (i > start) {
        Copy(start, ptr, i - start, STDCHAR);
        ptr += i - start;
    }

    b->ptr = b->buf;
    b->end = b->buf + (ptr - buf);

    if (buf != b->buf) {
        Copy(buf, b->buf, ptr - buf, STDCHAR);
        Safefree(buf);
    }

    return 0;
}

PerlIO *
PerlIOEOL_open(pTHX_ PerlIO_funcs *self, PerlIO_list_t *layers,
               IV n, const char *mode, int fd, int imode, int perm,
               PerlIO *old, int narg, SV **args)
{
    SV *arg = (narg > 0) ? *args : PerlIOArg;
    PerlIO *f = PerlIOBuf_open( aTHX_ self, layers, n, mode, fd, imode, perm, old, narg, args );

    if (f) {
        PerlIOEOL *s = PerlIOSelf(f, PerlIOEOL);
        s->name = savepv( SvPV_nolen(arg) );
    }

    return f;
}

PerlIO_funcs PerlIO_eol = {
    sizeof(PerlIO_funcs),
    "eol",
    sizeof(PerlIOEOL),
    PERLIO_K_BUFFERED | PERLIO_K_UTF8, 
    PerlIOEOL_pushed,
    PerlIOBuf_popped,
    PerlIOEOL_open,
    PerlIOBase_binmode,
    NULL,
    PerlIOBase_fileno,
    PerlIOBuf_dup,
    PerlIOBuf_read,
    PerlIOBuf_unread,
    PerlIOEOL_write,
    PerlIOBuf_seek,
    PerlIOBuf_tell,
    PerlIOBuf_close,
    PerlIOBuf_flush,
    PerlIOEOL_fill,
    PerlIOBase_eof,
    PerlIOBase_error,
    PerlIOEOL_clearerr,
    PerlIOBase_setlinebuf,
    PerlIOEOL_get_base,
    PerlIOBuf_bufsiz,
    PerlIOBuf_get_ptr,
    PerlIOBuf_get_cnt,
    PerlIOBuf_set_ptrcnt
};

MODULE = PerlIO::eol            PACKAGE = PerlIO::eol

BOOT:  
  #ifdef PERLIO_LAYERS
        PerlIO_define_layer(aTHX_ &PerlIO_eol);
  #endif

unsigned int
eol_is_mixed(arg)
        SV  *arg
    PROTOTYPE: $
    CODE:
        STRLEN len;
        register U8 *i, *end;
        register unsigned int seen = 0;
        i = (U8*)SvPV(arg, len);
        end = i + len;
        RETVAL = 0;
        for (; i < end; i++) {
            EOL_CheckForMixedCRLF( seen, EOL_Break, EOL_Seen( seen, EOL_CR, EOL_Break ), break, ( i++ ) );
        }
    OUTPUT:
        RETVAL

char *
CR()
    PROTOTYPE:
    CODE:
        RETVAL = "\015";
    OUTPUT:
        RETVAL

char *
LF()
    PROTOTYPE:
    CODE:
        RETVAL = "\012";
    OUTPUT:
        RETVAL

char *
CRLF()
    PROTOTYPE:
    CODE:
        RETVAL = "\015\012";
    OUTPUT:
        RETVAL

char *
NATIVE()
    PROTOTYPE:
    CODE:
        RETVAL = (
            (EOL_NATIVE == EOL_CR)   ? "\015" :
            (EOL_NATIVE == EOL_LF)   ? "\012" :
            (EOL_NATIVE == EOL_CRLF) ? "\015\012" : ""
        );
    OUTPUT:
        RETVAL