#!/usr/bin/perl
use feature "switch";
use strict;
use warnings;
use perunServicesInit;
use perunServicesUtils;
use Text::Unidecode;

sub processGroupMembership;

local $::SERVICE_NAME = "ldap";
local $::PROTOCOL_VERSION = "3.0.0";
my $SCRIPT_VERSION = "3.0.3";

perunServicesInit::init;
my $DIRECTORY = perunServicesInit::getDirectory;
my $fileName = "$DIRECTORY/$::SERVICE_NAME".".ldif";
my $baseDnFileName = "$DIRECTORY/baseDN";

my $data = perunServicesInit::getHashedDataWithGroups;

#Constants
our $A_F_BASE_DN;  *A_F_BASE_DN = \'urn:perun:facility:attribute-def:def:ldapBaseDN';
our $A_R_VO_SHORT_NAME;  *A_R_VO_SHORT_NAME = \'urn:perun:resource:attribute-def:virt:voShortName';
our $A_G_NAME;  *A_G_NAME = \'urn:perun:group:attribute-def:core:name';

# User attributes
our $A_USER_LOGIN; *A_USER_LOGIN = \'urn:perun:user_facility:attribute-def:virt:login';
our $A_FIRST_NAME;  *A_FIRST_NAME = \'urn:perun:user:attribute-def:core:firstName';
our $A_LAST_NAME;  *A_LAST_NAME = \'urn:perun:user:attribute-def:core:lastName';
our $A_DISPLAY_NAME;  *A_DISPLAY_NAME = \'urn:perun:user:attribute-def:core:displayName';
our $A_ORGANIZATION;  *A_ORGANIZATION = \'urn:perun:user:attribute-def:def:organization';
our $A_MAIL;  *A_MAIL = \'urn:perun:user:attribute-def:def:preferredMail';

# Member-specific attributes
our $A_M_ORGANIZATION;  *A_M_ORGANIZATION = \'urn:perun:member:attribute-def:def:organization';
our $A_M_MAIL;  *A_M_MAIL = \'urn:perun:member:attribute-def:def:mail';

# CHECK ON FACILITY ATTRIBUTES
my $ldapBaseDN = $data->getFacilityAttributeValue( attrName => $A_F_BASE_DN );
if (!defined($ldapBaseDN)) {
	exit 1;
}

# GATHER USERS
my $users;  # $users->{$login}->{ATTR} = $attrValue;
my $usersByVo;  # $usersByVo->{$voShortName}->{$login}->{ATTR} = $attrValue;

# GATHER GROUPS
my $groups;  # $groups->{$login}->{$voShortName . ":" . $groupName} = 1;
my $groupsByVo;  # $groupsByVo->{$login}->{$voShortName}->{$voShortName . ":" . $groupName} = 1;

# FOR EACH RESOURCE
foreach my $resourceId ( $data->getResourceIds() ) {
	my $voShortName = $data->getResourceAttributeValue( resource => $resourceId, attrName => $A_R_VO_SHORT_NAME );

	# FOR EACH MEMBER ON RESOURCE
	foreach my $memberId ($data->getMemberIdsForResource( resource => $resourceId )) {
		my $login = $data->getUserFacilityAttributeValue( member => $memberId, attrName => $A_USER_LOGIN );

		# Store member in VOs structure (use MEMBER attributes)
		my $displayName = $data->getUserAttributeValue( member => $memberId, attrName => $A_DISPLAY_NAME );
		my $firstName = $data->getUserAttributeValue( member => $memberId, attrName => $A_FIRST_NAME );
		my $lastName = $data->getUserAttributeValue( member => $memberId, attrName => $A_LAST_NAME );
		$usersByVo->{$voShortName}->{$login}->{$A_DISPLAY_NAME} = $displayName;
		$usersByVo->{$voShortName}->{$login}->{$A_FIRST_NAME} = $firstName;
		$usersByVo->{$voShortName}->{$login}->{$A_LAST_NAME} = $lastName;

		# if MEMBER variant for ORGANIZATION not defined, try to use USER variant
		my $userOrganization = $data->getUserAttributeValue( member => $memberId, attrName => $A_ORGANIZATION );
		my $organization = $data->getMemberAttributeValue( member => $memberId, attrName => $A_M_ORGANIZATION );
		if (defined $organization and length $organization) {
			$usersByVo->{$voShortName}->{$login}->{$A_M_ORGANIZATION} = $organization;
		} else {
			$usersByVo->{$voShortName}->{$login}->{$A_M_ORGANIZATION} = $userOrganization;
		}
		# if MEMBER variant for MAIL not defined, try to use USER variant
		my $userMail = $data->getUserAttributeValue( member => $memberId, attrName => $A_MAIL );
		my $mail = $data->getMemberAttributeValue( member => $memberId, attrName => $A_M_MAIL );
		if (defined $mail and length $mail) {
			$usersByVo->{$voShortName}->{$login}->{$A_M_MAIL} = $mail;
		} else {
			$usersByVo->{$voShortName}->{$login}->{$A_M_MAIL} = $userMail;
		}

		# Store same member in flat structure (use USER attributes)
		# FirstName can be undefined, display_name and last_name not (so set them to string null) to avoid it
		$users->{$login}->{$A_DISPLAY_NAME} = (defined $displayName and length $displayName) ? $displayName : "null";
		$users->{$login}->{$A_FIRST_NAME} = $firstName;
		$users->{$login}->{$A_LAST_NAME} = $lastName || "null";
		$users->{$login}->{$A_ORGANIZATION} = $userOrganization;
		$users->{$login}->{$A_MAIL} = $userMail;

	}

	foreach my $groupId ( $data->getGroupIdsForResource( resource => $resourceId ) ) {
		processGroupMembership( $groupId, $resourceId, $voShortName );
	}

}

