# Simple cron-like component.
# Implements a recurring event generator that
# is used like a plain Perl callback library.
# Copyright 2004 by Rocco Caputo.  Free software.
# Same terms as Perl itself.  Have fun!

package ChronicCallback;

use warnings;
use strict;

use POE;

# A POE session is used to manage multiple re-
# curring timers.

POE::Session->create(
  inline_states => {
    _start       => \&handle_setup,
    ticker_start => \&handle_new_ticker,
    ticker_stop  => \&handle_kill_ticker,
    got_tick     => \&handle_wakeup,
  }
);

# Internal event handlers.

# As the session starts, set an alias for it.
# The alias is the class name, so the public
# class members can easily refer to it.

sub handle_setup {
  $_[KERNEL]->alias_set(__PACKAGE__);
}

# Start a new recurring callback.  Record the
# request in the session's heap, and start a
# timer to trigger the callback.

sub handle_new_ticker {
  my $param = $_[ARG0];

  my $req_id = $param->{RequestId};
  $_[HEAP]->{req_to_timer}{$req_id} =
    $_[KERNEL]->delay_set(
      "got_tick", $param->{Interval},
      $param,
    );
}

# Kill a recurring callback.  Remove the saved
# information from the heap, and use it to remove
# the associated POE timer.

sub handle_kill_ticker {
  my $req_id = $_[ARG0];

  my $timer_id = delete(
    $_[HEAP]->{req_to_timer}{$req_id}
  );
  return unless $timer_id;

  $_[KERNEL]->alarm_remove($timer_id);
}

# A callback is triggered.  Invoke the calback,
# and schedule a new timer.  Recurring timers are
# implemented in POE by rescheduling them from
# their handlers.

sub handle_wakeup {
  my $param = $_[ARG0];

  $param->{Callback}->();

  my $req_id = $param->{RequestId};
  $_[HEAP]->{req_to_timer}{$req_id} =
    $_[KERNEL]->delay_set(
      "got_tick", $param->{Interval},
      $param,
    );
}

# These are the public methods.

my $next_id = 1;

# Start a new timer.  Post an event to the
# internal POE session, telling it to begin a
# recurring timer.  The new time ID is returned
# as a handle for stopping it.
#
# The post() and dispatch are a form of inter-
# session communication, allowing one session to
# trigger activities in another.

sub start {
  my ($class, %param) = @_;

  $param{RequestId} = $next_id++;

  $poe_kernel->post(
    __PACKAGE__, "ticker_start",
    \%param,
  );

  return $param{RequestId};
}

# Stop a timer, based on its handle.

sub stop {
  my ($class, %param) = @_;

  $poe_kernel->post(
    __PACKAGE__, "ticker_stop",
    $param{Id},
  );
}

# Run the timer(s) until they are done.

sub run {
  POE::Kernel->run();
}

1;
