/*
 * 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.web;

import org.apache.commons.beanutils.BeanUtils;
import org.kuali.kfs.krad.keyvalues.KeyValuesFinder;
import org.kuali.kfs.krad.util.GlobalVariables;
import org.kuali.kfs.module.tem.businessobject.AccountingDocumentRelationship;
import org.kuali.kfs.module.tem.document.service.AccountingDocumentRelationshipService;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.rice.core.api.util.type.KualiDecimal;

import java.util.List;
import java.util.Map;

import static java.lang.Class.forName;

/**
 * Full of static methods for JSTL function access.
 */
public final class JstlFunctions {
    protected static final String SETTING_PARAMS_PROLOG = "Setting params ";
    protected static final String PROPERTY_SETTING_EXC_PROLOG = "Could not set property ";
    protected static final String IN_PREPOSITION = " in ";
    protected static final String VALUES_FINDER_CLASS_EXC_PROLOG = "Could not find valuesFinder class ";
    protected static final org.apache.commons.logging.Log LOG = org.apache.commons.logging.LogFactory.getLog(JstlFunctions.class);

    private JstlFunctions() {
    }

    /**
     * Returns a list of key/value pairs for displaying in an HTML option for a select list. This is a customized approach to retrieving
     * key/value data from database based on criteria specified in the <code>params {@link Map}</code><br/>
     * <br/>
     * Here is an example of how the code is used from a JSP:<br/>
     * <code>
     * <jsp:useBean id="paramMap" class="java.util.HashMap"/>
     * <c:set target="${paramMap}" property="forAddedPerson" value="true" />
     * <kul:checkErrors keyMatch="${proposalPerson}.proposalPersonRoleId" auditMatch="${proposalPerson}.proposalPersonRoleId"/>
     * <c:set var="roleStyle" value=""/>
     * <c:if test="${hasErrors==true}">
     * <c:set var="roleStyle" value="background-color:#FFD5D5"/>
     * </c:if>
     * <html:select property="${proposalPerson}.proposalPersonRoleId" tabindex="0" style="${roleStyle}">
     * <c:forEach items="${krafn:getOptionList('org.kuali.kra.proposaldevelopment.lookup.keyvalue.ProposalPersonRoleValuesFinder', paramMap)}" var="option">
     * <c:choose>
     * <c:when test="${KualiForm.document.proposalPersons[personIndex].proposalPersonRoleId == option.key}">
     * <option value="${option.key}" selected>${option.label}</option>
     * </c:when>
     * <c:otherwise>
     * <option value="${option.key}">${option.label}</option>
     * </c:otherwise>
     * </c:choose>
     * </c:forEach>
     * </html:select>
     * </code>
     *
     * @param valuesFinderClassName
     * @param params                mapped parameters
     * @return List of key values
     */
    @SuppressWarnings("unchecked")
    public static List getOptionList(String valuesFinderClassName, Map params) {
        return setupValuesFinder(valuesFinderClassName, params).getKeyValues();
    }

    /**
     * Initiates the values finder by its <code>valuesFinderClassName</code>. First locates the class in the class path. Then,
     * creates an instance of it. A <code>{@link Map}</code> of key/values <code>{@link String}</code> instances a is used
     * to set properties on the values finder instance. Uses the apache <code>{@link PropertyUtils}</code> class to set properties
     * by the name of the key in the <code>{@link Map}</code>.<br/>
     * <br/>
     * Basically, a new values finder is created. the <code>params</code> parameter is a <code>{@link Map}</code> of arbitrary values
     * mapped to properties of the values finder class.<br/>
     * <br/>
     * Since this is so flexible and the ambiguity of properties referenced in the <code>{@link Map}</code>, a number of exceptions are caught
     * if a property cannot be set or if the values finder cannot be instantiated. All of these exceptions are handled within the method. None
     * of these exceptions are thrown back.
     *
     * @param valuesFinderClassName
     * @param params
     * @return KeyValuesFinder
     * @see PropertyUtils#setProperty(Object, String, Object)
     */
    private static KeyValuesFinder setupValuesFinder(String valuesFinderClassName, Map<String, Object> params) {
        KeyValuesFinder retval = getKeyFinder(valuesFinderClassName);

        if (LOG.isDebugEnabled()) {
            LOG.debug(SETTING_PARAMS_PROLOG + params);
        }

        addParametersToFinder(params, retval);

        return retval;
    }

