/*
 *
 *  Copyright (C) 2008  LGC Wireless, Inc
 *  Written by: Xinyu Yan <xyyan@lgcwireless.com>
 *
 *-
 */
#include "./include/isup_debug.h"
#include "./include/isup_public.h"
#include "./include/isup_if.h"
#include "./include/isup_struct.h"
#include "./include/isup_def.h"
#include "./include/isup_const.h"

#define MAX_ISUP_VAR	10
#define MAX_ISUP_OPT	50

struct param_unit {
	u8 len;
	u8 *ptr;
};

struct isup_gen_fmt {
	u8 var_num;
	u8 opt_num;
	struct param_unit fix;
	struct param_unit var[MAX_ISUP_VAR];
	struct param_unit opt[MAX_ISUP_OPT];
};

#define MAX_ISUP_MSGTYPE	100	//not sure, just an assumption
#define LEN_OF_LV(ptr) (*ptr + 1)
#define LEN_OF_TLV(ptr) (*(ptr+1) + 2)

struct isup_fmt_desc {
	u8 exist;
	u8 fixed_len;	/* fixed len start from message type(include) */
	u8 var_num;
};

const static struct isup_fmt_desc fmt_desc_itu[MAX_ISUP_MSGTYPE] = {
	{0},
	{1, 6, 1},	//Initial address(1)
	{1, 1, 1},	//Subsequent address(2)
	{1,3,0},//Information request (national use)(3)
	{1,3,0},//Information (national use)(4)
	{1,2,0},//Continuity(5)
	{1,3,0},//Address complete(6)
	{1,3,0},//Connect(7)
	{1,1,0},//Forward transfer(8)
	{1,1,0},//Answer(9)
	{0},//10
	{0},//11
	{1,1,1},//Release(12)
	{1,2,0},//Suspend(13)
	{1,2,0},//Resume(14)
	{0},//15
	{1,1,0},//Release complete(16)
	{0},
	{0},
	{0},
	{0},//Unblocking(20)
	{0},
	{0},//Unblocking acknowledgement(22)
	{0},
	{0},
	{0},//25
	{0},
	{0},
	{0},
	{0},
	{0},//30
	{1,2,0},//Facility request(31)
	{1,2,0},//Facility accepted(32)
	{1,2,1},//Facility reject(33)
	{0},
	{0},//35
	{0},//Loop Back Acknowledgement(36)
	{0},
	{0},
	{0},
	{0},//Pass-Along(40)
	{0},
	{0},
	{0},
	{1,2,0},//Call progress(44)
	{1,1,1},//User-to-user information(45)
	{0},//Unequipped CIC (national use)(46)
	{1,1,1},//Confusion(47)
	{0},//Overload(48)
	{0},//Charge information (national use)(49)
	{1,1,0},//Network resource management(50)
	{1,1,0},//Facility (51)
	{1,1,0},//User Part Test(52)
	{1,1,0},//User Part Available(53)
	{1,1,0},//Identification request(54)
	{1,1,0},//Identification response(55)
	{1,1,0},//Segmentation(56)	
	{0},
	{0},
	{0},
	{0},//60	
	{0},
	{0},
	{0},
	{1,1,0},//Loop Prevention(64)	
	{1,1,0},//Application transport message(65)
	{1,1,0},//Pre-release information message (66)
};

