package Insteon_PLC;

use X10_Interface;
@Insteon_PLC::ISA = ('X10_Interface');

# -[ Events ]-----------------------------------------------------------------

my %events_txt = (
   0x00 => "PLC Init ",
   0x01 => "Insteon receive my message",
   0x02 => "Insteon message",
   0x03 => "Insteon message (hop dup)",
   0x04 => "ACK",
   0x05 => "NAK",
   0x06 => "Bad ID",
   0x07 => "Enroll",

   0x08 => "X10 Receive",
   0x09 => "X10 Extended Receive",
   0x0A => "Button Tap",
   0x0B => "Button Hold",
   0x0C => "Button Release",
   0x0D => "Tick",
   0x0E => "Alarm",
   0x0F => "Midnight",
   0x10 => "2 AM",
   0x11 => "RX_COM",
   0x12 => "SRX_COM",
   0x13 => "Daughter board",
   0x14 => "Load On",
   0x15 => "Load Off",

   0x32 => "Init DB clear",
   0x33 => "Init DB no clear",

   0x41 => "User pushed set button",
   0x42 => "User pushed set button plus single tap",
   0x43 => "Unlink",
   0x44 => "Multi-unlink",
   0x45 => "End link",
   0x46 => "Continue link",

   0x4C => "Unknown 4C", # Need to look at the Core App for this!

   0xFE => "Unknown FE",
   0xFF => "Unknown FF",

);

# -[ General ]----------------------------------------------------------------
use constant STX => 0x02;
use constant ETX => 0x03;
use constant ACK => 0x06;
use constant NAK => 0x15;

# -[ Commands ]---------------------------------------------------------------

my %serial_cmd = (
  0x40 => "DL",
  0x41 => "MSG_FIXED",
  0x42 => "UL",
  0x43 => "MSG_VAR",
  0x44 => "CHKSUM",
  0x45 => "EVENT",
  0x46 => "MASK",
  0x47 => "SIM",
  0x48 => "VERSION",
  0x49 => "DEBUG",
  0x4A => "RX10",
  0x4F => "RINSTEON",
);

# ----------------------------------------------------------------------------
                 # These tables are used in receiving data
my %x10_hc = qw( 6 A  e B  2 C  a D 10 D 14 B
                 1 E  9 F  5 G  d H 11 L 15 J
                 7 I  f J  3 K  b L 12 P
                 0 M  8 N  4 O  c P 13 H );

my %x10_uc = qw( 6 1  e 2  2 3  a 4 10 4 14 2
                 1 5  9 6  5 7  d 8 13 8
                 7 9  f A  3 B  b C 11 C 15 A
                 0 D  8 E  4 F  c G 12 G );
                 # I'm not sure what this is (next line) but I will leave it as is. NJC
                 # Yikes!  L and M are swapped!   If we fix it here, we also
                 # have to fix it elsewhere (maybe only in bin/mh, $f_code test)

my %x10_cc = qw( 2 J  3 K
                 4 L  5 M
                 1 O  0 P
                 6 *  7 Z
                 8 *  9 *
                10 PRESET_DIM1  a PRESET_DIM1
                11 PRESET_DIM2  b PRESET_DIM2
                12 *            c *
                13 STATUS_ON    d STATUS_ON
                14 STATUS_OFF   e STATUS_OFF
                15 STATUS       f STATUS );

# ----------------------------------------------------------------------------

### --------------------------------------------------------------------------
###

sub plc_dl {
    my ($str, $buf) = @_;
    &::print_log("plc_dl (@$buf)");

    my $l = @$buf;

    if($l < 7) {
	my @cmd = @$buf;
	print "Buf  short\n";
	@$buf = (); # Clear out the old buffer
	return @cmd;
    }

    my @cmd = pull(7, \@$buf);

    return ;
}

### --------------------------------------------------------------------------
###

sub plc_msg_fixed {
    my ($str, $buf) = @_;
    &::print_log("plc_msg_fixed (@$buf)");
# Var length
    
    my $l = @$buf;

    #min msg
    my $t = @$buf[2] + 3;

    if($l < $t) {
	my @cmd = @$buf;
	print "Buf  short\n";
	@$buf = (); # Clear out the old buffer
	return @cmd;
    }

    my @cmd = pull($t, \@$buf);

    return ;
}

