package Display::Notifications;

use Clutter;
use POE::Session;
use Set::Object;
use strict;

my $delay = 15;

sub new {
  my ($proto, $kernel, $session, $stage) = @_;
  my $class = ref($proto) || $proto;

  my $self = {
    'kernel' => $kernel,
    'session' => $session,
    'stage' => $stage,
    'moving' => 0,
  };

  $self->{'blocks'} = Set::Object->new();
  @{ $self->{'new'} } = ();
  @{ $self->{'paths'} } = ();
  @{ $self->{'pending_move'} } = ();

  bless ($self, $class);

  $self->{'kernel'}->state('notifications_add', $self, 'add');
  $self->{'kernel'}->state('notifications_expire', $self, 'expire');

  return $self;
}

sub add {
  my ($self, $kernel, $notification, $logo_file) = @_[OBJECT, KERNEL, ARG0, ARG1];

  my $text = undef;
  if ($notification->isa("Clutter::Label")) {
    $text = $notification;
  } else {
    $text = Clutter::Label->new('Sans 20', $notification);
    $text->set_color(Clutter::Color->parse('White'));
    $text->set_ellipsize('end');
  }

  my $block = Clutter::Group->new();
  $block->set_opacity(0);

  my $bg = Clutter::Rectangle->new(Clutter::Color->parse('Black'));
  $bg->set_width($self->{'stage'}->get_width() - 20);
  $bg->set_opacity(100);

  $bg->set_height($text->get_height() + 10);

  $text->set_position(5, 5);

  $block->set_height($bg->get_height());
  $block->set_anchor_point(1, $block->get_height());
  $block->set_position(10, $self->{'stage'}->get_height() - 10);

  $block->add($bg);
  $block->add($text);

  if (defined $logo_file) {
    my $logo = Clutter::Texture->new($logo_file);
    $block->add($logo);
    $logo->set('keep-aspect-ratio' => 1);
    $logo->set('sync-size' => 1);
    $logo->set_height($text->get_height());
    $logo->set_position(5, 5);

    $text->set_width($bg->get_width() - $logo->get_width() - 20 - 20);
    $text->set_x(20 + $logo->get_width() + 20);
  } else {
    $text->set_width($bg->get_width() - 20);
    $text->set_x(20);
  }

  if ($self->{'blocks'}->size() > 0) {
    if (! $self->{'moving'}) {
      push @{ $self->{'new'} }, $block;

      $self->move_blocks();
    } else {
      push @{ $self->{'pending_move'} }, $block;
    }
  } else {
    push @{ $self->{'new'} }, $block;

    fade_in(undef, $self);
  }
}

sub expire {
  my ($self) = @_[OBJECT];

  for my $block ($self->{'blocks'}->members()) {
    if ($block->{'expire'} <= time()) {
      my $old_effect =
        Clutter::EffectTemplate->new_for_duration(1000,
                                                  'main::smoothstep_inc' );
      my $old =
        Clutter::Effect->fade($old_effect, $block->{'block'}, 0,
                              $self->can('post_fade_out'), $self);
      $old->start();
    }
  }

  # What a hack!  It seems that multiple alarm_ardds aren't being
  # honoured.  So, we check and see when the next expire should run, and
  # call ourselves again then.
  while (scalar(@{ $self->{'expire_times'} }) > 0
      && $self->{'expire_times'}[0] <= time()) {
    my $old = shift @{ $self->{'expire_times'} };
  }

  if (defined $self->{'expire_times'}[0]) {
    my $timer = shift @{ $self->{'expire_times'} };
    $self->{'kernel'}->alarm_add('notifications_expire', $timer);
  }

  return 1;
}

sub post_fade_out {
  my ($old_timeline, $self) = @_;

  for my $block ($self->{'blocks'}->members()) {
    if ($block->{'expire'} <= time()) {
      $self->{'blocks'}->remove($block);

      $block->{'block'}->remove_all()
    }
  }
}

sub fade_in {
  my ($score, $self) = @_;

  # Clean out no longer required state.
  $self->{'paths'} = ();

  while (my $block = shift @{ $self->{'new'} }) {
    if (defined $block) {
      $self->{'stage'}->add($block);
      my $effect = Clutter::EffectTemplate->new_for_duration(1000, 'main::smoothstep_inc');
      my $timeline = Clutter::Effect->fade($effect, $block, 255);

      my $expire = time() + $delay;

      $self->{'blocks'}->insert( {
        'expire' => $expire, 
        'block'  => $block
      });

      $self->{'kernel'}->delay_add('notifications_expire', $delay);
      push @{ $self->{'expire_times'} }, $expire;

      $timeline->start();
      $self->{'moving'} = 0;
    }
  }

  # Check and see if there are any blocks that have been added since we
  # started moving, and start the process of showing them.
  if (scalar(@{ $self->{'pending_move'} }) > 0) {
    while (my $block = shift @{ $self->{'pending_move'} }) {
      push @{ $self->{'new'} }, $block;
    }

    $self->move_blocks();
  }
}

sub move_blocks {
  my ($self) = @_;

  # We're in the process of moving blocks.
  $self->{'moving'} = 1;

  my $score = Clutter::Score->new();
  $score->signal_connect('completed' => $self->can('fade_in'), $self);

  # Work out the total height
  my $height = 10;
  for my $new (@{ $self->{'new'} }) {
    $height += $new->get_height();
  }

  for my $obj ($self->{'blocks'}->members()) {
    my $block = $obj->{'block'};
    my $timeline = Clutter::Timeline->new_for_duration(1000);
    my $alpha = Clutter::Alpha->new($timeline, 'main::smoothstep_inc');

    my $path = Clutter::Behaviour::Path->new($alpha,
      [ $block->get_position() ],
      [ $block->get_x(), $block->get_y() - $height ]);
    $path->apply($block);

    push @{ $self->{'paths'} }, $path;

    $score->append(undef, $timeline);
  }

  $score->start();
  $self->{'score'} = $score;
}

1;
