#define PERL_NO_GET_CONTEXT 1
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#define PERL_VERSION_DECIMAL(r,v,s) (r*1000000 + v*1000 + s)
#define PERL_DECIMAL_VERSION \
	PERL_VERSION_DECIMAL(PERL_REVISION,PERL_VERSION,PERL_SUBVERSION)
#define PERL_VERSION_GE(r,v,s) \
	(PERL_DECIMAL_VERSION >= PERL_VERSION_DECIMAL(r,v,s))

#ifndef cBOOL
# define cBOOL(x) ((bool)!!(x))
#endif /* !cBOOL */

#ifndef C_ARRAY_LENGTH
# define C_ARRAY_LENGTH(a) (sizeof(a)/sizeof(*(a)))
#endif /* !C_ARRAY_LENGTH */

#ifndef newSVpvs_share
# define newSVpvs_share(STR) newSVpvn_share(""STR"", sizeof(STR)-1, 0)
#endif /* !newSVpvs_share */

#ifndef SvSHARED_HASH
# define SvSHARED_HASH(SV) SvUVX(SV)
#endif /* !SvSHARED_HASH */

#ifndef OpMORESIB_set
# define OpMORESIB_set(o, sib) ((o)->op_sibling = (sib))
# define OpLASTSIB_set(o, parent) ((o)->op_sibling = NULL)
# define OpMAYBESIB_set(o, sib, parent) ((o)->op_sibling = (sib))
#endif /* !OpMORESIB_set */
#ifndef OpSIBLING
# define OpHAS_SIBLING(o) (cBOOL((o)->op_sibling))
# define OpSIBLING(o) (0 + (o)->op_sibling)
#endif /* !OpSIBLING */

#ifndef op_contextualize
# define scalar(op) Perl_scalar(aTHX_ op)
# define list(op) Perl_list(aTHX_ op)
# define scalarvoid(op) Perl_scalarvoid(aTHX_ op)
# define op_contextualize(op, c) THX_op_contextualize(aTHX_ op, c)
static OP *THX_op_contextualize(pTHX_ OP *o, I32 context)
{
	switch (context) {
		case G_SCALAR: return scalar(o);
		case G_ARRAY:  return list(o);
		case G_VOID:   return scalarvoid(o);
		default:
			croak("panic: op_contextualize bad context");
			return o;
	}
}
#endif /* !op_contextualize */

#if !PERL_VERSION_GE(5,9,3)
typedef OP *(*Perl_check_t)(pTHX_ OP *);
#endif /* <5.9.3 */

#if !PERL_VERSION_GE(5,10,1)
typedef unsigned Optype;
#endif /* <5.10.1 */

#ifndef wrap_op_checker
# define wrap_op_checker(c,n,o) THX_wrap_op_checker(aTHX_ c,n,o)
static void THX_wrap_op_checker(pTHX_ Optype opcode,
	Perl_check_t new_checker, Perl_check_t *old_checker_p)
{
	if(*old_checker_p) return;
	OP_REFCNT_LOCK;
	if(!*old_checker_p) {
		*old_checker_p = PL_check[opcode];
		PL_check[opcode] = new_checker;
	}
	OP_REFCNT_UNLOCK;
}
#endif /* !wrap_op_checker */

#ifndef pad_alloc
# define pad_alloc(optype, tmptype) Perl_pad_alloc(aTHX_ optype, tmptype)
#endif /* !pad_alloc */

static SV *base_hint_key_sv;
static U32 base_hint_key_hash;
static OP *(*THX_nxck_substr)(pTHX_ OP *o);
static OP *(*THX_nxck_index)(pTHX_ OP *o);
static OP *(*THX_nxck_rindex)(pTHX_ OP *o);
static OP *(*THX_nxck_pos)(pTHX_ OP *o);

#define current_base() THX_current_base(aTHX)
static IV THX_current_base(pTHX)
{
	HE *base_ent = hv_fetch_ent(GvHV(PL_hintgv), base_hint_key_sv, 0,
					base_hint_key_hash);
	return base_ent ? SvIV(HeVAL(base_ent)) : 0;
}

