#!/usr/bin/perl
use strict;
use warnings;
use perunServicesInit;
use perunServicesUtils;
use Time::Piece;

sub calculateExpiration;
sub isExpired;

local $::SERVICE_NAME = "vsup_k4";
local $::PROTOCOL_VERSION = "3.0.0";
my $SCRIPT_VERSION = "3.0.2";

perunServicesInit::init;
my $DIRECTORY = perunServicesInit::getDirectory;
my $fileName = "$DIRECTORY/$::SERVICE_NAME".".csv";
my $groupFileName = "$DIRECTORY/$::SERVICE_NAME"."_groups.csv";
my $data = perunServicesInit::getHashedDataWithGroups;

# Constants
our $A_UCO; *A_UCO= \'urn:perun:user:attribute-def:def:ucoVsup';
our $A_TITLE_BEFORE;  *A_TITLE_BEFORE = \'urn:perun:user:attribute-def:core:titleBefore';
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_ARTISTIC_FIRST_NAME; *A_ARTISTIC_FIRST_NAME = \'urn:perun:user:attribute-def:def:artisticFirstName';
our $A_ARTISTIC_LAST_NAME; *A_ARTISTIC_LAST_NAME = \'urn:perun:user:attribute-def:def:artisticLastName';
our $A_GENDER;  *A_GENDER = \'urn:perun:user:attribute-def:def:gender';
our $A_EXPIRATION_KOS;  *A_EXPIRATION_KOS = \'urn:perun:user:attribute-def:def:expirationKos';
our $A_EXPIRATION_DC2;  *A_EXPIRATION_DC2 = \'urn:perun:user:attribute-def:def:expirationDc2';
our $A_EXPIRATION_MANUAL;  *A_EXPIRATION_MANUAL = \'urn:perun:user:attribute-def:def:expirationManual';
our $A_NAV;  *A_NAV = \'urn:perun:user:attribute-def:def:k4Nav';
our $A_STALEAKT;  *A_STALEAKT = \'urn:perun:user:attribute-def:def:k4Staleakt';
our $A_BLACKLISTED;  *A_BLACKLISTED = \'urn:perun:user_facility:attribute-def:virt:blacklisted';
our $A_IFIS_ID;  *A_IFIS_ID = \'urn:perun:user:attribute-def:def:osbIdifis';
our $A_EXPIRED;  *A_EXPIRED = \'urn:perun:member_group:attribute-def:def:groupMembershipExpiration';

our $A_R_ID;  *A_R_ID = \'urn:perun:resource:attribute-def:def:k4GroupId';
our $A_R_NAME;  *A_R_NAME = \'urn:perun:resource:attribute-def:def:k4GroupName';
our $A_R_CODE;  *A_R_CODE = \'urn:perun:resource:attribute-def:def:k4GroupCode';
our $A_R_PRIORITY;  *A_R_PRIORITY = \'urn:perun:resource:attribute-def:def:k4GroupPriority';

# GATHER USERS
my $users;  # $users->{$uco}->{ATTR} = $attrValue;
# GATHER GROUPS
my $groups; # $groups->{$id}->{name|code|priority|members} = $value;

