/*
 * The Kuali Financial System, a comprehensive financial management system for higher education.
 *
 * Copyright 2005-2016 The Kuali Foundation
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.kuali.kfs.module.tem.document.authorization;

import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.kns.document.MaintenanceDocument;
import org.kuali.kfs.kns.document.authorization.DocumentAuthorizer;
import org.kuali.kfs.kns.document.authorization.MaintenanceDocumentAuthorizer;
import org.kuali.kfs.krad.document.Document;
import org.kuali.kfs.module.tem.TemConstants;
import org.kuali.kfs.module.tem.businessobject.TemProfile;
import org.kuali.kfs.sys.document.authorization.FinancialSystemMaintenanceDocumentAuthorizerBase;
import org.kuali.rice.kim.api.identity.Person;
import org.kuali.rice.krad.bo.BusinessObject;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * This authorizer needs to override the canCreate and canMaintain methods...which are final on the base MaintDocAuthorizer class that every other authorizer simply extends.  Therefore,
 * this uses an "assistant" authorizer to defer most of its calls to (rootDocumentAuthorizer) and overrides the logic for canCreate and canMaintain to perform the very special logic for TemProfiles.
 * It's acknowledged that this whole thing is going to be harder to customize than the regular authorizer.
 */
public class TemProfileAuthorizer implements MaintenanceDocumentAuthorizer, DocumentAuthorizer {
    protected TemProfileAuthorizerAssistant rootDocumentAuthorizer;

    /**
     * Not overridden; we'll just rely on the Create / Maintain Document check for this one
     *
     * @see org.kuali.rice.krad.maintenance.MaintenanceDocumentAuthorizer#canCreate(java.lang.Class, org.kuali.rice.kim.api.identity.Person)
     */
    @Override
    public boolean canCreate(Class boClass, Person user) {
        return getRootDocumentAuthorizer().canCreate(boClass, user);
    }

    /**
     * Overridden to verify that the user has KFS-TEM Edit Own Tem Profile permission before maintaining their own record, and that the user
     *
     * @see org.kuali.rice.krad.maintenance.MaintenanceDocumentAuthorizer#canMaintain(java.lang.Object, org.kuali.rice.kim.api.identity.Person)
     */
    @Override
    public boolean canMaintain(Object dataObject, Person user) {
        boolean result = getRootDocumentAuthorizer().canMaintain(dataObject, user);

        TemProfile profile = null;
        if (dataObject instanceof TemProfile) {
            profile = (TemProfile) dataObject;
        } else if (dataObject instanceof MaintenanceDocument) {
            profile = (TemProfile) ((MaintenanceDocument) dataObject).getNewMaintainableObject().getBusinessObject();
        }
        result &= ((doesProfileMatchUser(profile, user) && canEditOwnProfile((BusinessObject) dataObject, user)) || canEditAllProfiles((BusinessObject) dataObject, user));

        return result;
    }

    /**
     * Overridden to check that the user has the normal Create / Maintain permission and also, if it's their own profile, the ability to edit their own profile; if it's not their own profile,
     * it checks if they can edit anyone's profile
     *
     * @see org.kuali.kfs.krad.maintenance.MaintenanceDocumentAuthorizer#canCreateOrMaintain(org.kuali.kfs.krad.maintenance.MaintenanceDocument, org.kuali.rice.kim.api.identity.Person)
     */
    @Override
    public boolean canCreateOrMaintain(org.kuali.kfs.krad.maintenance.MaintenanceDocument maintenanceDocument, Person user) {
        boolean result = getRootDocumentAuthorizer().canCreateOrMaintain(maintenanceDocument, user);

        final TemProfile profile = (TemProfile) maintenanceDocument.getNewMaintainableObject().getDataObject();
        result &= ((doesProfileMatchUser(profile, user) && canEditOwnProfile(maintenanceDocument, user)) || canEditAllProfiles(maintenanceDocument, user));

        return result;
    }

