#include "libtcap.h"

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>

#include "asn_SET_OF.h"
#include "asn_SEQUENCE_OF.h"
#include "constr_CHOICE.h"

#include "CalledPartyNumber.h"
#include "Component.h"
#include "ComponentPortion.h"
#include "ConnectArg.h"
#include "FurnishChargingInformationArg.h"
#include "INTEGER.h"
#include "InitialDPArg.h"
#include "Invoke.h"
#include "TCMessage.h"




#define ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x)))

#define INVOKE_OPCODE_INITIALDP				0
#define INVOKE_OPCODE_CONNECT				20
#define INVOKE_OPCODE_FURNISHCHARGINGINFORMATION	34


static asn_TYPE_descriptor_t *opcode_type_map[] = {
	[INVOKE_OPCODE_INITIALDP]			= &asn_DEF_InitialDPArg,
	[INVOKE_OPCODE_CONNECT]				= &asn_DEF_ConnectArg,
	[INVOKE_OPCODE_FURNISHCHARGINGINFORMATION]	= &asn_DEF_FurnishChargingInformationArg,
};



static int buf_append(const void *buffer, size_t size, void *app_key) {
	FILE *fp = app_key;
	size_t ret;

	if (!buffer || !size)
		return 0;
	//fprintf(stderr, "writing %p %lu\n", buffer, size);
	ret = fwrite(buffer, size, 1, fp);
	return (ret == 1) ? 0 : -1;
}

static int tcap_encode(char **out, TCMessage_t *msg) {
	FILE *fp;
	char *str;
	size_t len;
	asn_enc_rval_t ec;

	fp = open_memstream(&str, &len);
	if (!fp)
		return -1;

	ec = der_encode(&asn_DEF_TCMessage, msg, buf_append, fp);

	fclose(fp);

	if (ec.encoded == -1) {
		free(str);
		return -1;
	}

	*out = str;
	return len;
}

int tcap_encode_with_routing(char **out, const uint8_t *routing, size_t routing_len) {
	int ret;
	TCMessage_t msg;
	ComponentPortion_t cp;
	Component_t *cmp_arr;
	Component_t cmp;
	ConnectArg_t ca;
	CalledPartyNumber_t *cpn_arr;
	CalledPartyNumber_t cpn;

	// init first
	memset(&msg, 0, sizeof(msg));
	memset(&cp, 0, sizeof(cp));
	memset(&cmp, 0, sizeof(cmp));
	memset(&ca, 0, sizeof(ca));
	memset(&cpn, 0, sizeof(cpn));

	cpn.buf = (uint8_t *)routing;
	cpn.size = routing_len;

	// fill structs
	msg.present = TCMessage_PR_begin;
	msg.choice.begin.components = &cp;

	cmp_arr = &cmp;
	cp.list.count = 1;
	cp.list.array = &cmp_arr;

	cmp.present = Component_PR_invoke;
	cmp.choice.invoke.opCode.present = OPERATION_PR_localValue;
	cmp.choice.invoke.opCode.choice.localValue = 20; // connect

	cpn_arr = &cpn;
	ca.destinationRoutingAddress.list.count = 1;
	ca.destinationRoutingAddress.list.array = &cpn_arr;

	cmp.choice.invoke.parameter = ANY_new_fromType(&asn_DEF_ConnectArg, &ca);

	ret = tcap_encode(out, &msg);

	asn_DEF_ANY.free_struct(&asn_DEF_ANY, cmp.choice.invoke.parameter, 0);

	return ret;
}

static TCMessage_t *tcap_decode(const char *buf, size_t len) {
	TCMessage_t *ret = NULL;
	asn_dec_rval_t rv;

	rv = ber_decode(0, &asn_DEF_TCMessage, (void **) &ret, buf, len);

	if (rv.code == RC_OK)
		return ret;

	asn_DEF_TCMessage.free_struct(&asn_DEF_TCMessage, ret, 0);
	return NULL;
}