#
# AGGREGATE DATA
#
# FOR EACH RESOURCE
foreach my $resourceId ($data->getResourceIds()) {

	# RESOURCE => GROUP in K4
	my $k4GroupId = $data->getResourceAttributeValue(resource => $resourceId, attrName => $A_R_ID);
	my $k4GroupName = $data->getResourceAttributeValue(resource => $resourceId, attrName => $A_R_NAME);
	my $k4GroupCode = $data->getResourceAttributeValue(resource => $resourceId, attrName => $A_R_CODE);
	my $k4GroupPriority = $data->getResourceAttributeValue(resource => $resourceId, attrName => $A_R_PRIORITY);

	if (exists $groups->{$k4GroupId}->{"code"}) {
		if ($groups->{$k4GroupId}->{"code"} ne $k4GroupCode) {
			die "K4 Group with $k4GroupId has different values for CODE: $k4GroupCode vs $groups->{$k4GroupId}->{'code'}";
		}
	} else {
		$groups->{$k4GroupId}->{"code"} = $k4GroupCode;
	}
	if (exists $groups->{$k4GroupId}->{"name"}) {
		if ($groups->{$k4GroupId}->{"name"} ne $k4GroupName) {
			die "K4 Group with $k4GroupId has different values for NAME: $k4GroupName vs $groups->{$k4GroupId}->{'name'}";
		}
	} else {
		$groups->{$k4GroupId}->{"name"} = $k4GroupName;
	}
	if (exists $groups->{$k4GroupId}->{"priority"}) {
		if ($groups->{$k4GroupId}->{"priority"} ne $k4GroupPriority) {
			die "K4 Group with $k4GroupId has different values for NAME: $k4GroupPriority vs $groups->{$k4GroupId}->{'priority'}";
		}
	} else {
		$groups->{$k4GroupId}->{"priority"} = $k4GroupPriority;
	}
	unless (exists $groups->{$k4GroupId}->{"members"}) {
		$groups->{$k4GroupId}->{"members"} = ();
	}

	# GET MEMBERS FROM PERUN GROUPS TO DETERMINE EXPIRATION

	foreach my $groupId ($data->getGroupIdsForResource( resource => $resourceId )) {
		foreach my $memberId ($data->getMemberIdsForResourceAndGroup(resource => $resourceId, group => $groupId)) {

			my $blacklisted = $data->getUserFacilityAttributeValue(member => $memberId, attrName => $A_BLACKLISTED);

			if (defined $blacklisted and ($blacklisted == 1)) {
				# skip blacklisted users !security ban!
				next;
			}

			my $key = $data->getUserAttributeValue( member => $memberId, attrName => $A_UCO);
			my $expired = $data->getMemberGroupAttributeValue(member => $memberId, group => $groupId, attrName => $A_EXPIRED);

			if (isExpired($expired)) {
				# skip expired members from group
				next;
			}

			my $titleBefore = $data->getUserAttributeValue(member => $memberId, attrName => $A_TITLE_BEFORE);
			my $artisticFirstName = $data->getUserAttributeValue(member => $memberId, attrName => $A_ARTISTIC_FIRST_NAME);
			my $firstName = $data->getUserAttributeValue(member => $memberId, attrName => $A_FIRST_NAME);
			my $artisticLastName = $data->getUserAttributeValue(member => $memberId, attrName => $A_ARTISTIC_LAST_NAME);
			my $lastName = $data->getUserAttributeValue(member => $memberId, attrName => $A_LAST_NAME);
			my $gender = $data->getUserAttributeValue(member => $memberId, attrName => $A_GENDER);
			my $nav = $data->getUserAttributeValue(member => $memberId, attrName => $A_NAV);
			my $staleakt = $data->getUserAttributeValue(member => $memberId, attrName => $A_STALEAKT);
			my $ifisId = $data->getUserAttributeValue(member => $memberId, attrName => $A_IFIS_ID);
			my $expirationKOS = $data->getUserAttributeValue(member => $memberId, attrName => $A_EXPIRATION_KOS);
			my $expirationDC2 = $data->getUserAttributeValue(member => $memberId, attrName => $A_EXPIRATION_DC2);
			my $expirationManual = $data->getUserAttributeValue(member => $memberId, attrName => $A_EXPIRATION_MANUAL);

			$users->{$key}->{$A_TITLE_BEFORE} = (defined $titleBefore) ? substr($titleBefore, 0, 15) : '';
			$users->{$key}->{$A_FIRST_NAME} = ($artisticFirstName || ($firstName || ''));
			$users->{$key}->{$A_LAST_NAME} = ($artisticLastName || ($lastName || ''));
			$users->{$key}->{$A_GENDER} = $gender || '';
			$users->{$key}->{$A_NAV} = $nav || '0';
			$users->{$key}->{$A_STALEAKT} = $staleakt || '0';
			$users->{$key}->{$A_IFIS_ID} = $ifisId || '';
			$users->{$key}->{"EXPIRATION"} = calculateExpiration($expirationKOS, $expirationDC2, $expirationManual);

			if (length $users->{$key}->{$A_FIRST_NAME} > 20) {
				$users->{$key}->{$A_FIRST_NAME} = substr($firstName, 0, 20);
			}
			if (length $users->{$key}->{$A_LAST_NAME} > 20) {
				$users->{$key}->{$A_LAST_NAME} = substr($lastName, 0, 20);
			}
			if (length $users->{$key}->{$A_TITLE_BEFORE} > 15) {
				$users->{$key}->{$A_TITLE_BEFORE} = substr($titleBefore, 0, 15);
			}

			$groups->{$k4GroupId}->{"members"}->{$key} = 1;

		}

	}

}

