/*
 * Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved.
 *
 * The contents of this file are subject to the CCM Public
 * License (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.redhat.com/licenses/ccmpl.html.
 *
 * Software distributed under the License is distributed on an
 * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express
 * or implied. See the License for the specific language
 * governing rights and limitations under the License.
 *
 */
package com.arsdigita.formbuilder;


// Bebop components that we generate here
import com.arsdigita.bebop.FormSection;
import com.arsdigita.bebop.Label;
import com.arsdigita.bebop.form.TextField;
import com.arsdigita.bebop.form.Widget;
// We use different parameter models depending on data type
import com.arsdigita.bebop.parameters.ParameterModel;
import com.arsdigita.bebop.parameters.StringParameter;
import com.arsdigita.bebop.parameters.IntegerParameter;

import com.arsdigita.bebop.event.ParameterListener;

import com.arsdigita.bebop.parameters.NotEmptyValidationListener;

// We need reflection sometimes
import java.lang.reflect.Method;

import java.util.List;
import java.util.Iterator;

// We use a process listener that will invoke any set methods of the
// attribute provider object
import com.arsdigita.bebop.event.FormProcessListener;
import com.arsdigita.bebop.event.FormSectionEvent;
import com.arsdigita.bebop.FormData;

// For loading class etc.
import com.arsdigita.formbuilder.util.FormBuilderUtil;

// Domain objects get special treatment in that they are saved
import com.arsdigita.domain.DomainObject;

// ACS 5 uses Log4J for logging
import org.apache.log4j.Logger;


/**
 * This class can generate a Bebop Form given an object implementing the
 * <code>AttributeMetaDataProvider</code> interface. The Form Builder uses
 * this class for its own admin UI to generate forms for the various persistent
 * components. It is unclear how useful this class will be applicable outside
 * the Form Builder since its functionality is still restricted.
 *
 * @author Peter Marklund
 * @version $Id: //core-platform/dev/src/com/arsdigita/formbuilder/FormSectionGenerator.java#10 $
 *
 */
public class FormSectionGenerator {

    public static final String versionId = "$Id: //core-platform/dev/src/com/arsdigita/formbuilder/FormSectionGenerator.java#10 $ by $Author: dennis $, $DateTime: 2004/04/07 16:07:11 $";

    private static final Logger s_log =
        Logger.getLogger(FormSectionGenerator.class.getName());

    private AttributeMetaDataProvider m_metaDataProvider;

    private boolean m_isAdd = true;

    public FormSectionGenerator(AttributeMetaDataProvider metaDataProvider) {

        m_metaDataProvider = metaDataProvider;
    }

    public FormSection generateFormSection() {

        return generateFormSection(true, true);
    }

    /**
     * Generate a form section from the contained object with attribute
     * metadata
     *
     * @param addProcessListener A process listener will be added if this is true.
     *                           The process listener will attempt to use set-methods
     *                           to set all attributes (this is the listener returned by
     *                           getSetProcessListener()). If this form section is for a
     *                           domain object, the domain object will be saved after the
     *                           set-methods have been invoked.
     */
    public FormSection generateFormSection(boolean addProcessListener, boolean isAdd) {

        FormSection formSection = new FormSection();

        m_isAdd = isAdd;

        // Loop over the attributes and add appropriate widgets to the Form
        AttributeMetaDataList attributeList = m_metaDataProvider.getAttributeMetaData();
        attributeList.setIteratorAtStart();
        while(attributeList.hasNext()) {

            // Get the attribute
            AttributeMetaData attribute = attributeList.next();

            // Get the attribute properties
            String parameterName = attribute.getParameterName();
            String label = attribute.getLabel();
            boolean isRequired = attribute.isRequired();
            AttributeType attributeType = attribute.getAttributeType();

            // Create and add the label component
            String labelText = label == null ? parameterName : label;
            // Indicate if the attribute is required in the label
            if (isRequired) {
                labelText = labelText + " (required) ";
            }
            formSection.add(new Label(labelText));

            // Add an appropriate component
            // Use provided parameter model or look at the set method to figure one
            // out
            ParameterModel parameterModel  = attribute.getParameterModel();
            if (parameterModel == null) {
                parameterModel = getModelFromReflection(parameterName);
            }
            TextField textField = new TextField(parameterModel);

            if (isRequired) {
                textField.addValidationListener(new NotEmptyValidationListener());
            }

            // Add other validation listeners
            if (attributeType != null) {
                addValidationListeners(textField, attributeType.getValidationListeners());
            }

            // Set the default value
            setDefaultValue(textField, parameterName);

            formSection.add(textField);

        }

        if (addProcessListener) {

            // Add a process listener that sets the attributes
            formSection.addProcessListener(getSetProcessListener());

            // Save if this is a domain object
            // This is a hack - I should check for a save method instead
            Class domainObjectClass =
                FormBuilderUtil.loadClass("com.arsdigita.domain.DomainObject");
            if (domainObjectClass.isAssignableFrom(m_metaDataProvider.getClass())
                || m_metaDataProvider instanceof com.arsdigita.formbuilder.PersistentComponentFactory) {

                formSection.addProcessListener(getDomainObjectSaveListener());
            }
        }

        return formSection;
    }

