package SM::Server;

use strict;
use warnings;
use SM::Simulator;
use overload '""' => sub { $_[0]->id };

my $Counter;

sub new {
    my $class = ref $_[0] ? ref shift : shift;
    bless {
        id    => $Counter++,
        queue => [],
        busy  => 0,
        service_time => @_ ? shift : sub { 1 },
    }, $class;
}

sub _serve_next {
    my $self = shift;
    my $queue = $self->_queue;
    if ($self->busy and @$queue) {
        my $client = shift @$queue;
        $self->log("==> Client $client");
    }
    if (@$queue) {
        $self->{busy} = 1;
        $self->log("serves Client $queue->[0].");
        my $service_time = $self->gen_service_time();
        my $now = SM::Simulator->now;
        SM::Simulator->schedule(
            $now + $service_time,
            =>
            sub { $self->_serve_next }
        );
    } else {
        $self->{busy} = 0;
    }
}

sub id {
    $_[0]->{id};
}

sub busy {
    $_[0]->{busy};
}

sub gen_service_time {
    $_[0]->{service_time}->();
}

sub _queue {
    $_[0]->{queue};
}

sub join_queue {
    my $self = shift;
    $self->log("<== Client @_");
    push @{ $self->{queue} }, @_;
    if (not $self->busy) { $self->_serve_next; }
}

sub queue_len {
    my $self = shift;
    scalar(@{$self->_queue});
}

sub log {
    my $self = shift;
    SM::Simulator::log("<Server $self> @_");
}

1;
__END__

=head1 NAME

SM::Server - Server entity in the supermarket

=head1 SYNOPSIS

    use SM::Server;
    my $server = SM::Server->new( sub { rand(3) } );
    $server->join_queue($client_1, $client_2);

    print $server->id, " : ", $server->queue_len();

    # or equivalently:
    print "$server : ", $server->queue_len();

    if ($server->busy) { ... }

=head1 DESCRIPTION

This class implements the server entity in the supermarket problem space.
Every server has an ID which is guaranteed to be unique in the whole application.

=head1 METHODS

=over

=item C<< $obj->new( sub {...} ) >>

Server instance constructor. It takes an optional argument,
which is an anonymous sub generating service time periods for each client.

The C<service_time> argument defaults to sub { 1 }

=item C<< $id = $obj->id() >>

Reads the server's ID, which is generated by Server class's internal counter.

=item C<< $bool = $obj->busy() >>

Tests the status of the server, busy or not.

=item C<< $obj->join_queue(@client) >>

Makes a client (or more) join the server's client queue (which could be empty).

=item C<< $len = $obj->queue_len() >>

Returns the length of the server's client queue (in number of clients).

=item C<< $time = $obj->gen_service_time() >>

Generate the next serving time period using the generator passed in via the
constructor.

=item C<< $obj->log("log message") >>

A message logger for the current server.

=back

=head1 INTERNAL METHODS

The following methods are all started by a leading underscore (_), which are not meant
to be used by the user directly.

=over

=item C<< $obj->_serve_next() >>

Informs the server that it should serve the next client. if the server is
currently busy, then the client at service will be popped from the client queue.

=item C<< $obj->_queue() >>

Returns the reference to the server's internal client queue.

=back

=head1 AUTHOR

Agent Zhang E<lt>agentzh@gmail.comE<gt>

=head1 COPYRIGHT

Copyright 2006 by Agent Zhang. All rights reserved.

This library is free software; you can modify and/or modify it under the same terms as
Perl itself.

=head1 SEE ALSO

L<SM>, L<SM::Client>, L<SM::Simulator>.
