package Railway::Train;
use Evo::Base 'Railway::Cleanup';
use Evo::Util;

has 'manager';

__PACKAGE__->attr_cleanup([qw(done cargo index)]);
__PACKAGE__->attr_cleanup('way' => sub { $_[0]->manager->build_way });
__PACKAGE__->register_cleanup('power');

sub power {
  my $self = shift;
  $self->{power} //= 0;
  return $self->{power} unless @_;

  my $switch = shift;
  Carp::croak qq{Same power ($switch) setted. Using a wrong type of train?}
    if !!$self->{power} == !!$switch;
  $self->{power} = $switch;
  $self;
}

# main loop
sub run($self) {
  Carp::croak 'no power, dispatch or start first' unless $self->power;

  while ($self->can_run) {
    $self->handle_station($self->get_station);
    return unless $self->power;
    $self->depart;
  }

  Evo::Util::_guard { $self->cleanup }
  sub { $self->done && $self->done->($self) };
  return;
}

# prepare train or die
sub prepare($self) {
  Carp::croak "already started" if defined $self->{index};
  $self->{index} = 0;
  Carp::croak "no stations" unless $self->can_run;
  $self->power(1);
}

# prepare and run
sub start($self, $done=undef) {
  $self->prepare;
  $done && $self->done($done);
  $self->run;
}

sub can_run($self) {
  my $way = $self->way or return;
  $self->{index} < $way->stations->@*;
}

# invoke or cleanup
sub handle_station($self, $station) {
  Evo::Util::_guard_on_die { $self->cleanup } sub { $station->($self) };
}

# leave current station
sub depart($self) {
  Carp::croak 'start train first' unless defined $self->{index};
  $self->{index}++;
  $self;
}

sub get_station($self) { $self->way->get($self->{index}); }

1;

# ABSTRACT: a running engine

__END__

=pod

=encoding UTF-8

=head1 NAME

Railway::Train - a running engine

=head1 VERSION

version 0.009

=head1 SYNOPSIS

  my $bldr  = Railway::Builder->new;
  my $train = $bldr->build_train;

  $train->way->add(sub { say "station1 " })->add(sub { say "station2" });
  $train->start;    # or $train->prepare->done(sub{say "done"})->run;

=head1 DESCRIPTION

An abstract instance for running your code. Prevent logic errors

=head1 ATTRIBUTES

=head2 manager

A L<Railway::Builder> instance

=head2 index

An index

=head2 way

A L<Railway::Way> instance

=head2 cargo

Cargo for a train

=head2 done

A code that will be passed after train reaches the end of the L</"way">

=head1 METHODS

=head2 power

A switcher for a train. Accepts C<true> or C<false>.
Throws an exception if the same value provided twice 

  $train->power(1)->power(0)

  # error
  $train->power(1)->power(1);

=head2 run

Run an engine

=head2 prepare

Prepare an engine. Starts L</"index"> to 0, L</"power"> to 1, check stations;
If something is wrong, an exception will be thrown

=head2 start

  $train->start();
  $train->start(sub {});

Prepare and run a train. You can provide a callback for L</"done">

=head2 can_run

Check if a way has a stations more than current L</"index">

=head2 finish

This method invokes a L</"done"> callback. Will be called just after a train
will have reached the end of the way

=head2 handle_station

Safely executes a code

=head2 depart

Increase index by one. Raises an error if index isn't defined

=head2 get_station

Get current station

=head1 AUTHOR

alexbyk.com

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2015 by alexbyk.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut
