package ebpacket;

use Data::Dumper;

use ebflags;
use ebdebug;
use eberror;

# internal flags

$FLAG_HAMMING = 1;
$FLAG_INCLUDE_SRC = 2;
$FLAG_INCLUDE_CRC = 4;
$FLAG_REQUEST = 8;
$FLAG_CRCERROR = 16;
$FLAG_HAS_HEADER = 32;
$FLAG_IS_PACKET = 64;
$FLAG_SIGNED = 128;


# lookup table for lens

@FRAMELEN = (
	1,   # 0
	2,   # 1
	4,   # 2
	8,   # 3
	16,  # 4
	32,  # 5
	64,  # 6
	128, # 7
	256, # 8
	132, # 9
	264, # 10
	0,   # 11
	0,   # 12
	0,   # 13
	0,   # 14
	0);  # 15

@HAMMINGLEN = (
	3,   # 0
	3,   # 1
	6,   # 2
	12,  # 3
	24,  # 4
	48,  # 5
	96,  # 6
	192, # 7
	384, # 8
	198, # 9
	396, # 10
	0,   # 11
	0,   # 12
	0,   # 13
	0,   # 14
	0,   # 15
	);

@CRCS = (1, # 0
	1,  # 1
	1,  # 2
	1,  # 3
	2,  # 4
	2,  # 5
	2,  # 6
	2,  # 7
	2,  # 8
	2,  # 9
	2,  # 10
	2,  # 11
	0,  # 12
	0,  # 13
	0,
	0); # 15

sub new {
	my $self = {};
	$self->{debug} = 0xFFFF;
	$self->{sequence_number} = 0;
	$self->{current_position} = 0;
	$self->{subpacket} = 0;
	$self->{flags} = 0;
	$self->{pbuf} = ();
	$self->{size} = 0;
	$self->{payloadsize} = 0;
	$self->{timestamp} = time();
	$self->{src_addr} = 0;
	$self->{dst_addr} = 0;
	$self->{cmd} = 0;
	$self->{status} = 0;

	bless($self);
	return($self);
}

sub debug {
	my $self = shift;
	my $debug_flag = shift;
	my $debug_in;

#	print "Debug ************* $debug_flag " . $self->{debug} . "\n";	
	if(!($self->{debug} & $debug_flag)) {
		return;
	}
	
	while(defined($debug_in = shift)) {
		print "$debug_in\n";
	}
}
sub resize {
	my $self = shift;
	$self->{size} = @{$self->{pbuf}};
}

sub get_len {
	my $self = shift;
	my $len = (($self->{pbuf}[1] & 0xF0) >> 4);

#	$self->{payloadzie} = $FRAMELEN[$len] - 2 - $self->number_of_crcs(); # remove if no one is using

#	$self->{payloadsize} = $len - 2;

#	$len = $self->hl($len) if ($self->is_hamming());

	return $len;
}

sub frame_length {
	my $self = shift;
	my $len = $self->get_len();

	if($self->is_hamming()) {
		return($self->hl($FRAMELEN[$len]));
	} else {
		return($FRAMELEN[$len]);
	}
}

sub frame_length_wlt {
	my $self = shift;
	my $len = $self->get_len();

	print "frame_length_wlt: " . ($FRAMELEN[$len] + 2) . " (enclen: $len)\n" if($self->{debug} & $ebdebug::DEBUG_LEN);
	
	return($FRAMELEN[$len] + 2); # voodoo coding at it's best
}

sub number_of_crcs {
	my $self = shift;
	my $len = $self->get_len();

	if (!($self->get_type() & $ebflags::FLAG_CRC_INCLUDED)) {
		print "number_of_crcs: flag not set (" . $self->get_type() . "), returning 0\n" if($self->{debug} & ($ebdebug::DEBUG_VERBOSE | $ebdebug::DEBUG_CRC));
		return(0);
	}

	return($CRCS[$len]);
}

sub get_type {
	my $self = shift;
	my $type = ($self->{pbuf}[1] & 0x0F);
	
	return $type;
}

sub reset {
	my $self = shift;
	$self->{sequence_number} = 0;
	$self->{current_position} = 0;
	$self->{subpacket} = 0;
	$self->{flags} = 0;
	$self->{pbuf} = ();
	$self->{size} = 0;
	$self->{payloadsize} = 0;
	$self->{timestamp} = time();
	$self->{src_addr} = 0;
	$self->{dst_addr} = 0;
	$self->{cmd} = 0;
	$self->{status} = 0;
}

sub add_subpacket {
	# add these bytes to the packet
	my $self = shift;
	my $byte;
	
	return -1 if($self->{flags} & $FLAG_IS_PACKET);

	$self->make_header() if(!($self->{flags} & $FLAG_HAS_HEADER));
	$self->{subpacket}++;

	while(defined($byte = shift)) {
		push(@{$self->{pbuf}},$byte);
		$self->{size}++;
	}
	
	return $self->{subpacket};
}

sub make_header {
	# make sure that there's a header
	my $self = shift;
	my $pos;
	
	$self->{pbuf}[0] = 42;

	if($self->{flags} & $FLAG_INCLUDE_SRC) {
		$self->{pbuf}[3] = $self->{src_addr};
		$self->{pbuf}[2] = $self->{dst_addr}; 
		$pos = 4;
	} else {
		$self->{pbuf}[2] = $self->{dst_addr};
		$pos = 3;
	}

	$self->{current_position} = $pos if($self->{current_position} < $pos); # todo: delete this if not used
	$self->{flags} |= $FLAG_HAS_HEADER;

	return 1;
}

	

	
sub make_packet {
	# turn a bunch of data in the outgoing packet buffer into a actual packet
	my $self = shift;
	my $flags = shift | undef;

	$self->set_flags($flags) if($flags);
	
	

	$self->set_type();
	$self->set_len();	 # will optimize the current buffer
	$self->space_for_crcs();
	$self->set_len();	 # will optimize the current buffer (with CRCs)
	$self->pad();
				# it is a very good idea to have these be the last things that run
	$self->crc_sign();	 # signs a CRC	 
	$self->_encode() if($self->{flags} & $FLAG_HAMMING);
	$self->{flags} |= $FLAG_IS_PACKET;
}

sub set_buffer {
	my $self = shift;

	while((defined($in = shift))) {
		push(@{$self->{pbuf}},$in);
	}

	$self->resize();
}

sub set_addresses {
	my $self = shift;
	$self->{src_addr} = shift;
	$self->{dst_addr} = shift;
}

sub set_type {
	my $self = shift;
	my $oflags = shift; # override flags - these will be set in the resulting packet
	my $vflags = ($self->{flags} & 0x0F) | $oflags;

	$self->{flags} |= $oflags;
	$self->{pbuf}[1] = ($vflags & 0x0f) | ($self->{pbuf}[1] & 0xF0); # preseve len

	print "Setting type to $vflags (" . $self->flags_text($vflags) . ")\n" if($self->{debug} & $ebdebug::DEBUG_FLAGS);
}

sub set_flag {
	my $self = shift;
	my $flag = shift;
	$self->{flags} |= $flag;
}

sub set_flags {
	my $self = shift;
	my $flag = shift;
	my $preserve = $flag & 0xF0;
	
	$self->{flags} = $flag;
	$self->{flags} |= $preserve;

	print "Setting flags to " . $self->{flags} . " ("   . $self->flags_text($self->{flags}) . ")\n" if($self->{debug} & $ebdebug::DEBUG_FLAGS);
	return($self->{flags});
}

sub clear_flag {
	my $self = shift;
	my $flag = shift;

	$self->{flag} &= ~$flag;
}

sub set_len {
	my $self = shift;
	my $olen = shift || $self->{size} || @{$self->{pbuf}};
	my $mylen = $self->optimize_len($olen);
	my $yourlen;

	die "illegal length" if($mylen < 0); # todo: something a little more constructive?

	$yourlen = $mylen << 4;

	$self->{pbuf}[1] = ($self->{pbuf}[1] & 0x0F) | $yourlen;
	print "Input len $olen. Setting opt len $mylen => " . $FRAMELEN[$mylen] . " [<4 $yourlen]\n" if($self->{debug} & $ebdebug::DEBUG_LEN);

}	

sub optimize_len {
	my $self = shift;
	my $reqlen = shift;
	my $i;


	for($i=0;$i<16;$i++) {
		# if we fit, it's ours
		if($FRAMELEN[$i] >= $reqlen) {
			return($i);
		};
	}
	
	return -1;
}
	

	


