#include "apache_request.h"

#ifdef WIN32

#ifdef uid_t
#define apache_uid_t uid_t
#undef uid_t
#endif
#define uid_t apache_uid_t

#ifdef gid_t
#define apache_gid_t gid_t
#undef gid_t
#endif
#define gid_t apache_gid_t

#ifdef stat
#define apache_stat stat
#undef stat
#endif

#ifdef lstat
#define apache_lstat lstat
#undef lstat
#endif

#ifdef isnan
#define apache_isnan isnan
#undef isnan
#endif

#ifdef sleep
#define apache_sleep sleep
#undef sleep
#endif

#endif /* WIN32 */

#undef __attribute__
#include "mod_perl.h"

#ifdef WIN32

#undef uid_t
#ifdef apache_uid_t
#define uid_t apache_uid_t
#undef apache_uid_t
#endif

#undef gid_t
#ifdef apache_gid_t
#define gid_t apache_gid_t
#undef apache_gid_t
#endif

#ifdef apache_isnan
#undef isnan
#define isnan apache_isnan
#undef apache_isnan
#endif

#ifdef apache_lstat
#undef lstat
#define lstat apache_lstat
#undef apache_lstat
#endif

#ifdef apache_stat
#undef stat
#define stat apache_stat
#undef apache_stat
#endif

#ifdef apache_sleep
#undef sleep
#define sleep apache_sleep
#undef apache_sleep
#endif

#endif /* WIN32 */

typedef ApacheRequest * Apache__Request;
typedef ApacheUpload  * Apache__Upload;

typedef struct {
    SV *data;
    SV *sub;
} UploadHook;

#define XsUploadHook       ((UploadHook *)RETVAL->hook_data)
#define XsUploadHookNew(p) (void *)ap_pcalloc(p, sizeof(UploadHook))
#define XsUploadHookSet(slot, sv) \
     if (RETVAL->hook_data) { \
         if (XsUploadHook->slot) { \
             SvREFCNT_dec(XsUploadHook->slot); \
         } \
     } \
     else { \
         RETVAL->hook_data = XsUploadHookNew(r->pool); \
         ap_register_cleanup(r->pool, (void*)XsUploadHook, \
                             upload_hook_cleanup, ap_null_cleanup); \
     } \
     XsUploadHook->slot = SvREFCNT_inc(sv)

#define ApacheUpload_fh(upload)       upload->fp
#define ApacheUpload_name(upload)     upload->name
#define ApacheUpload_filename(upload) upload->filename
#define ApacheUpload_next(upload)     upload->next
#define ApacheUpload_tempname(upload) upload->tempname

#ifdef PerlIO
typedef PerlIO * ApreqInputStream;

/* XXX: or should this be #ifdef PERL_IMPLICIT_SYS ? */
#ifdef WIN32
#   ifndef PerlIO_importFILE
#      define PerlIO_importFILE(fp,flags)	(PerlIO*)fp
#   endif
#endif

#ifdef SFIO
#undef PerlIO_importFILE
#define PerlIO_importFILE(fp,flags) 	(PerlIO*)fp
#endif /*SFIO*/

#else /*PerlIO not defined*/

typedef FILE * ApreqInputStream;
#define PerlIO_importFILE(fp,flags) 	fp
#define PerlIO_write(a,b,c)  		fwrite((b),1,(c),(a))

#endif /*PerlIO*/

static char *r_keys[] = { "_r", "r", NULL };

static SV *r_key_sv(SV *in)
{
    SV *sv;
    int i;

    for (i=0; r_keys[i]; i++) {
	int klen = strlen(r_keys[i]);
	if(hv_exists((HV*)SvRV(in), r_keys[i], klen) &&
	   (sv = *hv_fetch((HV*)SvRV(in), 
			   r_keys[i], klen, FALSE)))
	{
	    return sv;
	}
    }

    return Nullsv;
}

static ApacheRequest *sv_2apreq(SV *sv)
{
    if (SvROK(sv) && sv_derived_from(sv, "Apache::Request")) { 
	SV *obj = sv;

	switch (SvTYPE(SvRV(obj))) {
	case SVt_PVHV :
            do {
                obj = r_key_sv(obj);
            } while (SvROK(obj) && (SvTYPE(SvRV(obj)) == SVt_PVHV));
	    break;
	default:
	    break;
	};
	return (ApacheRequest *)SvIV((SV*)SvRV(obj)); 
    }
    else {
	return ApacheRequest_new(perl_request_rec(NULL));
    }
} 

static SV *upload_bless(ApacheUpload *upload) 
{ 
    SV *sv = newSV(0);  
    sv_setref_pv(sv, "Apache::Upload", (void*)upload);  
    return sv; 
} 

