/**
 * 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 that represents a List element.
 * 
 * The list element is used to display sets of sequential data. It contains an
 * optional head element, as well as any number of item elements. Items contain
 * textual information or other list elements. An item can also be associated
 * with a label element that annotates an item with a number, a textual
 * description of some sort, or a simple bullet. The list type (ordered,
 * bulletted, gloss, etc.) is then determined either by the content of labels on
 * items or by an explicit value of the "type" attribute. Note that if labels
 * are used in conjunction with any items in a list, all of the items in that
 * list must have a label. It is also recommended to avoid mixing label styles
 * unless an explicit type is specified.
 * 
 * typically rendering types are not predefined, but for lists there is a set of
 * standard rendering options available for the rend attribute to be set too.
 * This is not an exhaustive list.
 * 
 * horizontal: The list should be rendered horizontally.
 * 
 * vertical: The list should be rendered vertically.
 * 
 * columns: The list should be rendered in equal length columns as determined by
 * the theme.
 * 
 * columns2: The list should be rendered in two equal columns.
 * 
 * columns3: The list should be rendered in three equal columns.
 * 
 * alphabet: The list should be rendered as an alphabetical index.
 * 
 * numeric: The list should be rendered as a numeric index.
 * 
 * @author Scott Phillips
 */

import java.util.ArrayList;

import org.dspace.app.xmlui.wing.AttributeMap;
import org.dspace.app.xmlui.wing.Message;
import org.dspace.app.xmlui.wing.WingConstants;
import org.dspace.app.xmlui.wing.WingContext;
import org.dspace.app.xmlui.wing.WingException;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.ext.LexicalHandler;
import org.xml.sax.helpers.NamespaceSupport;

