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

#include "asn1parser.h"
#include "asn1p_expr.h"

void
asn1p_value_set_source(asn1p_value_t *value, asn1p_module_t *module,
                       int lineno) {
    if(value) {
        switch(value->type) {
        case ATV_TYPE:
            asn1p_expr_set_source(value->value.v_type, module, lineno);
            break;
        case ATV_REFERENCED:
            asn1p_ref_set_source(value->value.reference, module, lineno);
            break;
        case ATV_VALUESET:
            asn1p_constraint_set_source(value->value.constraint, module,
                                        lineno);
            break;
        default:
            break;
        }
    }
}

int
asn1p_value_compare(const asn1p_value_t *a, const asn1p_value_t *b) {
    if(a->type != b->type) {
        return -1;
    }

    switch(a->type) {
    case ATV_NULL:
    case ATV_NOVALUE:
    case ATV_MAX:
    case ATV_MIN:
    case ATV_FALSE:
    case ATV_TRUE:
        break;
    case ATV_TYPE:
        return asn1p_expr_compare(a->value.v_type, b->value.v_type);
    case ATV_REAL:
        return (a->value.v_double == b->value.v_double) ? 0 : -1;
    case ATV_INTEGER:
    case ATV_TUPLE:
    case ATV_QUADRUPLE:
        return (a->value.v_integer == b->value.v_integer) ? 0 : -1;
    case ATV_STRING:
    case ATV_UNPARSED:
        if(a->value.string.size != b->value.string.size
           || memcmp(a->value.string.buf, b->value.string.buf,
                     a->value.string.size)
                  != 0) {
            return -1;
        }
        return 0;
    case ATV_BITVECTOR:
        if(a->value.binary_vector.size_in_bits
               != b->value.binary_vector.size_in_bits
           || memcmp(a->value.binary_vector.bits, b->value.binary_vector.bits,
                     (a->value.binary_vector.size_in_bits+7) >> 3)
                  != 0) {
            return -1;
        }
    case ATV_VALUESET:
        return asn1p_constraint_compare(a->value.constraint,
                                        b->value.constraint);
    case ATV_REFERENCED:
        return asn1p_ref_compare(a->value.reference, b->value.reference);
    case ATV_CHOICE_IDENTIFIER:
        if(strcmp(a->value.choice_identifier.identifier,
                  b->value.choice_identifier.identifier)
           != 0) {
            return -1;
        }
        return asn1p_value_compare(a->value.choice_identifier.value,
                                   b->value.choice_identifier.value);
    }

    return 0;
}

asn1p_value_t *
asn1p_value_fromref(asn1p_ref_t *ref, int do_copy) {
	if(ref) {
		asn1p_value_t *v = calloc(1, sizeof *v);
		if(v) {
			if(do_copy) {
				v->value.reference = asn1p_ref_clone(ref);
				if(v->value.reference == NULL) {
					free(v);
					return NULL;
				}
			} else {
				v->value.reference = ref;
			}
			v->type = ATV_REFERENCED;
		}
		return v;
	} else {
		errno = EINVAL;
		return NULL;
	}
}

asn1p_value_t *
asn1p_value_fromconstr(asn1p_constraint_t *ct, int do_copy) {
	if(ct) {
		asn1p_value_t *v = calloc(1, sizeof *v);
		if(v) {
			if(do_copy) {
				v->value.constraint
					= asn1p_constraint_clone(ct);
				if(v->value.constraint == NULL) {
					free(v);
					return NULL;
				}
			} else {
				v->value.constraint = ct;
			}
			v->type = ATV_VALUESET;
		}
		return v;
	} else {
		errno = EINVAL;
		return NULL;
	}
}

asn1p_value_t *
asn1p_value_frombits(uint8_t *bits, int size_in_bits, int do_copy) {
	if(bits) {
		asn1p_value_t *v = calloc(1, sizeof *v);
		assert(size_in_bits >= 0);
		if(v) {
			if(do_copy) {
				int size = ((size_in_bits + 7) >> 3);
				void *p;
				p = malloc(size + 1);
				if(p) {
					memcpy(p, bits, size);
					((char *)p)[size] = '\0'; /* JIC */
				} else {
					free(v);
					return NULL;
				}
				v->value.binary_vector.bits = p;
			} else {
				v->value.binary_vector.bits = (void *)bits;
			}
			v->value.binary_vector.size_in_bits = size_in_bits;
			v->type = ATV_BITVECTOR;
		}
		return v;
	} else {
		errno = EINVAL;
		return NULL;
	}
}

asn1p_value_t *
asn1p_value_frombuf(char *buffer, int size, int do_copy) {
	if(buffer) {
		asn1p_value_t *v = calloc(1, sizeof *v);
		assert(size >= 0);
		if(v) {
			if(do_copy) {
				void *p = malloc(size + 1);
				if(p) {
					memcpy(p, buffer, size);
					((char *)p)[size] = '\0'; /* JIC */
				} else {
					free(v);
					return NULL;
				}
				v->value.string.buf = p;
			} else {
				v->value.string.buf = (uint8_t *)buffer;
			}
			v->value.string.size = size;
			v->type = ATV_STRING;
		}
		return v;
	} else {
		errno = EINVAL;
		return NULL;
	}
}