static int upload_hook(void *ptr, char *buf, int len, ApacheUpload *upload)
{
    UploadHook *hook = (UploadHook *)ptr;

#ifdef dTHX
    dTHX;  /* crude 5005thread support */
#endif

    if (!(upload->fp || ApacheRequest_tmpfile(upload->req, upload)))
        return -1; /* error */

    {
    	SV *sv;
    	dSP;

    	PUSHMARK(SP);
    	EXTEND(SP, 4);
        ENTER;
    	SAVETMPS;

    	sv = sv_newmortal();
    	sv_setref_pv(sv, "Apache::Upload", (void*)upload);
    	PUSHs(sv);

    	sv = sv_2mortal(newSVpvn(buf,len));
    	SvTAINT(sv);
    	PUSHs(sv);

    	sv = sv_2mortal(newSViv(len));
    	SvTAINT(sv);
    	PUSHs(sv);

    	PUSHs(hook->data);

    	PUTBACK;
    	perl_call_sv(hook->sub, G_EVAL|G_DISCARD);
    	FREETMPS;
    	LEAVE;
    }

    if (SvTRUE(ERRSV))
        return -1;

    return PerlIO_write(PerlIO_importFILE(upload->fp,0), buf, len);
}

static void upload_hook_cleanup(void *ptr)
{
    UploadHook *hook = (UploadHook *)ptr;

    if (hook->sub) {
        SvREFCNT_dec(hook->sub);
        hook->sub = Nullsv;
    }
    if (hook->data) {
        SvREFCNT_dec(hook->data);
        hook->data = Nullsv;
    }
}

#define upload_push(upload) \
    XPUSHs(sv_2mortal(upload_bless(upload))) 

static void apreq_add_magic(SV *sv, SV *obj, ApacheRequest *req)
{
    sv_magic(SvRV(sv), obj, '~', "dummy", -1);
    SvMAGIC(SvRV(sv))->mg_ptr = (char *)req->r;
}

#ifdef CGI_COMPAT
static void register_uploads (ApacheRequest *req) {
    ApacheUpload *upload;

    for (upload = req->upload; upload; upload = upload->next) {
	if(upload->fp && upload->filename) {
	    GV *gv = gv_fetchpv(upload->filename, TRUE, SVt_PVIO);
	    if (do_open(gv, "<&", 2, FALSE, 0, 0, upload->fp)) { 
		ap_register_cleanup(req->r->pool, (void*)gv, 
				    apreq_close_handle, ap_null_cleanup);
	    } 
	}
    }
}
#else
#define register_uploads(req)
#endif

MODULE = Apache::Request    PACKAGE = Apache::Request   PREFIX = ApacheRequest_

PROTOTYPES: DISABLE 

BOOT:
    av_push(perl_get_av("Apache::Request::ISA",TRUE), newSVpv("Apache",6));

Apache::Request
ApacheRequest_new(class, r, ...)
    SV *class
    Apache r

    PREINIT:
    int i;
    SV *robj;
	
    CODE:
    class = class; /* -Wall */ 
    robj = ST(1);
    RETVAL = ApacheRequest_new(r);
    register_uploads(RETVAL);

    for (i=2; i<items; i+=2) {
        char *key = SvPV(ST(i),na);

        switch (toLOWER(*key)) {
          case 'd':
            if (strcaseEQ(key, "disable_uploads")) {
                RETVAL->disable_uploads = (int)SvIV(ST(i+1));
                break;
            }

          case 'h':
            if (strcaseEQ(key, "hook_data")) {
                XsUploadHookSet(data, ST(i+1));
                break;
            }

          case 'p':
            if (strcaseEQ(key, "post_max")) {
                RETVAL->post_max = (int)SvIV(ST(i+1));
                break;
            }

          case 't':
            if (strcaseEQ(key, "temp_dir")) {
                RETVAL->temp_dir = (char *)SvPV(ST(i+1), PL_na);
                break;
            }

          case 'u':
            if (strcaseEQ(key, "upload_hook")) {
                XsUploadHookSet(sub, ST(i+1));
                RETVAL->upload_hook = upload_hook;
                break;
            }

          default:
            croak("[libapreq] unknown attribute: `%s'", key);
        }
    }

    OUTPUT:
    RETVAL

    CLEANUP:
    apreq_add_magic(ST(0), robj, RETVAL);

char *
ApacheRequest_script_name(req)
    Apache::Request req

int
ApacheRequest_parse(req)
    Apache::Request req

void
ApacheRequest_parms(req, parms=NULL)
    Apache::Request req
    Apache::Table parms

    CODE:
    if (parms) {
        req->parms = parms->utable;
        req->parsed = 1;
    }
    else {
        ApacheRequest_parse(req);
    }
    ST(0) = mod_perl_tie_table(req->parms);

