The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/*
	PerlIO::fse - File System Encoding

*/

#define PERL_NO_GET_CONTEXT
#include <EXTERN.h>
#include <perl.h>
#include <XSUB.h>

#include <perliol.h>

#include "ppport.h"

#define LayerFetch(layer, n) ((layer)->array[n].funcs)
#define LayerFetchSafe(layer, n) ( ((n) >= 0 && (n) < (layer)->cur) \
				? (layer)->array[n].funcs : PERLIO_FUNCS_CAST(&PerlIO_unix) )


#define DEFAULT_FSE "UTF-8"

#ifdef __CYGWIN__
#include <windows.h>
#endif

static PerlIO*
PerlIOUtil_openn(pTHX_ PerlIO_funcs* const force_tab, PerlIO_list_t* const layers, IV const n,
		const char* const mode, int const fd, int const imode, int const perm,
		PerlIO* f, int const narg, SV** const args){
	PerlIO_funcs* tab = NULL;
	IV i = n;

	while(--i >= 0){ /* find a layer with Open() */
		tab = LayerFetch(layers, i);
		if(tab && tab->Open){
			break;
		}
	}

	if(force_tab) tab = force_tab;

	if(tab && tab->Open){
		f = tab->Open(aTHX_ tab, layers, i,  mode,
				fd, imode, perm, f, narg, args);

		/* apply 'upper' layers
		   e.g. [ :unix :perlio :utf8 :creat ]
		                        ~~~~~        
		*/

		if(f && ++i < n){
			if(PerlIO_apply_layera(aTHX_ f, mode, layers, i, n) != 0){
				PerlIO_close(f);
				f = NULL;
			}
		}

	}
	else{
		SETERRNO(EINVAL, LIB_INVARG);
	}

	return f;
}

static IV
PerlIOUtil_useless_pushed(pTHX_ PerlIO* fp, const char* mode, SV* arg,
		PerlIO_funcs* tab){
	PERL_UNUSED_ARG(fp);
	PERL_UNUSED_ARG(mode);
	PERL_UNUSED_ARG(arg);

	if(ckWARN(WARN_LAYER)){
		Perl_warner(aTHX_ packWARN(WARN_LAYER), "Too late for %s layer", tab->name);
	}

	return -1;
}


static SV*
PerlIOFSE_get_fse(pTHX){
	SV* const fse = get_sv("PerlIO::fse::fse", GV_ADDMULTI);

	if (!SvOK(fse)) {
#if defined(WIN32) || defined(__CYGWIN__)
		unsigned long const codepage = GetACP();
		if(codepage != 0){
			Perl_sv_setpvf(aTHX_ fse, "cp%lu", codepage);
		}
#endif

		if(!PL_tainting){
			const char* const env_fse = PerlEnv_getenv("PERLIO_FSE");
			if(env_fse && *env_fse){
				sv_setpv(fse, env_fse);
			}
		}

		if(!SvOK(fse)){
			sv_setpvs(fse, DEFAULT_FSE);
		}
		PerlIO_debug("PerlIOFSE_initialize: encoding=%" SVf , fse);
	}

	return fse;
}

static SV*
PerlIOFSE_encode(pTHX_ SV* const enc, SV* const str){
	dSP;

	PUSHMARK(SP);
	EXTEND(SP, 2);
	PUSHs(enc);
	PUSHs(str);
	PUTBACK;

	call_pv("Encode::encode", G_SCALAR);

	SPAGAIN;

	return POPs; /* bytes */
}

static PerlIO*
PerlIOFSE_open(pTHX_ PerlIO_funcs* self, PerlIO_list_t* layers, IV n,
		const char* mode, int fd, int imode, int perm,
		PerlIO* f, int narg, SV** args){
	PERL_UNUSED_ARG(self);

	if(SvUTF8(args[0])){
		SV* const arg = PerlIOArg;
		SV* fse;
		SV* save;

		if(arg && SvOK(arg)){
			fse = arg;
		}
		else{
			fse = PerlIOFSE_get_fse(aTHX);
		}

		if(!SvOK(fse)){
			Perl_croak(aTHX_ "fse: encoding not set");
		}

		ENTER;
		SAVETMPS;

		save = args[0];
		args[0] = PerlIOFSE_encode(aTHX_ fse, args[0]);
	
		f = PerlIOUtil_openn(aTHX_ NULL, layers, n,
				mode, fd, imode, perm, f, narg, args);

		args[0] = save;

		FREETMPS;
		LEAVE;

		return f;
	}

	return PerlIOUtil_openn(aTHX_ NULL, layers, n,
			mode, fd, imode, perm, f, narg, args);

}

PERLIO_FUNCS_DECL(PerlIO_fse) = {
	sizeof(PerlIO_funcs),
	"fse",
	0, /* size */
	PERLIO_K_DUMMY, /* kind */
	PerlIOUtil_useless_pushed,
	NULL, /* popped */
	PerlIOFSE_open,
	NULL, /* binmode */
	NULL, /* arg */
	NULL, /* fileno */
	NULL, /* dup */
	NULL, /* read */
	NULL, /* unread */
	NULL, /* write */
	NULL, /* seek */
	NULL, /* tell */
	NULL, /* close */
	NULL, /* flush */
	NULL, /* fill */
	NULL, /* eof */
	NULL, /* error */
	NULL, /* clearerr */
	NULL, /* setlinebuf */
	NULL, /* get_base */
	NULL, /* bufsiz */
	NULL, /* get_ptr */
	NULL, /* get_cnt */
	NULL  /* set_ptrcnt */
};

MODULE = PerlIO::fse	PACKAGE = PerlIO::fse

PROTOTYPES: DISABLE

BOOT:
	PerlIO_define_layer(aTHX_ PERLIO_FUNCS_CAST(&PerlIO_fse));

SV*
get_fse(klass)
CODE:
	RETVAL = PerlIOFSE_get_fse(aTHX);
	SvREFCNT_inc_simple_void_NN(RETVAL);
OUTPUT:
	RETVAL

SV*
set_fse(klass, SV* encoding)
CODE:
	RETVAL = PerlIOFSE_get_fse(aTHX);
	SvREFCNT_inc_simple_void_NN(RETVAL);
	sv_setsv(RETVAL, encoding);
OUTPUT:
	RETVAL