#  You may distribute under the terms of the GNU General Public License
#
#  (C) Paul Evans, 2008-2010 -- leonerd@leonerd.org.uk

package Circle::WindowItem;

# An abstract role used by objects that should be placed in FE windows or tabs
# Combines the behaviours of:
#   having display events
#   responding to typed lines of text

use strict;
use warnings;

use Carp;

use Tangence::Constants;

use base qw( Circle::Commandable );

use Circle::TaggedString;

use Circle::Widget::Box;
use Circle::Widget::Scroller;

our %METHODS = (
   reset_level => {
      args => [],
      ret  => '',
   },

   get_widget => {
      args => [],
      ret  => 'obj',
   },
);

our %PROPS = (
   tag => {
      dim   => DIM_SCALAR,
      type  => 'str',
      smash => 1,
   },

   level => {
      dim   => DIM_SCALAR,
      type  => 'int',
      smash => 1,
   },
);

sub init_prop_level
{
   return 0;
}

sub bump_level
{
   my $self = shift;
   my ( $newlevel ) = @_;

   return if $self->get_prop_level >= $newlevel;

   $self->set_prop_level( $newlevel );
}

sub method_reset_level
{
   my $self = shift;

   $self->set_prop_level( 0 );
}

sub push_displayevent
{
   my $self = shift;
   my ( $event, $args ) = @_;

   foreach ( values %$args ) {
      if( !ref $_ ) { 
         next;
      }
      elsif( eval { $_->isa( "Circle::TaggedString" ) } ) {
         $_ = $_->squash;
      }
      else { 
         $_ = "[[TODO: Not sure how to handle $_]]";
      }
   }

   my $scroller = $self->get_widget_scroller;
   $scroller->push_event( $event, $args );
}

sub respond
{
   my $self = shift;
   my ( $text, %opts ) = @_;

   $self->push_displayevent( "response", { text => $text } );
   $self->bump_level( $opts{level} ) if $opts{level};

   return;
}

sub responderr
{
   my $self = shift;
   my ( $text, %opts ) = @_;

   $self->push_displayevent( "error", { text => $text } );
   $self->bump_level( $opts{level} ) if $opts{level};

   return;
}

sub respond_table
{
   my $self = shift;
   my ( $tableref, %opts ) = @_;

   # We need to avoid using join() or sprintf() here, because any of the table
   # cell arguments might be TaggedString objects. The CORE functions won't
   # respect this taggnig.

   my $colsep = exists $opts{colsep} ? delete $opts{colsep} : "  ";

   my $headings = delete $opts{headings};

   my @table = @$tableref;

   my @width;

   foreach my $r ( $headings, @table ) {
      next unless defined $r;

      foreach my $c ( 0 .. $#$r ) {
         my $d = $r->[$c];
         $width[$c] = length $d if !defined $width[$c] or length $d > $width[$c];
      }
   }

   if( $headings ) {
      my $text = Circle::TaggedString->new();
      foreach my $c ( 0 .. $#$headings ) {
         $text->append( $colsep ) if $c > 0;

         my $col = $headings->[$c];
         $text->append_tagged( $col . ( " " x ( $width[$c] - length $col ) ),
                               u => 1 );
      }
      $self->respond( $text, %opts );
   }

   foreach my $tr ( @table ) {
      my $text = Circle::TaggedString->new();
      foreach my $c ( 0 .. $#width ) {
         $text->append( $colsep ) if $c > 0;

         my $col = $tr->[$c];
         $text->append( $col . ( " " x ( $width[$c] - length $col ) ) );
      }
      $self->respond( $text, %opts );
   }
}

sub command_clear
   : Command_description("Clear the scrollback buffer")
   : Command_opt('keeplines=$', desc => "keep this number of lines")
{
   my $self = shift;
   my ( $opts, $cinv ) = @_;

   my $keeplines = $opts->{keeplines} || 0;

   my $scroller = $self->get_widget_scroller;

   my $to_delete = scalar @{ $scroller->get_prop_displayevents } - $keeplines;

   $scroller->shift_prop_displayevents( $to_delete ) if $to_delete > 0;

   return;
}

###
# Widget
###

sub method_get_widget
{
   my $self = shift;

   return $self->{widget} ||= $self->make_widget();
}

# Subclasses might override this, but we'll provide a default
sub make_widget
{
   my $self = shift;

   my $registry = $self->{registry};

   my $box = $registry->construct(
      "Circle::Widget::Box",
      orientation => "vertical",
   );

   $box->add( $self->get_widget_scroller, expand => 1 );

   $box->add( $self->get_widget_statusbar ) if $self->can( "get_widget_statusbar" );

   $box->add( $self->get_widget_commandentry );

   return $box;
}

sub get_widget_scroller
{
   my $self = shift;

   return $self->{widget_displayevents} if defined $self->{widget_displayevents};

   my $registry = $self->{registry};

   my $widget = $registry->construct(
      "Circle::Widget::Scroller",
      scrollback => 1000, # TODO
   );

   return $self->{widget_displayevents} = $widget;
}

1;
