#!/usr/bin/perl
######################################################################
#
# tellstickControllerRfcmd program
#
# Copyright (C) 2008-2010 Rickard Andersson (ran42ran@gmail.com)
# Version 2.0.0
#
######################################################################
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
######################################################################


use warnings;
use strict;
use sigtrap 'handler', \&daemon_exit, 'normal-signals';
use sigtrap 'die', 'untrapped', 'normal-signals';
use sigtrap 'stack-trace', 'any', 'error-signals';
use POSIX 'setsid';


use DB_File;
use DateTime;
use DateTime::Event::Sunrise;


######################################################################

# All these cfg settings can be altered from the configuration file.
#
my %cfg_set = (
  "configfile" => "/etc/tellstickController.conf",
  "dbfile" => "/var/lib/tellstickController.db",
  "logfile" => "/var/log/tellstickController.log",
  "pidfile" => "/var/run/tellstickController.pid",
  "program" => "/usr/local/bin/rfcmd",
  "altitude" => "-0.833",
  "latitude" => "58.24",
  "longitude" => "15.31",
  "timezone" => "Europe/Stockholm",
);
my %cfg_group = ();
my %cfg_alias = ();
my %cfg_dimmer = ();
my @cfg_rule = ();


my %option = (
  "aliases" => 0,
  "check" => 0,
  "daemon" => 0,
  "list" => 0,
  "device" => "",
  "state" => "",
  "swapfirst" => 0,
  "swap" => 0,
  "test" => 0,
  "verbose" => 0,
);
my %device_state = ();
my @device_on = ();
my @device_off = ();


######################################################################


sub check_args(@) {
  my (@args) = @_;
 
  if (not @args) {
    usage();
    exit;
  }
  while (@args) {
    if ($args[0] =~ /^-(h|-help)$/) {
      usage();
      exit;
    } elsif ($args[0] =~ /^-(a|-aliases)$/) {
      shift(@args);
      $option{"aliases"} = 1;
      next;
    } elsif ($args[0] =~ /^-(c|-check)$/) {
      shift(@args);
      $option{"check"} = 1;
      next;
    } elsif ($args[0] =~ /^-(d|-daemon)$/) {
      shift(@args);
      $option{"daemon"} = 1;
      next;
    } elsif ($args[0] =~ /^-(f|-file)$/) {
      shift(@args);
      $cfg_set{"configfile"} = shift(@args);
      next;
    } elsif ($args[0] =~ /^-(g|-get)$/) {
      shift(@args);
      $option{"get"} = 1;
      $option{"device"} = lc($args[0]);
      shift(@args);
      next;
    } elsif ($args[0] =~ /^-(l|-list)$/) {
      shift(@args);
      $option{"list"} = 1;
      next;
    } elsif ($args[0] =~ /^-(s|-set)$/) {
      shift(@args);
      $option{"set"} = 1;
      $option{"device"} = lc($args[0]);
      shift(@args);
      $option{"state"} = lc($args[0]);
      shift(@args);
      next;
    } elsif ($args[0] =~ /^-(w|-swapfirst)$/) {
      shift(@args);
      $option{"swapfirst"} = 1;
      $option{"device"} = lc($args[0]);
      shift(@args);
      next;
    } elsif ($args[0] =~ /^-(x|-swap)$/) {
      shift(@args);
      $option{"swap"} = 1;
      $option{"device"} = lc($args[0]);
      shift(@args);
      next;
    } elsif ($args[0] =~ /^-(t|-test)$/) {
      shift(@args);
      $option{"test"} = 1;
      next;
    } elsif ($args[0] =~ /^-(v|-verbose)$/) {
      shift(@args);
      $option{"verbose"} = 1;
      next;
    }
      
    usage();
    exit;
  }
}


sub usage() {
  while (<DATA>) {
    if ($_ =~ /^#-(.*)/) {
      print "$1\n";
    }
  }
}


sub println($) {
  my ($text) = @_;

  print "$text\n";
}