static OP *THX_myck_substr(pTHX_ OP *op)
{
	IV base;
	if((base = current_base()) != 0) {
		OP *pop, *sop, *iop, *rest;
		if(!(op->op_flags & OPf_KIDS)) {
			bad_ops:
			croak("strange op tree prevents applying string base");
		}
		pop = cLISTOPx(op)->op_first;
		if(!(pop->op_type == OP_PUSHMARK ||
				(pop->op_type == OP_NULL &&
					pop->op_targ == OP_PUSHMARK)))
			goto bad_ops;
		sop = OpSIBLING(pop);
		if(!sop) goto bad_ops;
		iop = OpSIBLING(sop);
		if(!iop) goto bad_ops;
		rest = OpSIBLING(iop);
		OpMAYBESIB_set(sop, rest, op);
		OpLASTSIB_set(iop, NULL);
		if(!rest) cLISTOPx(op)->op_last = sop;
		iop = newBINOP(OP_I_SUBTRACT, 0,
				op_contextualize(iop, G_SCALAR),
				newSVOP(OP_CONST, 0, newSViv(base)));
		OpMAYBESIB_set(iop, rest, op);
		OpMORESIB_set(sop, iop);
		if(!rest) cLISTOPx(op)->op_last = iop;
	}
	return THX_nxck_substr(aTHX_ op);
}

static OP *THX_myck_index(pTHX_ OP *op)
{
	IV base;
	if((base = current_base()) != 0) {
		OP *pop, *hop, *nop, *iop;
		if(!(op->op_flags & OPf_KIDS)) {
			bad_ops:
			croak("strange op tree prevents applying string base");
		}
		pop = cLISTOPx(op)->op_first;
		if(!(pop->op_type == OP_PUSHMARK ||
				(pop->op_type == OP_NULL &&
					pop->op_targ == OP_PUSHMARK)))
			goto bad_ops;
		hop = OpSIBLING(pop);
		if(!hop) goto bad_ops;
		nop = OpSIBLING(hop);
		if(!nop) goto bad_ops;
		iop = OpSIBLING(nop);
		if(iop) {
			OP *rest = OpSIBLING(iop);
			OpMAYBESIB_set(nop, rest, op);
			OpLASTSIB_set(iop, NULL);
			if(!rest) cLISTOPx(op)->op_last = nop;
			iop = newBINOP(OP_I_SUBTRACT, 0,
					op_contextualize(iop, G_SCALAR),
					newSVOP(OP_CONST, 0, newSViv(base)));
			OpMAYBESIB_set(iop, rest, op);
			OpMORESIB_set(nop, iop);
			if(!rest) cLISTOPx(op)->op_last = iop;
		}
		op = (op->op_type == OP_INDEX ? THX_nxck_index :
						THX_nxck_rindex)
			(aTHX_ op);
		if((PL_opargs[op->op_type] & OA_TARGET) && !op->op_targ)
			op->op_targ = pad_alloc(op->op_type, SVs_PADTMP);
		return newBINOP(OP_I_ADD, 0, op_contextualize(op, G_SCALAR),
				newSVOP(OP_CONST, 0, newSViv(base)));
	} else {
		return (op->op_type == OP_INDEX ? THX_nxck_index :
						THX_nxck_rindex)
			(aTHX_ op);
	}
}

static OP *THX_pp_dup(pTHX)
{
	dSP;
	SV *val = TOPs;
	XPUSHs(val);
	PUTBACK;
	return PL_op->op_next;
}

#define newUNOP_dup(argop) THX_newUNOP_dup(aTHX_ argop)
static OP *THX_newUNOP_dup(pTHX_ OP *argop)
{
	OP *dupop;
	NewOpSz(0, dupop, sizeof(UNOP));
#ifdef XopENTRY_set
	dupop->op_type = OP_CUSTOM;
#else /* !XopENTRY_set */
	dupop->op_type = OP_RAND;
#endif /* !XopENTRY_set */
	dupop->op_ppaddr = THX_pp_dup;
	cUNOPx(dupop)->op_flags = OPf_KIDS;
	cUNOPx(dupop)->op_first = argop;
	OpLASTSIB_set(argop, dupop);
	return dupop;
}

static OP *THX_pp_foldsafe_null(pTHX)
{
	return PL_op->op_next;
}

#ifdef XopENTRY_set
static void THX_cpeep_foldsafe_null(pTHX_ OP *o, OP *oldop)
{
	PERL_UNUSED_ARG(oldop);
# if PERL_VERSION_GE(5,19,10)
	op_null(o);
# else /* <5.19.10 */
	PERL_UNUSED_ARG(o);
# endif /* <5.19.10 */
}
#endif /* XopENTRY_set */

