/*
 * Copyright (c) 2017 Lev Walkin <vlm@lionet.info>. All rights reserved.
 * Redistribution and modifications are permitted subject to BSD license.
 */
#include <asn_internal.h>
#include <OPEN_TYPE.h>
#include <constr_CHOICE.h>
#include <per_opentype.h>
#include <errno.h>

asn_TYPE_operation_t asn_OP_OPEN_TYPE = {
	OPEN_TYPE_free,
	OPEN_TYPE_print,
	OPEN_TYPE_compare,
	OPEN_TYPE_constraint,
	OPEN_TYPE_decode_ber,
	OPEN_TYPE_encode_der,
	OPEN_TYPE_decode_xer,
	OPEN_TYPE_encode_xer,
	0, 0,	/* No OER support, use "-gen-OER" to enable */
	OPEN_TYPE_decode_uper,
	OPEN_TYPE_encode_uper,
	0,	/* Use generic outmost tag fetcher */
};

#undef  XER_ADVANCE
#define XER_ADVANCE(num_bytes)           \
    do {                                 \
        size_t num = num_bytes;          \
        ptr = ((const char *)ptr) + num; \
        size -= num;                     \
        consumed_myself += num;          \
    } while(0)

asn_dec_rval_t
OPEN_TYPE_xer_get(asn_codec_ctx_t *opt_codec_ctx, asn_TYPE_descriptor_t *td,
                  void *sptr, asn_TYPE_member_t *elm, const void *ptr,
                  size_t size) {
    size_t consumed_myself = 0;
    asn_type_selector_result_t selected;
    void *memb_ptr;   /* Pointer to the member */
    void **memb_ptr2; /* Pointer to that pointer */
    void *inner_value;
    const char *xml_tag;
    asn_dec_rval_t rv;

    int xer_context = 0;
    ssize_t ch_size;
    pxer_chunk_type_e ch_type;

    if(!(elm->flags & ATF_OPEN_TYPE) || !elm->type_selector) {
        ASN__DECODE_FAILED;
    }

    selected = elm->type_selector(td, sptr);
    if(!selected.presence_index) {
        ASN__DECODE_FAILED;
    }

    /* Fetch the pointer to this member */
    assert(elm->flags == ATF_OPEN_TYPE);
    if(elm->flags & ATF_POINTER) {
        memb_ptr2 = (void **)((char *)sptr + elm->memb_offset);
    } else {
        memb_ptr = (char *)sptr + elm->memb_offset;
        memb_ptr2 = &memb_ptr;
    }
    if(*memb_ptr2 != NULL) {
        /* Make sure we reset the structure first before encoding */
        if(CHOICE_variant_set_presence(elm->type, *memb_ptr2, 0)
           != 0) {
            ASN__DECODE_FAILED;
        }
    }

    /*
     * Confirm wrapper.
     */
    for(;;) {
        ch_size = xer_next_token(&xer_context, ptr, size, &ch_type);
        if(ch_size <= 0) {
            ASN__DECODE_FAILED;
        } else {
            switch(ch_type) {
            case PXER_WMORE:
                ASN__DECODE_STARVED;
            case PXER_COMMENT:
            case PXER_TEXT:
                XER_ADVANCE(ch_size);
                continue;
            case PXER_TAG:
                break;
            }
            break;
        }
    }

    /*
     * Wrapper value confirmed.
     */
    switch(xer_check_tag(ptr, ch_size, elm->name)) {
    case XCT_OPENING:
        XER_ADVANCE(ch_size);
        break;
    case XCT_BROKEN:
    default:
        ASN__DECODE_FAILED;
    }

    inner_value =
        (char *)*memb_ptr2
        + elm->type->elements[selected.presence_index - 1].memb_offset;

    rv = selected.type_descriptor->op->xer_decoder(
        opt_codec_ctx, selected.type_descriptor, &inner_value, NULL, ptr, size);
    XER_ADVANCE(rv.consumed);
    rv.consumed = 0;
    switch(rv.code) {
    case RC_OK:
        if(CHOICE_variant_set_presence(elm->type, *memb_ptr2,
                                       selected.presence_index)
           == 0) {
            break;
        } else {
            rv.code = RC_FAIL;
        }
        /* Fall through */
    case RC_FAIL:
        /* Point to a best position where failure occurred */
        rv.consumed = consumed_myself;
        /* Fall through */
    case RC_WMORE:
        /* Wrt. rv.consumed==0:
         * In case a genuine RC_WMORE, the whole Open Type decoding
         * will have to be restarted.
         */
        if(*memb_ptr2) {
            asn_CHOICE_specifics_t *specs = selected.type_descriptor->specifics;
            if(elm->flags & ATF_POINTER) {
                ASN_STRUCT_FREE(*selected.type_descriptor, inner_value);
                *memb_ptr2 = NULL;
            } else {
                ASN_STRUCT_FREE_CONTENTS_ONLY(*selected.type_descriptor,
                                              inner_value);
                memset(*memb_ptr2, 0, specs->struct_size);
            }
        }
        return rv;
    }

    /*
     * Finalize wrapper.
     */
    for(;;) {
        ch_size = xer_next_token(&xer_context, ptr, size, &ch_type);
        if(ch_size <= 0) {
            ASN__DECODE_FAILED;
        } else {
            switch(ch_type) {
            case PXER_WMORE:
                ASN__DECODE_STARVED;
            case PXER_COMMENT:
            case PXER_TEXT:
                XER_ADVANCE(ch_size);
                continue;
            case PXER_TAG:
                break;
            }
            break;
        }
    }

    /*
     * Wrapper value confirmed.
     */
    switch(xer_check_tag(ptr, ch_size, elm->name)) {
    case XCT_CLOSING:
        XER_ADVANCE(ch_size);
        break;
    case XCT_BROKEN:
    default:
        ASN__DECODE_FAILED;
    }

    rv.consumed += consumed_myself;

    return rv;
}


