#!/usr/bin/perl

use strict;
use warnings;
use perunServicesInit;
use perunServicesUtils;
use Text::Unidecode;
use XML::Simple;

local $::SERVICE_NAME = "voms_dirac";
local $::PROTOCOL_VERSION = "3.1.1";

perunServicesInit::init;
my $DIRECTORY = perunServicesInit::getDirectory;
my $data = perunServicesInit::getHashedDataWithGroups;

#Constants
our $A_R_VO_SHORT_NAME; *A_R_VO_SHORT_NAME =   \'urn:perun:resource:attribute-def:virt:voShortName';
our $A_R_VOMS_VO_NAME;  *A_R_VOMS_VO_NAME =    \'urn:perun:resource:attribute-def:def:vomsVoName';
our $A_USER_MAIL;       *A_USER_MAIL =         \'urn:perun:user:attribute-def:def:preferredMail';
our $A_USER_CERT_DNS;   *A_USER_CERT_DNS =     \'urn:perun:user:attribute-def:virt:userCertDNs';
our $A_MEMBER_STATUS;   *A_MEMBER_STATUS =     \'urn:perun:member:attribute-def:core:status';
our $A_R_VOMS_ROLES;    *A_R_VOMS_ROLES =      \'urn:perun:resource:attribute-def:def:vomsRoles';
our $A_GR_VOMS_GR_NAME; *A_GR_VOMS_GR_NAME =   \'urn:perun:group_resource:attribute-def:def:vomsGroupName';
our $A_GR_VOMS_ROLES;   *A_GR_VOMS_ROLES =     \'urn:perun:group_resource:attribute-def:def:vomsRoles';
our $A_USER_NICKNAME;   *A_USER_NICKNAME =     \'urn:perun:user:attribute-def:virt:vomsDiracNickname';
our $STATUS_VALID;      *STATUS_VALID =        \'VALID';

my $struc = {};
my $uniquenessMapping = {};

