# Copyright (c) vhffs project and its contributors
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without 
# modification, are permitted provided that the following conditions 
# are met:
#
# 1. Redistributions of source code must retain the above copyright 
#   notice, this list of conditions and the following disclaimer.
#2. Redistributions in binary form must reproduce the above copyright
#   notice, this list of conditions and the following disclaimer in 
#   the documentation and/or other materials provided with the 
#   distribution.
#3. Neither the name of vhffs nor the names of its contributors 
#   may be used to endorse or promote products derived from this 
#   software without specific prior written permission.
#
#THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
#"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
#LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
#FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
#COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
#INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
#BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
#LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
#CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
# POSSIBILITY OF SUCH DAMAGE.

package Vhffs::Group;

use base qw(Vhffs::Object);

use strict;
use utf8;
use DBI;
use Vhffs::Constants;


=head1 NAME

Vhffs::Object - Handle a group in VHFFS

=head1 SYNOPSIS

TODO

=head1 DESCRIPTION

TODO

=head1 METHODS

=cut

sub check_groupname($) {
    my $groupname = shift;
    return ( defined $groupname  &&  $groupname =~ /^[a-z0-9]{3,12}$/ );
}
        

=pod

=head2 create

    my $group = Vhffs::Group::create($main, $groupname, $realname, $owner_uid, $gid, $description)

Create in DB and return a fully functional group.

=cut

sub create {
    my ($main, $groupname, $realname, $owner_uid, $gid, $description) = @_;
    return undef unless check_groupname($groupname);
    return undef unless defined($owner_uid);

    my $groupconf = $main->get_config->get_groups;
    my $group;

    use Vhffs::Functions;
    my $badgroups = Vhffs::Functions::readfile( $groupconf->{'bad_groupname_file'} );
    if( defined $badgroups ) {
        foreach ( @{$badgroups} ) {
            return undef if ( $_ eq $groupname );
        }
    }

    $realname = $groupname unless defined $realname;

    my $dbh = $main->get_db_object;
    local $dbh->{RaiseError} = 1;
    local $dbh->{PrintError} = 0;

    # Avoid an error if we're already in a transaction
    # (eg. when creating user).
    my $transaction_started;
    if($dbh->{AutoCommit}) {
        # AutoCommit is on => we're not yet in a
        # transaction, let's start one
        $dbh->begin_work;
        $transaction_started = 1;
    } else {
        # We're already in a transaction, ensure that
        # we don't corrupt it.
        $transaction_started = 0;
    }

    eval {
        # Special case : sometimes, gid can't be passed to create
        # to avoid updates (cf Vhffs::User::create)
        ($gid) = $dbh->selectrow_array('SELECT nextval(\'vhffs_groups_gid_seq\')') unless defined $gid;

        my $parent = Vhffs::Object::create($main, $owner_uid, $gid, $description, undef, Vhffs::Constants::TYPE_GROUP);
        die('Unable to create parent object') unless(defined $parent);
    
        my $quota = $groupconf->{default_quota} || 10;
    
        my $query = 'INSERT INTO vhffs_groups(gid, groupname, realname, passwd, quota, quota_used, object_id) VALUES(?, ?, ?, NULL, ?, 0, ?)';
        my $sth = $dbh->prepare( $query );
        $sth->execute($gid, $groupname, $realname, $quota, $parent->get_oid);

        $dbh->commit if($transaction_started);
        $group = get_by_gid($main, $gid);
    };

    if($transaction_started && $@) {
        warn "Unable to create group $groupname: $@\n";
        $dbh->rollback;
    }

    return $group;

}

=head2 remove_user

    $group->remove_user( $uid );

Remove an user from a given group.
Return false if an error occurs or if the
user wasn't in the group.

=cut

sub remove_user
{
	my $self = shift;
	my $uid = shift;

	my $sql = 'UPDATE vhffs_user_group SET state=? WHERE gid=? AND uid=?';
	return $self->{db}->do( $sql, {}, Vhffs::Constants::TO_DELETE, $self->{gid}, $uid ) > 0;
}



sub commit
{
    my $self = shift;

    my $sql = 'UPDATE vhffs_groups SET realname = ?, quota = ?, quota_used = ? WHERE gid = ?';
    my $sth = $self->{db}->prepare($sql);
    $sth->execute( $self->{'realname'}, $self->{'quota'}, $self->{'quota_used'}, $self->{'gid'}) or return -1;

    #Exec the super method !
    return -2 if( $self->SUPER::commit < 0 );

	return 1;
}