@hamming = 
(
  0x00,  0x03,  0x05,  0x06,  0x06,  0x05,  0x03,  0x00,
  0x07,  0x04,  0x02,  0x01,  0x01,  0x02,  0x04,  0x07,
  0x09,  0x0A,  0x0C,  0x0F,  0x0F,  0x0C,  0x0A,  0x09,
  0x0E,  0x0D,  0x0B,  0x08,  0x08,  0x0B,  0x0D,  0x0E,
  0x0A,  0x09,  0x0F,  0x0C,  0x0C,  0x0F,  0x09,  0x0A,
  0x0D,  0x0E,  0x08,  0x0B,  0x0B,  0x08,  0x0E,  0x0D,
  0x03,  0x00,  0x06,  0x05,  0x05,  0x06,  0x00,  0x03,
  0x04,  0x07,  0x01,  0x02,  0x02,  0x01,  0x07,  0x04,
  0x0B,  0x08,  0x0E,  0x0D,  0x0D,  0x0E,  0x08,  0x0B,
  0x0C,  0x0F,  0x09,  0x0A,  0x0A,  0x09,  0x0F,  0x0C,
  0x02,  0x01,  0x07,  0x04,  0x04,  0x07,  0x01,  0x02,
  0x05,  0x06,  0x00,  0x03,  0x03,  0x00,  0x06,  0x05,
  0x01,  0x02,  0x04,  0x07,  0x07,  0x04,  0x02,  0x01,
  0x06,  0x05,  0x03,  0x00,  0x00,  0x03,  0x05,  0x06,
  0x08,  0x0B,  0x0D,  0x0E,  0x0E,  0x0D,  0x0B,  0x08,
  0x0F,  0x0C,  0x0A,  0x09,  0x09,  0x0A,  0x0C,  0x0F,
  0x0C,  0x0F,  0x09,  0x0A,  0x0A,  0x09,  0x0F,  0x0C,
  0x0B,  0x08,  0x0E,  0x0D,  0x0D,  0x0E,  0x08,  0x0B,
  0x05,  0x06,  0x00,  0x03,  0x03,  0x00,  0x06,  0x05,
  0x02,  0x01,  0x07,  0x04,  0x04,  0x07,  0x01,  0x02,
  0x06,  0x05,  0x03,  0x00,  0x00,  0x03,  0x05,  0x06,
  0x01,  0x02,  0x04,  0x07,  0x07,  0x04,  0x02,  0x01,
  0x0F,  0x0C,  0x0A,  0x09,  0x09,  0x0A,  0x0C,  0x0F,
  0x08,  0x0B,  0x0D,  0x0E,  0x0E,  0x0D,  0x0B,  0x08,
  0x07,  0x04,  0x02,  0x01,  0x01,  0x02,  0x04,  0x07,
  0x00,  0x03,  0x05,  0x06,  0x06,  0x05,  0x03,  0x00,
  0x0E,  0x0D,  0x0B,  0x08,  0x08,  0x0B,  0x0D,  0x0E,
  0x09,  0x0A,  0x0C,  0x0F,  0x0F,  0x0C,  0x0A,  0x09,
  0x0D,  0x0E,  0x08,  0x0B,  0x0B,  0x08,  0x0E,  0x0D,
  0x0A,  0x09,  0x0F,  0x0C,  0x0C,  0x0F,  0x09,  0x0A,
  0x04,  0x07,  0x01,  0x02,  0x02,  0x01,  0x07,  0x04,
  0x03,  0x00,  0x06,  0x05,  0x05,  0x06,  0x00,  0x03,
);


@corrections = 
(
  0x00,   #* 0 result means no error  */
  0x00,   #*  C1 is a Hamming bit, not a data bit */
  0x00,   #*  C2 is a Hamming bit, not a data bit */
  0x01,   #*  C3 is data bit D0                   */
  0x00,   #*  C4 is a Hamming bit, not a data bit */
  0x02,   #*  C5 is data bit D1                   */
  0x04,   #*  C6 is data bit D2                   */
  0x08,   #*  C7 is data bit D3                   */
  0x00,   #*  C8 is a Hamming bit, not a data bit */
  0x10,   #*  C9 is data bit D4                   */
  0x20,   #* C10 is data bit D5                   */
  0x40,   #* C11 is data bit D6                   */
  0x80,   #* C12 is data bit D7                   */
);

