#!/usr/bin/perl
# vim: ts=2 sw=2 expandtab

use strict;
use warnings;

use POE;             # We're going to use POE here.
POE::Kernel->run();  # Silence run() warning.  See POE docs.

use File::Basename;
use lib dirname($0) . '/lib';
use ExampleHelpers qw(
  initialize_completion update_time print_input
);
use Term::ReadLine 1.09;

# Mark time with a single-purpose POE session.
#
# It's often better to divide programs with multiple concerns into
# loosely coupled units, each addressing a single concern.

POE::Session->create(
  inline_states => {
    _start => sub {
      $_[KERNEL]->delay(tick => 1);
      $_[KERNEL]->alias_set('ticker');
    },
    tick => sub {
      $_[KERNEL]->delay(tick => 1);
      update_time();
    },
    shutdown => sub {
      $_[KERNEL]->delay(tick => undef);
    },
  },
);

# Create a Term::ReadLine object.
# Initialize completion to test whether tab-completion works.
# Hook Term::ReadLine into POE so everybody is happy.
# Get a line of input while POE continues to dispatch events.
# Display the line and the time it took to receive.

my $term = Term::ReadLine->new('...');
initialize_completion($term);
drive_with_poe($term);

my $input = $term->readline('> ');
print_input($input);

# Unhook Term::ReadLine from POE.  This clears the POE::Session
# callbacks in Term::ReadLine.
#
# POE shuts down the Term::ReadLine driver session when it sees that
# the callbacks have been relinquished.  This happens automatically
# because the session isn't doing anything else.  If it were also
# doing other things, those would need to be shut down too.
#
# This is significant in programs that call POE::Kernel->run(), as the
# main event loop will not exit until all sessions shut down.

$term->event_loop(undef);

# As previously mentioned, POE::Kernel->run() will continue to run for
# as long as sessions are doing work.  We must shut down the separate
# time-keeping session so that POE::Kernel->run() will return.

POE::Kernel->post(ticker => 'shutdown');

# Actually call POE::Kernel->run().  This is only a test to verify
# whether the time-keeping and Term::ReadLine sessions do indeed shut
# down.  It's otherwise not needed in this very simple example.

POE::Kernel->run();
exit;

# This function takes a Term::ReadLine object and drives it with POE.
# Abstracted into a function for easy reuse.

sub drive_with_poe {
  my ($term_readline) = @_;

  my $waiting_for_input;

  POE::Session->create(
    inline_states => {

      # Initialize the session that will drive Term::ReadLine.
      # Tell Term::ReadLine to invoke a couple POE event handlers when
      # it's ready to wait for input, and when it needs to register an
      # I/O watcher.

      _start => sub {
        $term_readline->event_loop(
          $_[SESSION]->callback('term_readline_waitfunc'),
          $_[SESSION]->callback('term_readline_regfunc'),
        );
      },

      # This callback is invoked every time Term::ReadLine wants to
      # read something from its input file handle.  It blocks
      # Term::ReadLine until input is seen.
      #
      # It sets a flag indicating that input hasn't arrived yet.
      # It watches Term::ReadLine's input filehandle for input.
      # It runs while it's waiting for input.
      # It turns off the input watcher when it's no longer needed.
      #
      # POE::Kernel's run_while() dispatches other events (including
      # "term_readline_readable" below) until $waiting_for_input goes
      # to zero.

      term_readline_waitfunc => sub {
        my $input_handle = $_[ARG1][0];
        $waiting_for_input = 1;
        $_[KERNEL]->select_read($input_handle => 'term_readline_readable');
        $_[KERNEL]->run_while(\$waiting_for_input);
        $_[KERNEL]->select_read($input_handle => undef);
      },

      # This callback is invoked as Term::ReadLine is starting up for
      # the first time.  It saves the exposed input filehandle where
      # the "term_readline_waitfunc" callback can see it.

      term_readline_regfunc => sub {
        my $input_handle = $_[ARG1][0];
        return $input_handle;
      },

      # This callback is invoked when data is seen on Term::ReadLine's
      # input filehandle.  It clears the $waiting_for_input flag.
      # This causes run_while() to return in "term_readline_waitfunc".

      term_readline_readable => sub {
        $waiting_for_input = 0;
      },
    },
  );
}