/**
 * The contents of this file are subject to the license and copyright
 * detailed in the LICENSE and NOTICE files at the root of the source
 * tree and available online at
 *
 * http://www.dspace.org/license/
 */
package org.dspace.app.xmlui.wing.element;

/**
 * A class representing an an abstract input control (which is just a fancy name
 * for a field :) )
 *
 * The field element is a container for all information necessary to create a
 * form field. The required "type" attribute determines the type of the field,
 * while the children tags carry the information on how to build it. Fields can
 * only occur in divisions of type "interactive".
 *
 * There are several types of possible fields and each of these field types
 * determine the appropriate parameters on the parameter object. This is the
 * only place in the schema where this design pattern is used. It limits the
 * proliferation of elements, such as a special element for textarea, select
 * lists, text fields etc... as HTML does. It also forces us to treat all fields
 * the same.
 *
 * text: A single-line text input control.
 *
 * textarea: A multi-line text input control.
 *
 * password: A single-line text input control where the input text is rendered
 * in such a way as to hide the characters from the user.
 *
 * hidden: An input control that is not rendered on the screen and hidden from
 * the user.
 *
 * button: A button input control that when activated by the user will submit
 * the form, including all the fields, back to the server for processing.
 *
 * checkbox: A boolean input control which may be toggled by the user. A
 * checkbox may have several fields which share the same name and each of those
 * fields may be toggled independently. This is distinct from a radio button
 * where only one field may be toggled.
 *
 * file: An input control that allows the user to select files to be submitted
 * with the form. Note that a form which uses a file field must use the
 * multipart method.
 *
 * radio: A boolean input control which may be toggled by the user. Multiple
 * radio button fields may share the same name. When this occurs only one field
 * may be selected to be true. This is distinct from a checkbox where multiple
 * fields may be toggled.
 *
 * select: A menu input control which allows the user to select from a list of
 * available options.
 *
 * composite: A combination of multile fields into one input control.
 *
 * @author Scott Phillips
 */

import java.util.ArrayList;
import java.util.List;

import org.dspace.app.xmlui.wing.AttributeMap;
import org.dspace.app.xmlui.wing.Message;
import org.dspace.app.xmlui.wing.WingContext;
import org.dspace.app.xmlui.wing.WingException;

import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.ext.LexicalHandler;
import org.xml.sax.helpers.NamespaceSupport;

