package X10;

use Data::Dumper;
use File::Basename;
use FileHandle;
use POSIX;

use strict;

use X10::Macro;
use X10::MacroProc;
use X10::SchedEvent;
use X10::Scheduler;
use X10::Server;

use vars qw($VERSION);

$VERSION = 0.03;

sub new
{
   my $type = shift;

   my $self = bless { @_ }, $type;

   $self->{verbose} = 1 if $self->{debug};
   $self->{logger} = sub { $self->syslog(@_) };

   unless ($self->{controller_type})
   {
      warn "Interface type must be specified\n";
      return undef;
   }

   my $controller = $self->{controller_type};

   if (eval "require $controller")
   {
      $controller->import;	# just in case
   }
   else
   {
      die "Can't load module for $controller: ", $@;
   }

   $self->{controller} = $controller->new(
	port => $self->{controller_port},
	debug => $self->{debug},
	verbose => $self->{verbose},
	logger => sub { $self->syslog(@_) },
	);

   $self->{controller}->register_listener(
	sub { $self->syslog('info', "Event: %s", $_[0]->as_string) }
	);

   if (exists $self->{devices})
   {
      # load device config
   }

   if (exists $self->{schedulerconfig})
   {
      $self->{scheduler} = new X10::Scheduler(
	configfile => $self->{schedulerconfig},
	controller => $self->{controller},
	debug => $self->{debug},
	verbose => $self->{verbose},
	logger => sub { $self->syslog(@_) },
	latitude => 38.74274,
	longitude => -90.560143,
	);

      unless ($self->{scheduler})
      {
         warn "Problem creating macro processor";
         return undef;
      }

   }

   if (exists $self->{macroconfig})
   {
      $self->{macrop} = new X10::MacroProc(
	configfile => $self->{macroconfig},
	controller => $self->{controller},
	debug => $self->{debug},
	verbose => $self->{verbose},
	logger => sub { $self->syslog(@_) },
	);

      unless ($self->{macrop})
      {
         warn "Problem creating macro processor";
         return undef;
      }

   }

   if (exists $self->{server_port})
   {
      $self->{server} = new X10::Server(
	controller => $self->{controller},
	debug => $self->{debug},
	verbose => $self->{verbose},
	server_port => $self->{server_port},
	logger => sub { $self->syslog(@_) },
	);

      unless ($self->{server})
      {
         warn "Problem creating network server";
         return undef;
      }
   }

   return $self;
}


sub run
{
   my $self = shift;

   # this method plans to never return...

   $self->{running} = 1;

   $SIG{'INT'} = sub { $self->{running} = 0; };
   $SIG{'TERM'} = sub { $self->{running} = 0; };

   $self->syslog('info', "%s service starting", $self->{controller_type});

   my $next_wakeup = 0;

   X10RUNMAINLOOP:
   while ($self->{running})
   {
      $self->{logger}->('info', "Entering mainloop") if $self->{debug};

      my %fdindex;
      foreach my $module (
		grep { exists $self->{$_} } qw(controller server macrop)
		)
      {
         foreach my $fd ($self->{$module}->select_fds)
         {
            $fdindex{$fd} = $self->{$module};
         }
      }

      $self->syslog('info', "All FDs are %s\n", join(', ', keys %fdindex)) if $self->{debug};

      my $rfd = '';
      foreach (keys %fdindex) { vec($rfd, $_, 1) = 1; }

      # done setting up FD array

      # figure out if we have to wake up at a certain time:

      my $timeout = undef;

      if ($self->{scheduler})
      {
         my $next_event_time = $self->{scheduler}->next_event_time;

         if ($next_event_time)
         {
            $timeout = $next_event_time - int(time);
            $timeout = 0 if ($timeout < 0);
         }

         if ( (defined $timeout) && $next_event_time != $next_wakeup)
         {
            $next_wakeup = $next_event_time;
            $self->syslog('info', "Next Scheduled Event: %s (%s seconds away)",
		strftime("%a %b %e %H:%M %Y", localtime($next_event_time)),
		$timeout,
		);
         }

      }

      # done calculating wakeup time

      my $readers;
      my $fdcount = select($readers=$rfd, undef, undef, $timeout);

      if ($fdcount > 0)
      {
         $self->{logger}->('info', "Got %s FDs to handle", $fdcount) if $self->{debug};

         foreach (keys %fdindex)
         {
            if (vec($readers, $_, 1))
            {
               $self->syslog('info', "Processing input on FD %s (%s)\n", $_, $fdindex{$_}) if $self->{debug};
               $fdindex{$_}->handle_input;
            }
         }

      }
      elsif ($fdcount < 0 && $! != 4)	# ignore Interrupted System Call
      {
         $self->{logger}->('info', "Error %d in select(): %s", $!, $!);
      }

      if ($self->{scheduler})
      {
         $self->{scheduler}->run;
      }

   }

   $self->syslog('info', "%s service shutting down", $self->{controller_type});

}

sub syslog
{
   my $self = shift;

   my $level = shift;
   my $format = shift;
   my $message = sprintf($format, @_);

   my $facility = "local5";
   my $tag = sprintf "%s[%s]",
        basename($0, ".pl"),
        $$,
        ;

   if ($self->{debug})
   {
      printf "syslog message: %s\n", $message;
   }
   else
   {
      my $fh = new FileHandle;
      $fh->open("|/usr/bin/logger -p $facility.$level -t $tag");
      $fh->print($message);
      $fh->close;
   }
}



1;