public class List extends AbstractWingElement implements WingMergeableElement,
        StructuralElement
{
    /** The name of the list element */
    public static final String E_LIST = "list";

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

    /** Has this element been merged? */
    private boolean merged = false;
    
    /** Has a child element been merged: head, list, or item */
    private boolean childMerged = false;

    /** The possible list types * */
    public static final String TYPE_SIMPLE = "simple";

    public static final String TYPE_ORDERED = "ordered";

    public static final String TYPE_BULLETED = "bulleted";

    public static final String TYPE_GLOSS = "gloss";

    public static final String TYPE_PROGRESS = "progress";
    
    public static final String TYPE_FORM = "form";

    public static final String TYPE_DSO_LIST = "dsolist";

    /** All the possible list types collected into one array */
    public static final String[] TYPES = { TYPE_SIMPLE, TYPE_ORDERED,
            TYPE_BULLETED, TYPE_GLOSS, TYPE_PROGRESS, TYPE_FORM, TYPE_DSO_LIST };

    /** The list's name */
    private String name;

    /** The list's type, see types above. * */
    private String type;

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

    /** The lists head * */
    private Head head;

    /** All content of this container, items & lists */
    private java.util.List<AbstractWingElement> contents = new ArrayList<AbstractWingElement>();

    /**
     * Construct a new list.
     * 
     * 
     * @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 local identifier used to differentiate the
     *            element from its siblings.
     * @param type
     *            (May be null) determines the list type. If this is blank the
     *            list type is inferred from the context and use.
     * @param rend
     *            (May be null) a rendering hint used to override the default
     *            display of the element. There are a set of predefined
     *            rendering values, see the class documentation above.
     */
    protected List(WingContext context, String name, String type, String rend)
            throws WingException
    {
        super(context);
        require(name, "The 'name' parameter is required for all lists.");
        restrict(
                type,
                TYPES,
                "The 'type' parameter must be one of these values: 'simple', 'ordered', 'bulleted', 'gloss', or 'form'.");

        this.name = name;
        this.type = type;
        this.rend = rend;
    }

    /**
     * Set the head element which is the label associated with this list. This
     * method should be called before any other elements have been added to the
     * list.
     */
    public Head setHead() throws WingException
    {
        Head head = new Head(context, null);
        this.head = head;
        return head;
    }

    /**
     * Set the head element which is the label associated with this list. This
     * method should be called before any other elements have been added to the
     * list.
     * 
     * @param characters
     *            (Required) Untranslated character data to be included as the
     *            list's head.
     */
    public void setHead(String characters) throws WingException
    {
        Head head = setHead();
        head.addContent(characters);
    }

    /**
     * Set the head element which is the label associated with this list. This
     * method should be called before any other elements have been added to the
     * list.
     * 
     * @param key
     *            (Required) Key to the i18n catalogue to translate the content
     *            into the language preferred by the user.
     */
    public void setHead(Message key) throws WingException
    {
        Head head = setHead();
        head.addContent(key);
    }

    /**
     * Add a label element, they are associated with an item and annotates that
     * item with a number, a textual description of some sort, or a simple
     * bullet.
     * 
     * @param name
     *            (May be null) a local identifier used to differentiate the
     *            element from its siblings.
     * @param rend
     *            (May be null) a rendering hint used to override the default
     *            display of the element.
     */
    public Label addLabel(String name, String rend) throws WingException
    {
        Label label = new Label(context, name, rend);
        contents.add(label);
        return label;
    }

    /**
     * Add a label element, they are associated with an item and annotates that
     * item with a number, a textual description of some sort, or a simple
     * bullet.
     * 
     * @param characters
     *            (Required) Untranslated character data to be included.
     */
    public void addLabel(String characters) throws WingException
    {
        require(characters,
                "The 'characters' parameter is required for list labels.");

        Label label = new Label(context, null, null);
        label.addContent(characters);
        contents.add(label);
    }

    /**
     * Add a label element, they are associated with an item and annotates that
     * item with a number, a textual description of some sort, or a simple
     * bullet. This version of label provides no textual label but may be used 
     * to indicate some implicit labeling such as ordered lists.
     * 
     */
    public void addLabel() throws WingException
    {
        Label label = new Label(context, null, null);
        contents.add(label);
    }
    
    /**
     * Add a label element, they are associated with an item and annotates that
     * item with a number, a textual description of some sort, or a simple
     * bullet.
     * 
     * @param key
     *            (Required) Key to the i18n catalogue to translate the content
     *            into the language preferred by the user.
     */
    public void addLabel(Message key) throws WingException
    {
        require(key, "The 'key' parameter is required for list labels.");

        Label label = new Label(context, null, null);
        label.addContent(key);
        contents.add(label);
    }

    /**
     * Add an empty unnamed item.
     * 
     * @return a new Item
     */
    public Item addItem() throws WingException
    {
        return addItem(null,null);
    }
    
    /**
     * Add an item element, which serves a dual purpose. It can contain other
     * lists, allowing for hierarchies and recursive lists. Alternatively it can
     * serve as a character container to display textual data, possibly enhanced
     * with hyperlinks, emphasized blocks of text, images and form fields. An
     * item cannot be both a character container and contain a list.
     * 
     * @param name
     *            (May be null) a local identifier used to differentiate the
     *            element from its siblings.
     * @param rend
     *            (May be null) a rendering hint used to override the default
     *            display of the element. *
     * @return a new Item
     */
    public Item addItem(String name, String rend) throws WingException
    {
        Item item = new Item(context, name, rend);
        contents.add(item);
        return item;
    }

    /**
     * Add an item element that contains only character content.
     * 
     * @param characters
     *            (Required) Untranslated character data to be included.
     */
    public void addItem(String characters) throws WingException
    {
        require(characters,
                "The 'characters' parameter is required for list items.");

        Item item = this.addItem(null, null);
        item.addContent(characters);
    }

    /**
     * Add an item element that contains only translated content.
     * 
     * @param key
     *            (Required) Key to the i18n catalogue to translate the content
     *            into the language preferred by the user.
     */
    public void addItem(Message key) throws WingException
    {
        require(key, "The 'key' parameter is required for list items.");

        Item item = this.addItem(null, null);
        item.addContent(key);
    }

    /**
     * Add an item to the list that contains a link. The link will consist of
     * the given content and linked to the given target.
     * 
     * @param target
     *            (Required) The link target.
     * @param characters
     *            (Required) Untranslated character data to be included as the
     *            link's body.
     */
    public void addItemXref(String target, String characters)
            throws WingException
    {
        Item item = this.addItem(null, null);
        item.addXref(target, characters);
    }

    /**
     * Add an item to the list that contains a link. The link will consist of
     * the given content and linked to the given target.
     * 
     * @param target
     *            (Required) The link target.
     * @param key
     *            (Required) i18n key for translating content into the user's
     *            preferred language.
     */
    public void addItemXref(String target, Message key) throws WingException
    {

        Item item = this.addItem(null, null);
        item.addXref(target, key);
    }

    /**
     * Add a new sublist to this list.
     * 
     * @param name
     *            (Required) a local identifier used to differentiate the
     *            element from its siblings.
     * @param type
     *            (May be null) determines the list type. If this is blank the
     *            list type is inferred from the context and use.
     * @param rend
     *            (May be null) a rendering hint used to override the default
     *            display of the element.
     * @return A new sub list.
     */
    public List addList(String name, String type, String rend)
            throws WingException
    {
        List list = new List(context, name, type, rend);
        contents.add(list);
        return list;
    }

    /**
     * Add a new sublist to this list.
     * 
     * @param name
     *            (Required) a local identifier used to differentiate the
     *            element from its siblings.
     * @param type
     *            (May be null) determines the list type. If this is blank the
     *            list type is inferred from the context and use.
     * @return A new sub list.
     */
    public List addList(String name, String type)
            throws WingException
    {
        List list = new List(context, name, type, null);
        contents.add(list);
        return list;
    }
    
    /**
     * Add a new sublist to this list.
     * 
     * @param name
     *            (Required) a local identifier used to differentiate the
     *            element from its siblings.
     * @return A new sub list.
     */
    public List addList(String name) throws WingException
    {
        return addList(name, null, null);
    }
    
    
    
    /**
     * Determine if the given SAX startElement event is equivalent to this list.
     * 
     * @param namespace
     *            The element's name space
     * @param localName
     *            The local, unqualified, name for this element
     * @param qName
     *            The qualified name for this element
     * @param attributes
     *            The element's attributes
     * @return True if this list is equivalent to the given SAX Event.
     */
    public boolean mergeEqual(String namespace, String localName, String qName,
            Attributes attributes)
    {
        // Check if it's in our name space and an options element.
        if (!WingConstants.DRI.URI.equals(namespace))
        {
            return false;
        }
        if (!E_LIST.equals(localName))
        {
            return false;
        }
        String name = attributes.getValue(A_NAME);
        if (name == null)
        {
            return false;
        }
        if (!name.equals(this.name))
        {
            return false;
        }
        return true;
    }

    /**
     * Merge the given SAX startElement event into this list's child. If this
     * SAX event matches a child element of this list then it should be removed
     * from the internal book keep of this element and returned. Typically this
     * is accomplished by looping through all children elements and returned the
     * first one that returns true for the mergeEqual method.
     * 
     * @param namespace
     *            The element's name space
     * @param localName
     *            The local, unqualified, name for this element *
     * @param qName
     *            The qualified name for this element
     * @param attributes
     *            The element's attributes
     * @return The child element
     */
    public WingMergeableElement mergeChild(String namespace, String localName,
            String qName, Attributes attributes) throws SAXException,
            WingException
    {
    	this.childMerged = true;
    	
        WingMergeableElement found = null;
        for (AbstractWingElement content : contents)
        {
            if (content instanceof WingMergeableElement)
            {
                WingMergeableElement candidate = (WingMergeableElement) content;
                if (candidate.mergeEqual(namespace, localName, qName,
                        attributes))
                {
                    found = candidate;
                }
            }
        }
        contents.remove(found);
        return found;
    }

    /**
     * Inform this list that it is being merged with an existing element.
     * Practically this means that when this method is being transformed to SAX
     * it should assume that the element's SAX events have already been sent.
     * In this case the element would only need to transform to SAX the children
     * of this element.
     * 
     * Furthermore, if the element needs to add any attributes to the SAX
     * startElement event it may modify the attributes object passed to make
     * changes.
     * 
     * @return The attributes for this merged element
     */
    public Attributes merge(Attributes attributes) throws SAXException,
            WingException
    {
        this.merged = true;
        return attributes;
    }

    /**
     * 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
    {

        if (!this.merged)
        {
            AttributeMap attributes = new AttributeMap();
            attributes.put(A_NAME, this.name);
            attributes.put(A_ID, this.context.generateID(E_LIST, this.name));
            if (this.type != null)
            {
                attributes.put(A_TYPE, this.type);
            }
            if (this.rend != null)
            {
                attributes.put(A_RENDER, this.rend);
            }

            startElement(contentHandler, namespaces, E_LIST, attributes);
            
        }
            
        if (!childMerged && head != null)
        {
            head.toSAX(contentHandler, lexicalHandler, namespaces);
        }

        for (AbstractWingElement content : contents)
        {
            content.toSAX(contentHandler, lexicalHandler, namespaces);
        }

        if (!this.merged)
        {
            endElement(contentHandler, namespaces, E_LIST);
        }
    }

    /**
     * dispose
     */
    public void dispose()
    {
        if (head != null)
        {
            head.dispose();
        }
        head = null;
        for (AbstractWingElement content : contents)
        {
            content.dispose();
        }
        contents.clear();
        contents = null;
        super.dispose();
    }
}