static void *inap_decode(Invoke_t *invoke, asn_TYPE_descriptor_t **type) {
	long opcode;
	int rv;
	void *arg = 0;

	if (!invoke || !invoke->parameter || !type)
		goto nothing;

	if (invoke->opCode.present != OPERATION_PR_localValue)
		goto nothing;

	opcode = invoke->opCode.choice.localValue;

	if (opcode >= ARRAY_SIZE(opcode_type_map))
		goto nothing;

	*type = opcode_type_map[opcode];
	if (!*type)
		goto nothing;

	rv = ANY_to_type(invoke->parameter, *type, &arg);

	if (!rv)
		return arg;

	if (arg)
		(*type)->free_struct(*type, arg, 0);

nothing:
	if(type)
		*type = NULL;
	return NULL;
}

static inline int next_token_1(const char **token, int *token_len, const char **c) {
	*c = strchr(*token, '.');
	if (!*c)
		*token_len = strlen(*token);
	else
		*token_len = *c - *token;

	if (*token_len <= 0)
		return -1;

	return 0;
}

static inline int next_token_2(const char **token, const char **c) {
	if (!*c)
		return -1;

	*token = *c + 1;
	return 0;
}

static int cb(const void *buf, size_t s, void *app_key) {
	struct output_buffer *out = app_key;

	if (out->buf_size - out->used < s)
		return -1;
	memcpy(out->buf + out->used, buf, s);
	out->used += s;
	return 0;
}

static int asn1_extract(const char *spec, struct output_buffer *out, asn_TYPE_descriptor_t *type,
		void *element)
{
	const char *token, *c;
	int token_len, i, num;
	asn_TYPE_member_t *member;
	asn_anonymous_set_ *list;
	asn_CHOICE_specifics_t *choice_spec;

	if (!element)
		goto error;

	token = spec;

	while (1) {
		if (next_token_1(&token, &token_len, &c))
			goto error;

		if (type->elements_count <= 0)
			goto error; /* ? */

		/* check if we have a number */
		for (i = 0; i < token_len; i++) {
			if (token[i] < '0' || token[i] > '9')
				goto word;
		}

		/* we should have an array here */
		num = atoi(token);
		if (num < 0)
			goto error;

		/* single element with empty name == array ??? */
		if (type->elements_count > 1
				|| type->elements[0].name[0] != '\0')
			goto error;

		list = _A_SET_FROM_VOID(element);
		if (num >= list->count)
			goto error;

		element = list->array[num];
		type = type->elements[0].type;
		goto found_element;

word:
		/* has to be a CHOICE, SET or SEQUENCE */
		for (i = 0; i < type->elements_count; i++) {
			member = &type->elements[i];
			if (!strncmp(token, member->name, token_len))
				goto found_member;
		}

		goto error;

found_member:
		/* if this is a CHOICE, we need to confirm the member. we use the
		 * print function pointer to determine. i is the struct index */
		if (type->print_struct == CHOICE_print) {
			choice_spec = type->specifics;
			if (choice_spec->pres_size != sizeof(int))
				goto error;
			num = *((int *) element + choice_spec->pres_offset);
			if (num != i + 1)
				goto error;
		}

		type = member->type;
		element = (char *)element + member->memb_offset;
		if (element && (member->flags & ATF_POINTER))
			element = *((void **) element);
		if (!element)
			break;

found_element:
		if (next_token_2(&token, &c))
			break;
	}

	if (type->print_struct && out)
		type->print_struct(type, element, 0, cb, out);

	return 0;

error:
	return -1;
}



int tcap_extract(const char *buf, size_t len, const char *spec, struct output_buffer *out) {
	TCMessage_t *tcm;
	int ret;

	tcm = tcap_decode(buf, len);
	if (!tcm)
		return -1;

	ret = asn1_extract(spec, out, &asn_DEF_TCMessage, tcm);

	asn_DEF_TCMessage.free_struct(&asn_DEF_TCMessage, tcm, 0);
	return ret;
}