sub is_empty
{
	my $self = shift;
	return -1 unless defined $self;

	my $query = 'SELECT COUNT(*) FROM vhffs_object WHERE owner_gid=? AND object_id!=?';
	my $request = $self->{'db'}->prepare( $query );
	$request->execute( $self->get_gid, $self->get_oid );
	my ( $rows ) = $request->fetchrow();

	return 1 if( $rows == 0 );
	return 0;
}


sub is_user_in_group
{
	my $self = shift;
	my $user = shift;

	my $query ='SELECT COUNT(*) FROM vhffs_user_group WHERE gid=? AND uid=?';
	my $request = $self->{'db'}->prepare( $query );
	$request->execute( $self->get_gid, $user->get_uid );
	my ( $rows ) = $request->fetchrow();

	return 1 if( $rows == 1 );
	return 0;
}


#Delete a group in the database
sub delete
{
    my $self;
    my $request;
    my $request2;
    my $request3;

    $self = shift;

	use Vhffs::Services::MailGroup;
	my $mg = init Vhffs::Services::MailGroup( $self->{'main'} , $self );
	if( defined $mg )
	{
		$mg->delbox;
		$mg->delforward;
	}

    $request = $self->{'db'}->prepare('DELETE FROM vhffs_groups WHERE gid=?');
    $request->execute($self->{'gid'}) or return -1;
    
    $self->SUPER::delete;

    return 1;
}



#modify a group
#use it as modify_user, but with the fields of the vhffs_groups table
sub modify
{
    my $request;
    
    my ( $self , $field , $value ) = @_;
    
    $self->{$field} = $value;

    # We don't need a special case for groupname, uniqueness is ensured by DB (we return -1 if groupname already exists)
	$request = $self->{'db'}->prepare("UPDATE vhffs_groups SET $field=? WHERE gid=?");
    $request->execute( $value, $self->{'gid'} ) or return -1;
    
    return 1;
}

=head2 add_user

    $group->add_user($uid);

Adds an user to a group. Returns false if user
doesn't exists or if there was an error.

=cut

sub add_user
{
	my( $self , $uid ) = @_;

    my $sql = 'INSERT INTO vhffs_user_group(uid, gid, state) VALUES(?, ?, ?)';
    my $res = $self->{db}->do( $sql, {}, $uid, $self->{gid}, Vhffs::Constants::WAITING_FOR_CREATION );
    return $res && $res > 0;
}

sub set_quota
{
	my $self = shift;
	my $value = shift;
	$self->{'quota'} = $value;
}


sub set_realname
{
	my $self = shift;
	my $value = shift;
	$self->{'realname'} = $value;
}


sub set_quota_used
{
	my $self = shift;
	my $value = shift;
	$self->{'quota_used'} = $value;
}



sub get_quota_used
{
	my $self = shift;
	return $self->{'quota_used'};
}


sub get_realname
{
	my $self = shift;
	return $self->{'realname'};
}


=head2 get_label

See C<Vhffs::Object::get_label>.

=cut

sub get_label {
    my $self = shift;
    return $self->{groupname};
}

sub get_quota
{
	my $self = shift;
	return $self->{'quota'};
}

sub get_groupname
{
    my $self = shift;
    return $self->{'groupname'};
}

sub get_gid
{
    my $self = shift;
    return $self->{'gid'};
}


sub get_name_by_gid
{
	my $main = shift;
	my $gid = shift;
	my $request = $main->{'db'}->prepare('SELECT groupname FROM vhffs_groups where gid= ?') or return -2;
	my $rows = $request->execute($gid);

	if( $rows == 0 )
	{
		return( "nobody" );
	}
	else
	{
		my ( $result ) = $request->fetchrow();
		return( $result );
	}

}


sub get_owner_username
{
	my $self = shift;
	my $request = $self->{'db'}->prepare('SELECT username FROM vhffs_users where uid=?') or return -2;
	my $rows = $request->execute($self->{'owner_uid'});

	if( $rows == 0 )
	{
		return( "nobody" );
	}
	else
	{
		my ($result) = $request->fetchrow();
		return( $result );
	}
	
}




