#!/usr/bin/perl

use strict;
use warnings;
use perunServicesInit;
use perunServicesUtils;

#forward declaration
sub processGroupData; 
sub processMembersData; 
sub processResourceData;

our $SERVICE_NAME = "group_nfs4";
our $PROTOCOL_VERSION = "3.1.0";
my $SCRIPT_VERSION = "3.0.5";

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

#Constants
our $A_RESOURCE_UNIX_GROUP_NAME;              *A_RESOURCE_UNIX_GROUP_NAME =              \'urn:perun:resource:attribute-def:virt:unixGroupName';
our $A_RESOURCE_UNIX_GID;                     *A_RESOURCE_UNIX_GID =                     \'urn:perun:resource:attribute-def:virt:unixGID';
our $A_GROUP_RESOURCE_UNIX_GROUP_NAME;        *A_GROUP_RESOURCE_UNIX_GROUP_NAME =        \'urn:perun:group_resource:attribute-def:virt:unixGroupName';
our $A_GROUP_RESOURCE_UNIX_GID;               *A_GROUP_RESOURCE_UNIX_GID =               \'urn:perun:group_resource:attribute-def:virt:unixGID';
our $A_USER_KERBEROS_LOGINS;                  *A_USER_KERBEROS_LOGINS =                  \'urn:perun:user:attribute-def:def:kerberosLogins';
our $A_MEMBER_STATUS;                         *A_MEMBER_STATUS =                         \'urn:perun:member:attribute-def:core:status';
our $A_FACILITY_GID_RANGES;                   *A_FACILITY_GID_RANGES =                   \'urn:perun:facility:attribute-def:virt:GIDRanges';
our $A_GROUP_RESOURCE_IS_SYSTEM_UNIX_GROUP;   *A_GROUP_RESOURCE_IS_SYSTEM_UNIX_GROUP =   \'urn:perun:group_resource:attribute-def:def:isSystemUnixGroup';
our $A_GROUP_RESOURCE_SYSTEM_UNIX_GID;        *A_GROUP_RESOURCE_SYSTEM_UNIX_GID =        \'urn:perun:group_resource:attribute-def:def:systemUnixGID';
our $A_GROUP_RESOURCE_SYSTEM_UNIX_GROUP_NAME; *A_GROUP_RESOURCE_SYSTEM_UNIX_GROUP_NAME = \'urn:perun:group_resource:attribute-def:def:systemUnixGroupName';

our $STATUS_VALID;                            *STATUS_VALID =                            \'VALID';


#Global data structure
our $STRUC_GID;          *STRUC_GID = \0;
our $STRUC_USERS;        *STRUC_USERS = \1;
our $STRUC_SYSTEM;       *STRUC_SYSTEM = \2;
our $groupStruc = {};     #$groupStruc->{$groupName}->{$STRUC_GID} = $gid;         # gid of group $groupname
													#$groupStruc->{$groupName}->{$STRUC_USERS}->{$login} = 1;  #this mean user exists in group
													#$groupStruc->{$groupName}->{$STRUC_SYSTEM} = 1;         # group is system group