#resource one by one
foreach my $resourceId ($data->getResourceIds()) {
	#information about VO itself (shortname and roles for every user in vo from this resource)
	#if attribute for voms name exists, use it, if not, use VO short name instead
	my $vomsVoName = $data->getResourceAttributeValue(attrName => $A_R_VOMS_VO_NAME, resource => $resourceId);
	unless($vomsVoName) { $vomsVoName = $data->getResourceAttributeValue(attrName => $A_R_VO_SHORT_NAME, resource => $resourceId); }

	#create info about existing vo (even if it is empty)
	if(!defined($struc->{$vomsVoName})) { $struc->{$vomsVoName}->{'name'} = $vomsVoName; }
	
	my @rolesInVoForResource = ();
	my $resourceRoles = $data->getResourceAttributeValue(attrName => $A_R_VOMS_ROLES, resource => $resourceId);
	if(defined($resourceRoles)) { @rolesInVoForResource = @$resourceRoles; }
	
	#groups of resource one by one
	foreach my $groupId ($data->getGroupIdsForResource(resource => $resourceId)) {
		#get vomsGroupNameIfExists
		my $vomsGroupName = $data->getGroupResourceAttributeValue(attrName => $A_GR_VOMS_GR_NAME, group => $groupId, resource => $resourceId);
		my @rolesInVoForGroup = ();
		my $groupRoles = $data->getGroupResourceAttributeValue(attrName => $A_GR_VOMS_ROLES, group => $groupId, resource => $resourceId);
		if(defined($groupRoles)) { @rolesInVoForGroup = @$groupRoles; }

		#group members one by one
		foreach my $memberId ($data->getMemberIdsForResourceAndGroup(resource => $resourceId, group => $groupId)) {
			my $memberUniqueIdentifier;
			#skip member if his status is not valid
			next unless $data->getMemberAttributeValue(attrName => $A_MEMBER_STATUS, member => $memberId) eq $STATUS_VALID;
			#get mail
			my $email = $data->getUserAttributeValue(attrName => $A_USER_MAIL, member => $memberId);

			#each DN of user is separate instance of user in voms
			#skip users with no certificates
			my $userCertDns = $data->getUserAttributeValue(attrName => $A_USER_CERT_DNS, member => $memberId);
			foreach my $subjectDN (sort keys %$userCertDns) {
				#set uniqueIdentifier for member (his first certificate DN+CA)
				unless($memberUniqueIdentifier) { $memberUniqueIdentifier = $subjectDN . "---------------" .  $userCertDns->{$subjectDN}; }

				#unique user is defined by "'subjectDN+DNofCA'" without prefix, with simple white spaces, case-insensitive (lowercase there)
				chomp $userCertDns->{$subjectDN};
				my $subjectDNWithoutPrefix = $subjectDN;
				$subjectDNWithoutPrefix =~ s/^[0-9]+[:]//;
				my $CADN = $userCertDns->{$subjectDN};
				my $uniqueVomsUser = $subjectDNWithoutPrefix . $CADN;
				$uniqueVomsUser =~ s/\s+/ /g;
				$uniqueVomsUser = lc($uniqueVomsUser);

				#if this member is not unique (there are two different members with same uniqueVomsUser settings, give me info about that and continue
				unless($uniquenessMapping->{$uniqueVomsUser}) {
					$uniquenessMapping->{$uniqueVomsUser} = $memberUniqueIdentifier;
				} else {
					if($uniquenessMapping->{$uniqueVomsUser} ne $memberUniqueIdentifier) {
						print "WARNING: There is more than one Perun Member with same (unified) certificate (DN+CA): '" . $memberUniqueIdentifier  . "' against '" . $uniquenessMapping->{$uniqueVomsUser} . "' !\n";
					}
				}

				#create new member if not exists in VO yet
				my $nickname = $data->getUserAttributeValue(attrName => $A_USER_NICKNAME, member => $memberId);
				if(!defined($struc->{$vomsVoName}->{'users'}->{$uniqueVomsUser})) {
					$struc->{$vomsVoName}->{'users'}->{$uniqueVomsUser}->{'email'} = $email;
					$struc->{$vomsVoName}->{'users'}->{$uniqueVomsUser}->{'roles'} = {};	
					$struc->{$vomsVoName}->{'users'}->{$uniqueVomsUser}->{'groups'} = {};
					if($vomsVoName eq 'auger' && defined($nickname)) {
						$struc->{$vomsVoName}->{'users'}->{$uniqueVomsUser}->{'nickname'} = $nickname;
					}
				}
				$struc->{$vomsVoName}->{'users'}->{$uniqueVomsUser}->{'CA'} = $CADN;
				$struc->{$vomsVoName}->{'users'}->{$uniqueVomsUser}->{'DN'} = $subjectDNWithoutPrefix;
				
				#fill vo roles
				foreach my $role (@rolesInVoForResource) {
					$struc->{$vomsVoName}->{'users'}->{$uniqueVomsUser}->{'roles'}->{$role} = 1;
				}

				#set it just for filled vomsGroupName (it can be null)
				if(defined($vomsGroupName)) {
					#fill groups
					if(!defined($struc->{$vomsVoName}->{'users'}->{$uniqueVomsUser}->{'groups'}->{$vomsGroupName})) {
						$struc->{$vomsVoName}->{'users'}->{$uniqueVomsUser}->{'groups'}->{$vomsGroupName}->{'name'} = $vomsGroupName;
						$struc->{$vomsVoName}->{'users'}->{$uniqueVomsUser}->{'groups'}->{$vomsGroupName}->{'roles'} = {};
					}


					#fill group roles
					foreach my $role (@rolesInVoForGroup) {
						$struc->{$vomsVoName}->{'users'}->{$uniqueVomsUser}->{'groups'}->{$vomsGroupName}->{'roles'}->{$role} = 1;
					}
				# if name is not filled set all these roles for vo instead of group
				} else {
					#fill vo roles defined by group without name
					foreach my $role (@rolesInVoForGroup) {
						$struc->{$vomsVoName}->{'users'}->{$uniqueVomsUser}->{'roles'}->{$role} = 1;
					}
				}
			}
		}
	}
}


for my $vo (keys %$struc) {

	for my $user ( values %{$struc->{$vo}->{'users'}} ) {
		for my $group ( values %{$user->{'groups'}} ) {
			$group->{'roles'} = {'role' => [ keys %{$group->{'roles'}} ] };
		}
		$user->{'roles'} = {'role' => [ keys %{$user->{'roles'}} ] };

		$user->{'groups'} = { 'group' => [ values %{$user->{'groups'}} ] };
	}

	my $tmp = $struc->{$vo}->{'users'};
	$struc->{$vo}->{'users'} = {'user' => [ values %$tmp ] };
}

$struc = {'vo' => [ values %$struc ] };


my $fileName = "$DIRECTORY/voms";
open FILE,">$fileName" or die "Cannot open $fileName: $! \n";
binmode FILE, ":utf8";
print FILE XMLout($struc, RootName => 'vos', NoAttr => 1);
close FILE or die "Cannot close $fileName: $! \n";

perunServicesInit::finalize;
