/**
 * Copyright 2005-2018 The Kuali Foundation
 *
 * Licensed under the Educational Community License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.opensource.org/licenses/ecl2.php
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.kuali.rice.krad.uif.element;

import org.apache.commons.lang.StringUtils;
import org.kuali.rice.krad.datadictionary.parse.BeanTag;
import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
import org.kuali.rice.krad.uif.UifConstants;
import org.kuali.rice.krad.uif.component.Component;
import org.kuali.rice.krad.uif.container.CollectionGroup;
import org.kuali.rice.krad.uif.container.Container;
import org.kuali.rice.krad.uif.container.LightTable;
import org.kuali.rice.krad.uif.container.PageGroup;
import org.kuali.rice.krad.uif.field.FieldGroup;
import org.kuali.rice.krad.uif.field.InputField;
import org.kuali.rice.krad.uif.layout.StackedLayoutManager;
import org.kuali.rice.krad.uif.layout.TableLayoutManager;
import org.kuali.rice.krad.uif.util.ScriptUtils;
import org.kuali.rice.krad.uif.view.View;
import org.kuali.rice.krad.util.GlobalVariables;

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

/**
 * ValidationMessages for logic and options specific to groups.
 *
 * @author Kuali Rice Team (rice.collab@kuali.org)
 */
@BeanTag(name = "groupValidationMessages", parent = "Uif-GroupValidationMessages")
public class GroupValidationMessages extends ValidationMessages {

    private static final long serialVersionUID = -5389990220206079052L;

    private boolean closeable;

    private boolean displayFieldLabelWithMessages;
    private boolean collapseAdditionalFieldLinkMessages;
    private boolean displayHeaderMessageSummary;

    private static final String SECTION_TOKEN = "s$";
    private static final String FIELDGROUP_TOKEN = "f$";
    private static final String TABLE_COLLECTION_TOKEN = "c$";

    /**
     * {@inheritDoc}
     */
    @Override
    public void generateMessages(View view, Object model, Component parent) {
        super.generateMessages(view, model, parent);

        addValidationMessageDataAttributes(parent);
    }