sub printlogger($) {
  my ($text) = @_;

  if ($option{"daemon"}) {
    my $now = get_datetime_now();
    my $logfile = $cfg_set{"logfile"};
    my $output = "echo '$now $text' >> $logfile";
    system ($output);
  }
}


######################################################################


sub daemon_init() {
  chdir "/";
  umask 0;
  my $pid = fork;
  exit if $pid;
  die "Couldn't fork program" unless defined(\$pid);
  setsid() or die "Couldn't start new program session"; 
  open STDIN, '/dev/null' or die "Couldn't read /dev/null!";
  open STDOUT, '>/dev/null' or die "Couldn't write to /dev/null!";
  open STDERR, '>/dev/null' or die "Couldn't write to /dev/null!";

  my $pidfile = $cfg_set{"pidfile"};
  $0="tellstickControllerDaemon";
  $pid = $$;
  system("echo $pid > $pidfile") if (defined($pidfile));

  printlogger "> Daemon started";
}


sub daemon_exit() {
  printlogger "> Daemon stopped";

  unlink $cfg_set{"pidfile"} if (defined($cfg_set{"pidfile"}));
  my $pid = $$;
  kill('TERM', $pid);
  exit;
}


sub get_datetime_now() {
  my $now = DateTime->now(time_zone => $cfg_set{"timezone"});

  return $now;
}


sub get_datetime_sunrise($) {
  my ($now) = @_;

  my $sunrise = DateTime::Event::Sunrise->sunrise (
    altitude => $cfg_set{"altitude"},
    latitude => $cfg_set{"latitude"},
    longitude => $cfg_set{"longitude"},
    iteration => '2'
  );
  my $time = $sunrise->next($now);

  return $time;
}


sub get_datetime_sunset($) {
  my ($now) = @_;

  my $sunset = DateTime::Event::Sunrise->sunset (
    altitude => $cfg_set{"altitude"},
    latitude => $cfg_set{"latitude"},
    longitude => $cfg_set{"longitude"},
    iteration => '2'
  );
  my $time = $sunset->next($now);

  return $time;
}

  
sub read_db($) {
  my ($dbfile) = @_;

  println "> Reading db started" if ($option{"verbose"});

  tie %device_state, "DB_File", $dbfile, O_RDWR|O_CREAT, 0666, $DB_HASH
    or die "Couldn't read dbfile '$dbfile'!\n";

  foreach my $key (sort keys %cfg_alias) {
    if (not $device_state{$key}) {
      $device_state{$key} = "unknown";
      println "Added device $key to db!" if ($option{"verbose"});
    }
  }
    
  foreach my $key (sort keys %device_state) {
    if (not $cfg_alias{$key}) {
      delete $device_state{$key};
      println "Removed device $key from db!" if ($option{"verbose"});
    }
  }

  println "> Reading db finished" if ($option{"verbose"});
}


sub write_db($) {
  my ($dbfile) = @_;

  println "> Writing db started" if ($option{"verbose"});

  untie %device_state;

  println "> Writing db finished" if ($option{"verbose"});
}


