/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#define mp_xs_sv2_modperl_filter(sv)                                    \
    ((SvROK(sv) && (SvTYPE(SvRV(sv)) == SVt_PVMG))                      \
     || (Perl_croak(aTHX_ "argument is not a blessed reference"),0) ?   \
     modperl_filter_mg_get(aTHX_ sv) : NULL)

#define mpxs_Apache2__Filter_TIEHANDLE(stashsv, sv)      \
    modperl_newSVsv_obj(aTHX_ stashsv, sv)

#define mpxs_Apache2__Filter_PRINT mpxs_Apache2__Filter_print

static MP_INLINE apr_size_t mpxs_Apache2__Filter_print(pTHX_ I32 items,
                                                      SV **MARK, SV **SP)
{
    modperl_filter_t *modperl_filter;
    apr_size_t bytes = 0;

    mpxs_usage_va_1(modperl_filter, "$filter->print(...)");

    MP_TRACE_f(MP_FUNC, "from %s",
               ((modperl_filter_ctx_t *)modperl_filter->f->ctx)->handler->name);
    if (modperl_filter->mode == MP_OUTPUT_FILTER_MODE) {
        mpxs_write_loop(modperl_output_filter_write,
                        modperl_filter, "Apache2::Filter::print");
    }
    else {
        mpxs_write_loop(modperl_input_filter_write,
                        modperl_filter, "Apache2::Filter::print");
    }

    /* XXX: ap_rflush if $| */

    return bytes;
}

static MP_INLINE apr_size_t mpxs_Apache2__Filter_read(pTHX_ I32 items,
                                                     SV **MARK, SV **SP)
{
    modperl_filter_t *modperl_filter;
    apr_size_t wanted, len=0;
    SV *buffer;

    mpxs_usage_va_2(modperl_filter, buffer, "$filter->read(buf, [len])");

    MP_TRACE_f(MP_FUNC, "from %s",
               ((modperl_filter_ctx_t *)modperl_filter->f->ctx)->handler->name);

    if (items > 2) {
        wanted = SvIV(*MARK);
    }
    else {
        wanted = MP_IOBUFSIZE;
    }

    if (modperl_filter->mode == MP_INPUT_FILTER_MODE) {
        /* XXX: if we ever will have a need to change the read
         * discipline: (input_mode, block, readbytes) from the filter
         * we can provide an accessor method to modify the values
         * supplied by the filter chain */
        len = modperl_input_filter_read(aTHX_ modperl_filter, buffer, wanted);
    }
    else {
        len = modperl_output_filter_read(aTHX_ modperl_filter, buffer, wanted);
    }

    /* must run any set magic */
    SvSETMAGIC(buffer);

    SvTAINTED_on(buffer);

    return len;
}

static MP_INLINE U16 *modperl_filter_attributes(pTHX_ SV *package, SV *cvrv)
{
    return modperl_code_attrs(aTHX_ (CV*)SvRV(cvrv));
}

#ifdef MP_TRACE
#define trace_attr()                                                       \
    MP_TRACE_f(MP_FUNC, "applied %s attribute to %s handler", attribute, \
               HvNAME(stash))
#else
#define trace_attr()
#endif

/* we can't eval at this stage, since the package is not compiled yet,
 * we are still parsing the source.
 */
#define MODPERL_FILTER_ATTACH_ATTR_CODE(cv, string, len)        \
    {                                                           \
        char *str;                                              \
        len -= 2;           /* s/ \( | \) //x       */          \
        string++;           /* skip the opening '(' */          \
        New(0, str, len+1, char);                               \
        Copy(string, str, len+1, char);                         \
        str[len] = '\0';    /* remove the closing ')' */        \
        sv_magic(cv, Nullsv, '~', NULL, -1);                    \
        SvMAGIC(cv)->mg_ptr = str;                              \
    }


