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

asn_random_fill_result_t
SET_OF_random_fill(const asn_TYPE_descriptor_t *td, void **sptr,
                   const asn_encoding_constraints_t *constraints,
                   size_t max_length) {
    const asn_SET_OF_specifics_t *specs =
        (const asn_SET_OF_specifics_t *)td->specifics;
    asn_random_fill_result_t res_ok = {ARFILL_OK, 0};
    asn_random_fill_result_t result_failed = {ARFILL_FAILED, 0};
    asn_random_fill_result_t result_skipped = {ARFILL_SKIPPED, 0};
    const asn_TYPE_member_t *elm = td->elements;
    void *st = *sptr;
    long max_elements = 5;
    long slb = 0;  /* Lower size bound */
    long sub = 0;  /* Upper size bound */
    size_t rnd_len;

    if(max_length == 0) return result_skipped;

    if(st == NULL) {
        st = (*sptr = CALLOC(1, specs->struct_size));
        if(st == NULL) {
            return result_failed;
        }
    }

    switch(asn_random_between(0, 6)) {
    case 0: max_elements = 0; break;
    case 1: max_elements = 1; break;
    case 2: max_elements = 5; break;
    case 3: max_elements = max_length; break;
    case 4: max_elements = max_length / 2; break;
    case 5: max_elements = max_length / 4; break;
    default: break;
    }
    sub = slb + max_elements;

#if !defined(ASN_DISABLE_UPER_SUPPORT) || !defined(ASN_DISABLE_APER_SUPPORT)
    if(!constraints || !constraints->per_constraints)
        constraints = &td->encoding_constraints;
    if(constraints->per_constraints) {
        const asn_per_constraint_t *pc = &constraints->per_constraints->size;
        if(pc->flags & APC_SEMI_CONSTRAINED) {
            slb = pc->lower_bound;
            sub = pc->lower_bound + max_elements;
        } else if(pc->flags & APC_CONSTRAINED) {
            slb = pc->lower_bound;
            sub = pc->upper_bound;
            if(sub - slb > max_elements) sub = slb + max_elements;
        }
    }
#else
    if(!constraints) constraints = &td->encoding_constraints;
#endif  /* !defined(ASN_DISABLE_UPER_SUPPORT) || !defined(ASN_DISABLE_APER_SUPPORT) */

    /* Bias towards edges of allowed space */
    switch(asn_random_between(-1, 4)) {
    default:
    case -1:
#if !defined(ASN_DISABLE_UPER_SUPPORT) || !defined(ASN_DISABLE_APER_SUPPORT)
        /* Prepare lengths somewhat outside of constrained range. */
        if(constraints->per_constraints
           && (constraints->per_constraints->size.flags & APC_EXTENSIBLE)) {
            switch(asn_random_between(0, 5)) {
            default:
            case 0:
                rnd_len = 0;
                break;
            case 1:
                if(slb > 0) {
                    rnd_len = slb - 1;
                } else {
                    rnd_len = 0;
                }
                break;
            case 2:
                rnd_len = asn_random_between(0, slb);
                break;
            case 3:
                if(sub < (ssize_t)max_length) {
                    rnd_len = sub + 1;
                } else {
                    rnd_len = max_length;
                }
                break;
            case 4:
                if(sub < (ssize_t)max_length) {
                    rnd_len = asn_random_between(sub + 1, max_length);
                } else {
                    rnd_len = max_length;
                }
                break;
            case 5:
                rnd_len = max_length;
                break;
            }
            break;
        }
#endif  /* !defined(ASN_DISABLE_UPER_SUPPORT) || !defined(ASN_DISABLE_APER_SUPPORT) */
        /* Fall through */
    case 0:
        rnd_len = asn_random_between(slb, sub);
        break;
    case 1:
        if(slb < sub) {
            rnd_len = asn_random_between(slb + 1, sub);
            break;
        }
        /* Fall through */
    case 2:
        rnd_len = asn_random_between(slb, slb);
        break;
    case 3:
        if(slb < sub) {
            rnd_len = asn_random_between(slb, sub - 1);
            break;
        }
        /* Fall through */
    case 4:
        rnd_len = asn_random_between(sub, sub);
        break;
    }

    for(; rnd_len > 0; rnd_len--) {
        asn_anonymous_set_ *list = _A_SET_FROM_VOID(st);
        void *ptr = 0;
        asn_random_fill_result_t tmpres = elm->type->op->random_fill(
            elm->type, &ptr, &elm->encoding_constraints,
            (max_length > res_ok.length ? max_length - res_ok.length : 0)
                / rnd_len);
        switch(tmpres.code) {
        case ARFILL_OK:
            ASN_SET_ADD(list, ptr);
            res_ok.length += tmpres.length;
            break;
        case ARFILL_SKIPPED:
            break;
        case ARFILL_FAILED:
            assert(ptr == 0);
            return tmpres;
        }
    }

    return res_ok;
}
