#include "asn1c_internal.h"
#include "asn1c_naming.h"
#include "asn1c_misc.h"
#include "asn1c_misc.h"
#include <asn1_buffer.h>

struct intl_name {
    asn1p_expr_t *expr;
    asn1p_expr_t *clashes_with;
    const char *name;
    TQ_ENTRY(struct intl_name) next;
};
static TQ_HEAD(struct intl_name) used_names;

void
c_name_clash_finder_init() {
    TQ_INIT(&used_names);
}

void
c_name_clash_finder_destroy() {
    struct intl_name *n;

    while((n = TQ_REMOVE(&used_names, next))) {
        union {
            const char *c_buf;
            char *nc_buf;
        } const_cast;

        asn1p_expr_free(n->expr);
        asn1p_expr_free(n->clashes_with);
        const_cast.c_buf = n->name;
        free(const_cast.nc_buf);
        free(n);
    }
}

static void
register_global_name(asn1p_expr_t *expr, const char *name) {
    struct intl_name *n;

    TQ_FOR(n, &used_names, next) {
        if(strcmp(n->name, name) == 0) {
            if(!(expr->_mark & TM_NAMEGIVEN) && expr != n->expr) {
                n->clashes_with = expr;
                expr->ref_cnt++;
                return;
            }
        }
    }

    if(expr->_mark & TM_NAMEGIVEN)
        return;

    n = calloc(1, sizeof(*n));
    assert(n);
    n->expr = expr;
    expr->ref_cnt++;
    n->name = strdup(name);
    TQ_ADD(&used_names, n, next);
}

int
c_name_clash(arg_t *arg) {
    struct intl_name *n;
    size_t n_clashes = 0;
    const size_t max_clashes = 5;

    TQ_FOR(n, &used_names, next) {
        if(n->clashes_with) {
            if(n_clashes++ > max_clashes) continue;
            FATAL(
                "Name \"%s\" is generated by %s.%s at line %s:%d and "
                "%s.%s at line %s:%d",
                n->name, n->expr->module->ModuleName, n->expr->Identifier,
                n->expr->module->source_file_name, n->expr->_lineno,
                n->clashes_with->module->ModuleName,
                n->clashes_with->Identifier,
                n->clashes_with->module->source_file_name,
                n->clashes_with->_lineno);
        }
    }

    if(n_clashes > max_clashes) {
        FATAL("... %zu more name clashes not shown", n_clashes - max_clashes);
    }

    return n_clashes > 0;
}


static abuf *
construct_base_name(abuf *buf, asn1p_expr_t *expr, int compound_names,
                    int avoid_keywords) {
    const char *id;

    assert(buf);

    if(compound_names && expr->parent_expr) {
        construct_base_name(buf, expr->parent_expr, compound_names, 0);
        if(buf->length) {
            abuf_str(buf, "__"); /* component separator */
        }
    }

    id = asn1c_make_identifier(
        ((avoid_keywords && !buf->length) ? AMI_CHECK_RESERVED : 0), expr, 0);

    abuf_str(buf, id);

    return buf;
}