sub set_owner_username
{
	my( $self , $username ) = @_;

	my $request = $self->{'db'}->prepare('SELECT uid FROM vhffs_users where username=?') or return -2;
	my $rows = $request->execute( $username );

	if( $rows == 0 )
	{
		return -1;
	}
	else
	{
		my $result = $request->fetchrow_hashref();
		$self->{'owner_uid'} = $result->{'uid'};
	}
}

sub get_users
{
	use Vhffs::User;
    my $self = shift;

    my @users;
    my $gid = $self->get_gid;
    my $query = 'SELECT ug.uid FROM vhffs_user_group ug WHERE ug.gid = ?';
    my $request = $self->{'db'}->prepare( $query );
    $request->execute($gid);
    while( my ($uid) = $request->fetchrow_array )
    {
        my $user = Vhffs::User::get_by_uid( $self->{'main'} , $uid );
        push( @users , $user ) if( defined $user );
    }
    return \@users;
}

sub get_owner
{
	my $self = shift;

	if( ! defined $self->{'user'} )
	{
		$self->{'user'} = Vhffs::User::get_by_uid( $self->{'main'}, $self->{'owner_uid'} );
	}

	return( $self->{'user'} );

}

sub getall_by_user
{
    my $vhffs = shift;
    my $state = shift;
    my $user = shift;
	my $name;	
	return undef if( ! defined $user );

    my $db = $vhffs->get_db_object;
    my @result;

    my $query = 'SELECT g.gid  FROM vhffs_groups g INNER JOIN vhffs_object o ON o.object_id=g.object_id INNER JOIN vhffs_user_group ug ON ug.gid=g.gid LEFT OUTER JOIN vhffs_users u ON u.username = g.groupname WHERE u.username IS NULL AND ug.uid = '.$user->get_uid;

    $query .= " AND o.state=$state " if( defined $state );
    $query .= " ORDER BY g.groupname";

    my $request = $db->prepare( $query );
    my $rows = $request->execute;

    return undef if( $rows <= 0 );

    my $gids = $request->fetchall_arrayref;

    my $group;
    foreach my $gid ( @{$gids} )
    {

        $group = Vhffs::Group::get_by_gid( $vhffs , $gid->[0] );
        push( @result , $group) if( defined $group );
    }
    return \@result;
}


sub getall
{
	my $vhffs = shift;
	my $state = shift;
	my $name = shift;

	my $db = $vhffs->get_db_object;
	my @result;	
	my $query = 'SELECT groupname FROM vhffs_groups g INNER JOIN vhffs_object o ON  g.object_id=o.object_id LEFT OUTER JOIN vhffs_users u ON u.username = g.groupname WHERE u.username IS NULL ';
	
	$query .= " AND o.state=$state " if( defined $state );
	$query .= " AND g.groupname LIKE '%".$name."%' " if( defined $name );

	$query .= " ORDER BY g.groupname";

	my $request = $db->prepare( $query );
	my $rows = $request->execute;

	return undef if( $rows <= 0 );
	
	my $names = $request->fetchall_arrayref;

	my $group;
	foreach $name ( @{$names} )
	{
		$group = Vhffs::Group::get_by_groupname( $vhffs , $name->[0] );
		push( @result , $group) if( defined $group );
    }
    return \@result;
}

sub getall_by_letter
{
    my $vhffs = shift;
    my $letter = shift;
    my $state = shift;

    return getall($vhffs, $state) if(! defined $letter );
    $letter .= '%';

    my $db = $vhffs->get_db_object;
    my @result;
    my $query = 'SELECT groupname FROM vhffs_groups g INNER JOIN vhffs_object o ON g.object_id=o.object_id LEFT OUTER JOIN vhffs_users u ON u.username = g.groupname WHERE u.username IS NULL AND g.groupname LIKE ?';
    $query .= "AND o.state=$state " if( defined $state );
    
    my $request = $db->prepare( $query );
    return undef if( ! $request->execute( $letter ) );

    my $names = $request->fetchall_arrayref;

    my $group;
    foreach my $name ( @{$names} )
    {   

        $group = Vhffs::Group::get_by_groupname( $vhffs , $name->[0] );
        push( @result , $group) if( defined $group );
    }
    return \@result;

}