sub hl {
	my $self = shift;
	my $len = shift;

	return 3 if ($len <= 3);

	return ($len + (int($len / 2)));
}

sub is_hamming {
	my $self = shift;
	my $flags = $self->get_type();
	
	return 1 if($flags & $ebflags::FLAG_HAMMING);
	return 0;
}

sub is_crc {
	my $self = shift;
	my $flags = $self->get_type();
	
	return 1 if($flags & $ebflags::FLAG_CRC_INCLUDED);
	return 0;
}


sub _encode {
	my $self = shift;

	$bufsize = @{$self->{pbuf}};
	my $a, $b, $c, @int;

	@int = @{$self->{pbuf}};

	$self->{pbuf} = ();

	if ($bufsize % 2) {
		print "Error: Unexpected bufsize: $bufsize\n" if($self->{debug} & $ebdebug::DEBUG_HAMMING);
		return $eberror::ERROR_BAD_SIZE;
	}

	push(@{$self->{pbuf}},shift(@int));
	push(@{$self->{pbuf}},shift(@int));

	while((defined($a = shift(@int))) && (defined($b = shift(@int)))) {
		push(@{$self->{pbuf}}, $a);
		push(@{$self->{pbuf}}, $b);
		$c = (($hamming[$a] << 4) + $hamming[$b]);

		push(@{$self->{pbuf}}, $c);
#		print "A: $a B: $b C: $c\n";
	}

	$self->{flags} |= $FLAG_HAMMING;
	$self->resize();
}

sub _decode {
	my $self = shift;
	my $bufsize = @{$self->{pbuf}};
	my $a, $b, $c, @int;
	my $mod_ham, $ham;

	$self->debug($ebdebug::DEBUG_CALLS, "_decode called");

	return $eberror::ERROR_BAD_SIZE if (($bufsize - 2) % 3);

	@int = @{$self->{pbuf}};
	$self->{pbuf} = ();

	push(@{$self->{pbuf}},shift(@int));
	push(@{$self->{pbuf}},shift(@int));


	while((defined($a = shift(@int))) && (defined($b = shift(@int))) && (defined($c = shift(@int)))) {
       	 	$mod_ham = $hamming[$a];
       	 	$ham = ($c & 0xF0) >> 4;
       		push(@{$self->{pbuf}}, ($a ^ $corrections[$ham ^ $mod_ham]));

        	$mod_ham = $hamming[$b];
        	$ham = ($c & 0x0F);
        	push(@{$self->{pbuf}}, ($b ^ $corrections[$ham ^ $mod_ham]));
	}

	$self->clear_flag($FLAG_HAMMING);
	$self->resize();
	print "_decode done\n" if($self->{debug} & $ebdebug::DEBUG_CALLS);
	return 0;

}

sub flags_text {
	my $self = shift;
	my $out = "";
	my $flags = $self->{flags};

	$out .= "SIGN " if($flags & $FLAG_SIGNED);
	$out .= "CRCE " if($flags & $FLAG_CRCERROR);
	$out .= "HDR " if($flags & $FLAG_HAS_HEADER);
	$out .= "DONE " if($flags & $FLAG_IS_PACKET);

	$out .= "SRC " if($flags & $FLAG_INCLUDE_SRC);
	$out .= "HAM " if($flags & $FLAG_HAMMING);
	$out .= "REQ " if($flags & $FLAG_REQUEST);
	$out .= "CRC " if($flags & $FLAG_INCLUDE_CRC);


	return($out);
}

sub header_flags_text {
	my $self = shift;
	my $ltflags = $self->get_type();
	my $out = "";

	$out .= "HAM " if ($ltflags & $ebflags::FLAG_HAMMING);
	$out .= "SRC " if ($ltflags & $ebflags::FLAG_SRC_INCLUDED);
	$out .= "CRC " if ($ltflags & $ebflags::FLAG_CRC_INCLUDED);
	$out .= "REQ " if ($ltflags & $ebflags::FLAG_REQUEST);


	return($out);
}