#define newOP_foldsafe_null() THX_newOP_foldsafe_null(aTHX)
static OP *THX_newOP_foldsafe_null(pTHX)
{
	OP *op;
	NewOpSz(0, op, sizeof(OP));
#ifdef XopENTRY_set
	op->op_type = OP_CUSTOM;
#else /* !XopENTRY_set */
	op->op_type = OP_RAND;
#endif /* !XopENTRY_set */
	op->op_ppaddr = THX_pp_foldsafe_null;
	op->op_next = op;
	return op;
}

static OP *THX_myck_pos(pTHX_ OP *op)
{
	IV base;
	if((base = current_base()) != 0) {
		op = THX_nxck_pos(aTHX_ op);
		if((PL_opargs[op->op_type] & OA_TARGET) && !op->op_targ)
			op->op_targ = pad_alloc(op->op_type, SVs_PADTMP);
		return newCONDOP(0,
			newUNOP(OP_DEFINED, 0,
				newUNOP_dup(op_contextualize(op, G_SCALAR))),
			newBINOP(OP_I_ADD, 0, newOP_foldsafe_null(),
				newSVOP(OP_CONST, 0, newSViv(base))),
			newOP(OP_NULL, 0));
	} else {
		return THX_nxck_pos(aTHX_ op);
	}
}

MODULE = String::Base PACKAGE = String::Base

PROTOTYPES: DISABLE

BOOT:
{
#ifdef XopENTRY_set
	struct {
		char const *name, *desc;
		U32 class;
		Perl_cpeep_t THX_cpeep;
		Perl_ppaddr_t THX_pp;
	} const ops_to_register[] = {
		{ "dup", "duplicate", OA_UNOP, (Perl_cpeep_t)0, THX_pp_dup },
		{ "foldsafe_null", "non-foldable null", OA_BASEOP,
			THX_cpeep_foldsafe_null, THX_pp_foldsafe_null },
	}, *otr;
	int i;
	for(i = C_ARRAY_LENGTH(ops_to_register); i--; ) {
		XOP *xop;
		Newxz(xop, 1, XOP);
		otr = &ops_to_register[i];
		XopENTRY_set(xop, xop_name, otr->name);
		XopENTRY_set(xop, xop_desc, otr->desc);
		XopENTRY_set(xop, xop_class, otr->class);
		if(otr->THX_cpeep) XopENTRY_set(xop, xop_peep, otr->THX_cpeep);
		Perl_custom_op_register(aTHX_ otr->THX_pp, xop);
	}
#endif /* XopENTRY_set */
}

BOOT:
{
	base_hint_key_sv = newSVpvs_share("String::Base/base");
	base_hint_key_hash = SvSHARED_HASH(base_hint_key_sv);
	wrap_op_checker(OP_SUBSTR, THX_myck_substr, &THX_nxck_substr);
	wrap_op_checker(OP_INDEX, THX_myck_index, &THX_nxck_index);
	wrap_op_checker(OP_RINDEX, THX_myck_index, &THX_nxck_rindex);
	wrap_op_checker(OP_POS, THX_myck_pos, &THX_nxck_pos);
}

void
import(SV *classname, IV base)
CODE:
	PERL_UNUSED_VAR(classname);
	PL_hints |= HINT_LOCALIZE_HH;
	gv_HVadd(PL_hintgv);
	if(base == 0) {
		(void) hv_delete_ent(GvHV(PL_hintgv), base_hint_key_sv,
				G_DISCARD, base_hint_key_hash);
	} else {
		SV *base_sv = newSViv(base);
		HE *he = hv_store_ent(GvHV(PL_hintgv), base_hint_key_sv,
				base_sv, base_hint_key_hash);
		if(he) {
			SV *val = HeVAL(he);
			SvSETMAGIC(val);
		} else {
			SvREFCNT_dec(base_sv);
		}
	}

void
unimport(SV *classname)
CODE:
	PERL_UNUSED_VAR(classname);
	PL_hints |= HINT_LOCALIZE_HH;
	gv_HVadd(PL_hintgv);
	(void) hv_delete_ent(GvHV(PL_hintgv), base_hint_key_sv,
			G_DISCARD, base_hint_key_hash);