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

import java.io.File;
import java.io.FilenameFilter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;

import org.apache.log4j.Logger;
import org.dspace.app.webui.util.XMLUtil;
import org.dspace.core.ConfigurationManager;
import org.w3c.dom.Document;

/**
 * A Tag to load and display controlled vocabularies
 * 
 * @author Miguel Ferreira
 * @version $Revision: 5845 $
 * 
 */
public class ControlledVocabularyTag extends TagSupport
{
    // path to the jsp that outputs the results of this tag
    private static final String CONTROLLEDVOCABULARY_JSPTAG = "/controlledvocabulary/controlledvocabularyTag.jsp";

    // the log
    private static Logger log = Logger.getLogger(ControlledVocabularyTag.class);

    // a tag attribute that contains the words used to trim the vocabulary tree
    private String filter;

    // a tag attribute that activates multiple selection of vocabulary terms
    private boolean allowMultipleSelection;

    // a tag attribute that specifies the vocabulary to be displayed
    private String vocabulary;

    // an hashtable containing all the loaded vocabularies
    public Map<String, Document> controlledVocabularies;

    /**
     * Process tag
     */
    public int doStartTag() throws JspException
    {
        HttpServletRequest request = (HttpServletRequest) pageContext
                .getRequest();

        String vocabulariesPath = ConfigurationManager
                .getProperty("dspace.dir")
                + "/config/controlled-vocabularies/";
        String addonBaseDirectory = pageContext.getServletContext()
                .getRealPath("")
                + "/controlledvocabulary/";
        String vocabularyPrunningXSLT = addonBaseDirectory
                + "vocabularyprune.xsl";
        String controlledVocabulary2HtmlXSLT = addonBaseDirectory
                + "vocabulary2html.xsl";

        // Load vocabularies on startup
        controlledVocabularies = (Map<String, Document>) pageContext.getServletContext().getAttribute("controlledvocabulary.controlledVocabularies");
        if (controlledVocabularies == null)
        {
            controlledVocabularies = loadControlledVocabularies(vocabulariesPath);
            pageContext.getServletContext().setAttribute("controlledvocabulary.controlledVocabularies", controlledVocabularies);
        }

        try
        {
            Map<String, Document> prunnedVocabularies = needsFiltering() ?
                    filterVocabularies(controlledVocabularies, vocabularyPrunningXSLT)
                    : controlledVocabularies;

            String html = "";
            if (vocabulary != null && !vocabulary.equals(""))
            {
                html = renderVocabularyAsHTML(prunnedVocabularies.get(vocabulary + ".xml"),
                        controlledVocabulary2HtmlXSLT,
                        isAllowMultipleSelection(), request.getContextPath());
            }
            else
            {
                html = renderVocabulariesAsHTML(prunnedVocabularies,
                        controlledVocabulary2HtmlXSLT,
                        isAllowMultipleSelection(), request.getContextPath());
            }
            request.getSession().setAttribute(
                    "controlledvocabulary.vocabularyHTML", html);

            pageContext.include(CONTROLLEDVOCABULARY_JSPTAG);

        }
        catch (Exception e)
        {
            log.warn("Exception", e);
        }

        return SKIP_BODY;
    }

    /**
     * End processing tag
     */
    public int doEndTag()
    {
        return EVAL_PAGE;
    }

    /**
     * Do we gave a filter to apply to the controlled vocabularies?
     * 
     * @return true if a filter was provided.
     */
    private boolean needsFiltering()
    {
        return getFilter() != null && getFilter().length() > 0;
    }

    /**
     * Converts a XML Vocabulary to a HTML tree
     * 
     * @param vocabularies
     *            A hashtable with all the XML taxonomies/vocabularies loaded as
     *            values
     * @param xslt
     *            the filename of the stylesheet to apply the XML taxonomies
     * @param allowMultipleSelection
     *            include checkboxes next to the taxonomy terms
     * @param contextPath
     *            The context path
     * @return the HTML that represents the vocabularies
     */
    private String renderVocabulariesAsHTML(Map<String, Document> vocabularies,
            String xslt, boolean allowMultipleSelection, String contextPath)
    {
        StringBuilder result = new StringBuilder();
        Iterator<Document> iter = vocabularies.values().iterator();
        while (iter.hasNext())
        {
            Document controlledVocabularyXML = iter.next();
            result.append(renderVocabularyAsHTML(controlledVocabularyXML, xslt,
                    allowMultipleSelection, contextPath));
        }
        return result.toString();
    }

    /**
     * Applies a filter to the vocabularies, i.e. it prunes the trees by
     * removing all the branches that do not contain the words in the filter.
     * 
     * @param vocabularies
     *            A hashtable with all the XML taxonomies/vocabularies loaded as
     *            values
     * @param vocabularyPrunningXSLT
     *            the filename of the stylesheet that trimms the taxonomies
     * @return An hashtable with all the filtered vocabularies
     */
    private Map<String, Document> filterVocabularies(Map<String, Document> vocabularies, String vocabularyPrunningXSLT)
    {
        Map<String, Document> prunnedVocabularies = new HashMap<String, Document>();
        for (Map.Entry<String, Document> entry : vocabularies.entrySet())
        {
            prunnedVocabularies.put(entry.getKey(), filterVocabulary(entry.getValue(), vocabularyPrunningXSLT, getFilter()));
        }
        return prunnedVocabularies;
    }