sub dump {
        my $self = shift;
        my $ncols = shift || 8;
        my $data1, $data2;
        my $col, $addr;
        my $len = $self->{size};
	my $flen = $self->frame_length();
        my $seq = $self->{sequence_number};
	my $flags = $self->{flags};
	my $ftext = $self->flags_text();
	my $ltflags = $self->get_type();
	my $lftext = $self->header_flags_text();
	my $nocrcs = $self->number_of_crcs();
	my $flwlt = $self->frame_length_wlt();

        print "Packet Seq $seq Wirelen $len FrameLen $flen PktFlags $flags ($ftext) HdrFlags $ltflags ($lftext)\n";
	print "CRCS $nocrcs FrameLenWlt $flwlt\n";
        print "----\t";

        for($col=0;$col<$ncols;$col++) {
                # code shamelessly lifted right out of sim.c

                printf("%02X-%02x-",($col*2),($col*2)+1);
        }

        print "\n";

        for($addr=0;$addr < ($len / 2); $addr++) {
                if(!($addr % $ncols)) {
                        printf("\n%02x%02x|\t",(($addr*2) & 0xFF00), (($addr*2) & 0x00FF));
                }

                $data1 = $self->{pbuf}[$addr*2];
                $data2 = $self->{pbuf}[($addr*2) + 1];

                printf("%02X %02x ",$data1,$data2);
        }

        print "\n";


}

sub space_for_crcs {
	# this is where we get tricky
	# the problem is that the crcs may push us over a packet boundry!
	# hence, the algorythm is, if we don't need any, return 0
	# if we need one, push one
	# if we need two, push two but check to see if we've already pushed one first

	my $self = shift;
	my $pushed = 0;

	if($self->number_of_crcs() == 0) {
		print "number_of_crcs() == 0 in space_for_crcs\n" if($self->{debug} & $ebdebug::DEBUG_LEN);
		return;
	}
		
	if($self->number_of_crcs() == 1) {
		print "number_of_crcs() == 1 in space_for_crcs\n" if($self->{debug} & $ebdebug::DEBUG_LEN);
		push(@{$self->{pbuf}},0);
		$pushed++;
	}

	if($self->number_of_crcs() == 2) {
		print "number_of_crcs() == 2 in space_for_crcs\n" if($self->{debug} & $ebdebug::DEBUG_LEN);
		push(@{$self->{pbuf}},0);
		push(@{$self->{pbuf}},0) if(!$pushed);
	}

	$self->resize();
}

sub pad {
	# ensure that packet matches wire size
	
	my $self = shift;
	my $i;
	my $short;

	if(($short = $self->short()) > 0) {
		print "Pad() Short: $short\n" if($self->{debug} & $ebdebug::DEBUG_LEN);
		for($i=0;$i<$short;$i++) {
			push(@{$self->{pbuf}},0);
		}
	}

}

sub trim {
	my $self = shift;
	my $i;
	my $long = @{$self->{pbuf}} - $self->frame_length_wlt(); 
	print "long: buffer_length $buffer_length long: $long\n" if($self->{debug} & $ebdebug::DEBUG_LEN);

	for($i=0;$i<$long;$i++) {
		pop(@{$self->{pbuf}});
	}
}

sub short {
	# return the number of bytes I am short of a load
	
	my $self = shift;
	my $buffer_length = @{$self->{pbuf}};

	
	print "Short: buffer_length $buffer_length\n" if($self->{debug} & $ebdebug::DEBUG_LEN);

	my $short = ($self->frame_length_wlt() - $self->number_of_crcs()) - $buffer_length;

	return($short);
}

sub crc_base {
	my $self = shift;

	# return the first byte of the CRC of this packet

	$self->{crcbase} = $self->frame_length_wlt() - $self->number_of_crcs();
	print "CRC base is " . $self->{crcbase} . "\n" if ($self->{debug} & $ebdebug::DEBUG_CRC);
	return($self->{crcbase});
}