static struct c_names
c_name_impl(arg_t *arg, asn1p_expr_t *expr, int avoid_keywords) {
    asn1p_expr_type_e expr_type = expr->expr_type;
    struct c_names names;
    int compound_names = 0;

    static abuf b_type_asn_name;
    static abuf b_type_part_name;
    static abuf b_type_base_name;
    static abuf b_type_c_name;
    static abuf b_asn_name;
    static abuf b_part_name;
    static abuf b_base_name;
    static abuf b_short_name;
    static abuf b_full_name;
    static abuf b_as_member;
    static abuf b_presence_enum;
    static abuf b_presence_name;
    static abuf b_members_enum;
    static abuf b_members_name;

    abuf_clear(&b_type_asn_name);
    abuf_clear(&b_type_part_name);
    abuf_clear(&b_type_base_name);
    abuf_clear(&b_type_c_name);
    abuf_clear(&b_asn_name);
    abuf_clear(&b_base_name);
    abuf_clear(&b_part_name);
    abuf_clear(&b_short_name);
    abuf_clear(&b_full_name);
    abuf_clear(&b_as_member);
    abuf_clear(&b_presence_enum);
    abuf_clear(&b_presence_name);
    abuf_clear(&b_members_enum);
    abuf_clear(&b_members_name);


    abuf_str(&b_type_asn_name, asn1c_type_name(arg, expr, TNF_UNMODIFIED));
    abuf_str(&b_type_part_name, asn1c_type_name(arg, expr, TNF_SAFE));
    abuf_str(&b_type_base_name, asn1c_type_name(arg, expr, TNF_SAFE));
    abuf_str(&b_type_c_name, asn1c_type_name(arg, expr, TNF_CTYPE));


    if((arg->flags & A1C_COMPOUND_NAMES)) {
        if((expr_type & ASN_CONSTR_MASK)
           || expr_type == ASN_BASIC_ENUMERATED
           || ((expr_type == ASN_BASIC_INTEGER
                || expr_type == ASN_BASIC_BIT_STRING))) {
            compound_names = 1;
        }
    }

    construct_base_name(&b_asn_name, expr, 0, 0);
    construct_base_name(&b_part_name, expr, 0, 0);
    construct_base_name(&b_base_name, expr, compound_names, avoid_keywords);
    construct_base_name(&b_as_member, expr, 0, 1);

    static abuf tmp_compoundable_part_name;
    abuf_clear(&tmp_compoundable_part_name);
    construct_base_name(&tmp_compoundable_part_name, expr, compound_names, 0);

    if(!expr->_anonymous_type) {
        if(arg->embed) {
            abuf_printf(&b_short_name, "%s", b_as_member.buffer);
        } else {
            abuf_printf(&b_short_name, "%s_t", b_as_member.buffer);
        }
    }
    abuf_printf(&b_full_name, "struct %s", b_base_name.buffer);
    abuf_printf(&b_presence_enum, "enum %s_PR", tmp_compoundable_part_name.buffer);
    abuf_printf(&b_presence_name, "%s_PR", tmp_compoundable_part_name.buffer);
    abuf_printf(&b_members_enum, "enum %s", b_base_name.buffer);
    abuf_printf(&b_members_name, "e_%s", tmp_compoundable_part_name.buffer);

    names.type.asn_name = b_type_asn_name.buffer;
    names.type.base_name = b_type_base_name.buffer;
    names.type.part_name = b_type_part_name.buffer;
    names.type.c_name = b_type_c_name.buffer;
    names.asn_name = b_asn_name.buffer;
    names.part_name = b_part_name.buffer;
    names.base_name = b_base_name.buffer;
    names.short_name = b_short_name.buffer;
    names.full_name = b_full_name.buffer;
    names.as_member = b_as_member.buffer;
    names.presence_enum = b_presence_enum.buffer;
    names.presence_name = b_presence_name.buffer;
    names.members_enum = b_members_enum.buffer;
    names.members_name = b_members_name.buffer;

    /* A _subset_ of names is checked against being globally unique */
    register_global_name(expr, names.base_name);
    register_global_name(expr, names.full_name);
    register_global_name(expr, names.presence_enum);
    register_global_name(expr, names.presence_name);
    register_global_name(expr, names.members_enum);
    register_global_name(expr, names.members_name);

    expr->_mark |= TM_NAMEGIVEN;

    return names;
}

struct c_names
c_name(arg_t *arg) {
    return c_name_impl(arg, arg->expr, 1);
}

struct c_names
c_expr_name(arg_t *arg, asn1p_expr_t *expr) {
    return c_name_impl(arg, expr, 1);
}

const char *
c_member_name(arg_t *arg, asn1p_expr_t *expr) {
    static abuf ab;

    abuf_clear(&ab);

    /* NB: do not use part_name, doesn't work for -fcompound-names */
    abuf_str(&ab, c_name_impl(arg, arg->expr, 0).base_name);
    abuf_str(&ab, "_");
    abuf_str(&ab, asn1c_make_identifier(0, expr, 0));

    return ab.buffer;
}


const char *
c_presence_name(arg_t *arg, asn1p_expr_t *expr) {
    static abuf ab;

    abuf_clear(&ab);

    if(expr) {
        /* NB: do not use part_name, doesn't work for -fcompound-names */
        abuf_str(&ab, c_name_impl(arg, arg->expr, 0).base_name);
        abuf_str(&ab, "_PR_");
        abuf_str(&ab, asn1c_make_identifier(0, expr, 0));
    } else {
        abuf_printf(&ab, "%s_PR_NOTHING",
                    c_name_impl(arg, arg->expr, 0).base_name);
    }

    return ab.buffer;
}

const char *
c_names_format(struct c_names ns) {
    static abuf nbuf;
    abuf_clear(&nbuf);

#define FMT_COMPONENT(x) abuf_printf(&nbuf, " ." #x "=\"%s\",", ns.x);

    abuf_str(&nbuf, "{");
    FMT_COMPONENT(type.asn_name);
    FMT_COMPONENT(type.part_name);
    FMT_COMPONENT(type.base_name);
    FMT_COMPONENT(type.c_name);
    FMT_COMPONENT(asn_name);
    FMT_COMPONENT(part_name);
    FMT_COMPONENT(base_name);
    FMT_COMPONENT(full_name);
    FMT_COMPONENT(short_name);
    FMT_COMPONENT(full_name);
    FMT_COMPONENT(as_member);
    FMT_COMPONENT(presence_enum);
    FMT_COMPONENT(presence_name);
    FMT_COMPONENT(members_enum);
    FMT_COMPONENT(members_name);
    abuf_printf(&nbuf, " .members_name=\"%s\" }", ns.members_name);
    return nbuf.buffer;
}