# $Id: Room.pm,v 1.9 2004/02/04 17:40:58 mig Exp $
######################################
# Comas - Conference Management System
######################################
# Copyright 2003 CONSOL
# Congreso Nacional de Software Libre (http://www.consol.org.mx/)
#   Gunnar Wolf <gwolf@gwolf.cx>
#   Manuel Rabade <mig@mig-29.net>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
######################################

######################################
# Module: Comas::Schedule::Room
# Represents the schedule for a given room
######################################
# Depends on:
#
# Comas::Schedule::Timeslot - Represents a given timeslot in a Comas schedule
# Comas::Proposal - Handles the interaction with a proposal for Comas

package Comas::Schedule::Room;

use strict;
use warnings;
use Carp;
use Comas::Schedule::Timeslot;
use Comas::Proposal;

=head1 NAME
    
Comas::Schedule::Room - Represents the schedule for a given room

=head1 SYNOPSIS

  $room = Comas::Schedule::Room->new(-id=>$id, -db=>$db);

$id is the ID in the database for the room we are creating. $db is an
existing Comas::DB object.

  $ok = $tslot->refresh;

Queries the database and updates its in-memory representation. Every time a
Comas::Schedule::Room method that modifies something in the database is called,
it will call refresh before doing so in order to ensure no changes happened 
since the object was last refreshed.

  $ok = $room->schedule_proposal(-id=>$prop_id);
  $ok = $room->schedule_proposal(-id => $prop_id, -timeslot => $timeslot_id);
  $ok = $room->schedule_proposal(-id => $prop_id, -day => $day, 
                                -start_hr => $start_hr);

  $ok = $room->unschedule_proposal($id);

Removes the specified proposal from the schedule, freeing up (and making 
available, of course) the timeslot it was scheduled on. It will return true
if the proposal was successfully unscheduled or if it was not scheduled in the
first place, and false only if the proposal could not be unscheduled (i.e., if
it does not exist).

  $ok = $room->unschedule_timeslot($id);

Frees up the specified timeslot by removing from the schedule the proposal 
that was set for this timeslot. If the timeslot was already free, no action is
performed and a true value is returned. The specified timeslot will end up
being free and probably available (the availability can only be guaranteed if
the timeslot did originally hold a proposal).

  @tslots = $room->get_timeslots;

Returns the list of timeslots that belong to this room. Each element returned
is a L<Comas::Schedule::Timeslot> object.

  @tslots = $room->get_used_timeslots;

Returns the list of timeslots that belong to this room and have a proposal 
scheduled to them. Each element returned is a L<Comas::Schedule::Timeslot> 
object.

  @tslots = $room->get_free_timeslots;

Returns the list of timeslots that belong to this room and do not have a
proposal assigned to them. Each element returned is a 
L<Comas::Schedule::Timeslot> object.

  @tslots = $room->get_available_timeslots;
  @tslots = $room->get_available_timeslots($type[, $type2[, $type3[, ...]]]);

Returns the list of timeslots that belong to this room and can currently accept
a proposal (optionally, a proposal of the specified types). Each element returned is a L<Comas::Schedule::Timeslot> object.

For an explanation on what is the difference between get_free_timeslots and
get_available_timeslots, please check the discussion in the  B<ATTRIBUTE 
ACCESSORS AND STATUS QUERYING> in the L<Comas::Schedule::Timeslot> 
documentation.

=head1 REQUIRES

Comas::Schedule::Timeslot - Represents a given timeslot in a Comas schedule
Comas::Proposal - Handles the interaction with a proposal for Comas

=head1 SEE ALSO

L<Comas::Schedule> and L<Comas::Schedule::Timeslot> for related funcitonality

=head1 AUTHOR

Gunnar Wolf, gwolf@gwolf.cx

Manuel Rabade, mig@mig-29.net

Comas has been developed for CONSOL, Congreso Nacional de Software Libre,
http://www.consol.org.mx/

=head1 COPYRIGHT

Copyright 2003 Gunnar Wolf and Manuel Rabade

This library is free software, you can redistribute it and/or modify it
under the terms of the GPL version 2 or later.

=cut

sub new {
    my ($class, $room);
    $class = shift;
    $room = {@_};
    bless ($room, $class);

    if (!defined $room->{-id} or !defined $room->{-db} 
	or !ref $room->{-db} or (scalar keys %$room) != 2 ) {
	carp 'Invocation error';
	return undef;
    }

    $room->refresh or return undef;

    return $room;
}

