package LATMOS::Accounts::Bases::Ad::Group;

use 5.010000;
use strict;
use warnings;

use base qw(LATMOS::Accounts::Bases::Ad::objects);
use Net::LDAP;
use Net::LDAP::Entry;
use Net::LDAP::Control::Paged;
use Net::LDAP::Constant qw( LDAP_CONTROL_PAGED ); 
use Net::LDAP::Util     qw( escape_filter_value );
use LATMOS::Accounts::Log;

our $VERSION = (q$Rev$ =~ /^Rev: (\d+) /)[0];

=head1 NAME

LATMOS::Ad - Perl extension for blah blah blah

=head1 SYNOPSIS

  use LATMOS::Ad;
  blah blah blah

=head1 DESCRIPTION

Stub documentation for LATMOS::Ad, created by h2xs. It looks like the
author of the extension was negligent enough to leave the stub
unedited.

Blah blah blah.

=head1 FUNCTIONS

=cut

sub _class_filter { '(ObjectClass=group)' }

sub _key_attr { 'cn' } 

sub _my_ldap_classes { qw(top group) }

sub _get_attr_schema {
    my ($class, $base) = @_;
    my $info = {
        gidNumber  => { uniq => 1, },
        description => { },
        member => {
            delayed => 1,
            can_values => sub { $base->list_objects('user') },
            multiple => 1,
        },
        memberUID => {
            delayed => 1,
            multiple => 1,
        },
        sAMAccountName => { },
        managedBy => {
            delayed => 1,
            can_values => sub { $base->list_objects('user') },
        },
        cn => { ro => 1, },
        dn => { ro => 1, },
        objectClass => {
            ro => 1,
            multiple => 1,
        },
        msSFU30NisDomain => { },
        msSFU30Name => { },
    };

    $info->{$class->_key_attribute($base)}{ro} = 1;
    $info->{$class->_dn_attribute($base)}{ro} = 1;

    $info
}

sub _create {
    my ($class, $base, $id, %data) = @_;

    $data{sAMAccountName} = 'GR-' . $id;
    my $entry = Net::LDAP::Entry->new();

    $entry->dn(join(',',
        sprintf('%s=%s',
            $class->_dn_attribute($base),
            escape_filter_value($id)),
            $base->object_base_dn($class->type
        ),
    ));
    $entry->replace(objectClass => [ $class->_my_ldap_classes ],);
    $data{$class->_key_attribute($base)} = $id;
    my %delayed;
    foreach (keys %data) {
        /^(memberUID|member)$/ and do {
            $delayed{$_} = $data{$_};
            next;
        };
        $class->_populate_entry($entry, $_, $data{$_}, $base);
    }
    my $msg = $base->ldap->add($entry);
    $base->log(LA_ERR, "Cannot create group: %s", $msg->error) if ($msg->code);
    return if ($msg->code);
    if (! keys %delayed) { return 1 };
    my $res = $base->get_object('group', $id)->set_fields(%delayed);
    return defined($res) ? 1 : 0;
}

sub get_field {
    my ($self, $field) = @_;
    require LATMOS::Accounts::Bases::Ad::User;

    $field eq 'member' and do {
        my @res;
        $self->base->_unlimited_search(
            base => $self->base->object_base_dn('user'),
            filter => sprintf(
                '(&(objectClass=user)(memberOf=%s))',
                escape_filter_value($self->{entry}->dn),
            ),
            callback => sub {
                my ($mesg, $entry) = @_;
                ref $entry eq 'Net::LDAP::Entry' or return;
                push(@res, $entry->get_value(
                        LATMOS::Accounts::Bases::Ad::User->_key_attribute($self->base)
                    )
                );
            },
        );
        return [ sort(@res) ];
    };
    $field eq 'memberUID' and do {
        my $val = $self->SUPER::get_field($field);
        return ref $val ? $val : [ grep { $_ } $val ];
    };
    $self->SUPER::get_field($field);
}

sub _populate_entry {
    my ($self, $entry, $field, $value, $base) = @_;
    $base ||= $self->base;
    for ($field) {
        /managedBy$/ and do {
            if ($value && (my $user = $base->get_object('user', $value))) {
                $value = $user->get_field('dn');
            } else {
                $value = undef;
            }
            next;
        };
        /^sAMAccountName$/ and do {
            # Hack... to avoid clash with user
            if ($value !~ /^GR-/) {
                $value = 'GR-' . $value;
            }
	    next;
        };
    }
    $self->SUPER::_populate_entry($entry, $field, $value, $base);
}

sub set_fields {
    my ($self, %data) = @_;
    my %ndata;
    while (my ($f, $val) = each(%data)) {
        $f eq 'memberUID' and do {
            $val = [ $val ] unless(ref $val);
            my %users;
            $users{$_}{e} = 1 foreach (@{ $self->get_field('memberUID') || []});
            $users{$_}{n} = 1 foreach (@{ $val || []});
            foreach (keys %users) {
                $users{$_}{e} && $users{$_}{n} and next;
                if ($users{$_}{e}) {
                    $self->{entry}->delete(memberUID => $_);
                } elsif ($users{$_}{n} && $self->base->get_object('user', $_)) {
                    $self->{entry}->add(memberUID => $_);
                } # else {} # can't happen
            }
            next;
        };
        $f eq 'member' and do {
            my %users;
            $val = [ $val ] unless(ref $val);
            $users{$_}{e} = 1 foreach (@{ $self->get_field('member') || []});
            $users{$_}{n} = 1 foreach (@{ $val || []});
            foreach (keys %users) {
                $users{$_}{e} && $users{$_}{n} and next;
                my $user = $self->base->get_object('user', $_) or next;
                if ($users{$_}{e}) {
                    $self->{entry}->delete(member => $user->get_field('dn'));
                } elsif ($users{$_}{n}) {
                    $self->{entry}->add(member => $user->get_field('dn'));
                } # else {} # can't happen
            }
            next;
        };
        $ndata{$f} = $val;
    }
    $self->SUPER::set_fields(%ndata);
}

1;

__END__

=head1 SEE ALSO

=head1 AUTHOR

Olivier Thauvin, E<lt>olivier.thauvin@aerov.jussieu.frE<gt>

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2008 CNRS SA/CETP/LATMOS

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.10.0 or,
at your option, any later version of Perl 5 you may have available.


=cut