#ifndef  ASN_DISABLE_PER_SUPPORT

asn_dec_rval_t
OPEN_TYPE_uper_get(asn_codec_ctx_t *opt_codec_ctx,
                   asn_TYPE_descriptor_t *td, void *sptr,
                   asn_TYPE_member_t *elm, asn_per_data_t *pd) {
    asn_type_selector_result_t selected;
    void *memb_ptr;   /* Pointer to the member */
    void **memb_ptr2; /* Pointer to that pointer */
    void *inner_value;
    asn_dec_rval_t rv;

    if(!(elm->flags & ATF_OPEN_TYPE) || !elm->type_selector) {
        ASN__DECODE_FAILED;
    }

    selected = elm->type_selector(td, sptr);
    if(!selected.presence_index) {
        ASN__DECODE_FAILED;
    }

    /* Fetch the pointer to this member */
    assert(elm->flags == ATF_OPEN_TYPE);
    if(elm->flags & ATF_POINTER) {
        memb_ptr2 = (void **)((char *)sptr + elm->memb_offset);
    } else {
        memb_ptr = (char *)sptr + elm->memb_offset;
        memb_ptr2 = &memb_ptr;
    }
    if(*memb_ptr2 != NULL) {
        /* Make sure we reset the structure first before encoding */
        if(CHOICE_variant_set_presence(elm->type, *memb_ptr2, 0)
           != 0) {
            ASN__DECODE_FAILED;
        }
    }

    inner_value =
        (char *)*memb_ptr2
        + elm->type->elements[selected.presence_index - 1].memb_offset;

    rv = uper_open_type_get(opt_codec_ctx, selected.type_descriptor, NULL,
                            &inner_value, pd);
    switch(rv.code) {
    case RC_OK:
        if(CHOICE_variant_set_presence(elm->type, *memb_ptr2,
                                       selected.presence_index)
           == 0) {
            break;
        } else {
            rv.code = RC_FAIL;
        }
        /* Fall through */
    case RC_WMORE:
    case RC_FAIL:
        if(*memb_ptr2) {
            asn_CHOICE_specifics_t *specs = selected.type_descriptor->specifics;
            if(elm->flags & ATF_POINTER) {
                ASN_STRUCT_FREE(*selected.type_descriptor, inner_value);
                *memb_ptr2 = NULL;
            } else {
                ASN_STRUCT_FREE_CONTENTS_ONLY(*selected.type_descriptor,
                                              inner_value);
                memset(*memb_ptr2, 0, specs->struct_size);
            }
        }
    }
    return rv;
}

asn_enc_rval_t
OPEN_TYPE_encode_uper(asn_TYPE_descriptor_t *td,
                      const asn_per_constraints_t *constraints, void *sptr,
                      asn_per_outp_t *po) {
    asn_type_selector_result_t selected;
    void *memb_ptr;   /* Pointer to the member */
    asn_TYPE_member_t *elm; /* CHOICE's element */
    asn_enc_rval_t er;
    unsigned present;

    (void)constraints;

    present = CHOICE_variant_get_presence(td, sptr);
    if(present == 0 || present > td->elements_count) {
        ASN__ENCODE_FAILED;
    } else {
        present--;
    }

    ASN_DEBUG("Encoding %s OPEN TYPE element %d", td->name, present);

    elm = &td->elements[present];
    if(elm->flags & ATF_POINTER) {
        /* Member is a pointer to another structure */
        memb_ptr = *(void **)((char *)sptr + elm->memb_offset);
        if(!memb_ptr) ASN__ENCODE_FAILED;
    } else {
        memb_ptr = (char *)sptr + elm->memb_offset;
    }

    if(uper_open_type_put(elm->type, NULL, memb_ptr, po) < 0) {
        ASN__ENCODE_FAILED;
    }

    er.encoded = 0;
    ASN__ENCODED_OK(er);
}


#endif  /* ASN_DISABLE_PER_SUPPORT */
