/*
 * 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.sec.businessobject.lookup;

import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.kns.datadictionary.BusinessObjectEntry;
import org.kuali.kfs.kns.lookup.KualiLookupableHelperServiceImpl;
import org.kuali.kfs.kns.service.DataDictionaryService;
import org.kuali.kfs.kns.util.FieldUtils;
import org.kuali.kfs.kns.web.ui.Column;
import org.kuali.kfs.kns.web.ui.Field;
import org.kuali.kfs.kns.web.ui.Row;
import org.kuali.kfs.krad.util.KRADConstants;
import org.kuali.kfs.sec.SecConstants;
import org.kuali.kfs.sec.SecPropertyConstants;
import org.kuali.kfs.sec.businessobject.SecurityAttributeMetadata;
import org.kuali.kfs.sec.service.AccessSecurityService;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.service.UniversityDateService;
import org.kuali.rice.core.web.format.Formatter;
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.services.KimApiServiceLocator;
import org.kuali.rice.krad.bo.BusinessObject;

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


/**
 * Calls the access security service to simulate validation for the specified user, attribute, and action
 */
public class AccessSecuritySimulationLookupableHelperServiceImpl extends KualiLookupableHelperServiceImpl {
    protected AccessSecurityService accessSecurityService;
    protected UniversityDateService universityDateService;

    protected List<Row> rows;

    public AccessSecuritySimulationLookupableHelperServiceImpl() {
        rows = null;
    }

    /**
     * @see org.kuali.rice.kns.lookup.KualiLookupableHelperServiceImpl#getSearchResults(java.util.Map)
     * <p>
     * KRAD Conversion: Lookupable performs customization of the search results.
     * by retrieving the default sort columns using data dictionary service.
     */
    @Override
    public List<? extends BusinessObject> getSearchResults(Map<String, String> fieldValues) {
        String principalName = fieldValues.get(SecPropertyConstants.SECURITY_PERSON_PRINCIPAL_NAME);
        Person person = SpringContext.getBean(PersonService.class).getPersonByPrincipalName(principalName);

        String attributeName = fieldValues.get(SecPropertyConstants.ATTRIBUTE_NAME);
        String templateId = fieldValues.get(SecPropertyConstants.TEMPLATE_ID);

        Map<String, String> additionalPermissionDetails = new HashMap<String, String>();
        if (accessSecurityService.getInquiryWithFieldValueTemplate().getId().equals(templateId)) {
            String namespaceCode = fieldValues.get(SecPropertyConstants.INQUIRY_NAMESPACE_CODE);
            additionalPermissionDetails.put(KimConstants.AttributeConstants.NAMESPACE_CODE, namespaceCode);
        } else if (!accessSecurityService.getLookupWithFieldValueTemplate().getId().equals(templateId)) {
            String documentTypeCode = fieldValues.get(SecPropertyConstants.FINANCIAL_SYSTEM_DOCUMENT_TYPE_CODE);
            additionalPermissionDetails.put(KimConstants.AttributeConstants.DOCUMENT_TYPE_NAME, documentTypeCode);
        }

        return runSimulation(person, attributeName, templateId, additionalPermissionDetails);
    }

    /**
     * @param person
     * @param attribute
     * @param templateId
     * @param additionalPermissionDetails
     * @return KRAD Conversion: Lookupable performs customization of the search results.
     * by retrieving the columns using data dictionary service.
     */
    protected List<? extends BusinessObject> runSimulation(Person person, String attributeName, String templateId, Map<String, String> additionalPermissionDetails) {
        List<BusinessObject> resultRecords = new ArrayList<BusinessObject>();

        if (!SecConstants.ATTRIBUTE_SIMULATION_MAP.containsKey(attributeName)) {
            throw new RuntimeException("Unable to find attribute metadata for attribute: " + attributeName);
        }
        SecurityAttributeMetadata attributeMetadata = SecConstants.ATTRIBUTE_SIMULATION_MAP.get(attributeName);
        Class attributeClass = attributeMetadata.getAttributeClass();

        // build criteria for retrieving attribute records
        Map<String, Object> searchCriteria = new HashMap<String, Object>();
        List<String> fieldNames = getPersistenceStructureService().listFieldNames(attributeClass);
        if (fieldNames.contains(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR)) {
            searchCriteria.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, universityDateService.getCurrentFiscalYear());
        }