sub crc_sign {
	# sign a packet with a CRC
	my $self = shift;
	my $buffer_length = @{$self->{pbuf}};
	my $crcbase = $self->crc_base();
	my $bytes_to_strip = $buffer_length - $crcbase;
	my $crc16;
	my $i;

	$self->pad();

	my @workbuf = @{$self->{pbuf}};
	my $discard;

	if($bytes_to_strip) {
		for($i=0;$i<$bytes_to_strip;$i++) {
			$discard = pop(@workbuf); # discard no longer needed crc information
			print "Discarding $discard\n" if ($self->{debug} & $ebdebug::DEBUG_CRC);
		}
	} 

	$crc16 = $self->crc_16(@workbuf);
	my $hibyte = ($crc16 & 0xFF00) >> 8;
	my $lobyte = ($crc16 & 0x00FF);

	printf("crc: %02X %02x\n",$hibyte,$lobyte) if($self->{debug} & $ebdebug::DEBUG_CRC);

	if($self->number_of_crcs() == 1) {
		$self->{pbuf}[$crcbase] = $lobyte;
		$self->{flags} |= $FLAG_SIGNED;

		printf("crc: %02X %02x - 1 in use\n",$hibyte,$lobyte) if($self->{debug} & $ebdebug::DEBUG_CRC);

	} elsif ($self->number_of_crcs() == 2) {
		$self->{pbuf}[$crcbase+1] = $hibyte;
		$self->{pbuf}[$crcbase] = $lobyte;
		$self->{flags} |= $FLAG_SIGNED;

		printf("crc: %02X %02x - 2 in use\n",$hibyte,$lobyte) if($self->{debug} & $ebdebug::DEBUG_CRC);
	} else {
		# don't do anything because zero crc packet
		printf("crc: %02X %02x - 0 in use\n",$hibyte,$lobyte) if($self->{debug} & $ebdebug::DEBUG_CRC);
	}

	$self->resize();
}

sub crc_check {
	# check the CRC on the current packet
	# returns 0 if the CRC is incorrect
	# or 1 if the CRC is correct
	
	my $self = shift;
	my @workarray = @{$self->{pbuf}};
	my $crcbase = $self->frame_length_wlt() - $self->number_of_crcs();

	my $i;

	for($i=0;$i<=$self->number_of_crcs();$i++) {
		$discard = pop(@workarray); # strip off the existing CRCs
		print "$i: Discarding $discard\n";
	}

	my $crc16 = $self->crc_16(@workarray);


	my $lobyte = $crc16 & 0x00FF;
	my $hibyte = ($crc16 & 0xFF00) >> 8;

	printf( "crc: %x lobyte: %x hibyte: %x\n",$crc16,$lobyte,$hibyte) if($self->{debug} & $ebdebug::DEBUG_CRC);
	printf( "crcbase: %d", $crcbase) if($self->{debug} & $ebdebug::DEBUG_CRC);

	if($self->number_of_crcs() == 1) {
		if($lobyte != $self->{pbuf}[$crcbase]) {
			$self->{flags} |= $FLAG_CRCERROR;
			return $eberror::ERROR_BAD_CRC;
		}
		$self->clear_flag($FLAG_CRCERROR);
	} elsif($self->number_of_crcs() == 2) {
		if(($lobyte != $self->{pbuf}[$crcbase]) || ($hibyte != $self->{pbuf}[$crcbase+1])) {
			$self->{flags} |= $FLAG_CRCERROR;
			return $eberror::ERROR_BAD_CRC;
		}
		$self->clear_flag($FLAG_CRCERROR);
	} else {
		return 0;
		$self->clear_flag($FLAG_CRCERROR);
	}

	# can't get here
}


sub crc_16 {
	my $self = shift;
	my $crcval, $n, $q, $c, $t1, $t2;
	$crcval = 0;

	shift;
	shift; # discard wakeup and type/len

	while((defined($c = shift))) {
		$t1 = ($crcval ^ $c);
		$q = $t1 & 0x000f;

		$t1 = ($crcval >> 4);
		$t2 = $q * 0x1081;
		$crcval = $t1 ^ $t2;

		$t1 = ($c >> 4);
		$t2 = ($crcval ^ $t1);
		$q = ($t2 & 0x000f);

		$t1 = ($crcval >> 4);
		$t2 = $q * 0x1081;
		$crcval = $t1 ^ $t2;
	}

	return $crcval;

}

sub decode {
	my $self = shift;

	$self->debug($ebdebug::DEBUG_CALLS,"decode called");

	# decode a packet back to original format
	
	$self->_decode() if($self->get_type() & $ebflags::FLAG_HAMMING);

	$self->dump();

	return $eberror::ERROR_BAD_CRC if(!$self->crc_check());

	$self->debug($ebflags::DEBUG_CALLS,"decode done");
	
	return(0);
}


	
1;