    /**
     * Returns a process listener that can be used to set the attributes of
     * the object for which the form section is generated. Note that if this
     * is a domain object this process listener will not save that domain object.
     */
    public FormProcessListener getSetProcessListener() {

        return new FormProcessListener() {

                public void process(FormSectionEvent event) {

                    FormData formData = event.getFormData();

                    // Iterate over the attributes and set them
                    AttributeMetaDataList attributeList = m_metaDataProvider.getAttributeMetaData();
                    attributeList.setIteratorAtStart();
                    while(attributeList.hasNext()) {

                        AttributeMetaData attribute = attributeList.next();

                        String parameterName = attribute.getParameterName();

                        Object parameterValue = formData.get(parameterName);

                        // Only invoke set method if a value was supplied
                        if (parameterValue != null && !parameterValue.toString().trim().equals("")) {

                            Method setMethod = getParameterMethod(parameterName, true);

                            if (setMethod != null) {

                                try {
                                    FormBuilderUtil.invokeMethod(setMethod,
                                                                 m_metaDataProvider,
                                                                 new Object[] {parameterValue});

                                } catch (IllegalArgumentException e) {
                                    // This usually happens if the class of the provided
                                    // default value of a PersistentWidget does not match.
                                    // In this case we don't set a default value
                                }
                            }
                        }
                    }
                }
            };
    }

    //*** Internal Helper Methods

    private void setDefaultValue(Widget widget, String parameterName) {

        Method getMethod = getParameterMethod(parameterName, false);

        Object defaultValue =  FormBuilderUtil.invokeMethod(getMethod,
                                                            m_metaDataProvider,
                                                            new Object[] {});

        if (defaultValue != null) {
            widget.setDefaultValue(defaultValue);
        }
    }

    private ParameterModel getModelFromReflection(String parameterName) {

        // Get the Method
        Method parameterMethod = getParameterMethod(parameterName, true);

        // Get the argument types
        Class[] parameterTypes = parameterMethod.getParameterTypes();

        // Get the name of the first type
        String typeName = parameterTypes[0].getName();

        // Decide on a ParameterModel based on the type name
        if (typeName.equals("int")) {
            return new IntegerParameter(parameterName);
        } else if (typeName.equals("java.lang.String") || typeName.equals("java.lang.Object")) {
            return new StringParameter(parameterName);
        } else {
            throw new IllegalStateException("Trying to generate a Form with an attribute of type " +
                                            typeName + ". This type is currently not supported");
        }
    }

    /**
     * May return null
     */
    private Method getParameterMethod(String parameterName, boolean isSet) {

        Method returnMethod = null;

        // Simply loop over the methods and look for a matching set method name
        Method[] methods = m_metaDataProvider.getClass().getMethods();
        for (int i = 0; i < methods.length; ++i) {

            Method method = methods[i];
            String methodPrefix = "";
            if (isSet) {
                methodPrefix = "set";
            } else {
                methodPrefix = "get";
            }
            String setMethodName = methodPrefix + parameterName.substring(0,1).toUpperCase() + parameterName.substring(1);

            if (method.getName().equals(setMethodName)) {
                returnMethod = method;
                break;
            }
        }

        return returnMethod;
    }

    private FormProcessListener getDomainObjectSaveListener() {

        return new FormProcessListener() {

                public void process(FormSectionEvent event) {

                    if (m_metaDataProvider instanceof com.arsdigita.domain.DomainObject) {
                        ((DomainObject)m_metaDataProvider).save();
                    } else {

                        ((PersistentComponentFactory)m_metaDataProvider).save();
                    }
                }
            };
    }

    private void addValidationListeners(Widget widget, List validationListeners) {

        Iterator validationIter = validationListeners.iterator();
        while (validationIter.hasNext()) {

            widget.addValidationListener((ParameterListener)validationIter.next());
        }
    }
}