sub read_config($) {
  my ($infile) = @_;

  my $text = "> Reading configurationfile started";
  println $text if ($option{"verbose"});
  printlogger $text;

  open(my $indata, $infile) or die "Couldn't read configfile '$infile'!";

  while(my $line = <$indata>) {
    chomp($line);
    $line =~ s/\s+/ /g;

    if ($line =~ /^Set\s+/i) {
      #println $line if ($option{"verbose"});
      my (undef, $key, $val, $suffix) = split(/\s+/, $line);
      if (defined($suffix)) {
        println "Wrong argument '$suffix' in line '$line'!";
      } else {
        if (defined($key)) {
          if (defined($val)) {
            $cfg_set{lc($key)} = $val;
          } else {
            println "Wrong value in line '$line'!";
          }
        } else {
          println "Wrong key in line '$line'!";
        }
      }
      next;
    }	

    if ($line =~ /^Alias\s+/i) {
      #println $line if ($option{"verbose"});
      my (undef, $name, $sender, $dimmer, $prot, $code, $addr, $suffix)
        = split(/\s+/, $line);
      if (defined($suffix)) {
        println "Wrong argument '$suffix' in line '$line'!";
println $addr;
      } else {
        $name = lc($name);
        $sender = $sender;
        $dimmer = lc($dimmer);
        $prot = uc($prot);
        $code = uc($code);
        $addr = uc($addr);
        if ($dimmer =~ /^no|yes$/) {
          $cfg_dimmer{$name} = $dimmer;
          if ($prot =~ /^NEXA|WAVEMAN$/) {
            if ($code =~ /^[A-P]$/) {
              if ($addr =~ /^[1-9]{1}|1{1}[0-6]{1}$/) {
                $cfg_alias{$name} = "$sender $prot $code $addr";
              } else {
                println "Wrong channel '$addr' in line '$line'!";
              }
            } else {
              println "Wrong housecode '$code' in line '$line'!";
            }
          } elsif ($prot =~ /^SARTANO$/) {
            if ($code =~ /^[01]{10}$/) {
              if ($addr eq "") {
                $cfg_alias{$name} = "$sender $prot $code";
              } else {
                println "Wrong argument '$addr' in line '$line'!";
              }
            } else {
              println "Wrong channel '$code' in line '$line'!";
            }
          } else {
            println "Wrong protocol '$prot' in line '$line'!";
          }
        } else {
          println "Wrong dimmer mode '$dimmer' in line '$line'!";
        }
      }
      next;
    }	

    if ($line =~ /^Group\s+/i) {
      println $line if ($option{"verbose"});
      my (undef, $name, $delay, $aliases) = split(/\s+/, $line, 4);
      $name = lc($name);
      $delay = lc($delay);
      if ($delay =~ /^\d+$/) {
        my $error = 0;
        $aliases = lc($aliases);
        my @aliaslist = split(/\s+/, $aliases);
        foreach my $alias (@aliaslist) {
          if (! exists($cfg_alias{$alias})) {
            println "Wrong alias '$alias' in line '$line'!";
            $error += 1;
          }	
        }
        if ($error == 0) {
          $cfg_group{$name} = "$delay $aliases";
        }
      } else {
        println "Wrong delay time '$delay' in line '$line'!";
      }
      next;
    }	

    if ($line =~ /^Rule\s+/i) {
      #println $line if ($option{"verbose"});
      my (undef, $alias, $on, $off, $suffix) = split(/\s+/, $line);
      if (defined($suffix)) {
        println "Wrong argument '$suffix' in line '$line'!";
      } else {
        $alias = lc($alias);
        $on = lc($on);
        $off = lc($off);
        if ($on eq "") {
          println "Wrong on time '$on' in line '$line'!";
        } elsif ($off eq "") {
          println "Wrong off time '$off' in line '$line'!";
        } else {
          if ($on !~ /[\w\/\+\-\:\$\(\)]+/) {
            println "Wrong on time '$on' in line '$line'!";
          } elsif ($off !~ /[\w\/\+\-\:\$\(\)]+/) {
            println "Wrong off time '$off' in line '$line'!";
          }
        }
        if (exists($cfg_alias{$alias})) {
            push @cfg_rule, [$alias, $on, $off];
        } elsif (exists($cfg_group{$alias})) {
            push @cfg_rule, [$alias, $on, $off];
        } else {
          println "Wrong alias '$alias' in line '$line'!";
        }
      }
      next;
    }

    if ($line =~ /^#/) {
      next;
    }

    if ($line =~ /^\s*/) {
      next;
    }

    println "Unhandled config line: '$line'!" if ($option{"check"});
  }

  close $indata;

  $text = "=== Set ===";
  println $text if ($option{"check"});
  printlogger $text;
  foreach my $key (sort keys %cfg_set) {
    $text = "$key = $cfg_set{$key}";
    println $text if ($option{"check"});
    printlogger $text;
  }
  $text = "=== Alias ===";
  println $text if ($option{"check"});
  printlogger $text;
  foreach my $key (sort keys %cfg_alias) {
    $text = "$key = $cfg_alias{$key}";
    println $text if ($option{"check"});
    printlogger $text;
  }
  $text = "=== Group ===";
  println $text if ($option{"check"});
  printlogger $text;
  foreach my $key (sort keys %cfg_group) {
    my $val = $cfg_group{$key};
    my ($delay, $aliases) = split(/\s+/, $val, 2);
    $text = "$key =";
    $text .= " delay time $delay seconds";
    $text .= " between aliases ($aliases)";
    println $text if ($option{"check"});
    printlogger $text;
  }
  $text = "=== Rule ===";
  println $text if ($option{"check"});
  printlogger $text;
  foreach my $rule (@cfg_rule) {
    my ($alias, $on, $off) = @$rule;
    $text = "$alias =";
    $text .= " on at $on" if ($on !~ /no/);
    $text .= " and" if (($on !~ /no/) && ($off !~ /no/));
    $text .= " off at $off" if ($off !~ /no/);
    println $text if ($option{"check"});
    printlogger $text;
  }

  $text = "> Reading configurationfile finished";
  println $text if ($option{"verbose"});
  printlogger $text;
}


