The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package RT::Client::Console::Session;

use strict;
use warnings;

use parent qw(RT::Client::Console);

use POE;
use List::Util qw(max first);
use Curses::Widgets::Label;
use Curses::Forms;

use relative -to => "RT::Client::Console::Session", 
        -aliased => qw(Ticket);

use Params::Validate qw(:all);

# global vars

my %sessions;
my @modal_sessions;
my $modal_index = 0;

sub get_sessions { return %sessions }

sub get_modal_sessions { return @modal_sessions }

# a small wrapper around POE::Session
sub create {
    my ($class, $name, %args) = @_;
    my $is_modal = delete $args{_is_modal};
    $args{inline_states}{_start} = sub { 
        my ($kernel, $heap) = @_[ KERNEL, HEAP ];
        $kernel->alias_set($name);
#        $kernel->call($name, 'window_resize', undef, undef);
        $kernel->call($name, 'init');
        $kernel->call($name, '__window_resize', !$is_modal);
    };
    $args{inline_states}{_unalias} = sub {
        my ($kernel, $heap) = @_[ KERNEL, HEAP ];
        my @aliases = $kernel->alias_list();
        foreach my $alias (@aliases) {
            $kernel->alias_remove($alias);
        }
    };
    # automatic window change handling
	$args{inline_states}{__window_resize} = sub {
        my ($kernel, $heap, $is_init_mode) = @_[ KERNEL, HEAP, ARG0 ];
        my ($old_screen_h, $old_screen_w) = @{$heap}{qw(screen_h screen_w)};
        my ($screen_w, $screen_h);
        $class->get_curses_handler()->getmaxyx($screen_h, $screen_w);
        $heap->{screen_h} = $screen_h;
        $heap->{screen_w} = $screen_w;
        print STDERR " -- $name -- __window resize " . $heap->{screen_w} . " | " . $heap->{screen_h} . "\n";
        $is_init_mode or $kernel->yield('draw');
        # give a chance to the session to do something specific
        $kernel->call($name, 'window_resize', $old_screen_h, $old_screen_w);
    };

	if ($is_modal) {
        my $keys = delete $args{keys};
        $args{inline_states}{key_handler} = sub {
            my ( $kernel, $heap, $keystroke ) = @_[ KERNEL, HEAP, ARG0 ];
            if (!defined $keys) { 
                $kernel->yield('private_key_handler', $keystroke);
                $kernel->yield('draw');
                return;
            }
            exists $keys->{$keystroke} or return;
            if ($keys->{$keystroke}{code}->()) {
                $class->remove_modal($name);
            } else {
                $kernel->yield('draw');
            }
        };
        POE::Session->create(%args);
        push @modal_sessions, $name;
    } else {
        $sessions{$name} = {
                            poe_object => POE::Session->create(%args),
                            displayed => 1,
                           };
    }
    return $name;
}

# create a modal session
sub create_modal {
    my ($class, %args) = @_;
    $args{_is_modal} = 1;
    my $name = 'modal_' . ++$modal_index;
    return create($class, $name, %args);
}

sub create_choice_modal {
    my ($class, %args) = @_;

    my $title = "[ $args{title} ]";    
    my $text = $args{text} . "\n";

    exists $args{interactive} or $args{interactive} = 1;
    if ($args{interactive}) {
        $args{keys}{c} ||= { text => "close this dialog",
                             code => sub { return 1 },
                           };
        while (my ($k, $v) = each %{$args{keys}} ) {
            $text .= "$k : " . $v->{text} . "\n";
        }
        $args{keys}{'<^[>'} ||= { text => "close this dialog",
                             code => sub { return 1 },
                           };
    }

    my $height = scalar( () = $text =~ /(\n)/g) + 1;
    my $width = max (map { length } (split(/\n/, $text), $title));

    return create_modal(
        $class,
        keys => $args{keys},
        inline_states => {
            draw => sub {
                my ( $kernel, $heap ) = @_[ KERNEL, HEAP ];
                my $curses_handler = $class->get_curses_handler();
                my $label = Curses::Widgets::Label->new({
                                                         CAPTION     => $title,
                                                         CAPTIONCOL  => 'yellow',
                                                         BORDER      => 1,
                                                         LINES       => $height,
                                                         COLUMNS     => $width,
                                                         Y           => $heap->{screen_h}/2-($height+2)/2,
                                                         X           => $heap->{screen_w}/2-($width+2)/2,
                                                         VALUE       => $text,
                                                         FOREGROUND  => 'white',
                                                         BACKGROUND  => 'blue',
                                                         BORDERCOL   => 'white',
                                                        });
                $label->draw($curses_handler);
            }
        },
                       );

}