MP_STATIC XS(MPXS_modperl_filter_attributes)
{
    dXSARGS;
    U16 *attrs = modperl_filter_attributes(aTHX_ ST(0), ST(1));
    I32 i;
#ifdef MP_TRACE
    HV *stash = gv_stashsv(ST(0), TRUE);
#endif

    for (i=2; i < items; i++) {
        STRLEN len;
        char *pv = SvPV(ST(i), len);
        char *attribute = pv;

        if (strnEQ(pv, "Filter", 6)) {
            pv += 6;
        }

        switch (*pv) {
          case 'C':
            if (strEQ(pv, "ConnectionHandler")) {
                *attrs |= MP_FILTER_CONNECTION_HANDLER;
                trace_attr();
                continue;
            }
          case 'I':
            if (strEQ(pv, "InitHandler")) {
                *attrs |= MP_FILTER_INIT_HANDLER;
                trace_attr();
                continue;
            }
          case 'H':
            if (strnEQ(pv, "HasInitHandler", 14)) {
                STRLEN code_len;
                pv += 14; /* skip over the attr name */
                code_len = len - (pv - attribute);
                MODPERL_FILTER_ATTACH_ATTR_CODE(SvRV(ST(1)), pv, code_len);
                *attrs |= MP_FILTER_HAS_INIT_HANDLER;
                trace_attr();
                continue;
            }
          case 'R':
            if (strEQ(pv, "RequestHandler")) {
                *attrs |= MP_FILTER_REQUEST_HANDLER;
                trace_attr();
                continue;
            }
          default:
            /* XXX: there could be more than one attr to pass through */
            XPUSHs_mortal_pv(attribute);
            XSRETURN(1);
        }
    }

    XSRETURN_EMPTY;
}

static MP_INLINE SV *mpxs_Apache2__Filter_ctx(pTHX_
                                             ap_filter_t *filter,
                                             SV *data)
{
    modperl_filter_ctx_t *ctx = (modperl_filter_ctx_t *)(filter->ctx);

    /* XXX: is it possible that the same filter, during a single
     * request or connection cycle, will be invoked by different perl
     * interpreters? if that happens we are in trouble, if we need to
     * return an SV living in a different interpreter. may be there is
     * a way to use one of the perl internal functions to clone an SV
     * (and it can contain any references)
     */

    if (data != Nullsv) {
        if (ctx->data) {
            if (SvOK(ctx->data) && SvREFCNT(ctx->data)) {
                /* release the previously stored SV so we don't leak
                 * an SV */
                SvREFCNT_dec(ctx->data);
            }
        }

#ifdef USE_ITHREADS
        if (!ctx->perl) {
            ctx->perl = aTHX;
        }
#endif
        ctx->data = SvREFCNT_inc(data);
    }

    return ctx->data ? SvREFCNT_inc(ctx->data) : &PL_sv_undef;
}

static MP_INLINE SV *mpxs_Apache2__Filter_seen_eos(pTHX_ I32 items,
                                                  SV **MARK, SV **SP)
{
    modperl_filter_t *modperl_filter;

    if ((items < 1) || (items > 2) || !(mpxs_sv2_obj(modperl_filter, *MARK))) {
        Perl_croak(aTHX_ "usage: $filter->seen_eos([$set])");
    }
    MARK++;

    if (items == 2) {
        modperl_filter->seen_eos = SvTRUE(*MARK) ? 1 : 0;
    }

    return modperl_filter->seen_eos ? &PL_sv_yes : &PL_sv_no;
}

static MP_INLINE
void mpxs_Apache2__RequestRec_add_input_filter(pTHX_ request_rec *r,
                                              SV *callback)
{

    modperl_filter_runtime_add(aTHX_ r,
                               r->connection,
                               MP_FILTER_REQUEST_INPUT_NAME,
                               MP_INPUT_FILTER_MODE,
                               ap_add_input_filter,
                               callback,
                               "InputFilter");
}

static MP_INLINE
void mpxs_Apache2__RequestRec_add_output_filter(pTHX_ request_rec *r,
                                               SV *callback)
{

    modperl_filter_runtime_add(aTHX_ r,
                               r->connection,
                               MP_FILTER_REQUEST_OUTPUT_NAME,
                               MP_OUTPUT_FILTER_MODE,
                               ap_add_output_filter,
                               callback,
                               "OutputFilter");
}