    /**
     * Renders a taxonomy as HTML by applying a stylesheet.
     * 
     * @param vocabulary
     *            The XML document representing a taxonomy
     * @param controlledVocabulary2HtmlXSLT
     *            The filename of the stylesheet that converts the taxonomy to
     *            HTML
     * @param allowMultipleSelection
     *            include checkboxes next to the taxonomy terms
     * @param contextPath
     *            The context path
     * @return the provided taxonomy as HTML.
     */
    public String renderVocabularyAsHTML(Document vocabulary,
            String controlledVocabulary2HtmlXSLT,
            boolean allowMultipleSelection, String contextPath)
    {
        if (vocabulary == null)
        {
            return "";
        }

        String result = "";
        try
        {

            Map<String, String> parameters = new HashMap<String, String>();
            parameters.put("allowMultipleSelection", allowMultipleSelection ? "yes" : "no");
            parameters.put("contextPath", contextPath);
            result = XMLUtil.transformDocumentAsString(vocabulary, parameters, controlledVocabulary2HtmlXSLT);
        }
        catch (Exception e)
        {
            log.error("Error rendering HTML", e);
        }
        return result;
    }

    /**
     * Applies a filter to the provided vocabulary, i.e. it prunes the tree by
     * removing all the branches that do not contain the words in the filter.
     * 
     * @param vocabulary
     *            The vocabulary to be trimmed
     * @param vocabularyPrunningXSLT
     *            The filename of the stylesheet that trims the vocabulary
     * @param filter
     *            The filter to be applied
     * @return The trimmed vocabulary.
     */
    public Document filterVocabulary(Document vocabulary, String vocabularyPrunningXSLT, String filter)
    {
        if (vocabulary == null)
        {
            return null;
        }

        try
        {
            Map<String, String> parameters = new HashMap<String, String>();
            parameters.put("filter", filter);
            return XMLUtil.transformDocument(vocabulary, parameters, vocabularyPrunningXSLT);
        }
        catch (Exception e)
        {
            log.error("Error filtering vocabulary", e);
            return null;
        }

    }

    /**
     * Loads into memory all the vocabularies found in the given directory. All
     * files with .xml extension are considered to be controlled vocabularies.
     * 
     * @param directory
     *            where the files are positioned
     * @return an hashtable with the filenames of the vocabularies as keys and
     *         the XML documents representing the vocabularies as values.
     */
    private static Map<String, Document> loadControlledVocabularies(String directory)
    {
        Map<String, Document> controlledVocabularies = new HashMap<String, Document>();
        File dir = new File(directory);

        FilenameFilter filter = new FilenameFilter()
        {
            public boolean accept(File dir, String name)
            {
                return name.endsWith(".xml");
            }
        };
        String[] children = dir.list(filter);

        if (children != null && children.length > 0)
        {
            for (int i = 0; i < children.length; i++)
            {
                String filename = children[i];

                try
                {
                    Document controlledVocabulary = XMLUtil.loadXML(directory + filename);
                    controlledVocabularies.put(filename, controlledVocabulary);
                    log.warn("Loaded vocabulary: " + filename);
                }
                catch (Exception e)
                {
                    log.warn("Failed to load vocabulary from " + filename, e);
                }
            }
        }
        else
        {
            log.warn("Could not find any vocabularies...");
        }
        return controlledVocabularies;

    }

    /**
     * Gets the filter provided as parameter to the tag
     * 
     * @return the filter
     */
    public String getFilter()
    {
        return filter;
    }

    /**
     * Sets the filter
     * 
     * @param filter
     *            the filter
     */
    public void setFilter(String filter)
    {
        this.filter = filter;
    }

    /**
     * Returns the value of the multiple selection parameter
     * 
     * @return true if the multiple selection was selected
     */
    public boolean isAllowMultipleSelection()
    {
        return allowMultipleSelection;
    }

    /**
     * Defines if we want to be able to select multiple terms of the taxonomy
     * 
     * @param allowMultipleSelection
     *            true if we want to be able to select more than on term
     */
    public void setAllowMultipleSelection(boolean allowMultipleSelection)
    {
        this.allowMultipleSelection = allowMultipleSelection;
    }

    /**
     * Gets the name of the vocabulary to be displayed
     * 
     * @return the name of the vocabulary
     */
    public String getVocabulary()
    {
        return vocabulary;
    }

    /**
     * Sets the name of the vocabulary to be displayed. If no name is provided,
     * all vocabularies loaded will be rendered to the output
     * 
     * @param vocabulary
     *            the name of the vocabulary to be selected
     */
    public void setVocabulary(String vocabulary)
    {
        this.vocabulary = vocabulary;
    }

}
