#!/usr/bin/perl

use Time::HiRes qw( usleep );
use IO::Socket;
use IO::Interface qw(:flags);
use strict;
use Switch;
use Color::Rgb;
use List::Util qw( min max );

if((@ARGV < 2) || ($ARGV[0] eq "help") || ($ARGV[1] eq "help")) {
  usage();
  exit;
}

my $host = $ARGV[0];
shift;

my $verbose = 0;
if(@ARGV > 1) {
  if($ARGV[0] eq "verbose") {
    $verbose = 2;
    shift;
  } elsif ($ARGV[0] eq "status") {
    $verbose = 1;
    shift;
  }
}
# printf("verbose: %d\n", $verbose);

my $what = $ARGV[0];
shift;

my $cur_red = -1;
my $cur_green = -1;
my $cur_blue = -1;
my $cur_mode = -1;
my $cur_speed = -1;
my $cur_running = -1;
my $cur_onoff = -1;
my $cur_user = -1;

my $new_red = -1;
my $new_green = -1;
my $new_blue = -1;
my $new_mode = -1;
my $new_speed = -1;
my $new_running = -1;
my $new_onoff = -1;
my $new_user = -1;

my $on = sprintf("%c%c%c", 0xCC, 0x23, 0x33);
my $off = sprintf("%c%c%c", 0xCC, 0x24, 0x33);
my $stop = sprintf("%c%c%c", 0xCC, 0x20, 0x33);
my $run = sprintf("%c%c%c", 0xCC, 0x21, 0x33);
my $init = sprintf("%c%c%c", 0xEF, 0x01, 0x77);
my $mode_offset = 37;
#my $mode_offset = 0;

my $TIMEOUT = 5;

#printf("what: %s\n", $what);

if($what eq "scan") {
  find_controller();
  print "\n";
  die "all found";
}

my $sock = IO::Socket::INET->new(
    Proto    => 'tcp',
    PeerPort => 5577,
    PeerAddr => $host,
) or die "Could not create socket: $!\n";

($cur_onoff, $cur_mode, $cur_running, $cur_speed, $cur_red, $cur_green, $cur_blue, $cur_user) = init();

switch($what) {
  case "fade7" {
    fade_7(@ARGV);
  }
  case "twinkle" {
    random_twinkle(@ARGV);
  }
  case "flash" {
    random_flash(@ARGV);
  }
  case "shine" {
    shine(@ARGV);
  }
  case "rgb" {
    set_rgb(@ARGV);      
  }
  case "color" {
    set_color(@ARGV);
  }
  case "mode" {
    set_mode(@ARGV);
  }
  case "speed" {
    set_speed(@ARGV);
  }
  case "user" {
    set_user(@ARGV);
  }
  case "quick" {
    quick(@ARGV);
  }
  case "status" {
    status();
    exit;
  }
  case "on" {
    on();
  }
  case "off" {
    off();
  }
  case "run" {
    run();
  }
  case "stop" {
    stop();
  }
  case "toggle" {
    if($cur_onoff == 36) {
      on();
    } else {
      off();
    }
  }
  else {
    print "Unknown command: $what\n\n";
    usage();
    exit;
  }

}


if($verbose == 2) {
  ($new_onoff, $new_mode, $new_running, $new_speed, $new_red, $new_green, $new_blue, $new_user) = init();
  changed();
} elsif($verbose == 1) {
  ($cur_onoff, $cur_mode, $cur_running, $cur_speed, $cur_red, $cur_green, $cur_blue, $cur_user) = init();
  status();
}

exit(0);


sub random_twinkle {
  die "Wrong number of arguments: " . @_ . " instead of 1" if (@_ != 1); 
  my $num = $_[0];
  on();
  for( my $j = 0; $j < $num; $j++) {
    my $r = int(rand(256));
    my $g = int(rand(256));
    my $b = int(rand(256));
    set_rgb($r, $g, $b);
    my $s = int(rand(19));
    usleep($s*10000);
  }
  off();
}