sub create_wait_modal {
    my ($class, $text) = @_;
    return create_choice_modal($class,
                               title => 'Please wait',
                               text => $text,
                               interactive => 0,
                              );
}

sub execute_wait_modal {
    my ($class, $text, $code) = @_;
    my $modal_session_name = 
      create_wait_modal($class, $text);
    $poe_kernel->call($modal_session_name, 'draw');
    my @ret;
    eval { @ret = wantarray ? $code->() : scalar($code->()) };
	my $save_error = $@;
    remove_modal($class, $modal_session_name);
    $save_error and die $save_error;
    return @ret;
}

sub remove_modal {
    my ($class, $modal_session_name) = @_;
    # stop modal mode
    pop @modal_sessions;
    remove($class, $modal_session_name);
    $poe_kernel->post('key_handler', 'compute_keys');
    # if no ticket is displayed, we clear the background
    Ticket->get_current_ticket() or $class->cls();
   return;
}

# start the POE main loop
sub run {
    my ($class) = @_;
    $poe_kernel->run();
}

# display/hide a session
sub set_display {
    my ($class, $session_name, $display_state) = @_;
    $sessions{$session_name}{displayed} = $display_state ? 1 : 0;
}

=head2 remove

Removes a session

  input : a session name

=cut

sub remove {
    my ($class, $session_name) = @_;
    delete $sessions{$session_name};
    $poe_kernel->call($session_name, '_quit');
    $poe_kernel->call($session_name, '_unalias');

}

sub remove_all {
    my ($class) = @_;
    my %sessions = $class->get_sessions();
    my @sessions_names = keys(%sessions);
    foreach (@sessions_names) {
        $class->remove($_);
    }
}

sub execute_textmemo_modal {
    my $class = shift;
    my %params = validate( @_, { foreground => { type => SCALAR,
                                                 default => 'white',
                                               },
                                 background => { type => SCALAR,
                                                 default => 'blue',
                                               },
                                 border_color => { type => SCALAR,
                                                   default => 'yellow', 
                                                 },
                                 text       => { type => SCALAR, default => '' },
                                 title      => { type => SCALAR },
                               }
                         );

    my $title = "[ $params{title} ]";    
    my $text = $params{text};

	my ($fg, $bg, $cfg) = ('white', 'blue', 'yellow');

	my ($form, @lines, $max);
	my ($cols, $lines, $bx, $by);

	# Build array of buttons to display
	my @buttons = qw(OK Cancel);

	# Calculate the necessary dimensions of the message box, based
	# on both the button(s) and the length of the message.
	$max = max(15 * @buttons + 2 * $#buttons + 1, 30);
	use Curses;
	@lines = textwrap($text, $COLS - 4);

	foreach (@lines) { $max = length($_) if length($_) > $max };

	# Calculate cols and lines
	$cols = $max + 5;
	$lines = max(scalar(@lines), 10) + 3 + 5;

	# Exit if the geometry exceeds the display
	unless ($cols + 2 < $COLS && $lines + 2 < $LINES) {
		warn "dialog:  Calculated geometry exceeds display geometry!";
		return 0;
	}

	# Calculate upper-left corner of the buttons
	$bx = 10 * @buttons + 2 * $#buttons + 1;
	$bx = int(($cols - $bx) / 2);
	$by = $lines - 3;

	local *btnexit = sub {
		my $f = shift;
		my $key = shift;

		return unless $key eq "\n";
		$f->setField(EXIT => 1);
	};

	$form = Curses::Forms->new({
    AUTOCENTER    => 1,
    DERIVED       => 0,
    COLUMNS       => $cols,
    LINES         => $lines,
    CAPTION       => $title,
    CAPTIONCOL    => $cfg,
    BORDER        => 1,
    FOREGROUND    => $fg,
    BACKGROUND    => $bg,
    FOCUSED       => 'Memo',
    TABORDER      => ['Memo', 'Buttons'],
    WIDGETS       => {
      Buttons     => {
        TYPE      => 'ButtonSet',
        LABELS    => [@buttons],
        Y         => $by,
        X         => $bx,
        FOREGROUND    => $fg,
        BACKGROUND    => $bg,
        BORDER    => 1,
        OnExit    => *btnexit,
        },
      Memo       => {
        TYPE      => 'TextMemo',
        Y         => 0,
        X         => 0,
        COLUMNS   => $max ,
        LINES     => max(scalar @lines, 10) + 2,
        VALUE     => $text,
		CAPTION   => 'Enter new comment',
		CAPTIONCOL => $cfg,
        FOREGROUND => $fg,
        BACKGROUND => $bg,
        },
      },
    });
	$form->execute;

	return ( $form->getWidget('Buttons')->getField('VALUE'),
			 $form->getWidget('Memo')->getField('VALUE')
		   );
}

1;