    private static void addParametersToFinder(Map<String, Object> params, KeyValuesFinder finder) {
        if (finder != null && params != null) {
            for (Map.Entry<String, Object> entry : params.entrySet()) {
                try {
                    BeanUtils.setProperty(finder, entry.getKey(), entry.getValue());
//                    setProperty(finder, entry.getKey(), entry.getValue());
                } catch (Exception e) {
                    warn(PROPERTY_SETTING_EXC_PROLOG + entry.getKey(), e);
                    e.printStackTrace();
                }
            }
        }
    }

    private static KeyValuesFinder getKeyFinder(String valuesFinderClassName) {
        KeyValuesFinder retval = null;
        try {
            retval = (KeyValuesFinder) forName(valuesFinderClassName).newInstance();
        } catch (ClassNotFoundException e) {
            warnAboutValueFinderClassExceptions(valuesFinderClassName, e);
        } catch (InstantiationException e) {
            warnAboutValueFinderClassExceptions(valuesFinderClassName, e);
        } catch (IllegalAccessException e) {
            warnAboutValueFinderClassExceptions(valuesFinderClassName, e);
        }
        return retval;
    }

    private static void warnAboutValueFinderClassExceptions(String valuesFinderClassName, Exception e) {
        warn(VALUES_FINDER_CLASS_EXC_PROLOG + valuesFinderClassName, e);
    }

    private static void warn(String message, Exception e) {
        if (LOG.isWarnEnabled()) {
            LOG.warn(new StringBuilder(message).append(IN_PREPOSITION).append(buildTraceMessage(e)));
        }
    }

    /**
     * Get the stack trace from a <code>{@link Throwable}</code> and create a log message from it for tracing purposes
     *
     * @param thrownObj
     * @return String log message
     */
    private static String buildTraceMessage(Throwable thrownObj) {
        StackTraceElement stackTraceElement = thrownObj.getStackTrace()[0];
        return new StringBuilder(stackTraceElement.getClassName())
            .append("#")
            .append(stackTraceElement.getMethodName())
            .append(":")
            .append(stackTraceElement.getLineNumber())
            .append(" ")
            .append(thrownObj.getClass().getSimpleName())
            .append("\n")
            .append(thrownObj.getMessage())
            .toString();
    }

    public static Boolean canDeleteDocumentRelationship(String documentNumber, String relDocumentNumber) {
        AccountingDocumentRelationshipService accountingDocumentRelationshipService = SpringContext.getBean(AccountingDocumentRelationshipService.class);
        List<AccountingDocumentRelationship> adrList = accountingDocumentRelationshipService.find(new AccountingDocumentRelationship(documentNumber, relDocumentNumber));

        if (adrList != null && adrList.size() == 1) {
            return adrList.get(0).getPrincipalId().equals(GlobalVariables.getUserSession().getPerson().getPrincipalId());
        }

        return false;
    }

    public static KualiDecimal add(Object a, Object b) {
        KualiDecimal tempA = new KualiDecimal(0);
        KualiDecimal tempB = new KualiDecimal(0);
        if (a instanceof Double) {
            tempA = new KualiDecimal((Double) a);
        } else if (a instanceof Integer) {
            tempA = new KualiDecimal((Integer) a);
        } else if (a instanceof String) {
            tempA = new KualiDecimal((String) a);
        } else if (a instanceof KualiDecimal) {
            tempA = (KualiDecimal) a;
        }

        if (b instanceof Double) {
            tempB = new KualiDecimal((Double) b);
        } else if (b instanceof Integer) {
            tempB = new KualiDecimal((Integer) b);
        } else if (a instanceof String) {
            tempB = new KualiDecimal((String) b);
        } else if (b instanceof KualiDecimal) {
            tempB = (KualiDecimal) b;
        }
        return tempA.add(tempB);
    }
}