const static struct isup_fmt_desc fmt_desc_ansi[MAX_ISUP_MSGTYPE] = {
	{0},
	{1, 5, 2},	//Initial address(1)
	{1, 1, 1},	//Subsequent address(2)
	{1,3,0},//Information request (national use)(3)
	{1,3,0},//Information (national use)(4)
	{1,2,0},//Continuity(5)
	{1,3,0},//Address complete(6)
	{1,3,0},//Connect(7)
	{1,1,0},//Forward transfer(8)
	{1,1,0},//Answer(9)
	{0},//10
	{0},//11
	{1,1,1},//Release(12)
	{1,2,0},//Suspend(13)
	{1,2,0},//Resume(14)
	{0},//15
	{1,1,0},//Release complete(16)
	{0},
	{0},
	{0},
	{0},//Unblocking(20)
	{0},
	{0},//Unblocking acknowledgement(22)
	{0},
	{0},
	{0},//25
	{0},
	{0},
	{0},
	{0},
	{0},//30
	{1,2,0},//Facility request(31)
	{1,2,0},//Facility accepted(32)
	{1,2,1},//Facility reject(33)
	{0},
	{0},//35
	{0},//Loop Back Acknowledgement(36)
	{0},
	{0},
	{0},
	{0},//Pass-Along(40)
	{0},
	{0},
	{0},
	{1,2,0},//Call progress(44)
	{1,1,1},//User-to-user information(45)
	{0},//Unequipped CIC (national use)(46)
	{1,1,1},//Confusion(47)
	{0},//Overload(48)
	{0},//Charge information (national use)(49)
	{1,1,0},//Network resource management(50)
	{1,1,0},//Facility (51)
	{1,1,0},//User Part Test(52)
	{1,1,0},//User Part Available(53)
	{1,1,0},//Identification request(54)
	{1,1,0},//Identification response(55)
	{1,1,0},//Segmentation(56)	
	{0},
	{0},
	{0},
	{0},//60	
	{0},
	{0},
	{0},
	{1,1,0},//Loop Prevention(64)	
	{1,1,0},//Application transport message(65)
	{1,1,0},//Pre-release information message (66)
};

int raw_to_fmt(struct isup_gen_fmt *fmt, u8 *raw_head, u16 raw_len, u8 variant)
{
	int i;
	const struct isup_fmt_desc *desc;
	struct param_unit *opt;
	u8 msg_type;
	u8 *raw = raw_head;

	msg_type = *raw;
	if(msg_type >= MAX_ISUP_MSGTYPE)
		return 0;
	if(variant == VARIANT_ANSI)
		desc = &fmt_desc_ansi[msg_type];
	else	
		desc = &fmt_desc_itu[msg_type];
	if(desc->exist == 0)
		return 0;

	memset(fmt, 0, sizeof(struct isup_gen_fmt));
	
	// M Fixed //
	fmt->fix.len = desc->fixed_len;
	fmt->fix.ptr = raw;
	raw += fmt->fix.len;
	
	// M Variable //
	fmt->var_num = desc->var_num;
	raw += *raw;
	if(fmt->var_num != 0)
	{		
		for(i = 0; i < fmt->var_num; i++)
		{
			if(raw + LEN_OF_LV(raw) - raw_head > raw_len)
				return -1;
			fmt->var[i].len = LEN_OF_LV(raw);
			fmt->var[i].ptr = raw;
			raw += fmt->var[i].len;
		}
	}
	
	// Option //
	while(*raw != 0)
	{
		if(raw + LEN_OF_TLV(raw) - raw_head > raw_len)
			return -1;
		opt = &fmt->opt[fmt->opt_num++];
		opt->len = LEN_OF_TLV(raw);
		opt->ptr = raw;
		raw += opt->len;
	}
	return 1;
}

int param_need_merge(u8 param_name)
{
		switch(param_name)
		{
			case ME_REDIRGNUM:
			case ME_REDIRINFO:
			case ME_ORIGCALDNUM:
			case ME_LOCNMB:
			case ME_LASDIVLINID:
				return 1;
		}
	return 0;
}

int param_already_exist(u8 *index, struct isup_gen_fmt *org, u8 param_name)
{
	int i;

	for(i = 0; i < org->opt_num; i++)
	{
		if(*(org->opt[i].ptr) == param_name)
		{
			*index = i;
			return 1;
		}
	}
	return 0;
}

static u8 bw_call_ind[4];
void merge_cpg_acm(struct isup_gen_fmt *dst, struct isup_gen_fmt *cpg, struct isup_gen_fmt *acm)
{
	int i;
	u8 param_name;
	u8 index;
	
	memcpy(dst, cpg, sizeof(struct isup_gen_fmt));

	/* backward call ind, from fix part to opt */
	bw_call_ind[0] = ME_BACKCALLIND;
	bw_call_ind[1] = 2;
	memcpy(bw_call_ind+2, acm->fix.ptr+1, 2);
	dst->opt[dst->opt_num].len = 4;
	dst->opt[dst->opt_num].ptr = bw_call_ind;
	dst->opt_num++;

	for(i = 0; i < acm->opt_num; i++)
	{
		param_name = *(acm->opt[i].ptr);
		/* discard echo control infomation */
		if(param_name == ME_ECHOCNTRL)
			continue;
		if(param_already_exist(&index, dst, param_name) == 1)
			memcpy(&dst->opt[index], &acm->opt[i], sizeof(struct param_unit));
		else
			memcpy(&dst->opt[dst->opt_num++], &acm->opt[i], sizeof(struct param_unit));
	}
}