    /**
     * Determines if the given profile matches the given user
     *
     * @param profile the profile to check
     * @return true if the profile is for the current user, false otherwise
     */
    protected boolean doesProfileMatchUser(TemProfile profile, Person user) {
        if (profile != null && !StringUtils.isBlank(profile.getPrincipalId())) {
            final String userPrincipalId = (user == null) ? null : user.getPrincipalId();
            return StringUtils.equals(profile.getPrincipalId(), userPrincipalId);
        }
        return false; // no principal id?  then they couldn't log in and therefore they can't be the current user
    }

    /**
     * Determines if the given user is allowed to create or maintain their own profile
     *
     * @param profileOrDoc the maintenance document maintaining the profile to check, or the profile itself
     * @param user         the user asking for permission
     * @return true if the user has permission, false otherwise
     */
    public boolean canEditOwnProfile(BusinessObject profileOrDoc, Person user) {
        Map<String, String> roleQualifications = new HashMap<String, String>();
        if (profileOrDoc != null) {
            getRootDocumentAuthorizer().addRoleQualification(profileOrDoc, roleQualifications);
        }

        return this.isAuthorized(profileOrDoc, TemConstants.NAMESPACE, TemConstants.Permission.EDIT_OWN_PROFILE, user.getPrincipalId(), Collections.<String, String>emptyMap(), roleQualifications);
    }

    /**
     * Determines if the given user is allowed to maintain a profile for any user or customer
     *
     * @param profileOrDoc the maintenance document maintaining the profile to check, or the profile itself
     * @param user         the user asking for permission
     * @return true if the user has permission, false otherwise
     */
    public boolean canEditAllProfiles(BusinessObject profileOrDoc, Person user) {
        Map<String, String> roleQualifications = new HashMap<String, String>();
        if (profileOrDoc != null) {
            getRootDocumentAuthorizer().addRoleQualification(profileOrDoc, roleQualifications);
        }

        return this.isAuthorized(profileOrDoc, TemConstants.NAMESPACE, TemConstants.Permission.EDIT_ANY_PROFILE, user.getPrincipalId(), Collections.<String, String>emptyMap(), roleQualifications);
    }

    /**
     * Determines if the given user is allowed to create a profile for any user or customer
     *
     * @param profileOrDoc the maintenance document maintaining the profile to check, or the profile itself
     * @param user         the user asking for permission
     * @return true if the user has permission, false otherwise
     */
    public boolean canCreateAnyProfile(BusinessObject profileOrDoc, Person user) {
        Map<String, String> roleQualifications = new HashMap<String, String>();
        if (profileOrDoc != null) {
            getRootDocumentAuthorizer().addRoleQualification(profileOrDoc, roleQualifications);
        }

        return this.isAuthorized(profileOrDoc, TemConstants.NAMESPACE, TemConstants.Permission.CREATE_ANY_PROFILE, user.getPrincipalId(), Collections.<String, String>emptyMap(), roleQualifications);
    }

    @Override
    public boolean canCopy(Document document, Person user) {
        return getRootDocumentAuthorizer().canCopy(document, user);
    }

    @Override
    public boolean isAuthorized(Object dataObject, String namespaceCode, String permissionName, String principalId) {
        return getRootDocumentAuthorizer().isAuthorized(dataObject, namespaceCode, permissionName, principalId);
    }

    @Override
    public boolean isAuthorizedByTemplate(Object dataObject, String namespaceCode, String permissionTemplateName, String principalId) {
        return getRootDocumentAuthorizer().isAuthorizedByTemplate(dataObject, namespaceCode, permissionTemplateName, principalId);
    }

    @Override
    public boolean isAuthorized(Object dataObject, String namespaceCode, String permissionName, String principalId, Map<String, String> additionalPermissionDetails, Map<String, String> additionalRoleQualifiers) {
        return getRootDocumentAuthorizer().isAuthorized(dataObject, namespaceCode, permissionName, principalId, additionalPermissionDetails, additionalRoleQualifiers);
    }