        if (fieldNames.contains(KFSPropertyConstants.ACTIVE)) {
            searchCriteria.put(KFSPropertyConstants.ACTIVE, true);
        }

        // retrieve records for this attribute to iterate over and call security service
        List allAttributeData = (List) getBusinessObjectService().findMatching(attributeClass, searchCriteria);
        accessSecurityService.applySecurityRestrictions(allAttributeData, person, KimApiServiceLocator.getPermissionService().getPermissionTemplate(templateId), additionalPermissionDetails);

        // iterate through business object instances and construct simulation info result objects
        // for (Iterator iterator = allAttributeData.iterator(); iterator.hasNext();) {
        // BusinessObject businessObject = (BusinessObject) iterator.next();
        //
        // AccessSecuritySimulation securitySimulation = new AccessSecuritySimulation();
        //
        // Object boKeyFieldValue = ObjectUtils.getPropertyValue(businessObject, attributeMetadata.getAttributeField());
        // Object boNameFieldValue = ObjectUtils.getPropertyValue(businessObject, attributeMetadata.getAttributeNameField());
        //
        // securitySimulation.setAttributeValue(boKeyFieldValue.toString());
        // securitySimulation.setAttributeValueName(boNameFieldValue.toString());
        //
        // resultRecords.add(securitySimulation);
        // }

