#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <elib/table.h>
#include <elib/machine.h>
#include <uni.h>
#include <uni_debug.h>
#include <uni_enums.h>
#include <uni_mesg.h>
#include <uni_errors.h>
#include <uni_interpret.h>
#include <uni_message.h>
#include <uni_encode.h>

extern element_desc the_elements_tbl[];
static int reference_value;
u_char* gen_elem_encoder();
u_char* encode_octet_group();
int encode_message_header();
int encode_number();
void encode_short();
void encode_element_header();
u_char* bb_low_layer_info_encoder();


/******************************************************************************
*  encode_mesg
******************************************************************************/
u_char* encode_mesg(uni_message mesg, int *mesg_size)  
{
    int emi=0, mi=0, ei = 0;      /* table index variables */
    list e, r;                    /* list of elements */
    int mesg_len = 0;
    u_char *mesg_buf;
    int mbi = UNI_MESG_HDR_LEN; 
    element elem;
    u_char *ebuf;    

    debug("parse", "uni: encode_message\n");
    if ((mesg_len = calc_message_length(mesg, &mi)) < 0) {
        return 0;
    }
    if ((mesg_buf=(u_char *)allocate_memory(mesg_len+UNI_MESG_HDR_LEN)) == 0) {
        fprintf(stderr, "uni: encode_mesg: couldn't alloc memory for mesg\n");
        return 0;
    }
    bzero(mesg_buf, mesg_len + UNI_MESG_HDR_LEN);
    /* Encode message header. */
    encode_message_header(mesg_buf, mesg_len, mesg->hdr->crv, 
			              mesg->hdr->crv_flag, mi); 

    debug("parse", "uni: encode_message - message: %s\n",
	  messages_tbl[mi].name);
    debug("parse", "uni: encode_message - message len: %d\n", mesg_len);

    /*
     * Encode appropriate elements and add to append them to mesg_buf.
     * Elements can be repeated, the first occurance takes precedence.
     */
    e = mesg->elements;
    dolist(r, e) {
        elem = r->first;
        /* 
         * Check if element type and lenght is valid.
         */
        if ((emi=is_ie_valid_in_mesg(mi,elem->hdr.code,elem->hdr.length))<0){
	    printf("uni: encode_generic_elem - invalid element %02x in mesg\n",
                   elem->hdr.code);
            return 0;
        }
        /* 
         * Encode element header and increment mesg buf idx.
         */
        encode_element_header(elem, mesg_buf+mbi);
        mbi += UNI_ELEM_HDR_LEN;
        /* 
         * Encode rest of elements and copy in mesg buffer.
         */
        GET_TBL_IDX(ei, elem->hdr.code, the_elements_tbl, MAX_ELEMENTS);
        ebuf = (coders[ei].encoder_func)(mesg, mi, elem, ei,
					 messages_tbl[mi].elist[emi].def_ogs);
        bcopy(ebuf, (mesg_buf+mbi), elem->hdr.length);
        mbi += elem->hdr.length;
        deallocate_memory(ebuf);
    }
    *mesg_size = mesg_len + UNI_MESG_HDR_LEN; 
    return mesg_buf;
}

/******************************************************************************
 *  gen_elem_encoder()
 *****************************************************************************/
u_char* gen_elem_encoder(uni_message mesg, int mi, element elem, int ei, 
			 int oglist)
    /* mi: index of message in message table */
    /* elem: elem to encode */    
    /* ei: index of element in the_element_tbl */
    /* oglist: bit mask of default og's in elem */
{
    register int oi = 0;
    u_char *og_buf;
    u_char *elem_buf;                           /* element buffer */
    int ebi = 0;                                /* element buffer index */
    list q;
    octet_group og;
    octet_group_desc *ogs = the_elements_tbl[ei].ogs;

    debug("parse", "uni: encode_generic_element - elem name %s     size: %d\n",
           the_elements_tbl[ei].name, elem->hdr.length);
    elem_buf=(u_char*) allocate_memory (elem->hdr.length);
    dolist (q, elem->octet_groups) {
        og = (octet_group) q->first;
        while (!(oglist & 1)) {
            oi++;
            oglist >>= 1;
        }
        debug("parse","uni: encode_generic_element - octet_group #: %d\n", oi);
	if (og_buf = encode_octet_group(mesg, elem, oi, ei)) {
	    bcopy(og_buf, (elem_buf + ebi), ogs[oi].fixed_length);
	    ebi += ogs[oi].fixed_length;
	    deallocate_memory(og_buf);
	}
        oglist >>= 1;
        oi++;
    }    
    return elem_buf;
}

/******************************************************************************
 *  encode_short
 *****************************************************************************/