    @Override
    public Set<String> getSecurePotentiallyHiddenSectionIds() {
        return getRootDocumentAuthorizer().getSecurePotentiallyHiddenSectionIds();
    }

    @Override
    public boolean isAuthorized(BusinessObject businessObject, String namespaceCode, String permissionName, String principalId) {
        return getRootDocumentAuthorizer().isAuthorized(businessObject, namespaceCode, permissionName, principalId);
    }

    @Override
    public boolean isAuthorizedByTemplate(BusinessObject businessObject, String namespaceCode, String permissionTemplateName, String principalId) {
        return getRootDocumentAuthorizer().isAuthorizedByTemplate(businessObject, namespaceCode, permissionTemplateName, principalId);
    }

    @Override
    public boolean isAuthorized(BusinessObject businessObject, String namespaceCode, String permissionName, String principalId, Map<String, String> additionalPermissionDetails, Map<String, String> additionalRoleQualifiers) {
        return getRootDocumentAuthorizer().isAuthorized(businessObject, namespaceCode, permissionName, principalId, additionalPermissionDetails, additionalRoleQualifiers);
    }

    @Override
    public boolean isAuthorizedByTemplate(Object dataObject, String namespaceCode, String permissionTemplateName, String principalId, Map<String, String> additionalPermissionDetails, Map<String, String> additionalRoleQualifiers) {
        return getRootDocumentAuthorizer().isAuthorizedByTemplate(dataObject, namespaceCode, permissionTemplateName, principalId, additionalPermissionDetails, additionalRoleQualifiers);
    }

    @Override
    public Map<String, String> getCollectionItemRoleQualifications(BusinessObject collectionItemBusinessObject) {
        return getRootDocumentAuthorizer().getCollectionItemRoleQualifications(collectionItemBusinessObject);
    }

    @Override
    public Map<String, String> getCollectionItemPermissionDetails(BusinessObject collectionItemBusinessObject) {
        return getRootDocumentAuthorizer().getCollectionItemPermissionDetails(collectionItemBusinessObject);
    }

    @Override
    public boolean canInitiate(String documentTypeName, Person user) {
        return getRootDocumentAuthorizer().canInitiate(documentTypeName, user);
    }

    @Override
    public boolean canOpen(Document document, Person user) {
        return getRootDocumentAuthorizer().canOpen(document, user);
    }

    @Override
    public boolean canEdit(Document document, Person user) {
        return getRootDocumentAuthorizer().canEdit(document, user);
    }

    @Override
    public boolean canAnnotate(Document document, Person user) {
        return getRootDocumentAuthorizer().canAnnotate(document, user);
    }

    @Override
    public boolean canReload(Document document, Person user) {
        return getRootDocumentAuthorizer().canReload(document, user);
    }

    @Override
    public boolean canClose(Document document, Person user) {
        return getRootDocumentAuthorizer().canClose(document, user);
    }

    @Override
    public boolean canSave(Document document, Person user) {
        return getRootDocumentAuthorizer().canSave(document, user);
    }

    @Override
    public boolean canRoute(Document document, Person user) {
        return getRootDocumentAuthorizer().canRoute(document, user);
    }

    @Override
    public boolean canCancel(Document document, Person user) {
        return getRootDocumentAuthorizer().canCancel(document, user);
    }

    @Override
    public boolean canPerformRouteReport(Document document, Person user) {
        return getRootDocumentAuthorizer().canPerformRouteReport(document, user);
    }

    @Override
    public boolean canBlanketApprove(Document document, Person user) {
        return getRootDocumentAuthorizer().canBlanketApprove(document, user);
    }

    @Override
    public boolean canApprove(Document document, Person user) {
        return getRootDocumentAuthorizer().canApprove(document, user);
    }

    @Override
    public boolean canDisapprove(Document document, Person user) {
        return getRootDocumentAuthorizer().canDisapprove(document, user);
    }