sub call_program($$) {
  my ($device, $state) = @_;

  if ($option{"test"}) {
    my $text = "Test mode, no real device will be used";
    println $text if ($option{"verbose"});
    printlogger $text;
  } else {
    my $alias = $cfg_alias{$device};
    my $mode = 0;
    if ($state eq "on") {
      $mode = 1;
    }
    my $prog = $cfg_set{"program"};
    my $command = "$prog $alias $mode";
    my $text = "Executing command: '$command'";
    println $text if ($option{"verbose"});
    system($command);
  }
}


sub load_device_rules () {
  my $text = "> Loading device rules started";
  println $text if ($option{"verbose"});
  printlogger $text;

  my $now = get_datetime_now();
  $text = "Time = $now";
  println $text if ($option{"verbose"});
  printlogger $text;
  my $today = get_datetime_now();
  $today->set( hour => 0, minute => 0, second => 0 );
  $text = "Today = $today";
  println $text if ($option{"verbose"});
  printlogger $text;
  my $sunrise = get_datetime_sunrise($today);
  $text = "Sunrise = $sunrise";
  println $text if ($option{"verbose"});
  printlogger $text;
  my $sunset = get_datetime_sunset($today);
  $text = "Sunset = $sunset";
  println $text if ($option{"verbose"});
  printlogger $text;

  foreach my $rule (@cfg_rule) {
    my ($alias, $on, $off) = @$rule;
    if (exists($cfg_alias{$alias})) {
      my $time = get_rule_datetime($on, $today, $sunrise, $sunset);
      push @device_on, [$time, $alias] if (defined($time) && ($now <= $time));
      $time = get_rule_datetime($off, $today, $sunrise, $sunset);
      push @device_off, [$time, $alias] if (defined($time) && ($now <= $time));
    } else {
      foreach my $val ($cfg_group{$alias}) {
        my ($delay, $aliases) = split(/\s+/, $val, 2);
        my $timedelay = 0;
        my $ontime = get_rule_datetime($on, $today, $sunrise, $sunset);
        my $offtime = get_rule_datetime($off, $today, $sunrise, $sunset);
        my (@aliaslist) = split(/\s+/, $aliases);
        foreach my $device (@aliaslist) {
          if (defined($ontime)) {
            my $time = $ontime->clone->add(seconds => $timedelay);
            push @device_on, [$time, $device] if (defined($time) && ($now <= $time));
          }
          if (defined($offtime)) {
            my $time = $offtime->clone->add(seconds => $timedelay);
            push @device_off, [$time, $device] if (defined($time) && ($now <= $time));
          }
          $timedelay += $delay;
        }
      }
    }
  }

  @device_on = sort { $a->[0] cmp $b->[0] } @device_on;
  @device_off = sort { $a->[0] cmp $b->[0] } @device_off;

  $text = "=== Device on ===";
  println $text if ($option{"verbose"});
  printlogger $text;
  foreach my $rule (@device_on) {
    my ($time, $alias) = @$rule;
    $text = "$alias = $time";
    println $text if ($option{"verbose"});
    printlogger $text;
  }
  $text = "=== Device off ===";
  println $text if ($option{"verbose"});
  printlogger $text;
  foreach my $rule (@device_off) {
    my ($time, $alias) = @$rule;
    $text = "$alias = $time";
    println $text if ($option{"verbose"});
    printlogger $text;
  }
  
  $text = "> Loading device rules finished";
  println $text if ($option{"verbose"});
  printlogger $text;
}