sub random_flash {
  die "Wrong number of arguments: " . @_ . " instead of 1" if (@_ != 1); 
  my $num = $_[0];
  my $s = -1;
  on();
  for( my $j = 0; $j < $num; $j++) {
    my $r = int(rand(256));
    my $g = int(rand(256));
    my $b = int(rand(256));
    set_rgb($r, $g, $b);
    $s = int(rand(5));
    usleep($s * 10000);
    set_rgb(0, 0, 0);
    $s = int(rand(20));
    usleep($s * 10000);
  }
  off();
}

sub shine {
  die "Wrong number of arguments: " . @_ . " instead of 1" if (@_ != 1); 
  my $num = $_[0];
  my ($tmp_red, $tmp_blue, $tmp_green) = get_rgb($_[1]);
  my $min = min(($tmp_red, $tmp_blue, $tmp_green));
  printf("%d/%d/%d\n", $tmp_red, $tmp_blue, $tmp_green);
  printf("min: %d\n", $min);
  $tmp_red -= $min;
  $tmp_blue -= $min;
  $tmp_green -= $min;
  set_rgb($tmp_red, $tmp_green, $tmp_blue);
  on();
  printf("%d/%d/%d\n", $tmp_red, $tmp_blue, $tmp_green);
  for( my $j = 0; $j < $num; $j++) {
  my $i = 0;
    for (; (($tmp_red < 255) && ($tmp_green < 255) && ($tmp_blue < 255)); ($tmp_red++, $tmp_green++, $tmp_blue++, $i++)) {
      printf("%d: %d/%d/%d\n", $j, $tmp_red, $tmp_blue, $tmp_green);
      set_rgb($tmp_red, $tmp_green, $tmp_blue);
    }
    for (; $i > 0 ; ($tmp_red--, $tmp_green--, $tmp_blue--, $i--)) {
      printf("%d: %d/%d/%d\n", $j, $tmp_red, $tmp_blue, $tmp_green);
      set_rgb($tmp_red, $tmp_green, $tmp_blue);
    }
    my $s = int(rand(19));
    usleep(100*$s*10000);
  }
  off();
}

sub fade_7 {
  die "Wrong number of arguments: " . @_ . " instead of 2" if (@_ != 2); 
  my $delay = $_[0];
  my $maxbright = 255;
  if ((0 < $_[1]) && ($_[1] < 255)) {
     $maxbright = $_[1];
     printf("Maxbright: %d\n", $maxbright);
  }
  my $r = $maxbright;
  my $g = 0;
  my $b = 0;

  on();
  while (1) {
#printf("G+\n");
    while ( $g < $maxbright) {
      set_rgb($r, $g++, $b, $delay);
    }
#printf("R-\n");
    while ( $r > 0) {
      set_rgb($r--, $g, $b, $delay);
    }
#printf("B+\n");
    while ( $b < $maxbright) {
      set_rgb($r, $g, $b++, $delay);
    }
#printf("G-\n");
    while ( $g > 0) {
      set_rgb($r, $g--, $b, $delay);
    }
#printf("R+\n");
    while ( $r < $maxbright) {
      set_rgb($r++, $g, $b, $delay);
    }
#printf("B-\n");
    while ( $b > 0) {
      set_rgb($r, $g, $b--, $delay);
    }
  }
}

sub set_rgb {
  die "Wrong number of arguments: " . @_ . " instead of 3" if (@_ != 3); 
  my $r = $_[0];
  my $g = $_[1];
  my $b = $_[2];
#  printf("%d/%d/%d\n", $r, $b, $g);
  my $msg = sprintf("%c%c%c%c%c", 0x56, $r, $g, $b, 0xAA);
  $sock->send($msg) or die "Send error: $!\n";
  usleep(10000);
  if ($_[3] > 0) {
    usleep ($_[3]);
  }
}