void
ApacheRequest_param(req, key=NULL, sv=Nullsv)
    Apache::Request req	
    char *key
    SV *sv

    PPCODE:
    if ( !req->parsed ) ApacheRequest_parse(req);

    if (key) {

	if (sv != Nullsv) {

	    if (SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PVAV) {
	    	I32 i;
	    	AV *av = (AV*)SvRV(sv);
	    	const char *val;

            	ap_table_unset(req->parms, key);
	    	for (i=0; i<=AvFILL(av); i++) {
		    val = (const char *)SvPV(*av_fetch(av, i, FALSE),PL_na);
	            ap_table_add(req->parms, key, val);
	    	}
	    }
            else ap_table_set(req->parms, key, (const char *)SvPV(sv, PL_na));
	}

	switch (GIMME_V) {

        case G_SCALAR:			/* return (first) parameter value */
	    {
	    	const char *val = ap_table_get(req->parms, key);
	    	if (val) XPUSHs(sv_2mortal(newSVpv((char*)val,0)));
	    	else XSRETURN_UNDEF;
	    }
	    break;

	case G_ARRAY:			/* return list of parameter values */
	    {
  	        I32 i;
	        array_header *arr  = ap_table_elts(req->parms);
	        table_entry *elts = (table_entry *)arr->elts;
	        for (i = 0; i < arr->nelts; ++i) {
	            if (elts[i].key && strcaseEQ(elts[i].key, key))
	            	XPUSHs(sv_2mortal(newSVpv(elts[i].val,0)));
	        }
	    }
	    break;

	default:
            XSRETURN_UNDEF;
	} 
    } 
    else {		

	switch (GIMME_V) {

	case G_SCALAR:	    		/* like $apr->parms */
	    ST(0) = mod_perl_tie_table(req->parms);
	    XSRETURN(1); 
	    break;

	case G_ARRAY:			/* return list of unique keys */
            {
            	I32 i;
	    	array_header *arr  = ap_table_elts(req->parms);
	    	table_entry *elts = (table_entry *)arr->elts;
	    	for (i = 0; i < arr->nelts; ++i) {
		    I32 j;
	           if (!elts[i].key) continue;
		    /* simple but inefficient uniqueness check */
		    for (j = 0; j < i; ++j) { 
		        if (strcaseEQ(elts[i].key, elts[j].key))
			    break;
		    }
	            if ( i == j )
	                XPUSHs(sv_2mortal(newSVpv(elts[i].key,0)));
	        }
            }
	    break;

	default:
	    XSRETURN_UNDEF;
 	}
    }

void
ApacheRequest_upload(req, sv=Nullsv)
    Apache::Request req
    SV *sv

    PREINIT:
    ApacheUpload *uptr;

    PPCODE:
    if (sv && SvOBJECT(sv) && sv_isa(sv, "Apache::Upload")) {
        req->upload = (ApacheUpload *)SvIV((SV*)SvRV(sv));
        XSRETURN_EMPTY;
    }

    if ( !req->parsed ) ApacheRequest_parse(req);

    if (GIMME == G_SCALAR) {
        STRLEN n_a;
        char *name = sv ? SvPV(sv, n_a) : NULL;

	if (name) {
	    uptr = ApacheUpload_find(req->upload, name);
	    if (!uptr)
		XSRETURN_UNDEF;
	}
	else {
	    uptr = req->upload;
	}
	upload_push(uptr);
    }
    else {
	for (uptr = req->upload; uptr; uptr = uptr->next)
	    upload_push(uptr);
    }

char *
ApacheRequest_expires(req, time_str)
    Apache::Request req
    char *time_str

MODULE = Apache::Request    PACKAGE = Apache::Upload   PREFIX = ApacheUpload_

PROTOTYPES: DISABLE 

ApreqInputStream
ApacheUpload_fh(upload)
    Apache::Upload upload

    CODE:
    if (  ( RETVAL = PerlIO_importFILE(ApacheUpload_fh(upload),0) ) == NULL  )
	    XSRETURN_UNDEF;

    OUTPUT:
    RETVAL

    CLEANUP:
    if (ST(0) != &PL_sv_undef) {
	IO *io = GvIOn((GV*)SvRV(ST(0)));
	int fd = PerlIO_fileno(IoIFP(io));
	PerlIO *fp;

	fd = PerlLIO_dup(fd);
	if (!(fp = PerlIO_fdopen(fd, "r"))) { 
	    PerlLIO_close(fd);
	    croak("fdopen failed!");
	}
	if (upload->req->parsed)
	    PerlIO_seek(fp, 0, 0);

	IoIFP(io) = fp;  	
    }

long
ApacheUpload_size(upload)
    Apache::Upload upload

char *
ApacheUpload_name(upload)
    Apache::Upload upload

char *
ApacheUpload_filename(upload)
    Apache::Upload upload

char *
ApacheUpload_tempname(upload)
    Apache::Upload upload

Apache::Upload
ApacheUpload_next(upload)
    Apache::Upload upload 

const char *
ApacheUpload_type(upload)
    Apache::Upload upload 

char *
ApacheUpload_link(upload, name)
    Apache::Upload upload
    char *name

	CODE:
	RETVAL = (link(upload->tempname, name)) ? NULL : name;
	
	OUTPUT:
	RETVAL	

void
ApacheUpload_info(upload, key=NULL)
    Apache::Upload upload 
    char *key

    CODE:
    if (key) {
	const char *val = ApacheUpload_info(upload, key);
	if (!val)
	    XSRETURN_UNDEF;

	ST(0) = sv_2mortal(newSVpv((char *)val,0));
    }   
    else {
        ST(0) = mod_perl_tie_table(upload->info);
    }