int inap_extract(const char *buf, size_t len, const char *spec, struct output_buffer *out) {
	TCMessage_t *tcm;
	ComponentPortion_t *cp;
	Component_t *cmp;
	Invoke_t *inv;
	const char *token, *c;
	int token_len, i;
	void *parameter;
	asn_TYPE_descriptor_t *type;

	tcm = tcap_decode(buf, len);
	if (!tcm)
		goto error;

	switch (tcm->present) {
		case TCMessage_PR_unidirectional:
			cp = &tcm->choice.unidirectional.components;
			break;

		case TCMessage_PR_begin:
			cp = tcm->choice.begin.components;
			break;

		case TCMessage_PR_end:
			cp = tcm->choice.end.components;
			break;

		case TCMessage_PR_continue:
			cp = tcm->choice.Continue.components;
			break;

		default:
			goto error;
	}

	if (!cp)
		goto error;

	token = spec;

	/* first token is the type we're looking for */

	if (next_token_1(&token, &token_len, &c))
		goto error;

	for (i = 0; i < cp->list.count; i++) {
		cmp = cp->list.array[i];
		if (cmp->present != Component_PR_invoke)
			continue;
		inv = &cmp->choice.invoke;

		parameter = inap_decode(inv, &type);
		if (!parameter)
			continue;

		if (!strncmp(type->name, token, token_len))
			goto found_parameter;
	}

	goto error;

found_parameter:
	if (next_token_2(&token, &c))
		i = 0;
	else
		i = asn1_extract(token, out, type, parameter);

	asn_DEF_TCMessage.free_struct(&asn_DEF_TCMessage, tcm, 0);
	type->free_struct(type, parameter, 0);

	return i;

error:
	if (tcm)
		asn_DEF_TCMessage.free_struct(&asn_DEF_TCMessage, tcm, 0);
	return -1;
}


static inline char phone_digit(unsigned char d) {
	if (d <= 9)
		return '0' + d;
	switch (d) {
		case 11:
			return '*';
		case 12:
			return '#';
		case 15:
			return 'F';
		default:
			return '?';
	}
}

static inline char phone_digit_hex(unsigned char d) {
	if (d <= 9)
		return '0' + d;
	else if (d <= 15)
		return 'A' + d - 10;
	return '?';
}


static inline int isup_convert_number_common(const char *inp, int inlen, char *out,
		char (*func)(unsigned char))
{
	const char *pi;
	unsigned char raw[32] = "", *pr;
	int rawlen, odd;

	if (inlen < 0)
		inlen = strlen(inp);

	if (inlen < 5) /* min: 2 octets */
		goto error;
	if (inlen > 92) /* 31 * 2 + 30 */
		goto error;

	pr = raw;
	pi = inp;
	while (1) {
		if (!*pi)
			break;

		if (*pi >= '0' && *pi <= '9')
			*pr = (*pi - '0') << 4;
		else if (*pi >= 'A' && *pi <= 'F')
			*pr = (*pi - 'A' + 10) << 4;
		else if (*pi >= 'a' && *pi <= 'f')
			*pr = (*pi - 'a' + 10) << 4;
		else
			goto error;

		pi++;

		if (*pi >= '0' && *pi <= '9')
			*pr |= (*pi - '0');
		else if (*pi >= 'A' && *pi <= 'F')
			*pr |= (*pi - 'A' + 10);
		else if (*pi >= 'a' && *pi <= 'f')
			*pr |= (*pi - 'a' + 10);
		else
			goto error;

		pr++;

		pi++;
		if (!*pi)
			break;
		if (*pi != ' ')
			goto error;
		pi++;
	}

	rawlen = pr - raw;

	odd = raw[0] & 0x80;
	if (odd && rawlen == 2)
		goto error;

	pr = raw + 2;
	rawlen -= 2;
	while (rawlen) {
		*(out++) = func(*pr & 0xf);
		*(out++) = func(*pr >> 4);
		rawlen--;
		pr++;
	}

	if (odd)
		out--;

	*out = 0;

	return 0;

error:
	return -1;
}

int isup_convert_number(const char *inp, int inlen, char *out) {
	return isup_convert_number_common(inp, inlen, out, phone_digit);
}
int isup_convert_number_hex(const char *inp, int inlen, char *out) {
	return isup_convert_number_common(inp, inlen, out, phone_digit_hex);
}