void encode_short(unsigned int valn, u_char* buf, int bi)
{
    buf[bi++] = (valn>>8)&255;
    buf[bi] = (valn)&255;
}

/******************************************************************************
 *  encode_message_header()
 *****************************************************************************/
int encode_message_header(unsigned char* mesgbuf, int mesg_len, int call_ref,
			  int call_ref_flag, int mi)
{
   /*
    * fill in protocol discriminator, spare, ref_#_size, ref_# 
    */
    mesgbuf[0] = uni;
    mesgbuf[1] = 0x03;
    encode_number(call_ref, mesgbuf+2, 3); 
    if (call_ref_flag)
        *(mesgbuf + 2) |= 0x80;
    else
        *(mesgbuf + 2) &= 0x7f;
   /*
    * fill in type, and length 
    */
    mesgbuf[5] = (u_char) ((u_int)messages_tbl[mi].code); 
    mesgbuf[6] = 0x80; 
    encode_short(mesg_len, mesgbuf, 7); 
}

/******************************************************************************
 *  encode_element_header
 *****************************************************************************/
void encode_element_header(element elem, u_char* mesg_buf)
{
    mesg_buf[0] = (u_char) elem->hdr.code;    
    mesg_buf[1] = 0x80;
    encode_short(elem->hdr.length, mesg_buf, 2);
}


/******************************************************************************
 *  encode_octet_group
 *****************************************************************************/
u_char* encode_octet_group(uni_message mesg, element elem, int oi, int ei)
/* mesg,elem = message and element being encoded
 * oi = octet group table index (within elem table) 
 * ei = element table index 
 */
{
    register int fi=0, vlen, shift;
    u_char *og_buf;             /* buffer to hold encoded octet group */
    int og_buf_ptr=CHAR_7;      /* ptr in og_buf,used for 7-bit-encoded og's */
    int bi = 0;                 /* index of og_buf */
    int og_length;              /* lenght of octet group in bits */
    int done=0;
    field fd;
    u_int *ival, temp;
    u_char ctemp;
    uni_address *cval;
    octet_group_desc *ogd = &(the_elements_tbl[ei].ogs[oi]);

    og_buf = (u_char *) allocate_memory (ogd->fixed_length);
    bzero(og_buf, ogd->fixed_length);
    og_length = ogd->fixed_length * ((ogd->encoded==ext) ? CHAR_7 : CHAR);
    
    /*---------------------------------------------------------------
     * encode an "address" field, i.e. > than 24 bits and return
     *--------------------------------------------------------------*/
    if (ogd->encoded == addr_coded) {
	if ((cval = (uni_address *) field_lookup_in_elem(elem, 
               ogd->internal_fields[fi][0], addr_coded, 0))) {
            bcopy(cval, og_buf, UNI_ADDR_LEN);
            bi += UNI_ADDR_LEN;
            return og_buf;
        } 
        else 
	{
	    printf("uni: encode_octet_group - didn't find address.\n");
	    deallocate_memory(og_buf);
            return 0;
        }
    }
    
    while (og_length && !done) {
        /*---------------------------------------------------------------
	 * encode an encoded field, i.e. with an extension bit
	 *--------------------------------------------------------------*/
        if (ogd->encoded == ext) {
            if (!(ival = (u_int *) field_lookup_in_elem(elem,
				ogd->internal_fields[fi][0], ext, 0))) {
		printf("uni: encode_octet_group - couldn't find ext field\n");
                return 0;
            }
            vlen = ogd->internal_fields[fi][1];
            if (vlen > CHAR_7) {      /* variable greater than 7 bits */
                temp = *ival; 
                while(vlen > 0) {
                    og_buf[bi] = 0;
                    if (vlen > 7) {
                        shift = ((vlen/7)-1)*7;
                        if (vlen % 7) {
                            shift += (vlen%7);
			}
                        og_buf[bi] = (temp >> shift) & 255;
                        bi++;
                    } else {
                        og_buf[bi] <<= (og_buf_ptr = 7 - vlen);
		    }
                    vlen -= 7;
                }
            } else {                /* variable less than 7 bits */
	        ctemp = (u_char)*ival; 
		shift = ((og_buf_ptr - vlen) <= 0) ? 0 : og_buf_ptr - vlen;
		og_buf[bi] |=  ctemp << shift;
		vlen -= (og_buf_ptr - shift);
		og_buf_ptr -= (og_buf_ptr - shift);
		if (og_buf_ptr <= 0) {
                    if (!(og_length - ogd->internal_fields[fi][1])) {
		        done = 1;
			og_buf[bi] |= 0x80;  /* end of og, set ext bit */
		    } else {
                        og_buf[bi] &= 0x7f;  /* continue, set ext bit 0 */
                    }
		    bi++;
		    og_buf[bi] = 0;
		    og_buf_ptr = 7;
		}
            
            }
              
        }
        /*---------------------------------------------------------------
        * else, encode what has to be a "flat" field
        *--------------------------------------------------------------*/
        else 
        {
            if (ival =(u_int *)field_lookup_in_elem(elem,
                                        ogd->internal_fields[fi][0], flat, 0)){
                encode_number((u_int)*ival, og_buf+bi, 
                                ogd->internal_fields[fi][1]/8);
                bi += ogd->internal_fields[fi][1] / 8;
                og_buf[bi] = 0;
            } else {
                return 0;
            }
        }
        og_length -= ogd->internal_fields[fi++][1]; 
    }
    return og_buf;
}