sub refresh {
    my ($room, $sth);
    $room = shift;

    # First of all, get the data directly belonging to this room: Room
    # description
    unless ($sth = $room->{-db}->prepare('SELECT descr FROM room
            WHERE id = ?') and $sth->execute($room->{-id})) {
	carp "Could not query for the room's data";
	return undef;
    }
    unless ( ($room->{-descr}) = $sth->fetchrow_array) {
	carp 'Specified room does not exist';
	return undef;
    }

    # Get all the timeslots belonging to this room from the database
    unless ($sth = $room->{-db}->prepare('SELECT id FROM timeslot WHERE 
            room_id = ? ORDER BY id') and $sth->execute($room->{-id})) {
	carp "Could not query for the room's timeslots";
	return undef;
    }
    $room->{-timeslots} = [];
    while (my ($tslot) = $sth->fetchrow_array) {
	push(@{$room->{-timeslots}}, 
	     Comas::Schedule::Timeslot->new(-id=>$tslot, -db=>$room->{-db}));
    }

    return $room;
}

sub schedule_proposal {
    my ($room, $data, $prop, $prop_type, @tslots, $old_tslot);
    $room = shift;
    $data = { @_ };

    unless (defined $data->{-id} and 
	    $prop = Comas::Proposal->new(-id=>$data->{-id}, 
					 -db=>$room->{-db})) {
	carp 'Could not get information about the specified proposal';
	return undef;
    }
    $prop_type = $prop->get_prop_type_id;
    $old_tslot = $prop->get_timeslot_id;

    @tslots = ();
    for my $tslot (grep {$_->is_available($prop_type) or
			 ($old_tslot and $_->get_id == $old_tslot)} 
		   $room->get_timeslots) {
	push (@tslots, $tslot);
    }

    if (defined $data->{-timeslot}) {
	# We were asked to assign this proposal to a specific timeslot - Check
	# only if that timeslot is assigned to this room and assign it.
	my $tslot;
	unless (($tslot) = grep {$_->get_id == $data->{-timeslot}} 
		$room->get_timeslots) {
	    carp 'Requested timeslot is not part of this room';
	    return undef;
	}
	unless (grep {$_->get_id == $data->{-timeslot}} @tslots) {
	    carp 'Requested timeslot cannot hold specified proposal type';
	    return undef;
	}
	return $tslot->schedule($data->{-id});
    } elsif (defined $data->{-day} and defined $data->{-start_hr}) {
	# Find the compatible timeslots on this (day, start_hr) and assign the 
	# proposal to one of them
	my @tslots = grep {$_->get_start_hr eq $data->{-start_hr} and
			   $_->get_day eq $data->{-day}} @tslots;
	return $room->_schedule_in_tslot_list($data->{-id}, \@tslots);
    } else {
	# No specific data was received - Just assign the proposal to any
	# available compatible timeslot.
	return $room->_schedule_in_tslot_list($data->{-id}, [@tslots]);
    } 
}

sub unschedule_proposal {
    my ($room, $prop_id, $prop);
    $room = shift;
    $prop_id = shift;
    # We create a Comas::Proposal object in order to find out where is the
    # timeslot scheduled. Once we have it, we just call unschedule_timeslot.
    unless ($prop = Comas::Proposal->new(-id=>$prop_id, -db=>$room->{-db})) {
	carp 'Could not find the timeslot related to the specified proposal';
	return undef;
    }
    defined $prop->{-timeslot_id} &&
	return $room->unschedule_timeslot($prop->{-timeslot_id});
    return 1; 
}

sub unschedule_timeslot {
    my ($room, $tslot_id, $tslot);
    $room = shift;
    $tslot_id = shift;
    unless ($tslot = Comas::Schedule::Timeslot->new(-id=>$tslot_id, 
						    -db=>$room->{-db})) {
	carp 'Could not get the relevant data for the requested timeslot';
	return undef;
    }
    return $tslot->unschedule;
}

sub get_timeslots {
    my $room = shift;

    # Refresh the object, we do not want to report old cruft
    $room->refresh;

    if (!ref $room->{-timeslots}) {
	carp 'Could not find any timeslots belonging to this room';
	return undef;
    }
    return @{$room->{-timeslots}};
}

sub get_used_timeslots {
    my $room = shift;
    return grep {! $_->is_free} $room->get_timeslots;
}

sub get_free_timeslots {
    my $room = shift;
    return grep {$_->is_free} $room->get_timeslots;
}

sub get_available_timeslots {
    my ($room, @types, @free, @used, @res);
    $room = shift;
    @types = @_;
    return grep {$_->is_available(@types)} $room->get_timeslots;
}

################################################################################
# Private methods - Not for human consumption

sub _schedule_in_tslot_list {
    # Schedules a proposal in one of a list of timeslots
    # Receives a proposal ID and a reference to a list of timeslot objects. 
    # This method will schedule this proposal to one of the available timeslots
    # (randomly selected)
    # Yes, this is a very straightforward function, but it was separated
    # from schedule_proposal in order to allow for future, non-random timeslot
    # assignment.
    my ($room, $prop_id, $tslots, $prop);
    $room = shift;
    $prop_id = shift;
    $tslots = shift;
    if (ref $tslots ne 'ARRAY') {
	carp 'Invocation error';
	return undef;
    }

    if (! @$tslots) {
	carp 'No suitable timeslots were found';
	return undef;
    }

#    unless ($prop = Comas::Proposal->new(-id=>$prop_id, -db=>$room->{-db})) {
#	carp 'Could not create proposal object';
#	return undef;
#    }

    my $t=$tslots->[rand @$tslots];
    return $t->schedule($prop_id);
}

1;

# $Log: Room.pm,v $
# Revision 1.9  2004/02/04 17:40:58  mig
# - Elimino max_people de todo lo que tiene que ver con la tabla rooms
#
# Revision 1.8  2004/01/05 19:36:32  gwolf
# - Schedule.pm, Schedule/Room.pm: Por fin ya agendo sobre cualquier criterio!
#   (sólo falta hacer que evite sobrecargar de ponencais con el mismo track un
#   mismo espacio)
# - Admin/academic_committee.pm: Agrego unas funcioncillas que pidió Mig
# - Admin.pm: Quito todas las referencias a db_param, que ya no usaremos
#
# Revision 1.7  2003/12/20 04:14:51  mig
# - Agrego tags Id y Log que expanda el CVS
#