#
# PRINT user data
#
open FILE,">$fileName" or die "Cannot open $fileName: $! \n";
binmode FILE, ":utf8";

# FOR EACH USER ON FACILITY
my @keys = sort keys %{$users};
for my $key (@keys) {

	# print attributes, which are never empty
	print FILE $key . "\t" . $users->{$key}->{$A_TITLE_BEFORE} . "\t" .
		$users->{$key}->{$A_FIRST_NAME} . "\t" . $users->{$key}->{$A_LAST_NAME} . "\t" .
		$users->{$key}->{$A_GENDER} . "\t" . $users->{$key}->{$A_NAV} . "\t" . $users->{$key}->{$A_STALEAKT} . "\t" .
		$users->{$key}->{$A_IFIS_ID} . "\t" . $users->{$key}->{"EXPIRATION"} . "\n";

}

close(FILE);

#
# PRINT group data
#
open FILE,">$groupFileName" or die "Cannot open $groupFileName: $! \n";
binmode FILE, ":utf8";

# FOR EACH GROUP ON FACILITY
my @gKeys = sort keys %{$groups};
for my $key (@gKeys) {

	my $members = "";
	if (defined $groups->{$key}->{"members"}) {
		$members = join(",", sort keys %{$groups->{$key}->{"members"}});
	}
	print FILE $key . "\t" . $groups->{$key}->{"code"}. "\t" . $groups->{$key}->{"name"} .
		"\t" . $groups->{$key}->{"priority"} . "\t" . $members . "\n";

}

close(FILE);

perunServicesInit::finalize;

#
# Calculate later from three expiration dates based on users relations on VŠUP.
#
# 1. param - expiration in KOS (studies)
# 2. param - expiration in DC2 (employees)
# 3. param - manually set expiration
#
# Returns Unix timestamp of users account expiration
# - in case of expiration on 1.1.4000 -> Zero is returned as "expiration = never".
# - in case of any other exact date, pick the largest (future). If it comes from study system (KOS),
#   add 21 days grace period.
#
sub calculateExpiration() {

	# read input
	my $expirationKos = shift;
	my $expirationDc2 = shift;
	my $expirationMan = shift;
	# parse to time or undef
	my $expirationKosTime = ($expirationKos) ? Time::Piece->strptime($expirationKos,"%Y-%m-%d") : undef;
	my $expirationDc2Time = ($expirationDc2) ? Time::Piece->strptime($expirationDc2,"%Y-%m-%d") : undef;
	my $expirationManTime = ($expirationMan) ? Time::Piece->strptime($expirationMan,"%Y-%m-%d") : undef;

	my @expirations = ();
	if (defined $expirationKosTime) { push(@expirations, $expirationKosTime->epoch); }
	if (defined $expirationDc2Time) { push(@expirations, $expirationDc2Time->epoch); }
	if (defined $expirationManTime) { push(@expirations, $expirationManTime->epoch); }

	# sort all expirations
	my @sorted_expirations = sort { $a <=> $b } @expirations;
	my $latest_expiration = $sorted_expirations[$#sorted_expirations];

	my $result;

	if (!defined $expirationKos and !defined $expirationDc2 and !defined $expirationMan) {
		# if no expiration set in source data - take as "never"
		return 0;
	}

	# case expiration "never" = 1.1.4000
	if ($latest_expiration == Time::Piece->strptime("4000-01-01","%Y-%m-%d")->epoch) {
		# return without specified expiration date
		return 0;
	}

	# (will) expire by studies - add 21 days grace period
	if ($expirationKosTime and ($latest_expiration == $expirationKosTime->epoch)) {
		$result = $latest_expiration + (21*24*60*60);
	} else {
		# Expired by employment or manual - push exact date
		$result = $latest_expiration;
	}

	return (localtime($result)->ymd() . " 23:59:59");

}
#
# Check, if passed value is valid timestamp a decide, if it`s in a past or not.
#
sub isExpired() {

	my $expiration = shift;
	# no group expiration
	unless (defined $expiration) { return 0; }

	# Parse date
	my $expirationTime = Time::Piece->strptime($expiration,"%Y-%m-%d");
	# Add 23:59:59
	$expirationTime = $expirationTime->epoch + 86399;
	# current time
	my $currentTime = localtime(time)->epoch;
	# compare if user is expired in a group
	if ($currentTime > $expirationTime) {
		return 1;
	} else {
		return 0;
	}

}