foreach my $resourceId ($data->getResourceIds()) {
	processResourceData $resourceId;

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

my $group_file_name = "$DIRECTORY/$::SERVICE_NAME";
my $gid_ranges_file_name = "$DIRECTORY/gid_ranges";

open FILE,">$group_file_name" or die "Cannot open $group_file_name: $! \n";
open GID_RANGES,">$gid_ranges_file_name" or die "Cannot open $gid_ranges_file_name\n";

my %gidRanges = %{$data->getFacilityAttributeValue(attrName => $A_FACILITY_GID_RANGES)};
foreach my $minimum (sort { $a <=> $b } keys  %gidRanges) {
	my $maximum = $gidRanges{$minimum};
	print GID_RANGES $minimum . "-" . $maximum . "\n";
}
close(GID_RANGES) or die "Cannot close $gid_ranges_file_name: $! \n";

local $" = ',';
foreach my $groupName (sort keys %$groupStruc) {
	my @groupMembers = sort keys %{$groupStruc->{$groupName}->{$STRUC_USERS}};
	print FILE $groupName, ":x:", $groupStruc->{$groupName}->{$STRUC_GID}, ":", "@groupMembers" ,"\n";
}
close (FILE);
perunServicesInit::finalize;

##############################################################################
#   Only subs definitions down there
##############################################################################

# input: resource id
# stores resource members logins into $groupStruc structure
sub processResourceData {
	my $resourceId = shift;

	my $groupName = $data->getResourceAttributeValue(attrName => $A_RESOURCE_UNIX_GROUP_NAME, resource => $resourceId);
	my $gid = $data->getResourceAttributeValue(attrName => $A_RESOURCE_UNIX_GID, resource => $resourceId);

	unless(exists $groupStruc->{$groupName}) {
		$groupStruc->{$groupName} = {};
		$groupStruc->{$groupName}->{$STRUC_GID} = $gid;
		$groupStruc->{$groupName}->{$STRUC_USERS} = {};
	} else {
		if($groupStruc->{$groupName}->{$STRUC_GID} != $gid) { die "Consistency error! Two resources which represents same unix group have different GID"; }
	}

	my @memberIds = $data->getMemberIdsForResource(resource => $resourceId);

	processMembersData $groupName, @memberIds;
}

# input: (group id, resource id)
# stores groups members logins into $groupStruc structure
sub processGroupData {
	my ($groupId, $resourceId) = @_;
	my @memberIds = $data->getMemberIdsForResourceAndGroup(resource => $resourceId, group => $groupId);

	#check if this group is flagged as unix group
	my $unixGroupName = $data->getGroupResourceAttributeValue(attrName => $A_GROUP_RESOURCE_UNIX_GROUP_NAME, resource => $resourceId, group => $groupId);
	if($unixGroupName) {
		my $groupName = $unixGroupName;
		my $gid = $data->getGroupResourceAttributeValue(attrName => $A_GROUP_RESOURCE_UNIX_GID, resource => $resourceId, group => $groupId);

		if(exists $groupStruc->{$groupName}) { 
			if($groupStruc->{$groupName}->{$STRUC_SYSTEM}) {
				die "Error: System group and regular group have the same name. Name= $groupName";
			} elsif($groupStruc->{$groupName}->{$STRUC_GID} != $gid) {
				die "Consistency error! Two resources or groups with the same name and differents GIDs. Name=" . $groupName ." GID=" . $groupStruc->{$groupName}->{$STRUC_GID} . ", " . $gid;
			}
		}
		$groupStruc->{$groupName}->{$STRUC_GID} = $gid;

		processMembersData $groupName, @memberIds;

		#process system unix groups
	} elsif($data->getGroupResourceAttributeValue(attrName => $A_GROUP_RESOURCE_IS_SYSTEM_UNIX_GROUP, resource => $resourceId, group => $groupId)) {
		my $groupName = $data->getGroupResourceAttributeValue(attrName => $A_GROUP_RESOURCE_SYSTEM_UNIX_GROUP_NAME, resource => $resourceId, group => $groupId);
		my $gid = $data->getGroupResourceAttributeValue(attrName => $A_GROUP_RESOURCE_SYSTEM_UNIX_GID, resource => $resourceId, group => $groupId);

		if(exists $groupStruc->{$groupName}) { 
			if(!$groupStruc->{$groupName}->{$STRUC_SYSTEM}) {
				die "Error: System group and regular group (or resource) have the same name. Name= $groupName";
			} elsif($groupStruc->{$groupName}->{$STRUC_GID} != $gid) {
				die "Consistency error! Two system groups with the same name. Name=" . $groupName ." GID=" . $groupStruc->{$groupName}->{$STRUC_GID} . ", " . $gid;
			}
		}
		$groupStruc->{$groupName}->{$STRUC_GID} = $gid;
		$groupStruc->{$groupName}->{$STRUC_SYSTEM} = 1;

		processMembersData $groupName, @memberIds;
	}
}

# input: (groupName, member ids)
# stores members logins into $groupStruc structure
sub processMembersData {
	my ($groupName, @memberIds) = @_;
	for my $memberId (@memberIds) {
		my $status = $data->getMemberAttributeValue(attrName => $A_MEMBER_STATUS, member => $memberId);
		if($status eq $STATUS_VALID) {
			for my $login (@{$data->getUserAttributeValue(attrName => $A_USER_KERBEROS_LOGINS, member => $memberId)}) {
				$groupStruc->{$groupName}->{$STRUC_USERS}->{$login} = 1;
			}
		}
	}
}