sub getall_quotalimit {
    my ($vhffs, $limit) = @_;
    $limit = 10 unless(defined $limit);
    my $sql = q{SELECT g.gid, o.object_id, o.owner_uid, g.groupname, g.realname, g.passwd, g.quota,
    	g.quota_used, o.date_creation, o.description, o.state
    	FROM vhffs_groups g INNER JOIN vhffs_object o ON o.object_id = g.object_id
    	WHERE (g.quota_used / g.quota) >= 0.9 ORDER BY g.quota_used DESC LIMIT ?};
    
    my $dbh = $vhffs->get_db_object;
    my $sth = $dbh->prepare($sql);
    $sth->execute($limit) or return undef;
    my $groups = [];
    while(my @r = $sth->fetchrow_array) {
    	push @{$groups}, _new Vhffs::Group($vhffs, @r);
    }
    return $groups;
}

# Get all objects of the group
sub getall_objects
{
	my $self = shift;

	my $query = 'SELECT object_id FROM vhffs_object WHERE owner_gid=?';
	my $request = $self->get_main->get_db_object->prepare( $query ) or return -1;
	return undef unless $request->execute( $self->get_gid );

	my @objects;
	my $rows = $request->fetchall_arrayref();

	foreach (@$rows) {
		push @objects , Vhffs::Object::get_by_oid( $self->get_main, @$_ );
	}
	return \@objects;
}


=pod

=head2 get_by_gid

    my $group = Vhffs::Group::get_by_gid($main, $gid);
    die('Group not found') unless(defined $group);

Fetches the group whose gid is $gid.

=cut

sub get_by_gid {
    my ($vhffs, $gid) = @_;
    my $query = 'SELECT g.gid, o.object_id, o.owner_uid, g.groupname, g.realname, g.passwd, g.quota, g.quota_used, o.date_creation, o.description, o.state FROM vhffs_groups g INNER JOIN vhffs_object o ON o.object_id = g.object_id WHERE g.gid = ?';
    
    my $dbh = $vhffs->get_db_object;
    my @params = $dbh->selectrow_array($query, undef, $gid);
    return undef unless(@params);
    my $group = _new Vhffs::Group($vhffs, @params);
    return $group;
}

=pod

=head2 get_by_groupname

    my $group = Vhffs::Group::get_by_groupname($main, $groupname);
    die('Group not found') unless(defined $group);

Fetches the group whose name is $groupname.

=cut

sub get_by_groupname {
    my ($vhffs, $groupname) = @_;
    my $query = 'SELECT g.gid, o.object_id, o.owner_uid, g.groupname, g.realname, g.passwd, g.quota, g.quota_used, o.date_creation, o.description, o.state FROM vhffs_groups g INNER JOIN vhffs_object o ON o.object_id = g.object_id WHERE g.groupname = ?';

    my $dbh = $vhffs->get_db_object;
    my @params = $dbh->selectrow_array($query, undef, $groupname);
    return undef unless(@params);
    my $group = _new Vhffs::Group($vhffs, @params);
    return $group;
}


sub _new {
	no strict 'refs';
	my ($class, $main, $gid, $oid, $owner_uid, $groupname, $realname, $passwd, $quota, $quota_used, $date_creation, $description, $state) = @_;
	my $self = $class->SUPER::_new($main, $oid, $owner_uid, $gid, $date_creation, $description, '', $state, Vhffs::Constants::TYPE_GROUP);
	return undef unless(defined $self);

	$self->{gid} = $gid;
	$self->{groupname} = $groupname;
	$self->{realname} = $realname;
	$self->{passwd} = $passwd;
	$self->{quota} = $quota;
	$self->{quota_used} = $quota_used;

	return $self;
}

sub fill_object {
    my ($class, $obj) = @_;
    my $sql = q{SELECT gid, groupname, realname, passwd, quota, quota_used FROM
        vhffs_groups WHERE object_id = ?};
    return $class->SUPER::_fill_object($obj, $sql);
}

=head2 get_full_history

Returns an array containing all history entries for this group and its objects,
descending ordered by date.

=cut

sub get_full_history
{
	my $self = shift;

	my $sql = 'SELECT o.object_id,o.type,h.history_id,h.date,h.message,source.username as source FROM vhffs_history h INNER JOIN vhffs_object o ON o.object_id=h.object_id LEFT JOIN vhffs_users source ON source.uid = h.source_uid WHERE o.owner_gid=? ORDER BY date DESC';
    my $dbh = $self->get_db_object;
    return $dbh->selectall_arrayref($sql, {Slice => {}}, $self->{gid});
}


1;

__END__

=head1 AUTHORS

soda <dieu AT gunnm DOT org>

Sebastien Le Ray <beuss at tuxfamily dot org>