asn1p_value_t *
asn1p_value_fromdouble(double d) {
	asn1p_value_t *v = calloc(1, sizeof *v);
	if(v) {
		v->value.v_double = d;
		v->type = ATV_REAL;
	}
	return v;
}

asn1p_value_t *
asn1p_value_fromint(asn1c_integer_t i) {
	asn1p_value_t *v = calloc(1, sizeof *v);
	if(v) {
		v->value.v_integer = i;
		v->type = ATV_INTEGER;
	}
	return v;
}

asn1p_value_t *
asn1p_value_fromtype(asn1p_expr_t *expr) {
	asn1p_value_t *v = calloc(1, sizeof *v);
	if(v) {
		v->value.v_type = expr;
		v->type = ATV_TYPE;
		expr->ref_cnt++;
	}
	return v;
}

asn1p_value_t *
asn1p_value_clone(asn1p_value_t *v) {
	return asn1p_value_clone_with_resolver(v, 0, 0);
}

asn1p_value_t *
asn1p_value_clone_with_resolver(asn1p_value_t *v,
		asn1p_value_t *(*resolver)(asn1p_value_t *, void *rarg),
		void *rarg) {
	asn1p_value_t *clone = NULL;
	if(v) {
		switch(v->type) {
		case ATV_NOVALUE:
		case ATV_NULL:
			return calloc(1, sizeof(*clone));
		case ATV_REAL:
			return asn1p_value_fromdouble(v->value.v_double);
		case ATV_TYPE:
			return asn1p_value_fromtype(v->value.v_type);
		case ATV_INTEGER:
		case ATV_MIN:
		case ATV_MAX:
		case ATV_FALSE:
		case ATV_TRUE:
		case ATV_TUPLE:
		case ATV_QUADRUPLE:
			clone = asn1p_value_fromint(v->value.v_integer);
			if(clone) clone->type = v->type;
			return clone;
		case ATV_STRING:
			clone = asn1p_value_frombuf((char *)v->value.string.buf,
				v->value.string.size, 1);
			if(clone) clone->type = v->type;
			return clone;
		case ATV_UNPARSED:
			clone = asn1p_value_frombuf((char *)v->value.string.buf,
				v->value.string.size, 1);
			if(clone) clone->type = ATV_UNPARSED;
			return clone;
		case ATV_BITVECTOR:
			return asn1p_value_frombits(v->value.binary_vector.bits,
				v->value.binary_vector.size_in_bits, 1);
		case ATV_REFERENCED:
			if(resolver) {
				clone = resolver(v, rarg);
				if(clone) return clone;
				else if(errno != ESRCH) return NULL;
			}
			return asn1p_value_fromref(v->value.reference, 1);
		case ATV_VALUESET:
			if(resolver) {
				clone = resolver(v, rarg);
				if(clone) return clone;
				else if(errno != ESRCH) return NULL;
			}
			return asn1p_value_fromconstr(v->value.constraint, 1);
		case ATV_CHOICE_IDENTIFIER: {
			char *id = v->value.choice_identifier.identifier;
			clone = calloc(1, sizeof(*clone));
			if(!clone) return NULL;
			clone->type = v->type;
			id = strdup(id);
			if(!id) { asn1p_value_free(clone); return NULL; }
			clone->value.choice_identifier.identifier = id;
			v = asn1p_value_clone(v->value.choice_identifier.value);
			if(!v) { asn1p_value_free(clone); return NULL; }
			clone->value.choice_identifier.value = v;
			return clone;
		    }
		}

		assert(!"UNREACHABLE");
	}
	return v;
}

void
asn1p_value_free(asn1p_value_t *v) {
	if(v) {
		switch(v->type) {
		case ATV_NOVALUE:
		case ATV_NULL:
			break;
		case ATV_TYPE:
			asn1p_expr_free(v->value.v_type);
			break;
		case ATV_REAL:
		case ATV_INTEGER:
		case ATV_MIN:
		case ATV_MAX:
		case ATV_FALSE:
		case ATV_TRUE:
		case ATV_TUPLE:
		case ATV_QUADRUPLE:
			/* No freeing necessary */
			break;
		case ATV_STRING:
		case ATV_UNPARSED:
			assert(v->value.string.buf);
			free(v->value.string.buf);
			break;
		case ATV_BITVECTOR:
			assert(v->value.binary_vector.bits);
			free(v->value.binary_vector.bits);
			break;
		case ATV_REFERENCED:
			asn1p_ref_free(v->value.reference);
			break;
		case ATV_VALUESET:
			asn1p_constraint_free(v->value.constraint);
			break;
		case ATV_CHOICE_IDENTIFIER:
			free(v->value.choice_identifier.identifier);
			asn1p_value_free(v->value.choice_identifier.value);
			break;
		}
		memset(v, 0, sizeof(*v));
		free(v);
	}
}