#
# PRINT BASE_DN FILE
#
open FILE,">:encoding(UTF-8)","$baseDnFileName" or die "Cannot open $baseDnFileName: $! \n";
print FILE $ldapBaseDN;
close(FILE);

#
# PRINT LDIF FILE
#
open FILE,">:encoding(UTF-8)","$fileName" or die "Cannot open $fileName: $! \n";

# print base entry

print FILE "dn: " . $ldapBaseDN . "\n";
# IF base start with dc=[something]
if ($ldapBaseDN =~ m/^dc=/) {

	my $position = index($ldapBaseDN, ",");
	print $ldapBaseDN . "\n";
	print $position . "\n";
	my $dc = undef;
	if ($position > 0) {
		$dc = substr $ldapBaseDN, 3, $position-3;
	}
	print FILE "dc: " . $dc . "\n";
	print FILE "objectclass: top\n";
	print FILE "objectclass: domain\n";  # we need at least one structural class
	print FILE "objectclass: dcObject\n";
}
# IF base start with ou=[something]
if ($ldapBaseDN =~ m/^ou=/) {

	my $position = index($ldapBaseDN, ",");
	my $ou = undef;
	if ($position > 0) {
		$ou = substr($ldapBaseDN, 3, $position-3);
	}
	print FILE "ou: " . $ou . "\n";
	print FILE "objectclass: organizationalunit\n"
}

print FILE "\n";

# PRINT ou=people entry
print FILE "dn: ou=people," . $ldapBaseDN . "\n";
print FILE "ou: people\n";
print FILE "description: All users propagated from Perun to this LDAP.\n";
print FILE "objectclass: organizationalunit\n";
# There MUST be an empty line after each entry, so entry sorting and diff works on slave part
print FILE "\n";

# FLAT structure is stored in ou=people + base DN
for my $login (sort keys %$users) {

	# print attributes, which are never empty
	print FILE "dn: uid=" . $login . ",ou=people," . $ldapBaseDN . "\n";
	print FILE "uid: " . $login . "\n";
	print FILE "cn: " . $users->{$login}->{$A_DISPLAY_NAME} . "\n";
	print FILE "sn: " . $users->{$login}->{$A_LAST_NAME} . "\n";

	# skip attributes which are empty and LDAP can't handle it (FIRST_NAME, ORGANIZATION, EMAIL)
	my $givenName = $users->{$login}->{$A_FIRST_NAME};
	my $organization = $users->{$login}->{$A_ORGANIZATION};
	my $mail = $users->{$login}->{$A_MAIL};

	if (defined $givenName and length $givenName) {
		print FILE "givenName: " . $givenName . "\n";
	}
	if (defined $organization and length $organization) {
		print FILE "o: " . $organization . "\n";
	}
	if (defined $mail and length $mail) {
		print FILE "mail: " . $mail . "\n";
	}

	# print group membership information
	my @gs = keys %{$groups->{$login}};
	for my $g (@gs) {
		print FILE "memberOfPerunGroups: " . $g . "\n";
	}

	# print classes
	print FILE "objectclass: top\n";
	print FILE "objectclass: inetOrgPerson\n";
	print FILE "objectclass: perunEduroamUser\n";

	# There MUST be an empty line after each entry, so entry sorting and diff works on slave part
	print FILE "\n";

}