/******************************************************************************
 *  encode_number
 *****************************************************************************/
int encode_number(u_int num, u_char *buf, int field_len)
{
    register int i;

    for(i=0; i<field_len; i++) {
        buf[(field_len-1)-i] = (u_char)((num>>(i*8))&0xff);
    }
}


/******************************************************************************
 *  bb_low_layer_info_encoder()
 *****************************************************************************/
u_char* bb_low_layer_info_encoder(uni_message mesg, int mi, element elem, 
int ei, int oglist)
 
/* mi = index of message in message table */
/* elem = elem to encode */    
/* ei = index of element in the_element_tbl */
/* oglist = bit mask of default og's in elem */

{
    register int oi = 0;
    u_char *og_buf;
    u_char *elem_buf;                           /* element buffer */
    int ebi = 0;                                /* element buffer index */
    list q;
    int *t;
    octet_group og;
    octet_group_desc *ogs = the_elements_tbl[ei].ogs;
    
    elem_buf = (u_char*) allocate_memory (elem->hdr.length);
    /* Layer 1 parsing */
    if (field_lookup_in_elem(elem, layer_1_id, ext, 0)) {
        og_buf = encode_octet_group(mesg, elem, 0, ei);
        bcopy(og_buf, (elem_buf + ebi), ogs[0].fixed_length);
        ebi += ogs[0].fixed_length;
        deallocate_memory(og_buf);
    }
    /* Layer 2 parsing */
    if (field_lookup_in_elem(elem, layer_2_id, ext, 0)) {
        if (t = (int *)field_lookup_in_elem(elem, mode, flat, 0)) 
            og_buf = encode_octet_group(mesg, elem, oi=3, ei);
        else if (t = (int *)field_lookup_in_elem(elem, 
                                  user_spec_layer_2_proto_info, flat, 0)) 
            og_buf = encode_octet_group(mesg, elem, oi=2, ei);
        else
            og_buf = encode_octet_group(mesg, elem, oi=1, ei);
        bcopy(og_buf, (elem_buf + ebi), ogs[oi].fixed_length);
        ebi += ogs[oi].fixed_length;
        deallocate_memory(og_buf);
    }
    /* Layer 3 parsing */
    if (field_lookup_in_elem(elem, layer_3_id, ext, 0)) {
        if (t = (int *)field_lookup_in_elem(elem, mode, flat, 0)) 
            og_buf = encode_octet_group(mesg, elem, oi=10, ei);
        else if (t = (int *)field_lookup_in_elem(elem, 
                                  user_spec_layer_3_proto_info, flat, 0)) 
            og_buf = encode_octet_group(mesg, elem, oi=9, ei);
        else if (t = (int *)field_lookup_in_elem(elem, 
                                  init_proto_ident, flat, 0)) 
             og_buf = encode_octet_group(mesg, elem, oi=5, ei);
        else
             og_buf = encode_octet_group(mesg, elem, oi=4, ei);
        bcopy(og_buf, (elem_buf + ebi), ogs[oi].fixed_length);
        ebi += ogs[oi].fixed_length;
        deallocate_memory(og_buf);
    }
    /* SNAP parsing */
    if (field_lookup_in_elem(elem, snap_id, ext, 0)) {
        og_buf = encode_octet_group(mesg, elem, oi=6, ei); /* snap id */
        bcopy(og_buf, (elem_buf + ebi), ogs[oi].fixed_length);
        ebi += ogs[oi].fixed_length;
        deallocate_memory(og_buf);
        og_buf = encode_octet_group(mesg, elem, oi=7, ei); /* oui */
        bcopy(og_buf, (elem_buf + ebi), ogs[oi].fixed_length);
        ebi += ogs[oi].fixed_length;
        deallocate_memory(og_buf);
        og_buf = encode_octet_group(mesg, elem, oi=8, ei); /* pid */
        bcopy(og_buf, (elem_buf + ebi), ogs[oi].fixed_length);
        ebi += ogs[oi].fixed_length;
        deallocate_memory(og_buf);
    }
    return elem_buf;
}