    /**
     * Adds dataAttributes that are appropriate for group level validationMessages data.
     *
     * <p>This data is used by the validation framework clientside. Some special handling at this level includes
     * retrieving the groups and fields generated by different collection layouts and handling page and fieldGroup
     * scenarios slightly differently due to the nature of how they are built out in the js.</p>
     *
     * @param parent component that is the parent of the validation messages
     */
    protected void addValidationMessageDataAttributes(Component parent) {
        HashMap<String, Object> validationMessagesDataAttributes = new HashMap<String, Object>();

        Map<String, Object> parentContext = parent.getContext();
        Object parentContainer = parentContext == null ? null : parentContext.get(
                UifConstants.ContextVariableNames.PARENT);

        List<? extends Component> items = ((Container) parent).getItems();
        boolean skipSections = false;
        boolean isTableCollection = false;

        // Handle the special CollectionGroup case by getting the StackedGroups and DataFields generated by them
        if (parent instanceof CollectionGroup && ((CollectionGroup) parent)
                .getLayoutManager() instanceof StackedLayoutManager) {
            items = ((StackedLayoutManager) ((CollectionGroup) parent).getLayoutManager()).getStackedGroups();
        } else if ((parent instanceof CollectionGroup && ((CollectionGroup) parent)
                .getLayoutManager() instanceof TableLayoutManager) || parent instanceof LightTable) {
            // order is not needed  so null items
            items = null;
            skipSections = true;
            isTableCollection = true;
        }

        List<String> sectionIds = new ArrayList<String>();
        List<String> fieldOrder = new ArrayList<String>();
        collectIdsFromItems(items, sectionIds, fieldOrder, skipSections);

        boolean pageLevel = false;
        boolean forceShow = false;
        boolean showPageSummaryHeader = true;
        if (parent instanceof PageGroup) {
            pageLevel = true;
            forceShow = true;
            parent.addDataAttribute(UifConstants.DataAttributes.SERVER_MESSAGES, Boolean.toString(
                    GlobalVariables.getMessageMap().hasMessages() || !GlobalVariables.getAuditErrorMap().isEmpty()));
            if (this instanceof PageValidationMessages) {
                showPageSummaryHeader = ((PageValidationMessages) this).isShowPageSummaryHeader();
            }
        } else if (parentContainer instanceof FieldGroup) {
            Map<String, String> parentFieldGroupDataAttributes = ((FieldGroup) parentContainer).getDataAttributes();
            String role = parentFieldGroupDataAttributes == null ? null : parentFieldGroupDataAttributes.get(
                    UifConstants.DataAttributes.ROLE);
            if (StringUtils.isNotBlank(role) && role.equals("detailsFieldGroup")) {
                forceShow = false;
            } else {
                //note this means container of the parent is a FieldGroup
                forceShow = true;
            }
        }

        boolean hasMessages = false;
        if (!this.getErrors().isEmpty() || !this.getWarnings().isEmpty() || !this.getInfos().isEmpty()) {
            hasMessages = true;
        }

        Map<String, String> dataDefaults =
                (Map<String, String>) (KRADServiceLocatorWeb.getDataDictionaryService().getDictionaryBean(
                        "Uif-GroupValidationMessages-DataDefaults"));

        //add necessary data attributes to map
        //display related
        this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults,
                UifConstants.DataAttributes.SUMMARIZE, true);
        this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults,
                UifConstants.DataAttributes.DISPLAY_MESSAGES, this.isDisplayMessages());
        this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults,
                UifConstants.DataAttributes.CLOSEABLE, this.isCloseable());
        this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults,
                UifConstants.DataAttributes.COLLAPSE_FIELD_MESSAGES, collapseAdditionalFieldLinkMessages);
        this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults,
                UifConstants.DataAttributes.SHOW_PAGE_SUMMARY_HEADER, showPageSummaryHeader);
        this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults,
                UifConstants.DataAttributes.DISPLAY_LABEL, displayFieldLabelWithMessages);
        this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults,
                UifConstants.DataAttributes.DISPLAY_HEADER_SUMMARY, displayHeaderMessageSummary);
        this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults,
                UifConstants.DataAttributes.IS_TABLE_COLLECTION, isTableCollection);

        //options
        this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults,
                UifConstants.DataAttributes.HAS_OWN_MESSAGES, hasMessages);
        this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults,
                UifConstants.DataAttributes.PAGE_LEVEL, pageLevel);
        this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults,
                UifConstants.DataAttributes.FORCE_SHOW, forceShow);

        //order related
        this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults,
                UifConstants.DataAttributes.SECTIONS, sectionIds);
        this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults,
                UifConstants.DataAttributes.ORDER, fieldOrder);

        //server messages
        this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults,
                UifConstants.DataAttributes.SERVER_ERRORS, ScriptUtils.escapeHtml(this.getErrors()));
        this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults,
                UifConstants.DataAttributes.SERVER_WARNINGS, ScriptUtils.escapeHtml(this.getWarnings()));
        this.addValidationDataSettingsValue(validationMessagesDataAttributes, dataDefaults,
                UifConstants.DataAttributes.SERVER_INFO, ScriptUtils.escapeHtml(this.getInfos()));

        if (!validationMessagesDataAttributes.isEmpty()) {
            parent.addScriptDataAttribute(UifConstants.DataAttributes.VALIDATION_MESSAGES, ScriptUtils.translateValue(
                    validationMessagesDataAttributes));
        }
    }

    /**
     * Collects all the ids from the items passed into this method.
     *
     * <p>Puts the ids of items determined to be sections
     * into the sectionIds list, and orders all items by the order they appear on the page in the order list with
     * special identifiers
     * to determine the type of item they are (used by the client js).  When skipSections is true do not
     * include sectionIds found in the lists.</p>
     *
     * @param items items of the group
     * @param sectionIds list to put section ids into
     * @param order list to put order of ids into (both fields and sections)
     * @param skipSections skip adding sections
     */
    protected void collectIdsFromItems(List<? extends Component> items, List<String> sectionIds, List<String> order,
            boolean skipSections) {

        if (items == null) {
            return;
        }

        for (Component component : items) {
            String id = StringUtils.replace(component.getId(), "@id@", "");

            if (component instanceof FieldGroup) {
                boolean treatFieldGroupAsSection = ((FieldGroup) component).getFieldLabel().isRender() &&
                        !((FieldGroup) component).getFieldLabel().isHidden() &&
                        (StringUtils.isNotEmpty(((FieldGroup) component).getLabel()) || StringUtils.isNotEmpty(
                                ((FieldGroup) component).getFieldLabel().getLabelText()));

                if (!skipSections && treatFieldGroupAsSection) {
                    sectionIds.add(id);

                    // Add with field group identifier
                    order.add(FIELDGROUP_TOKEN + id);
                    continue;
                } else {
                    component = ((FieldGroup) component).getGroup();
                    if (component == null) {
                        continue;
                    }
                }
            }

            // Not an 'else if' because component being evaluated can change to container above^
            if (component instanceof Container) {
                id = StringUtils.replace(component.getId(), "@id@", "");

                //If any kind of header text is showing consider this group a section
                boolean isSection =
                        ((Container) component).getHeader() != null && ((Container) component).getHeader().isRender()
                                && (StringUtils.isNotBlank(((Container) component).getHeader().getHeaderText())
                                || StringUtils.isNotBlank(component.getTitle()));

                if (!skipSections && isSection) {
                    sectionIds.add(id);

                    // Add with section identifier
                    order.add(SECTION_TOKEN + id);
                } else if ((component instanceof CollectionGroup && ((CollectionGroup) component)
                        .getLayoutManager() instanceof TableLayoutManager) || component instanceof LightTable) {
                    // Add with collection identifier
                    order.add(TABLE_COLLECTION_TOKEN + id);
                } else {
                    // If a non-section container, collect ids from sub items
                    collectIdsFromItems(((Container) component).getItems(), sectionIds, order, skipSections);
                }
            } else if (component instanceof InputField) {
                order.add(id);
            }
        }
    }

    /**
     * If true, validation message divs are closeable(dismissable) by the user and
     * they do not return until another page level validation/component refresh/page refresh is invoked
     *
     * @return true
     */
    public boolean isCloseable() {
        return closeable;
    }

    /**
     * @see GroupValidationMessages#isCloseable()
     */
    public void setCloseable(boolean closeable) {
        this.closeable = closeable;
    }

    /**
     * If true, the error messages will display the an InputField's title
     * alongside the error, warning, and info messages related to it. This
     * setting has no effect on messages which do not relate directly to a
     * single InputField.
     *
     * @return the displayFieldLabelWithMessages
     */
    @BeanTagAttribute
    public boolean isDisplayFieldLabelWithMessages() {
        return this.displayFieldLabelWithMessages;
    }

    /**
     * If true, the error messages will display the an InputField's title
     * alongside the error, warning, and info messages related to it. This
     * setting has no effect on messages which do not relate directly to a
     * single InputField.
     *
     * @param displayFieldLabelWithMessages the displayFieldLabelWithMessages to set
     */
    public void setDisplayFieldLabelWithMessages(boolean displayFieldLabelWithMessages) {
        this.displayFieldLabelWithMessages = displayFieldLabelWithMessages;
    }

    /**
     * When collapseAdditionalFieldLinkMessages is set to true, the messages generated on field links will be
     * summarized to limit the space they take up with an appendage similar to [+n message type] appended for
     * additional
     * messages that are omitted.  When this flag is false, all messages will be part of the link separated by
     * a comma.
     *
     * @return if field link messages are being collapsed
     */
    @BeanTagAttribute
    public boolean isCollapseAdditionalFieldLinkMessages() {
        return collapseAdditionalFieldLinkMessages;
    }

    /**
     * Set collapseAdditionalFieldLinkMessages
     *
     * @param collapseAdditionalFieldLinkMessages - true if field link messages are being collapsed
     */
    public void setCollapseAdditionalFieldLinkMessages(boolean collapseAdditionalFieldLinkMessages) {
        this.collapseAdditionalFieldLinkMessages = collapseAdditionalFieldLinkMessages;
    }

    /**
     * If true, the header message summary will display (this is the message count message appended to section
     * headers).
     *
     * @return true if the summary will display, false otherwise
     */
    @BeanTagAttribute
    public boolean isDisplayHeaderMessageSummary() {
        return displayHeaderMessageSummary;
    }

    /**
     * Sets whether the header message summary will display or not for this section/page.
     */
    public void setDisplayHeaderMessageSummary(boolean displayHeaderMessageSummary) {
        this.displayHeaderMessageSummary = displayHeaderMessageSummary;
    }
}