static MP_INLINE
void mpxs_Apache2__Connection_add_input_filter(pTHX_ conn_rec *c,
                                              SV *callback)
{

    modperl_filter_runtime_add(aTHX_ NULL,
                               c,
                               MP_FILTER_CONNECTION_INPUT_NAME,
                               MP_INPUT_FILTER_MODE,
                               ap_add_input_filter,
                               callback,
                               "InputFilter");
}

static MP_INLINE
void mpxs_Apache2__Connection_add_output_filter(pTHX_ conn_rec *c,
                                               SV *callback)
{

    modperl_filter_runtime_add(aTHX_ NULL,
                               c,
                               MP_FILTER_CONNECTION_OUTPUT_NAME,
                               MP_OUTPUT_FILTER_MODE,
                               ap_add_output_filter,
                               callback,
                               "OutputFilter");
}

static MP_INLINE
void mpxs_Apache2__Filter_remove(pTHX_ I32 items, SV **MARK, SV **SP)
{
    modperl_filter_t *modperl_filter;
    ap_filter_t *f;

    if (items < 1) {
        Perl_croak(aTHX_ "usage: $filter->remove()");
    }

    modperl_filter = mp_xs_sv2_modperl_filter(*MARK);

    /* native filter */
    if (!modperl_filter) {
        f = INT2PTR(ap_filter_t *, SvIV(SvRV(*MARK)));
        MP_TRACE_f(MP_FUNC,
                   "   %s\n\n\t non-modperl filter removes itself",
                   f->frec->name);

        /* the filter can reside in only one chain. hence we try to
         * remove it from both, the input and output chains, since
         * unfortunately we can't tell what kind of filter is that and
         * whether the first call was successful
         */
        ap_remove_input_filter(f);
        ap_remove_output_filter(f);
        return;
    }

    f = modperl_filter->f;

    MP_TRACE_f(MP_FUNC, "   %s\n\n\tfilter removes itself",
               ((modperl_filter_ctx_t *)f->ctx)->handler->name);

    if (modperl_filter->mode == MP_INPUT_FILTER_MODE) {
        ap_remove_input_filter(f);
    }
    else {
        ap_remove_output_filter(f);
    }
}

static MP_INLINE
apr_status_t mpxs_Apache2__Filter_fflush(pTHX_ ap_filter_t *filter,
                                        apr_bucket_brigade *brigade)
{
    apr_status_t rc = ap_fflush(filter, brigade);
    /* if users don't bother to check the success, do it on their
     * behalf */
    if (GIMME_V == G_VOID && rc != APR_SUCCESS) {
        modperl_croak(aTHX_ rc, "Apache2::Filter::fflush");
    }

    return rc;
}

static MP_INLINE
apr_status_t mpxs_Apache2__Filter_get_brigade(pTHX_
                                             ap_filter_t *f,
                                             apr_bucket_brigade *bb,
                                             ap_input_mode_t mode,
                                             apr_read_type_e block,
                                             apr_off_t readbytes)
{
    apr_status_t rc = ap_get_brigade(f, bb, mode, block, readbytes);
    /* if users don't bother to check the success, do it on their
     * behalf */
    if (GIMME_V == G_VOID && rc != APR_SUCCESS) {
        modperl_croak(aTHX_ rc, "Apache2::Filter::get_brigade");
    }

    return rc;
}

static MP_INLINE
apr_status_t mpxs_Apache2__Filter_pass_brigade(pTHX_ ap_filter_t *f,
                                              apr_bucket_brigade *bb)
{
    apr_status_t rc = ap_pass_brigade(f, bb);
    /* if users don't bother to check the success, do it on their
     * behalf */
    if (GIMME_V == G_VOID && rc != APR_SUCCESS) {
        modperl_croak(aTHX_ rc, "Apache2::Filter::pass_brigade");
    }

    return rc;
}