    @Override
    public boolean canSendNoteFyi(Document document, Person user) {
        return getRootDocumentAuthorizer().canSendNoteFyi(document, user);
    }

    @Override
    public boolean canEditDocumentOverview(Document document, Person user) {
        return getRootDocumentAuthorizer().canEditDocumentOverview(document, user);
    }

    @Override
    public boolean canFyi(Document document, Person user) {
        return getRootDocumentAuthorizer().canFyi(document, user);
    }

    @Override
    public boolean canAcknowledge(Document document, Person user) {
        return getRootDocumentAuthorizer().canAcknowledge(document, user);
    }

    @Override
    public boolean canReceiveAdHoc(Document document, Person user, String actionRequestCode) {
        return getRootDocumentAuthorizer().canReceiveAdHoc(document, user, actionRequestCode);
    }

    @Override
    public boolean canAddNoteAttachment(Document document, String attachmentTypeCode, Person user) {
        return getRootDocumentAuthorizer().canAddNoteAttachment(document, attachmentTypeCode, user);
    }

    @Override
    public boolean canDeleteNoteAttachment(Document document, String attachmentTypeCode, String authorUniversalIdentifier, Person user) {
        return getRootDocumentAuthorizer().canDeleteNoteAttachment(document, attachmentTypeCode, authorUniversalIdentifier, user);
    }

    @Override
    public boolean canViewNoteAttachment(Document document, String attachmentTypeCode, String authorUniversalIdentifier, Person user) {
        return getRootDocumentAuthorizer().canViewNoteAttachment(document, attachmentTypeCode, authorUniversalIdentifier, user);
    }

    @Override
    public boolean canSendAdHocRequests(Document document, String actionRequestCd, Person user) {
        return getRootDocumentAuthorizer().canSendAdHocRequests(document, actionRequestCd, user);
    }

    @Override
    public boolean canSendAnyTypeAdHocRequests(Document document, Person user) {
        return getRootDocumentAuthorizer().canSendAnyTypeAdHocRequests(document, user);
    }

    @Override
    public boolean canTakeRequestedAction(Document document, String actionRequestCode, Person user) {
        return getRootDocumentAuthorizer().canTakeRequestedAction(document, actionRequestCode, user);
    }

    @Override
    public boolean canRecall(Document document, Person user) {
        return getRootDocumentAuthorizer().canRecall(document, user);
    }

    @Override
    public Set<String> getSecurePotentiallyReadOnlySectionIds() {
        return getRootDocumentAuthorizer().getSecurePotentiallyReadOnlySectionIds();
    }

    @Override
    public Set<String> getDocumentActions(Document document, Person user, Set<String> documentActions) {
        return getRootDocumentAuthorizer().getDocumentActions(document, user, documentActions);
    }

    @Override
    public boolean canViewNoteAttachment(Document document, String attachmentTypeCode, Person user) {
        return getRootDocumentAuthorizer().canViewNoteAttachment(document, attachmentTypeCode, user);
    }

    /**
     * This is what actually uses the TemProfileAuthorizerAssistant to use as the logic we defer calls to
     *
     * @return a document authorizer composed in, to defer to when we can
     */
    protected TemProfileAuthorizerAssistant getRootDocumentAuthorizer() {
        try {
            if (rootDocumentAuthorizer == null) {
                rootDocumentAuthorizer = TemProfileAuthorizerAssistant.class.newInstance();
            }
        } catch (InstantiationException ie) {
            throw new RuntimeException("Could not instantiate instance of " + FinancialSystemMaintenanceDocumentAuthorizerBase.class.getName(), ie);
        } catch (IllegalAccessException iae) {
            throw new RuntimeException("Access issues while instantiating instance of " + FinancialSystemMaintenanceDocumentAuthorizerBase.class.getName(), iae);
        }
        return rootDocumentAuthorizer;
    }
}
