#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <assert.h>

#include "asn1parser.h"

void
asn1p_constraint_set_source(asn1p_constraint_t *ct,
                            struct asn1p_module_s *module, int lineno) {
    if(ct) {
        ct->module = module;
        ct->_lineno = lineno;
        asn1p_value_set_source(ct->containedSubtype,module,lineno);
        asn1p_value_set_source(ct->value,module,lineno);
        asn1p_value_set_source(ct->range_start,module,lineno);
        asn1p_value_set_source(ct->range_stop,module,lineno);
        for(size_t i = 0; i < ct->el_count; i++) {
            asn1p_constraint_set_source(ct->elements[i], module, lineno);
        }
    }
}

int asn1p_constraint_compare(const asn1p_constraint_t *a,
                             const asn1p_constraint_t *b) {
    assert((a && b));

    if(a->type != b->type)
        return -1;

    /* Currently we only check VALUESET as a reference */
    if(a->type == ACT_EL_TYPE) {
        return strcmp(a->containedSubtype->value.v_type->reference->components[0].name,
                      b->containedSubtype->value.v_type->reference->components[0].name);
    }

    return 0;
}

asn1p_constraint_t *
asn1p_constraint_new(int _lineno, asn1p_module_t *mod) {
	asn1p_constraint_t *ct;

	ct = calloc(1, sizeof(*ct));
	if(ct) {
		ct->_lineno = _lineno;
		ct->module = mod;
	}

	return ct;
}


void
asn1p_constraint_free(asn1p_constraint_t *ct) {
	if(ct) {

		asn1p_value_free(ct->containedSubtype);
		asn1p_value_free(ct->value);
		asn1p_value_free(ct->range_start);
		asn1p_value_free(ct->range_stop);

		if(ct->elements) {
			while(ct->el_count--) {
				asn1p_constraint_free(
					ct->elements[ct->el_count]);
			}
			free(ct->elements);
		}

		free(ct);
	}
}

asn1p_constraint_t *
asn1p_constraint_clone(asn1p_constraint_t *src) {
	return asn1p_constraint_clone_with_resolver(src, 0, 0);
}

asn1p_constraint_t *
asn1p_constraint_clone_with_resolver(asn1p_constraint_t *src,
		asn1p_value_t *(*vr)(asn1p_value_t *, void *varg), void *varg) {
	asn1p_constraint_t *clone;

#define	CLONE(field, func)	do { if(src->field) {			\
			clone->field = func(src->field, vr, varg);	\
			if(clone->field == NULL) {			\
				asn1p_constraint_free(clone);		\
				return NULL;				\
			}						\
		} } while(0)

	clone = asn1p_constraint_new(src->_lineno, src->module);
	if(clone) {
		unsigned int i;

		clone->type = src->type;
		clone->presence = src->presence;
		CLONE(containedSubtype,	asn1p_value_clone_with_resolver);
		CLONE(value,		asn1p_value_clone_with_resolver);
		CLONE(range_start,	asn1p_value_clone_with_resolver);
		CLONE(range_stop,	asn1p_value_clone_with_resolver);

		for(i = 0; i < src->el_count; i++) {
			asn1p_constraint_t *t;
			t = asn1p_constraint_clone_with_resolver(src->elements[i], vr, varg);
			if(!t) {
				asn1p_constraint_free(clone);
				return NULL;
			}
			if(asn1p_constraint_insert(clone, t)) {
				asn1p_constraint_free(clone);
				asn1p_constraint_free(t);
				return NULL;
			}
		}
		assert(clone->el_count == src->el_count);
		clone->_lineno = src->_lineno;
	}

	return clone;
}

/*
 * Make sure there's enough space to add an element.
 */
static int
asn1p_constraint_make_memory(asn1p_constraint_t *ct) {
	if(ct->el_count == ct->el_size) {
		unsigned int newsize = ct->el_size ? ct->el_size << 2 : 4;
		void *p;
		p = realloc(ct->elements, newsize * sizeof(ct->elements[0]));
		if(p) {
			ct->elements = p;
			ct->el_size = newsize;
		} else {
			return -1;
		}
	}
	return 0;
}

int
asn1p_constraint_insert(asn1p_constraint_t *into, asn1p_constraint_t *what) {
	assert(into);
	assert(what);

	if(asn1p_constraint_make_memory(into))
		return -1;

	into->elements[into->el_count++] = what;
	what->parent_ct = into;

	return 0;
}

int
asn1p_constraint_prepend(asn1p_constraint_t *before, asn1p_constraint_t *what) {
	assert(before);
	assert(what);

	if(asn1p_constraint_make_memory(before))
		return -1;

	memmove(&before->elements[1], &before->elements[0],
		before->el_count * sizeof(before->elements[0]));

	before->elements[0] = what;
	before->el_count++;
	what->parent_ct = before;

	return 0;
}


const char *
asn1p_constraint_type2str(enum asn1p_constraint_type_e type) {
	switch(type) {
	case ACT_INVALID:
		return "INVALID";
	case ACT_EL_TYPE:
		return "ContainedSubtype";
	case ACT_EL_VALUE:
		return "SingleValue";
	case ACT_EL_RANGE:
	case ACT_EL_LLRANGE:
	case ACT_EL_RLRANGE:
	case ACT_EL_ULRANGE:
		return "ValueRange";
	case ACT_EL_EXT:
		return "...";
	case ACT_CT_SIZE:
		return "SizeConstraint";
	case ACT_CT_FROM:
		return "PermittedAlphabet";
	case ACT_CT_WCOMP:
		return "SingleTypeConstraint";
	case ACT_CT_WCOMPS:
		return "MultipleTypeConstraints";
	case ACT_CT_CTDBY:
		return "UserDefinedConstraint";
	case ACT_CT_CTNG:
		return "ContentsConstraint";
	case ACT_CT_PATTERN:
		return "PatternConstraint";
	case ACT_CA_SET:
		return "SET";
	case ACT_CA_CRC:
		return "ComponentRelationConstraint";
	case ACT_CA_CSV:
		return "CSV";
	case ACT_CA_UNI:
		return "UNION";
	case ACT_CA_INT:
		return "INTERSECTION";
	case ACT_CA_EXC:
		return "EXCEPT";
	case ACT_CA_AEX:
		return "ALL EXCEPT";
	}
	return "UNKNOWN";
}

const asn1p_constraint_t *
asn1p_get_component_relation_constraint(asn1p_constraint_t *ct) {
    if(ct) {
        if(ct->type == ACT_CA_CRC)
            return ct;
        if(ct->type == ACT_CA_SET) {
            for(size_t i = 0; i < ct->el_count; i++) {
                const asn1p_constraint_t *tmp =
                    asn1p_get_component_relation_constraint(ct->elements[i]);
                if(tmp) return tmp;
            }
        }
    }
    return NULL;
}