void merge_fmt(struct isup_gen_fmt *dst, struct isup_gen_fmt *org, struct isup_gen_fmt *add)
{
	int i;
	u8 param_name;
	u8 index;
	
	memcpy(dst, org, sizeof(struct isup_gen_fmt));
	
	if(*(add->fix.ptr) == M_CPG)
		memcpy(&dst->fix, &add->fix, sizeof(struct param_unit));

	if(*(add->fix.ptr) == M_IAM)
	{	
		for(i = 0; i < add->var_num; i++)
		{
			memcpy(&dst->var[i], &add->var[i], sizeof(struct param_unit));
		}
	}
	else
	{	
		for(i = 0; i < org->var_num; i++)
		{
			memcpy(&dst->var[i], &org->var[i], sizeof(struct param_unit));
		}		
	}
	
	for(i = 0; i < add->opt_num; i++)
	{
		param_name = *(add->opt[i].ptr);
		if(param_need_merge(param_name) == 0)
			continue;
		if(param_already_exist(&index, dst, param_name) == 1)
			memcpy(&dst->opt[index], &add->opt[i], sizeof(struct param_unit));
		else
			memcpy(&dst->opt[dst->opt_num++], &add->opt[i], sizeof(struct param_unit));
	}
}

int fmt_to_raw(u8 *raw_head, struct isup_gen_fmt *fmt)
{
	u8 *raw = raw_head;
	int i;
	u8 pre_pointer;
	u8 pre_varlen;

	// M Fixed //
	memcpy(raw, fmt->fix.ptr, fmt->fix.len);
	raw += fmt->fix.len;

	/* Set initial condition, note that if VAR not exist, it applys to OPT directly */
	pre_varlen = fmt->var_num + 1;
	pre_pointer = 1;
	
	// M Variable //
	if(fmt->var_num != 0)
	{
		for(i = 0; i < fmt->var_num; i++)
		{
			*raw = pre_pointer + pre_varlen - 1;
			memcpy(raw+*raw, fmt->var[i].ptr, fmt->var[i].len);
			pre_varlen = fmt->var[i].len;
			pre_pointer = *(raw++);
		}
		
//		/* "pointer of optional part" is set to 0 first */
//		*raw = 0;
	}

	// Option //
	if(fmt->opt_num != 0)
	{
		/* Set "pointer of optional part" */
		*raw = pre_pointer + pre_varlen - 1;
		
		raw += *raw;
		for(i = 0; i < fmt->opt_num; i++)
		{
			memcpy(raw, fmt->opt[i].ptr, fmt->opt[i].len);
			raw += fmt->opt[i].len;
		}
		
//		/* Set "end of optional parameter" */
//		*raw = 0;
	}

	/* Last octet set to 0. It may be "pointer of optional part" or "end of optional parameter" */
	*(raw++) = 0;
	if(fmt->opt_num == 0)
		return raw-raw_head+pre_pointer + pre_varlen - 1 -1;
	else	
		return raw-raw_head;
}

int isup_transit_msg(u8 *raw_dst, u8 *raw_add, u8 len_add, u8 *raw_org, u8 len_org, u8 variant)
{
	struct isup_gen_fmt fmt_org;
	struct isup_gen_fmt fmt_add;
	struct isup_gen_fmt fmt_dst;

	if(raw_to_fmt(&fmt_org, raw_org, len_org, variant)==0)
		return 0;
	if(raw_to_fmt(&fmt_add, raw_add, len_add, variant)==0)
		return 0;
	if((*(fmt_org.fix.ptr) == M_ACM) && (*(fmt_add.fix.ptr) == M_CPG))
		merge_cpg_acm(&fmt_dst, &fmt_add, &fmt_org);
	else
		merge_fmt(&fmt_dst, &fmt_org, &fmt_add);
	return fmt_to_raw(raw_dst, &fmt_dst);
}

