/*
 * 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.sys.service.impl;

import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.coa.businessobject.Chart;
import org.kuali.kfs.coa.businessobject.Organization;
import org.kuali.kfs.coa.identity.FinancialSystemUserRoleTypeServiceImpl;
import org.kuali.kfs.coa.service.ChartService;
import org.kuali.kfs.coa.service.OrganizationService;
import org.kuali.kfs.krad.util.ObjectUtils;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.businessobject.ChartOrgHolder;
import org.kuali.kfs.sys.identity.KfsKimAttributes;
import org.kuali.kfs.sys.service.FinancialSystemUserService;
import org.kuali.rice.kim.api.KimConstants;
import org.kuali.rice.kim.api.identity.Person;
import org.kuali.rice.kim.api.identity.PersonService;
import org.kuali.rice.kim.api.identity.entity.Entity;
import org.kuali.rice.kim.api.role.RoleService;
import org.kuali.rice.kim.api.services.KimApiServiceLocator;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class FinancialSystemUserServiceImpl implements FinancialSystemUserService {

    protected ChartService chartService;
    protected OrganizationService organizationService;
    protected RoleService roleService;
    protected PersonService personService;
    protected String userRoleId;
    private final List<String> userRoleIdList = new ArrayList<String>(1);

    protected String getUserRoleId() {
        if (userRoleId == null) {
            userRoleId = roleService.getRoleIdByNamespaceCodeAndName(KFSConstants.CoreModuleNamespaces.KFS, KFSConstants.SysKimApiConstants.KFS_USER_ROLE_NAME);
        }
        return userRoleId;
    }

    protected List<String> getUserRoleIdAsList() {
        if (userRoleIdList.isEmpty()) {
            userRoleIdList.add(getUserRoleId());
        }
        return userRoleIdList;
    }


    /**
     * @see org.kuali.kfs.sys.service.FinancialSystemUserService#isActiveFinancialSystemUser(java.lang.String)
     */
    @Override
    public boolean isActiveFinancialSystemUser(String principalId) {
        if (StringUtils.isBlank(principalId)) {
            return false;
        }
        return roleService.principalHasRole(principalId, getUserRoleIdAsList(), new HashMap<String, String>());
    }

    /**
     * @see org.kuali.kfs.sys.service.FinancialSystemUserService#getPrimaryOrganization(org.kuali.rice.kim.api.identity.Person,
     * java.lang.String)
     */
    @Override
    public ChartOrgHolder getPrimaryOrganization(Person person, String namespaceCode) {
        if (person == null) {
            return null;
        }
        ChartOrgHolder chartOrgHolder = getOrganizationForFinancialSystemUser(person.getPrincipalId(), namespaceCode);
        if (chartOrgHolder == null) {
            chartOrgHolder = getOrganizationForNonFinancialSystemUser(person);
        }
        return (chartOrgHolder == null) ? new ChartOrgHolderImpl() : chartOrgHolder;
    }

    /**
     * @see org.kuali.kfs.sys.service.FinancialSystemUserService#getOrganizationByNamespaceCode(org.kuali.rice.kim.api.identity.Person,
     * java.lang.String)
     */
    @Override
    public ChartOrgHolder getPrimaryOrganization(String principalId, String namespaceCode) {
        if (StringUtils.isBlank(principalId)) {
            return new ChartOrgHolderImpl();
        }
        ChartOrgHolder chartOrgHolder = getOrganizationForFinancialSystemUser(principalId, namespaceCode);
        if (chartOrgHolder == null) {
            chartOrgHolder = getOrganizationForNonFinancialSystemUser(personService.getPerson(principalId));
        }
        return (chartOrgHolder == null) ? new ChartOrgHolderImpl() : chartOrgHolder;
    }

    protected ChartOrgHolder getOrganizationForFinancialSystemUser(String principalId, String namespaceCode) {
        if (StringUtils.isBlank(principalId)) {
            return null;
        }
        Map<String, String> qualification = new HashMap<String, String>(2);
        qualification.put(FinancialSystemUserRoleTypeServiceImpl.PERFORM_QUALIFIER_MATCH, "true");
        qualification.put(KimConstants.AttributeConstants.NAMESPACE_CODE, namespaceCode);
        List<Map<String, String>> roleQualifiers = roleService.getRoleQualifersForPrincipalByNamespaceAndRolename(principalId, KFSConstants.CoreModuleNamespaces.KFS, KFSConstants.SysKimApiConstants.KFS_USER_ROLE_NAME, qualification);
        if ((roleQualifiers != null) && !roleQualifiers.isEmpty()) {
            int count = 0;
            while (count < roleQualifiers.size()) {
                if (!StringUtils.isBlank(roleQualifiers.get(count).get(KfsKimAttributes.CHART_OF_ACCOUNTS_CODE)) && !StringUtils.isBlank(roleQualifiers.get(count).get(KfsKimAttributes.ORGANIZATION_CODE))) {
                    return new ChartOrgHolderImpl(roleQualifiers.get(count).get(KfsKimAttributes.CHART_OF_ACCOUNTS_CODE), roleQualifiers.get(count).get(KfsKimAttributes.ORGANIZATION_CODE));
                }
                count += 1;
            }
        }
        return null;
    }

    @Deprecated
    protected ChartOrgHolder getOrganizationForNonFinancialSystemUser(Person person) {
        // HACK ALERT!!!!! - This is to support the original universal user table setup where the home department
        // was encoded as CAMPUS-ORG (Where campus happened to match the chart) in the original FS_UNIVERSAL_USR_T table.
        // This **REALLY** needs a new implementation
        if (person != null && person.getPrimaryDepartmentCode().contains("-")) {
            return new ChartOrgHolderImpl(StringUtils.substringBefore(person.getPrimaryDepartmentCode(), "-"), StringUtils.substringAfter(person.getPrimaryDepartmentCode(), "-"));
        }
        return null;
    }

    @Override
    public Collection<String> getPrincipalIdsForFinancialSystemOrganizationUsers(String namespaceCode, ChartOrgHolder chartOrg) {
        Map<String, String> qualification = new HashMap<String, String>(4);
        qualification.put(FinancialSystemUserRoleTypeServiceImpl.PERFORM_QUALIFIER_MATCH, "true");
        qualification.put(KimConstants.AttributeConstants.NAMESPACE_CODE, namespaceCode);
        qualification.put(KfsKimAttributes.CHART_OF_ACCOUNTS_CODE, chartOrg.getChartOfAccountsCode());
        qualification.put(KfsKimAttributes.ORGANIZATION_CODE, chartOrg.getOrganizationCode());
        return roleService.getRoleMemberPrincipalIds(KFSConstants.CoreModuleNamespaces.KFS, KFSConstants.SysKimApiConstants.KFS_USER_ROLE_NAME, qualification);
    }

    @Override
    public Collection<String> getPrincipalIdsForFinancialSystemOrganizationUsers(String namespaceCode, List<ChartOrgHolder> chartOrgs) {
        List<String> principalIds = new ArrayList<String>();
        for (ChartOrgHolder chartOrg : chartOrgs) {
            principalIds.addAll(getPrincipalIdsForFinancialSystemOrganizationUsers(namespaceCode, chartOrg));
        }
        return principalIds;
    }

    public class ChartOrgHolderImpl implements ChartOrgHolder {
        protected String chartOfAccountsCode;
        protected String organizationCode;


        public ChartOrgHolderImpl() {
        }

        public ChartOrgHolderImpl(String chartOfAccountsCode, String organizationCode) {
            this.chartOfAccountsCode = chartOfAccountsCode;
            this.organizationCode = organizationCode;
        }

        @Override
        public Chart getChartOfAccounts() {
            return chartService.getByPrimaryId(chartOfAccountsCode);
        }

        @Override
        public Organization getOrganization() {
            return organizationService.getByPrimaryIdWithCaching(chartOfAccountsCode, organizationCode);
        }

        @Override
        public String getChartOfAccountsCode() {
            return chartOfAccountsCode;
        }

        public void setChartOfAccountsCode(String chartOfAccountsCode) {
            this.chartOfAccountsCode = chartOfAccountsCode;
        }

        @Override
        public String getOrganizationCode() {
            return organizationCode;
        }

        public void setOrganizationCode(String organizationCode) {
            this.organizationCode = organizationCode;
        }

        @Override
        public boolean equals(Object obj) {
            if (!(obj instanceof ChartOrgHolder)) {
                return false;
            }
            return chartOfAccountsCode.equals(((ChartOrgHolder) obj).getChartOfAccountsCode()) && organizationCode.equals(((ChartOrgHolder) obj).getOrganizationCode());
        }

        @Override
        public int hashCode() {
            return chartOfAccountsCode.hashCode() + organizationCode.hashCode();
        }
    }

    /**
     * KFSCNTRB-1344 This implementation serves as a default place-holder. It uses IdentityService to retrieve Person name instead
     * of using PersonService, which is usually very slow with large date sets. Institutions that need to further speed up Person
     * name retrieval can override the implementation by using IdentityService.getDefaultNamesForPrincipalId (however that will only
     * work if PrincipalId is always the same as EmployeeId); Or, if speed is not a concern, fall back to using PersonService, which
     * is a lot slower than IdentityService.
     *
     * @see org.kuali.kfs.sys.service.FinancialSystemUserService#getPersonNameByEmployeeId(java.lang.String)
     */
    // TODO This method can be replaced if Rice adds method IdentityService.getPersonNameByEmployeeId.
    @Override
    public String getPersonNameByEmployeeId(String employeeId) {
        Entity entity = KimApiServiceLocator.getIdentityService().getEntityByEmployeeId(employeeId);
        if (ObjectUtils.isNotNull(entity) && ObjectUtils.isNotNull(entity.getDefaultName())) {
            return entity.getDefaultName().getCompositeName();
        }
        return null;
    }

    public void setChartService(ChartService chartService) {
        this.chartService = chartService;
    }

    public void setOrganizationService(OrganizationService organizationService) {
        this.organizationService = organizationService;
    }

    public void setRoleService(RoleService roleService) {
        this.roleService = roleService;
    }

    public void setPersonService(PersonService personService) {
        this.personService = personService;
    }
}