### --------------------------------------------------------------------------
###

sub plc_ul {
    my ($str, $buf) = @_;
    &::print_log("plc_ul (@$buf)");
# Var length
    skip(\@$buf); # Phony try to remove an UL dump
}

### --------------------------------------------------------------------------
###

sub plc_msg_var {
    my ($str, $buf) = @_;
    &::print_log("plc_msg_var (@$buf)");

    my $l = @$buf;
    my $x = 0;

    # Read until we get to ETX, don't worry about short data
    while( ($x < $l) && (${$buf}[$x] != ETX)) { $x++; }

    my @cmd = @$buf[0 .. $x-1]; # Uhm now what do I do with it?

    @$buf = @$buf[$x .. $#$buf];  #can't use "4..-1" cuz that's empty
}

### --------------------------------------------------------------------------
###

sub plc_chksum {
    my ($str, $buf) = @_;
    &::print_log("plc_chksum (@$buf)");

    my $l = @$buf;

    if($l < 9) {
	my @cmd = @$buf;
	print "Buf  short\n";
	@$buf = (); # Clear out the old buffer
	return @cmd;
    }
    
    my @cmd = pull(9, \@$buf);

    return ;
}

### --------------------------------------------------------------------------
### Length 3

my @event_queue = (); # An array of queue objects

sub plc_event($) {
    my ($str, $buf) = @_;
    #&::print_log("plc_event (@$buf)");

    my $l = @$buf;

    if($l < 3) {
	my @cmd = @$buf;
	print "Buf  short\n";
	@$buf = (); # Clear out the old buffer
	return @cmd;
    }

    my @cmd = pull(3, \@$buf);

    my $i = $cmd[2];
    my $s = $events_txt{$i};

    my $t = POSIX::strftime("\%m/\%e/\%C \%H:\%M:\%S", localtime);

    #my $e = new Insteon_Event($t, $s, $i);
    my $e = new Event_queue($t, $s, $i);

    if($s) {
	#&::print_log("PLU: Event: $s (@cmd) " . $e->print());
	
	push(@event_queue, $e); # Add new
	shift(@event_queue);    # remove old
    } else {
	&::print_log("PLU: Event: unknown ($i) (@cmd)");
    }

    return ;
}

### --------------------------------------------------------------------------
###

sub plc_mask {
    my ($str, $buf) = @_;
    &::print_log("plc_mask (@$buf)");

    my $l = @$buf;

    if($l < 7) {
	my @cmd = @$buf;
	print "Buf  short\n";
	@$buf = (); # Clear out the old buffer
	return @cmd;
    }

    my @cmd = pull(7, \@$buf);

    return ;
}

### --------------------------------------------------------------------------
###

sub plc_sim {
    my ($str, $buf) = @_;
    &::print_log("plc_sim (@$buf)");

    my $l = @$buf;

    if($l < 5) {
	my @cmd = @$buf;
	print "Buf  short\n";
	@$buf = (); # Clear out the old buffer
	return @cmd;
    }
    my @cmd = pull(5, \@$buf);

    return ;
}

### --------------------------------------------------------------------------
###

sub plc_ver {
    my ($str, $buf) = @_;
    &::print_log("plc_ver (@$buf)");

    my $l = @$buf;

    if($l < 9) {
	my @cmd = @$buf;
	print "Buf  short\n";
	@$buf = (); # Clear out the old buffer
	return @cmd;
    }

    my @cmd = pull(9, \@$buf);

    return ;
}

### --------------------------------------------------------------------------
###

sub plc_debug {
    my ($str, $buf) = @_;
    &::print_log("plc_debug (@$buf)");

    my $l = @$buf;

    if($l < 4) {
	my @cmd = @$buf;
	print "Buf  short\n";
	@$buf = (); # Clear out the old buffer
	return @cmd;
    }

    my @cmd = pull(4, \@$buf);

    return ;
}

### --------------------------------------------------------------------------
###

use constant X10_ADDR => 0;
use constant X10_CMD  => 1;

my %pstr;

sub plc_rx10 {
    my ($vstr, $buf) = @_;

    my $l = @$buf;

    if($l < 4) {
	my @cmd = @$buf;
	print "Buf  short\n";
	@$buf = (); # Clear out the old buffer
	return @cmd;
    }

    ##
    ## X10 handling
    ##
    my @cmd = pull(4, \@$buf);

    my $hc = ($cmd[3] & 0xF0) >> 4; # Hi nibble
    my $uc = ($cmd[3] & 0x0F);      # Lo nibble

    my $str = $x10_hc{$hc}; # HC:UC

    if($cmd[2] == X10_ADDR) {
	$pstr{$vstr} = $pstr{$vstr} . $str . $x10_uc{$uc}; # HC:UC
	#&::print_log("$str $pstr{$vstr} Insteon_$vstr"); # Hard coded strings not good
    } elsif($cmd[2] == X10_CMD) {
	$str = $pstr{$vstr} . $str . $x10_cc{$uc}; # HC:CC
	#&::print_log("X$str $pstr{$vstr} Insteon_$vstr"); # Hard coded strings not good
	&::process_serial_data('X' . $str, $pstr, 'Insteon_$vstr'); # Hard coded strings not good
	$pstr{$vstr} = ''; # Once we get the command portion we're happy (I think, dim?)
    } else {
	#$str = "??? $cmd[2]"; # Something else, extended?
	&::print_log("$vstr: X10: cmd[2] =  " . $cmd[2] . " (@cmd) (X$str/$pstr{$vstr})");
    }

    return ;
}

### --------------------------------------------------------------------------
### Insteon pkt

use constant FLAG     => 9;
use constant STANDARD => 0x10;

sub plc_rinsteon($) {
    my ($str, $buf) = @_;
    &::print_log("plc_rinsteon (@$buf)");

    my $l = @$buf;
    my @cmd;

    if($l < 12) {
	@cmd = @$buf;
	print "Buf  short 1\n";
	@$buf = (); # Clear out the old buffer
	return @cmd;
    }

    ##

    my $event = $events_txt{$$buf[2]}; # 0 - 7

    my $src  = "$$buf[3].$$buf[4].$$buf[5]";
    my $dst  = "$$buf[6].$$buf[7].$$buf[8]";

    my $flag = $$buf[FLAG];

    my $cmd1 = $$buf[10];
    my $cmd2 = $$buf[11];

    &::print_log("Event: '$event', src = $src, dst = $dst, Cmd = $cmd1/$cmd2");

    if($$buf[FLAG] || STANDARD) {
	# 9 byte cmd ( 9+3=12 0 - 11)
	&::print_log("Event: ($flag) Standard pkt");
	pull(12, \@$buf);
    } else {
	#
	if($l < 26) {
	    print "Buf short 2\n";
	    @cmd = @$buf;
	    @$buf = (); # Clear out the old buffer
	    return @cmd;
	}

	# 23 byte cmd ( 23+3=26 0 - 25)
	&::print_log("Event: ($flag) Extended pkt (" . @$buf[12 .. 25] . ")");
	pull(26, \@$buf);
    }

    return ;
}

### --------------------------------------------------------------------------
###

sub plc_unknown {
    my ($str, $buf) = @_;

    &::print_log("PLU: plc_unknown (@$buf)");

    my $l = @$buf;

    if($l < 2) {
	my @cmd = @$buf;
	print "Buf  short\n";
	@$buf = (); # Clear out the old buffer
	return @cmd;
    }

    if(defined($serial_cmd{$$buf[1]})) {
	&::print_log("$serial_cmd{$$buf[1]}" );
    } else {
	&::print_log("$$buf[1] not defined");
    }

    my @cmd = &skip(\@$buf);

    return @cmd;
}

### --------------------------------------------------------------------------
###

sub skip($) {
    my ($arr) = @_;

    my $l = @$arr;
    my $x = 0;

    while( ($x < $l) && (${$arr}[$x] != STX)) { $x++; }
    
    my @d = @$arr[0 .. $x-1];
    @$arr = @$arr[$x .. $#$arr];  #can't use "4..-1" cuz that's empty

    return @d;
}

### --------------------------------------------------------------------------
###

sub pull($$) {
    my ($l, $arr) = @_;

    my @d = @$arr[0 .. $l-1];
    @$arr = @$arr[$l .. $#$arr];  #can't use "4..-1" cuz that's empty

    return @d;
}

### --------------------------------------------------------------------------
### Take in the data from the USB and remove it's frame. A frame is 8 bytes
### long, the data coming in is usually 8 or more bytes long. The first byte
### of a frame is the data lenght, then follows the data (up to N length). The
### rest of the frame (7 - N) is padding. The data length is the low nibble of
### the first byte of a frame. The last bit is supposed to be the CTS bit.
###
### A b C .........
### 1 2 0 0 0 0 0 0 2 69 8 0 0 0 0 0 
### \=============/
###  8 byte frame
### A = 1 byte of data (length)
### B = data (of 1 byte)
### C = padding (7 - 1 = 6 bytes of padding)
###
### The above example translates to:
###
### 2 69 8
###
sub deframe {
    my ($self, @buf) = @_;
    my @d;
    my ($c, $i, $j, $l, $t);

    # 2 69 8 2 74 0 102

    # The current version of the usb iplc driver is that it sends 8
    # bytes at a time (a frame) but there may be many frames in the
    # buffer. The first byte is the byte count (low nibble).  the next
    # n bytes are the data. If n is less than 7 bytes then the rest is
    # null (0) padded
    $l = @buf;
    @d = () ;

    for($i = 0; $i < $l; $i++) {
	$c = shift(@buf);	# Grab the number of bytes to read

	for($j = 0; $j < 7; $j++) {
	    if($j < $c) {
		$t = shift(@buf); # Grab the string
		push(@d, $t);
	    } else {
		shift(@buf);	# dump the rest (null padding)
	    }
	}
    }

    return @d;
}

### --------------------------------------------------------------------------
### Parse PLU Functions

my %plu_func = (
#
   0x40 => \&plc_dl,
   0x41 => \&plc_msg_fixed,
   0x42 => \&plc_ul,
   0x43 => \&plc_msg_var,
   0x44 => \&plc_chksum,
   0x45 => \&plc_event,
   0x46 => \&plc_mask,
   0x47 => \&plc_sim,
   0x48 => \&plc_ver,
   0x49 => \&plc_debug,
   0x4A => \&plc_rx10,
#
   0x4F => \&plc_rinsteon,
);

### --------------------------------------------------------------------------
### Parse the serial commands

#my @oldd; # Storage I need for persistant data

sub parse_scmds {
    my ($self, $str, @buf) = @_;

    my ($l, @cmd);

    # Here's where I need to push the oldd onto the current d
    if(@{$$self{command_stack}}) {
	unshift(@buf, @{$$self{command_stack}});
    }

    #my $cnt = 0;
    my $oldl = 0;

    ## -----------------------------------------------------------------------
    ## What I need to do is to string of the leading data (starting with the
    ## STX) and hand that to the appropriate function pointer. But what about
    ## cmds that are short data, what about cmds that have too much data?
    ## Nope looks like we need to hand the whole buffer to the routing and let
    ## it return the unused portions (because msg_var could have a STX inside
    ## of it).
    ##
    ## Valid data will always start with STX but data that starts with STX
    ## isn't always valid. Also 'valid' data may also be short a few bytes
    ## and we'll need to save that data for the next loop assuming that there
    ## is no more data to be processed in this loop.
    ## -----------------------------------------------------------------------

    while(@buf) {
	&::print_log("$str:B:buf  (@buf)");
	&::print_log("$str:B:oldd (@oldd)");
	@{$$self{command_stack}} = (); # clean out the old buffer
	$l = @buf;

	if($oldl == $l) {
	    &::print_log("$str: Can't reduce data (@buf)");
	    return ; # Hmm, we didn't reduce it's size
	}

	if($l < 3) {
	    # print "%str: too small (@d)\n";
	    @{$$self{command_stack}} = @buf;
	    return ;
	}

	## Processing done here!

	if($buf[0] == STX) {
	    if(defined($plu_func{@buf[1]})) {
		# Trim the @buf and return the cmd if it's not valid (ie short)
		@cmd = $plu_func{@buf[1]}($str, \@buf); # Execute function, parse @d and rtn what's left
	    } else {
		print "Not Defined\n";
		@cmd = plc_unknown(\@buf); # clean up the array @d
	    }
	} else {
	    print "Not STX (@buf)\n";
	    skip(\@buf); # skip invalid data
	}
	$oldl = $l; #

	# I need to keep a copy of the left over cmd for later, but only if
	# there is not more data to process
	if(@cmd) {
	    @{$$self{command_stack}} = @cmd;
	}
    }
}


return 1;
__END__