sub get_rgb {
  die "Wrong number of arguments: " . @_ . " instead of 1" if (@_ != 1); 
  my $r = $cur_red;
  my $g = $cur_green;
  my $b = $cur_blue;
  my $rgb = new Color::Rgb(rgb_txt=>'/usr/share/X11/rgb.txt');
  if ( $_[0] =~ /^(#|0x)?([0-9a-fA-F]{6})/) {
#    printf("Hex: %s\n", $2);
    return($rgb->hex2rgb($2));
  } else {
    return($rgb->rgb($_[0]));
  }
}

sub set_color {
  die "Wrong number of arguments: " . @_ . " instead of 1" if (@_ != 1); 
  my ($r, $g, $b) = get_rgb($_[0]);
#  printf("%d %s / %d %s / %d %s\n", ord($r), $r, ord($g), $g, ord($b), $b);
  my $msg = sprintf("%c%c%c%c%c", 0x56, $r, $g, $b, 0xAA);
  $sock->send($msg) or die "Send error: $!\n";
  usleep(10000);
}

sub set_mode {
  die "Wrong number of arguments: " . @_ . " instead of 1" if (@_ != 1);
  my $prog = $_[0];
  my @usercmd = ("0", "50", "000000");
  switch($prog) {
    case 20 { @usercmd = ("0", "100", "200000", "500000", "200000", "500000", "200000", "500000", "200000", "000020", "000050", "000020", "000050", "000020", "000050", "000020") }
    case 21 { @usercmd = ("1", "100", "FF0000", "0000FF") }
    case 22 { @usercmd = ("2", "100", "FFFF00", "FF00FF") }
    case 23 { @usercmd = ("0", "100", "00FFFF", "FF00FF") }
    case 24 { @usercmd = ("2", "100", "FF0000", "800000") }
    case 24 { @usercmd = ("2", "100", "FF0000", "800000") }
    case 30 { @usercmd = ("2", "10", "ffffff", "000000", "000000", "000000", "ffffff", "000000", "000000", "000000", "ffffff", "000000", "000000", "000000", "000000", "000000", "000000", "000000") }
    case 31 { @usercmd = ("2", "10", "ff0000", "000000", "000000", "000000", "ff0000", "000000", "000000", "000000", "ff0000", "000000", "000000", "000000", "000000", "000000", "000000", "000000") }
    case 32 { @usercmd = ("2", "10", "00ff00", "000000", "000000", "000000", "00ff00", "000000", "000000", "000000", "00ff00", "000000", "000000", "000000", "000000", "000000", "000000", "000000") }
    case 33 { @usercmd = ("2", "10", "0000ff", "000000", "000000", "000000", "0000ff", "000000", "000000", "000000", "0000ff", "000000", "000000", "000000", "000000", "000000", "000000", "000000") }
    case 40 { @usercmd = ("1", "10", "ff0000", "800000", "800000") }
    case 41 { @usercmd = ("1", "10", "00ff00", "008000", "008000") }
    case 42 { @usercmd = ("1", "10", "0000ff", "000080", "000080") }
    case 51 { @usercmd = ("1", "3", "FF0000", "FFFFFF", "FF0000", "FFFFFF", "FF0000", "FFFFFF", "FF0000", "FFFFFF", "FF0000", "FFFFFF", "FF0000", "FFFFFF", "FF0000", "FFFFFF", "FF0000", "FFFFFF") }
    case 52 { @usercmd = ("1", "3", "00FF00", "FFFFFF", "00FF00", "FFFFFF", "00FF00", "FFFFFF", "00FF00", "FFFFFF", "00FF00", "FFFFFF", "00FF00", "FFFFFF", "00FF00", "FFFFFF", "00FF00", "FFFFFF") }
    case 53 { @usercmd = ("1", "3", "0000FF", "FFFFFF", "0000FF", "FFFFFF", "0000FF", "FFFFFF", "0000FF", "FFFFFF", "0000FF", "FFFFFF", "0000FF", "FFFFFF", "0000FF", "FFFFFF", "0000FF", "FFFFFF") }
    case 54 { @usercmd = ("1", "3", "FFFF00", "FFFFFF", "FFFF00", "FFFFFF", "FFFF00", "FFFFFF", "FFFF00", "FFFFFF", "FFFF00", "FFFFFF", "FFFF00", "FFFFFF", "FFFF00", "FFFFFF", "FFFF00", "FFFFFF") }
    case 55 { @usercmd = ("1", "3", "00FFFF", "FFFFFF", "00FFFF", "FFFFFF", "00FFFF", "FFFFFF", "00FFFF", "FFFFFF", "00FFFF", "FFFFFF", "00FFFF", "FFFFFF", "00FFFF", "FFFFFF", "00FFFF", "FFFFFF") }
    else {
      $prog = 20 if $prog > 20;
      my $mode = sprintf("%c%c%c%c", 0xBB, $mode_offset+$prog, $cur_speed, 0x44);
      $sock->send($mode) or die "Send error: $!\n";
      usleep(10000);
      return;
    }
  }
  set_user(@usercmd);
}

sub set_user {
  die "Too few arguments: " . @_ if (@_ < 3); 
  my $colors;
  my $i = 0;
  my $mode = $_[0];
  if ($mode > 2) { $mode = 2 };
  if ($mode < 0) { $mode = 0 };
  $mode += 0x3A;
  shift;
  my $speed = $_[0];
  if ($speed > 255) { $speed = 255 };
  if ($speed < 0) { $speed = 0 };
  shift;
  die "Too many colors: " . @_ if (@_ > 16); 
  foreach ( @_ ) {
    my $cc = $_;
#    printf(":%s:\n", $cc);
    my($r, $g, $b) = get_rgb($cc);
    $colors .= chr($r) . chr($g) . chr($b);
    $i += 3;
  }
  for(; $i < 48; $i++ ) {
    $colors .= chr(0x01); $i++;
    $colors .= chr(0x02); $i++;
    $colors .= chr(0x03); 
  }
  my $user = sprintf("%c%s%c%c%c%c", 0x99, $colors, $speed, $mode,  0xFF, 0x66);
  die "Wrong lentgth: " . length($user) . "\n" if ( length($user) != 53);
  my @msg_array = unpack("W*", $user);
  my $j=1;
  if($verbose == 2) {
    printf("   User: ");
    foreach ( @msg_array) {
      printf("%0.2x ", $_);
    }
    printf("\n");
  }
  $sock->send($user) or die "Send error: $!\n";
  usleep(150000);
}

sub set_speed {
  die "Wrong number of arguments: " . @_ . " instead of 1" if (@_ != 1); 
  my $mode = sprintf("%c%c%c%c", 0xBB, $cur_mode, $_[0], 0x44);
  $sock->send($mode) or die "Send error: $!\n";
  usleep(10000);
}

sub off {
  $sock->send($off) or die "Send error: $!\n";
  usleep(10000);
}

sub on {
  $sock->send($on) or die "Send error: $!\n";
  usleep(10000);
}

sub run {
  $sock->send($run) or die "Send error: $!\n";
  usleep(10000);
}

sub stop {
  $sock->send($stop) or die "Send error: $!\n";
  usleep(10000);
}

sub quick {
# on:5:yes:run:100:0:12:12
my ($tmp_onoff, $tmp_mode, $tmp_user, $tmp_running, $tmp_speed, $tmp_red, $tmp_green, $tmp_blue) = split(':', $_[0]);
# printf("%s / %s / %s / %s / %s / %s / %s / %s\n",  $tmp_onoff, $tmp_mode, $tmp_user, $tmp_running, $tmp_speed, $tmp_red, $tmp_green, $tmp_blue);
if($tmp_running eq "run") { run(); set_speed($tmp_speed); set_mode($tmp_mode) } else { stop(); set_rgb($tmp_red, $tmp_green, $tmp_blue) };
if($tmp_onoff eq "on") { on() } else { off() };
}

sub init {
  my $answer;
  my $init1;
  my $init2;
#  my $init3;
  my $init4;

  my $tmp_red = -1;
  my $tmp_green = -1;
  my $tmp_blue = -1;
  my $tmp_mode = -1;
  my $tmp_speed = -1;
  my $tmp_running = -1;
  my $tmp_onoff = -1;
  my $tmp_user = -1;

  $sock->send($init) or die "Send error: $!\n";
  local $SIG{ALRM} = sub { die "Alarm time out: $!\n" };
  alarm $TIMEOUT;
  $sock->recv($answer, 256);
  alarm 0;
  ($init1, $init2, $tmp_onoff, $tmp_mode, $tmp_running, $tmp_speed, $tmp_red, $tmp_green, $tmp_blue, $tmp_user, $init4) = unpack("W*", $answer);
  if (($init1 != 102) || ($init2 != 01) || ($init4 != 153)) {
    printf ("Answer (%d): %d %d %d %d %d %d %d %d %d %d %d\n", length($answer), $init1, $init2, $cur_onoff, $cur_mode, $cur_running, $cur_speed, $cur_red, $cur_green, $cur_blue, $cur_user, $init4);
    printf ("Answer (%d): %x %x %x %x %x %x %x %x %x %x %x\n", length($answer), $init1, $init2, $cur_onoff, $cur_mode, $cur_running, $cur_speed, $cur_red, $cur_green, $cur_blue, $cur_user, $init4);
    die "Receive error! Init do not match!";
  }
  usleep(10000);
  return($tmp_onoff, $tmp_mode, $tmp_running, $tmp_speed, $tmp_red, $tmp_green, $tmp_blue, $tmp_user);
}

sub changed {
  printf("  State: %s => %s\n", ($cur_onoff == 36) ? "off" : "on", ($new_onoff == 36) ? "off" : "on");
  printf("   Mode: %d => %d\n", $cur_mode-$mode_offset, $new_mode-$mode_offset);
  printf("   User: %s => %s\n", ($cur_user == 81) ? "no" : "yes", ($new_user == 81) ? "no" : "yes");
  printf("Running: %s => %s\n", ($cur_running == 32) ? "no" : "yes", ($new_running == 32) ? "no" : "yes");
  printf("  Speed: %d => %d\n", $cur_speed, $new_speed);
  printf("    Red: %d => %d\n", $cur_red, $new_red);
  printf("  Green: %d => %d\n", $cur_green, $new_green);
  printf("   Blue: %d => %d\n", $cur_blue, $new_blue);
}

sub status {
  printf("%s:", ($cur_onoff == 36) ? "off" : "on");
  printf("%d:", $cur_mode-$mode_offset);
  printf("%s:", ($cur_user == 81) ? "no" : "yes");
  printf("%s:", ($cur_running == 32) ? "stop" : "run");
  printf("%d:", $cur_speed);
  printf("%d:", $cur_red);
  printf("%d:", $cur_green);
  printf("%d", $cur_blue);
}

sub find_controller {
  my $answer;
  my $sock_udp_send;
  my $addr;
  my $bcast;

#  my @interfaces = $sock_udp_send->if_list;
#
#  for my $if (@interfaces) {
#    print "interface = $if\n";
#    my $flags = $sock_udp_send->if_flags($if);
#    print "addr =      ",$sock_udp_send->if_addr($if),"\n",
#          "broadcast = ",$sock_udp_send->if_broadcast($if),"\n",
#          "netmask =   ",$sock_udp_send->if_netmask($if),"\n",
#          "dstaddr =   ",$sock_udp_send->if_dstaddr($if),"\n",
#          "hwaddr =    ",$sock_udp_send->if_hwaddr($if),"\n";
#
#    print "is running\n"     if $flags & IFF_RUNNING;
#    print "is broadcast\n"   if $flags & IFF_BROADCAST;
#    print "is p-to-p\n"      if $flags & IFF_POINTOPOINT;
#    print "is loopback\n"    if $flags & IFF_LOOPBACK;
#    print "is promiscuous\n" if $flags & IFF_PROMISC;
#    print "is multicast\n"   if $flags & IFF_MULTICAST;
#    print "is notrailers\n"  if $flags & IFF_NOTRAILERS;
#    print "is noarp\n"       if $flags & IFF_NOARP;
#  }


  my $sock_udp_test = IO::Socket::INET->new(
    Proto    => 'udp',
#    LocalPort => 48899,
#    LocalAddr => '10.0.0.1',
#    ReuseAddr => 1,
#    timeout => $TIMEOUT
  ) or die "Could not create socket: $!\n";

#  $sock_udp_send->sockopt(SO_REUSEADDR, 1) or die "setsockopt: $!";
# $sock_udp_send->sockopt(SO_BROADCAST, 1) or die "setsockopt: $!";
#  $sock_udp_send->connect(48899, inet_aton($host)) or die("connect: $!\n"); 

  #local $SIG{ALRM} = sub { return "Alarm time out: $!\n" };
  my @interfaces = $sock_udp_test->if_list;

  print "looking for: ",$host, "\n";
  for my $if (@interfaces) {

    if(($sock_udp_test->if_broadcast($if) eq $host) || ($sock_udp_test->if_addr($if) eq $host) || ($sock_udp_test->if_hwaddr($if) eq $host) || ($if eq $host)) {

    print "Found: ", $if, "\n",
          "addr =      ",$sock_udp_test->if_addr($if),"\n",
          "broadcast = ",$sock_udp_test->if_broadcast($if),"\n",
          "netmask =   ",$sock_udp_test->if_netmask($if),"\n",
          "dstaddr =   ",$sock_udp_test->if_dstaddr($if),"\n",
          "hwaddr =    ",$sock_udp_test->if_hwaddr($if),"\n";

      $sock_udp_send = IO::Socket::INET->new(
        Proto    => 'udp',
        PeerPort => 48899,
#        LocalPort => 48899,
#        LocalAddr => $addr,
        PeerAddr => $sock_udp_test->if_broadcast($if),
        Broadcast => 1,
        ReuseAddr => 1,
        timeout => $TIMEOUT
      ) or die "Could not create socket: $!\n";


    my $sock_udp_recv = IO::Socket::INET->new(
    Proto    => 'udp',
    LocalPort => 48899,
    LocalAddr => $sock_udp_test->if_addr($if),
    ReuseAddr => 1,
    timeout => $TIMEOUT
  ) or die "Could not create socket: $!\n";

    $sock_udp_send->send("HF-A11ASSISTHREAD") or die "Send error: $!\n";

    local $SIG{ALRM} = sub { return };
    alarm $TIMEOUT;

    printf("\nAnswer Section:\n");
    while($sock_udp_recv->recv($answer, 256)) {
      printf("%s\n", $answer);
    } 
    alarm 0;
    }

  }
}

sub usage {

  print "Usage: ", $0, " <Destination> <Command> [Args..]\n";
  print "Commands:\n";
  print "  scan\n";
  print "      Destination is one of <interface|address|broadcast|hwaddress>\n";
  print "  toggle\n";
  print "  on\n";
  print "  off\n";
  print "  run\n";
  print "  stop\n";
  print "  color <color>\n";
  print "       where color is one of <[#]RRGGBB|name>\n";
  print "  rgb <red> <green> <blue>\n";
  print "       where <red|green|blue> is <0-255>\n";
  print "  mode <0-20>\n";
  print "  speed <0-255>\n";
  print "  user <transition> <speed> <color1> [[color2]-[color16]]\n";
  print "       where transition is 0: fade, 1: jump, 2: flash\n";
  print "  fade7 <speed> <max_brighntess>\n";
  print "  shine <count>\n";
  print "  twinkle <count>\n";
  print "  flash <address> <count>\n";
  
}