public abstract class Field extends AbstractWingElement implements
        StructuralElement
{
    /** The name of the field element */
    public static final String E_FIELD = "field";

    /** The name of the field type attribute */
    public static final String A_FIELD_TYPE = "type";

    /** The name of the disabled attribute */
    public static final String A_DISABLED = "disabled";

    /** The name of the required attribute */
    public static final String A_REQUIRED = "required";
    
    
    /** The possible field types */
    public static final String TYPE_BUTTON = "button";

    public static final String TYPE_CHECKBOX = "checkbox";

    public static final String TYPE_FILE = "file";

    public static final String TYPE_HIDDEN = "hidden";

    public static final String TYPE_PASSWORD = "password";

    public static final String TYPE_RADIO = "radio";

    public static final String TYPE_SELECT = "select";

    public static final String TYPE_TEXT = "text";

    public static final String TYPE_TEXTAREA = "textarea";
    
    public static final String TYPE_COMPOSITE = "composite";
    
    /** All the possible field types collected into one array. */
    public static final String[] TYPES = { TYPE_BUTTON, TYPE_CHECKBOX,
            TYPE_FILE, TYPE_HIDDEN, TYPE_PASSWORD, TYPE_RADIO, TYPE_SELECT,
            TYPE_TEXT, TYPE_TEXTAREA, TYPE_COMPOSITE };
    
    /** Possible field behavioral operations */
    public static final String OPERATION_ADD = "add";

    public static final String OPERATION_DELETE = "delete";
    
    public static final String[] OPERATIONS = {OPERATION_ADD, OPERATION_DELETE};
    
    /** The field's name */
    protected String name;

    /** The type of field, see TYPES above */
    protected String type;

    /** Whether this field is disabled */
    protected boolean disabled;

    /** Whether this field is required */
    protected boolean required;

    /** Any special rendering instructions */
    protected String rend;

    /** Additional field parameters */
    protected Params params;

    /** The fields Label */
    protected Label label;
    
    /** Help instructions for this field */
    protected Help help;
    
    /** Error instructions for this field */
    protected List<Error> errors = new ArrayList<Error>();
    
    /** All sub fields contained within a composite field */
    protected List<Field> fields = new ArrayList<Field>();
    
    /** The value of this field */
    protected List<Option> options = new ArrayList<Option>();
    
    /** The value of this field */
    protected List<Value> values = new ArrayList<Value>();
    
    /** The set of stored values */
    protected List<Instance> instances = new ArrayList<Instance>();

    /**
     * Construct a new field.
     *
     * @param context
     *            (Required) The context this element is contained in, such as
     *            where to route SAX events and what i18n catalogue to use.
     *
     * @param name
     *            (Required) a non-unique local identifier used to differentiate
     *            the element from its siblings within an interactive division.
     *            This is the name of the field use when data is submitted back
     *            to the server.
     * @param type
     *            (Required) Specify the type of field, must be one of the field
     *            types defined in the static variable TYPES.
     * @param rend
     *            (May be null) a rendering hint used to override the default
     *            display of the element.
     */
    protected Field(WingContext context, String name, String type,
            String rend) throws WingException
    {
        super(context);
        require(name, "The 'name' parameter is required for all fields.");
        require(type, "The 'type' parameter is required for all fields.");
        restrict(
                type,
                TYPES,
                "The 'type' parameter must be one of these values: 'button', 'checkbox', 'file', 'hidden', 'password', 'radio', 'select', 'text', 'textarea'.");

        this.name = name;
        this.type = type;
        this.disabled = false;
        this.required = false;
        this.rend = rend;
    }

    /** Parameters available on all fields */

    /**
     * Set this field as required.
     */
    public void setRequired()
    {
        this.required = true;
    }

    /**
     * Set this field to either be required or not required as determined by the
     * required parameter.
     *
     * @param required
     *            Determine if the field is required or not.
     */
    public void setRequired(boolean required)
    {
        this.required = required;
    }

    /**
     * Set this field to be disabled.
     *
     */
    public void setDisabled()
    {
        this.disabled = true;
    }

    /**
     * Set this field to either be disabled or enabled as determined by the
     * disabled parameter.
     *
     * @param disabled
     *            Determine if the field is required or not.
     */
    public void setDisabled(boolean disabled)
    {
        this.disabled = disabled;
    }

    /**
     * Set this field to be authority-controlled.
     *
     */
    public void setAuthorityControlled()
    {
        this.params.setAuthorityControlled(true);
    }

    /**
     * Set this field to be authority-controlled.
     *
     * @param value true if field is authority-controlled.
     */
    public void setAuthorityControlled(boolean value)
    {
        this.params.setAuthorityControlled(value);
    }

    /**
     * Set this field as authority_required.
     */
    public void setAuthorityRequired()
    {
        this.params.setAuthorityRequired();
    }

    /**
     * Set this field to either be required or not required as determined by the
     * required parameter.
     *
     * @param value
     *          Determine if the authority control is required or not on this field.
     */
    public void setAuthorityRequired(boolean value)
    {
        this.params.setAuthorityRequired(value);
    }

    /**
     * Set the field's autofocus attribute, an HTML5 feature.
     * Valid input values to enable autofocus are: autofocus, and empty string.
     * @param value To enable autofocus: "autofocus" or ""
     */
    public void setAutofocus(String value)
    {
        this.params.setAutofocus(value);
    }

    /**
     * Declare that this field has a value to be chosen from a choice
     * provider (see ChoicesManager).  Value is a metadata field key
     * naming the field whose choices to use.
     *
     * @param fieldKey pre-determined metadata field key
     */
    public void setChoices(String fieldKey)
    {
        params.setChoices(fieldKey);
    }

    /**
     * Set the kind of UI presentation requested for this choice, e.g.
     * select vs. suggest.  Value must match one of the PRESENTATIONS.
     *
     * @param value pre-determined metadata field key
     */
    public void setChoicesPresentation(String value)
        throws WingException
    {
        params.setChoicesPresentation(value);
    }

    /**
     * Set whether set of choices is "closed" to just the values
     * in menu, or allows arbitrary user-supplied values.
     *
     * @param value true if it is closed
     */
    public void setChoicesClosed(boolean value)
    {
        params.setChoicesClosed(value);
    }
    public void setChoicesClosed()
    {
        params.setChoicesClosed();
    }

    /** ******************************************************************** */
    /** Help * */
    /** ******************************************************************** */

    /**
     * The help element provides help instructions to assist the user in using
     * this field.
     *
     */
    public Help setHelp() throws WingException
    {
        this.help = new Help(context);
        return this.help;
    }

    /**
     * The help element provides help instructions to assist the user in using
     * this field.
     *
     * @param characters
     *            (May be null) Direct content or a dictionary tag to be
     *            inserted into the element.
     */
    public void setHelp(String characters) throws WingException
    {
        this.help = new Help(context);
        this.help.addContent(characters);
    }
    
    /**
     * The help element provides help instructions to assist the user in using
     * this field.
     *
     * @param message
     *            (Required) A key into the i18n catalogue for translation into
     *            the user's preferred language.
     */
    public void setHelp(Message message) throws WingException
    {
        this.help = new Help(context);
        this.help.addContent(message);
    }

    /** ******************************************************************** */
    /** Errors * */
    /** ******************************************************************** */

    /**
     * The error elements denotes that the fields value is invalid for the given
     * context. The message contained within the error message will provide
     * assistance to the user in correcting the problem.
     *
     */
    public Error addError() throws WingException
    {
        Error error = new Error(context);
        errors.add(error);
        return error;
    }

    /**
     * The error elements denotes that the fields value is invalid for the given
     * context. The message contained within the error message will provide
     * assistance to the user in correcting the problem.
     *
     * @param characters
     *            (May be null) Direct content or a dictionary tag to be
     *            inserted into the element.
     */
    public void addError(String characters) throws WingException
    {
        Error error = new Error(context);
        error.addContent(characters);
        errors.add(error);
    }

    /**
     * The error elements denotes that the fields value is invalid for the given
     * context. The message contained within the error message will provide
     * assistance to the user in correcting the problem.
     *
     * @param message
     *            (Required) A key into the i18n catalogue for translation into
     *            the user's preferred language.
     */
    public void addError(Message message) throws WingException
    {
        Error error = new Error(context);
        error.addContent(message);
        errors.add(error);
    }
    
    /** ******************************************************************** */
    /** Label * */
    /** ******************************************************************** */

    /**
     * The help element provides help instructions to assist the user in using
     * this field.
     *
     */
    public Label setLabel() throws WingException
    {
        this.label = new Label(context,null,null);
        return this.label;
    }

    /**
     * The help element provides help instructions to assist the user in using
     * this field.
     *
     * @param characters
     *            (May be null) Direct content or a dictionary tag to be
     *            inserted into the element.
     */
    public void setLabel(String characters) throws WingException
    {
        this.label = new Label(context,null,null);
        this.label.addContent(characters);
    }
    
    /**
     * The help element provides help instructions to assist the user in using
     * this field.
     *
     * @param message
     *            (Required) A key into the i18n catalogue for translation into
     *            the user's preferred language.
     */
    public void setLabel(Message message) throws WingException
    {
        this.label = new Label(context,null,null);
        this.label.addContent(message);
    }
    
    
    /**
     * Private function to remove all values of a particular type.
     *
     * @param removeType
     *            The type to be removed.
     */
    protected void removeValueOfType(String removeType)
    {
        List<Value> found = new ArrayList<Value>();
        for (Value value : values)
        {
            if (value.getType().equals(removeType))
            {
                found.add(value);
            }
        }

        for (Value remove : found)
        {
            values.remove(remove);
            remove.dispose();
        }
    }

    /**
     * Translate this element and all contained elements into SAX events. The
     * events should be routed to the contentHandler found in the WingContext.
     *
     * @param contentHandler
     *            (Required) The registered contentHandler where SAX events
     *            should be routed too.
     * @param lexicalHandler
     *            (Required) The registered lexicalHandler where lexical events
     *            (such as CDATA, DTD, etc) should be routed too.
     * @param namespaces
     *            (Required) SAX Helper class to keep track of namespaces able
     *            to determine the correct prefix for a given namespace URI.
     */
    public void toSAX(ContentHandler contentHandler,
            LexicalHandler lexicalHandler, NamespaceSupport namespaces)
            throws SAXException
    {
        AttributeMap attributes = new AttributeMap();

        attributes.put(A_NAME, this.name);
        attributes.put(A_ID, this.context.generateID(E_FIELD, this.name));
        attributes.put(A_FIELD_TYPE, this.type);
        if (this.disabled)
        {
            attributes.put(A_DISABLED, this.disabled);
        }
        if (this.required)
        {
            attributes.put(A_REQUIRED, this.required);
        }
        if (this.rend != null)
        {
            attributes.put(A_RENDER, this.rend);
        }

        startElement(contentHandler, namespaces, E_FIELD, attributes);

        if (params != null)
        {
            params.toSAX(contentHandler, lexicalHandler, namespaces);
        }

        if (label != null)
        {
            label.toSAX(contentHandler, lexicalHandler, namespaces);
        }
        
        if (help != null)
        {
            help.toSAX(contentHandler, lexicalHandler, namespaces);
        }

        for (Error error : errors)
        {
            error.toSAX(contentHandler, lexicalHandler, namespaces);
        }
        
        for (Field field : fields)
        {
            field.toSAX(contentHandler, lexicalHandler, namespaces);
        }

        for (Option option : options)
        {
            option.toSAX(contentHandler, lexicalHandler, namespaces);
        }
        
        for (Value value : values)
        {
            value.toSAX(contentHandler, lexicalHandler, namespaces);
        }
        
        for (Instance instance : instances)
        {
            instance.toSAX(contentHandler, lexicalHandler, namespaces);
        }

        endElement(contentHandler, namespaces, E_FIELD);
    }

    /**
     * Dispose
     */
    public void dispose()
    {
        if (params != null)
        {
            params.dispose();
        }

        if (label != null)
        {
            label.dispose();
        }
        
        if (help != null)
        {
            help.dispose();
        }

        if (errors != null)
        {
            for (Error error : errors)
            {
                error.dispose();
            }
            errors.clear();
        }

        if (fields != null)
        {
            for (Field field : fields)
            {
                field.dispose();
            }
            fields.clear();
        }
        
        if (options != null)
        {
            for (Option option : options)
            {
                option.dispose();
            }
            options.clear();
        }
        
        if (values != null)
        {
            for (Value value : values)
            {
                value.dispose();
            }
            values.clear();
        }

        if (instances != null)
        {
            for (Instance instance : instances)
            {
                instance.dispose();
            }
            instances.clear();
        }

        params = null;
        label = null;
        help = null;
        errors = null;
        fields = null;
        options = null;
        values = null;
        instances = null;
        super.dispose();
    }
}