        return allAttributeData;
    }

    /**
     * @see org.kuali.rice.kns.lookup.AbstractLookupableHelperServiceImpl#setRows()
     */
    @Override
    protected void setRows() {
        List<String> lookupFieldAttributeList = new ArrayList<String>();
        if (getParameters().containsKey(SecPropertyConstants.TEMPLATE_ID)) {
            String templateId = ((String[]) getParameters().get(SecPropertyConstants.TEMPLATE_ID))[0];

            if (accessSecurityService.getInquiryWithFieldValueTemplate().getId().equals(templateId)) {
                lookupFieldAttributeList = getInquiryTemplateFields();
            } else if (accessSecurityService.getLookupWithFieldValueTemplate().getId().equals(templateId)) {
                lookupFieldAttributeList = getLookupTemplateFields();
            } else {
                lookupFieldAttributeList = getDocumentTemplateFields();
            }
        } else {
            lookupFieldAttributeList = getLookupTemplateFields();
        }

        // construct field object for each search attribute
        List<Field> fields = new ArrayList<Field>();
        int numCols;
        try {
            fields = FieldUtils.createAndPopulateFieldsForLookup(lookupFieldAttributeList, getReadOnlyFieldsList(), getBusinessObjectClass());

            BusinessObjectEntry boe = (BusinessObjectEntry) SpringContext.getBean(DataDictionaryService.class).getDataDictionary().getBusinessObjectEntry(this.getBusinessObjectClass().getName());
            numCols = boe.getLookupDefinition().getNumOfColumns();

        } catch (InstantiationException e) {
            throw new RuntimeException("Unable to create instance of business object class" + e.getMessage());
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Unable to create instance of business object class" + e.getMessage());
        }

        if (numCols == 0) {
            numCols = KRADConstants.DEFAULT_NUM_OF_COLUMNS;
        }

        rows = FieldUtils.wrapFields(fields, numCols);
    }

    /**
     * @see org.kuali.rice.kns.lookup.AbstractLookupableHelperServiceImpl#getRows()
     */
    @Override
    public List<Row> getRows() {
        return rows;
    }

    /**
     * @see org.kuali.rice.kns.lookup.AbstractLookupableHelperServiceImpl#getColumns()
     * <p>
     * KRAD Conversion: Lookupable performs the customization of columns
     * by  using data dictionary service for attribute properties.
     */
    @Override
    public List<Column> getColumns() {
        String searchAttributeName = ((String[]) getParameters().get(SecPropertyConstants.ATTRIBUTE_NAME))[0];

        SecurityAttributeMetadata attributeMetadata = SecConstants.ATTRIBUTE_SIMULATION_MAP.get(searchAttributeName);
        Class attributeClass = attributeMetadata.getAttributeClass();

        ArrayList<Column> columns = new ArrayList<Column>();
        for (String attributeName : getBusinessObjectDictionaryService().getLookupResultFieldNames(attributeClass)) {
            Column column = new Column();
            column.setPropertyName(attributeName);

            String columnTitle = getDataDictionaryService().getAttributeLabel(attributeClass, attributeName);
            Boolean useShortLabel = getBusinessObjectDictionaryService().getLookupResultFieldUseShortLabel(attributeClass, attributeName);
            if (useShortLabel != null && useShortLabel) {
                columnTitle = getDataDictionaryService().getAttributeShortLabel(attributeClass, attributeName);
            }
            if (StringUtils.isBlank(columnTitle)) {
                columnTitle = getDataDictionaryService().getCollectionLabel(attributeClass, attributeName);
            }
            column.setColumnTitle(columnTitle);

            Integer fieldDefinedMaxLength = getBusinessObjectDictionaryService().getLookupResultFieldMaxLength(attributeClass, attributeName);
            if (fieldDefinedMaxLength == null) {
                try {
                    fieldDefinedMaxLength = Integer.valueOf(getParameterService().getParameterValueAsString(KRADConstants.KNS_NAMESPACE, KRADConstants.DetailTypes.LOOKUP_PARM_DETAIL_TYPE, KRADConstants.RESULTS_DEFAULT_MAX_COLUMN_LENGTH));
                } catch (NumberFormatException ex) {
                    LOG.error("Lookup field max length parameter not found for " + attributeClass.getName() + "." + attributeName + " -- and unable to parse default set in system parameters (RESULTS_DEFAULT_MAX_COLUMN_LENGTH).");
                    fieldDefinedMaxLength = 50;
                }
            }
            column.setMaxLength(fieldDefinedMaxLength.intValue());

            Class formatterClass = getDataDictionaryService().getAttributeFormatter(attributeClass, attributeName);
            if (formatterClass != null) {
                try {
                    column.setFormatter((Formatter) formatterClass.newInstance());
                } catch (InstantiationException e) {
                    LOG.error("Unable to get new instance of formatter class: " + formatterClass.getName());
                    throw new RuntimeException("Unable to get new instance of formatter class: " + formatterClass.getName());
                } catch (IllegalAccessException e) {
                    LOG.error("Unable to get new instance of formatter class: " + formatterClass.getName());
                    throw new RuntimeException("Unable to get new instance of formatter class: " + formatterClass.getName());
                }
            }

            columns.add(column);
        }

        return columns;
    }

    /**
     * Builds List of search field names for searching the inquiry template
     *
     * @return List<String> containing lookup field names
     */
    protected List<String> getInquiryTemplateFields() {
        List<String> lookupFields = new ArrayList<String>();

        lookupFields.add(SecPropertyConstants.SECURITY_PERSON_PRINCIPAL_NAME);
        lookupFields.add(SecPropertyConstants.ATTRIBUTE_NAME);
        lookupFields.add(SecPropertyConstants.TEMPLATE_ID);
        lookupFields.add(SecPropertyConstants.INQUIRY_NAMESPACE_CODE);

        return lookupFields;
    }

    /**
     * Builds List of search field names for searching the lookup template
     *
     * @return List<String> containing lookup field names
     */
    protected List<String> getLookupTemplateFields() {
        List<String> lookupFields = new ArrayList<String>();

        lookupFields.add(SecPropertyConstants.SECURITY_PERSON_PRINCIPAL_NAME);
        lookupFields.add(SecPropertyConstants.ATTRIBUTE_NAME);
        lookupFields.add(SecPropertyConstants.TEMPLATE_ID);

        return lookupFields;
    }

    /**
     * Builds List of search field names for searching document templates
     *
     * @return List<String> containing lookup field names
     */
    protected List<String> getDocumentTemplateFields() {
        List<String> lookupFields = new ArrayList<String>();

        lookupFields.add(SecPropertyConstants.SECURITY_PERSON_PRINCIPAL_NAME);
        lookupFields.add(SecPropertyConstants.ATTRIBUTE_NAME);
        lookupFields.add(SecPropertyConstants.TEMPLATE_ID);
        lookupFields.add(SecPropertyConstants.FINANCIAL_SYSTEM_DOCUMENT_TYPE_CODE);

        return lookupFields;
    }

    /**
     * Sets the accessSecurityService attribute value.
     *
     * @param accessSecurityService The accessSecurityService to set.
     */
    public void setAccessSecurityService(AccessSecurityService accessSecurityService) {
        this.accessSecurityService = accessSecurityService;
    }

    /**
     * Sets the universityDateService attribute value.
     *
     * @param universityDateService The universityDateService to set.
     */
    public void setUniversityDateService(UniversityDateService universityDateService) {
        this.universityDateService = universityDateService;
    }

}