sub get_rule_datetime($$$$) {
  my ($rule, $now, $sunrise, $sunset) = @_;

#println "Rule='$rule'";

  my ($date, $time) = split(/\//, $rule);
  if (not defined($time)) {
    $time = $date;
    $date = $now->ymd;
  } else {
    my $today = 0;
    my $match = 0;
    if ($date =~ /([\d#]{4})-([\d#]{2})-([\d#]{2})/) {
      $match = 1;
      $today = 1 if ((($1 =~ /####/) || ($now->year == $1))
                    && (($2 =~ /##/) || ($now->month == $2))
                    && (($3 =~ /##/) || ($now->day == $3)));
    }
    if ($date =~ /workweek/) {
      $match = 1;
      $today = 1 if ($now->day_of_week < 6);
    }
    if ($date =~ /weekend/) {
      $match = 1;
      $today = 1 if ($now->day_of_week > 5);
    }
    if ($date =~ /monday/) {
      $match = 1;
      $today = 1 if ($now->day_of_week == 1);
    }
    if ($date =~ /tuesday/) {
      $match = 1;
      $today = 1 if ($now->day_of_week == 2);
    }
    if ($date =~ /wednesday/) {
      $match = 1;
      $today = 1 if ($now->day_of_week == 3);
    }
    if ($date =~ /thursday/) {
      $match = 1;
      $today = 1 if ($now->day_of_week == 4);
    }
    if ($date =~ /friday/) {
      $match = 1;
      $today = 1 if ($now->day_of_week == 5);
    }
    if ($date =~ /saturday/) {
      $match = 1;
      $today = 1 if ($now->day_of_week == 6);
    }
    if ($date =~ /sunday/) {
      $match = 1;
      $today = 1 if ($now->day_of_week == 7);
    }

    if ($match) { 
      if ($today) {
        $date = $now->ymd;
      } else {
        $time = undef;
      }
    } else {
      println "Wrong date '$date' for rule '$rule'!";
      $time = undef;
    }
  }
  
  if (defined($time) && ($time !~ /^no$/)) {
    my $days = 0;
    my $hours = 0;
    my $minutes = 0;
    my $lastOp = "+";
    my $expr = "";
    my $op = "";
    my $rest = "";
    my $mins = 0;
#println "rule='$time'";

    while ($time ne "") {
      $op = $lastOp;
      if ($time =~ /(.+?)(\+|-)(.*)/) {
        $expr = $1;
        $lastOp = $2; 
        $rest = $3; 
        $time = $rest;
      } elsif ($time =~ /(.+)/) {
        $expr = $1;
        $lastOp = "";
        $rest = "";
        $time = "";
      }

      if ($expr =~ /^sunrise$/) {
        $expr = 0;
        if ($sunrise->hms =~ /(\d\d):(\d\d):(\d\d)/) {
          $expr = $1*60+$2;
        }
      } elsif ($expr =~ /^sunset$/) {
        $expr = 0;
        if ($sunset->hms =~ /(\d\d):(\d\d):(\d\d)/) {
          $expr = $1*60+$2;
        }
      } elsif ($expr =~ /^random\((\d\d):(\d\d)\)$/) {
        $expr = int rand($1*60+$2+1);
      } elsif ($expr =~ /^(\d\d):(\d\d)$/) {
        $expr = $1*60+$2;
      } else {
        println "Wrong time '$expr' for rule '$rule'!";
        $expr = "";
      }

#println "expr='$expr', op='$op', rest='$rest'";
      if ($op eq "+") {
        $mins += $expr;
      } elsif ($op eq "-") {
        $mins -= $expr;
      }
#println "mins='$mins'";
    }
    if ($mins <= 0) {
      $mins = $mins % (24*60);
      $days = 1;
    } elsif ($mins >= 24*60) {
      $mins = $mins % (24*60);
      $days = 1;
    }

    $hours = int $mins/60;
    $minutes = int $mins%60;
# UTC ?
    $time = get_datetime_now();
    $time->set(hour => 0, minute => 0, second => 0);
    $time->add(hours => $hours+24*$days, minutes => $minutes, seconds => 0);
#println "days='$days', hours='$hours', minutes='$minutes'";
#println "time='$time'";
  } else {
    $time = undef;
  }

  return $time;
}


sub check_device_rules($) {
  my ($now) = @_;
  
  println "Checking device rules '$now'" if ($option{verbose});

  my $rule = $device_on[0];
  while (defined(@$rule)) {
    my ($time, $device) = @$rule;
    if ($time <= $now) {
      shift(@device_on);
      change_device_state($device, 'off') if ($cfg_dimmer{$device} eq 'yes');
      change_device_state($device, 'on');
    } else {
      last;
    }
    $rule = $device_on[0];
  }

  $rule = $device_off[0];
  while (defined(@$rule)) {
    my ($time, $device) = @$rule;
    if ($time <= $now) {
      shift(@device_off);
      change_device_state($device, 'off');
    } else {
      last;
    }
    $rule = $device_off[0];
  }

}


sub change_device_state($$) {
  my ($device, $state) = @_;

  $device_state{$device} = $state;
  write_db($cfg_set{"dbfile"});
  read_db($cfg_set{"dbfile"});
  call_program($device, $state);
  my $text = "Device $device = $state";
  println $text if ($option{"verbose"});
  printlogger $text;
}


######################################################################


sub get_device_state() {
  if ($option{"device"}) {
    my $device = $option{"device"};
    if ($device_state{$device}) {
      my $state = $device_state{$device};
      println "$state" if (not $option{"verbose"});
      println "Device $device = $state" if ($option{"verbose"});
    } elsif (exists($cfg_group{$device})) {
      my $val = $cfg_group{$device};
      my ($delay, $aliases) = split(/\s+/, $val, 2);
      my $states = "";
      my (@aliaslist) = split(/\s+/, $aliases);
      foreach my $alias (@aliaslist) {
        $states .= " $device_state{$alias}";
      }
      println "Group $device =$states";
    } else {
      println "No alias or group found with name '$device'!";
    }
  }
}


sub set_device_state() {
  if ($option{"device"}) {
    if ($option{"state"}) {
      my $state = $option{"state"};
      if ($state !~ /^(on|off)$/i) {
        println "No state found with name '$state'!";
      }
      my $device = $option{"device"};
      if ($device_state{$device}) {
        change_device_state($device, $state);
      } elsif (exists($cfg_group{$device})) {
        my $val = $cfg_group{$device};
        my ($delay, $aliases) = split(/\s+/, $val, 2);
        my (@aliaslist) = split(/\s+/, $aliases);
        foreach my $alias (@aliaslist) {
          change_device_state($alias, $state);
          sleep(1);
        }
      } else {
        println "No alias or group found with name '$device'!";
      }
    }
  }
}


sub swap_first_device_state() {
  if ($option{"device"}) {
    if ($option{"swapfirst"}) {
      my $device = $option{"device"};
      if ($device_state{$device}) {
        my $state = $device_state{$device};
        if ($state =~ /^off$/i) {
          $state = 'on';
        } else {
          $state = 'off';
        }
        change_device_state($device, $state);
      } elsif (exists($cfg_group{$device})) {
        my $val = $cfg_group{$device};
        my ($delay, $aliases) = split(/\s+/, $val, 2);
        my ($alias) = split(/\s+/, $aliases);
        my $state = $device_state{$alias};
        if ($state =~ /^off$/i) {
          $state = 'on';
        } else {
          $state = 'off';
        }
        my (@aliaslist) = split(/\s+/, $aliases);
        foreach my $alias (@aliaslist) {
          change_device_state($alias, $state);
          sleep(1);
        }
      } else {
        println "No alias or group found with name '$device'!";
      }
    }
  }
}


sub swap_device_state() {
  if ($option{"device"}) {
    if ($option{"swap"}) {
      my $device = $option{"device"};
      if ($device_state{$device}) {
        my $state = $device_state{$device};
        if ($state =~ /^off$/i) {
          $state = 'on';
        } else {
          $state = 'off';
        }
        change_device_state($device, $state);
      } elsif (exists($cfg_group{$device})) {
        my $val = $cfg_group{$device};
        my ($delay, $aliases) = split(/\s+/, $val, 2);
        my (@aliaslist) = split(/\s+/, $aliases);
        foreach my $alias (@aliaslist) {
          my $state = $device_state{$alias};
          if ($state =~ /^off$/i) {
            $state = 'on';
          } else {
            $state = 'off';
          }
          change_device_state($alias, $state);
          sleep(1);
        }
      } else {
        println "No alias or group found with name '$device'!";
      }
    }
  }
}


sub list_all_devices() {
  foreach my $key (sort keys %device_state) {
    println "Device $key = $device_state{$key}";
  }
  foreach my $key (sort keys %cfg_group) {
    my $val = $cfg_group{$key};
    my ($delay, $aliases) = split(/\s+/, $val, 2);
    my $states = "";
    my (@aliaslist) = split(/\s+/, $aliases);
    foreach my $device (@aliaslist) {
      $states .= " $device_state{$device}";
    }
    println "Group $key =$states";
  }
}


sub list_all_aliases {
  foreach my $key (sort keys %cfg_alias) {
    my $dimmer = "";
    $dimmer = " dimmer" if $cfg_dimmer{$key} eq "yes";
    println "Alias $key = receiver ($cfg_alias{$key}$dimmer)";
  }
  foreach my $key (sort keys %cfg_group) {
    my $val = $cfg_group{$key};
    my ($delay, $aliases) = split(/\s+/, $val, 2);
    println "Group $key = delay $delay seconds, aliases ($aliases)";
  }
}


sub daemon_loop() {
  daemon_init();

  while ($option{"daemon"}) {
    my $now = get_datetime_now();
    my $next_day = get_datetime_now();
    $next_day->add( hours => 24 );
    $next_day->set( hour => 0, minute => 0, second => 0 );
    println "Next reload of device rules = $next_day" if ($option{"verbose"});

    my $wait_time = 5;
    my $loop = 1;

    check_device_rules($now);

    while ($loop) {
      $now = get_datetime_now();
      if ($now > $next_day) {
        load_device_rules();
        $next_day->add( hours => 24 );
      }
      check_device_rules($now);
      sleep($wait_time);
    } 
  }

  daemon_exit();
}


sub perform_action() {
  if ($option{"aliases"}) {
    list_all_aliases();
  } elsif ($option{"get"}) {
    get_device_state();
  } elsif ($option{"set"}) {
    set_device_state();
  } elsif ($option{"swapfirst"}) {
    swap_first_device_state();
  } elsif ($option{"swap"}) {
    swap_device_state();
  } elsif ($option{"list"}) {
    list_all_devices();
  } elsif ($option{"daemon"}) {
    daemon_loop();
  }
}


######################################################################


sub main() {
  # check arguments
  check_args(@ARGV);
    
  # read configuration file
  read_config($cfg_set{"configfile"});

  # read DB
  read_db($cfg_set{"dbfile"});

  # Load rules
  load_device_rules(); 

  # perform action
  perform_action();

  # write DB
  write_db($cfg_set{"dbfile"});
}


main();


######################################################################


__DATA__
#-
#- NAME
#-   tellstickControllerRfcmd
#-
#- SYNOPSIS
#-   tellstickControllerRfcmd [options] 
#-
#- DESCRIPTION
#-   tellstickController is used for controlling wireless recevier devices from
#-   a TellStick transmitter. This version uses the rfcmd software to perform
#-   the actual device control. tellstickController can be used for manual
#-   control of devices, or used as a daemon that is controlled by a number of
#-   configuration file rules.
#-   A small database is used for keeping track of device states between every
#-   execution of tellstickController.
#-    
#-   -h, --help          Show this help text.
#-   -v, --verbose       Show extra information.
#-   -d, --daemon        Starts in daemon mode.
#-   -f, --file F        Set configfile to file F.
#-   -c, --check         Check content of configuration file.
#-   -t, --test          Test mode, no real devices will used.
#-   -a, --aliases       List of aliases for devices/groups.
#-   -l, --list          List states for all devices/groups.
#-   -s, --set D S       Set device D to state S.
#-   -g, --get D         Get state for device/group D.
#-   -w, --swapfirst G   Swap states for group G based on first device state.
#-   -x, --swap D        Swap state for device/group D.
#-
#- EXAMPLES
#-   tellstickControllerRfcmd -l
#-   tellstickControllerRfcmd --set device_alias on
#-   tellstickControllerRfcmd --swap device_alias
#-   tellstickControllerRfcmd -d -f myConfigFile.conf
#-
#- DEPENDENCIES
#-   The following Perl modules needs to be installed:
#-   DB_File, DateTime and DateTime::Event::Sunrise
#-
#- FILES
#-   tellstickController.conf
#-   tellstickController.db
#-   tellstickController.log
#-   tellstickController.pid
#-
#- CONFIGURATION
#-   The configuration file consists of a number of settings 'Set', a number of
#-   device aliases 'Alias', and a number of device rules 'Rules'.
#-  
#-   The settings controls sunrise/sunset, logfile, pidfile, etc.
#-
#-   The aliases configures device name, channel, code, etc.
#- 
#-   The groups configures a list of devices and a delay time.
#- 
#-   The rules can be written as a string containing two parts.
#-   The first part is optional and ends with an '/'. It can contain one or more
#-   of these keywords 'Weekend', 'Workweek', 'Monday', 'Tuesday', 'Wednesday',
#-   'Thursday', 'Friday', 'Saturday' and 'Sunday'. A specified date like
#-   '2008-03-18', '2008-03-##', '####-03-##' or '####-##-15' is also allowed.
#-   The second part is mandatory and is either a expression or the keyword 'No'.
#-   The expression can contain a micture of these keywords 'Sunrise', 'Sunset',
#-   'Random(HH:MM)' and 'HH:MM'.
#-
#-   Example rule: Weekend/07:15
#-   Example rule: Monday+Sunday/07:15
#-   Example rule: 2008-03-##/12:10
#-   Example rule: 07:15+Random(02:00)
#-   Example rule: Sunset-00:30
#-
#- AUTHOR
#-   Original version written by Rickard Andersson
#-
#- LICENSE
#-   Copyright (C) 2008-2010 Rickard Andersson. Version 2.0.0
#-   This program comes with ABSOLUTELY NO WARRANTY.
#-   This is free software, and you are welcome to redistribute it
#-   under certain conditions; See license file for details.
#-