# HIERARCHICAL structure is stored in
# base DN (for group entries ou=voShortName)
# ou=voShortName + base DN (for user entries)
for my $voShortName (sort keys %$usersByVo) {

	# PRINT VO entry (create voShortName group in LDAP if not exists)
	print FILE "dn: ou=" . $voShortName . "," . $ldapBaseDN . "\n";
	print FILE "ou: " . $voShortName. "\n";
	print FILE "description: Virtual organization " . $voShortName . " contains all it's members, which are propagated from Perun to this LDAP\n";
	print FILE "objectclass: organizationalunit\n";

	# There MUST be an empty line after each entry, so entry sorting and diff works on slave part
	print FILE "\n";

	# PRINT USERs entries
	my $voUsers = $usersByVo->{$voShortName};
	for my $login (sort keys %$voUsers) {

		# HIERARCHICAL structure is stored in ou=voShortName
		print FILE "dn: uid=" . $login . ",ou=".$voShortName."," . $ldapBaseDN . "\n";
		print FILE "uid: " . $login . "\n";
		print FILE "cn: " . $voUsers->{$login}->{$A_DISPLAY_NAME} . "\n";
		print FILE "sn: " . $voUsers->{$login}->{$A_LAST_NAME} . "\n";

		# skip attributes which are empty and LDAP can't handle it (FIRST_NAME, ORGANIZATION, EMAIL)
		# fallback to user variant is done in aggregation part of the script !!
		my $givenName = $voUsers->{$login}->{$A_FIRST_NAME};
		my $organization = $voUsers->{$login}->{$A_M_ORGANIZATION};
		my $mail = $voUsers->{$login}->{$A_M_MAIL};

		if (defined $givenName and length $givenName) {
			print FILE "givenName: " . $givenName . "\n";
		}
		if (defined $organization and length $organization) {
			print FILE "o: " . $organization . "\n";
		}
		if (defined $mail and length $mail) {
			print FILE "mail: " . $mail . "\n";
		}

		# print group membership information
		my @gs = keys %{$groupsByVo->{$login}->{$voShortName}};
		for my $g (@gs) {
			print FILE "memberOfPerunGroups: " . $g . "\n";
		}

		# print classes
		print FILE "objectclass: top\n";
		print FILE "objectclass: inetOrgPerson\n";
		print FILE "objectclass: perunEduroamUser\n";

		# There MUST be an empty line after each entry, so entry sorting and diff works on slave part
		print FILE "\n";

	}

}

close(FILE);

perunServicesInit::finalize;


#
# Method for gathering group membership information for each user
#
sub processGroupMembership {

	# read input
	my $groupId = shift;
	my $resourceId = shift;
	my $voShortName = shift;

	# get group name as attribute
	my $groupName = $data->getGroupAttributeValue( group => $groupId, attrName => $A_G_NAME );

	# for each member, push groupName to storage
	for my $memberId ( $data->getMemberIdsForResourceAndGroup( resource => $resourceId, group => $groupId )) {
		# get login
		my $login = $data->getUserFacilityAttributeValue( member => $memberId, attrName => $A_USER_LOGIN );
		# store groupName
		$groupsByVo->{$login}->{$voShortName}->{$voShortName . ":" . $groupName} = 1;
		$groups->{$login}->{$voShortName . ":" . $groupName} = 1;

	}

}

# we do not support EPPN in initial version
#our $A_F_EPPN_SCOPE;  *A_F_EPPN_SCOPE = \'urn:perun:facility:attribute-def:def:eppnScope';
#our $A_F_EPPN_O_BASE_DN;  *A_F_EPPN_O_BASE_DN = \'urn:perun:facility:attribute-def:def:eppnOrgDNBase';
#print FILE "eduPersonPrincipalName: " . $memberAttributes->{$A_USER_LOGIN} . "\@" . $facilityAttributes{$A_F_EPPN_SCOPE} . "\n";
#print FILE "eduPersonAffiliation: member\n";
#print FILE "eduPersonOrgDN: o=" . $memberAttributes->{$A_ORGANIZATION} . "," . $facilityAttributes{$A_F_EPPN_O_BASE_DN} . "\n";
