/*
 * Copyright (C) 2004 Red Hat Inc. All Rights Reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 */

package com.arsdigita.aplaws.ui;

import com.arsdigita.london.terms.Domain;
import com.arsdigita.london.terms.Term;

import com.arsdigita.aplaws.Aplaws;
import com.arsdigita.bebop.form.Widget;
import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.parameters.ArrayParameter;
import com.arsdigita.bebop.parameters.StringParameter;
import com.arsdigita.bebop.parameters.BigDecimalParameter;
import com.arsdigita.categorization.Category;
import com.arsdigita.domain.DomainCollection;
import com.arsdigita.domain.DomainObjectFactory;
import com.arsdigita.persistence.DataCollection;
import com.arsdigita.persistence.SessionManager;
import com.arsdigita.xml.Element;
import com.arsdigita.xml.XML;

import com.arsdigita.cms.CMS;
import com.arsdigita.cms.ContentSection;

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * A Widget for selecting Terms. Based heavily on CategoryWidget.
 *
 * @author mbooth@redhat.com
 * 
 * Chris Gilbert - updated to identify each node uniquely (correct behaviour
 * for polyhierarchical trees) - also, allow ajax update on all branches or
 * just top level branch
 * 
 * nb - widget applies to allocation of categories to any ACSObject hence
 * xml prefix should be more generic eg bebop rather than cms. cms retained
 * for compatibility with existing stylesheets
 */
public class TermWidget extends Widget {
    private StringParameter m_mode;
    private ACSObjectCategoryPicker m_picker;

    public TermWidget(StringParameter mode, ACSObjectCategoryPicker picker) {
	super(new ArrayParameter(new BigDecimalParameter("category")));

	m_mode = mode;
	m_picker = picker;
        
    }
    
	

    protected String getType() {
        return "category";
    }

    public boolean isCompound() {
        return false;
    }

    protected void generateWidget(PageState state,
                                  Element parent) {
        Domain domain = m_picker.getDomain(state);

        Element widget = parent.newChildElement("cms:categoryWidget",
                                                CMS.CMS_XML_NS);
        exportAttributes(widget);

        widget.addAttribute("mode", (String)state.getValue(m_mode));
        widget.addAttribute("name", getName());
        
        Set ids = new HashSet();

        BigDecimal[] values = (BigDecimal[])getValue(state);
        if (values != null) {
            for (int i = 0 ; i < values.length ; i++) {
                ids.add(values[i]);
            }
        }

        // only root terms at first, the rest is loaded on-demand via AJAX
        DomainCollection terms = domain.getRootTerms();  
        terms.addPath("model.parents.link.sortKey");
        terms.addPath("model.parents.id");
        terms.addPath("domain.key");

        // Pull out everything related to the category, otherwise
        // another query per row is executed when doing term.getModel();
        terms.addPath("model.objectType");
        terms.addPath("model.displayName");
        terms.addPath("model.defaultDomainClass");
        terms.addPath("model.name");
        terms.addPath("model.description");
        terms.addPath("model.url");
        terms.addPath("model.isEnabled");
        terms.addPath("model.isAbstract");
        terms.addPath("model.defaultAncestors");

        List roots = new LinkedList();
        while (terms.next()) {
            Term term = (Term) terms.getDomainObject();
            roots.add(new TermSortKeyPair
                          (term,(BigDecimal)terms.get("model.parents.link.sortKey")));
        }

        Element el = generateCategory(widget, domain.getModel(), ids, null);
	if (Aplaws.getAplawsConfig().ajaxExpandAllBranches()) {
 	    // add attribute to the parent node, so that in stylesheet 
	    // we can look for any ancestor with this attribute (can't 
	    // add attribute to categoryWidget element as that is not 
	    // visible when subbranches are transformed)
	    el.addAttribute("expand",  "all" );
	}	
        for (Iterator i=roots.iterator(); i.hasNext(); ) {
            TermSortKeyPair pair = (TermSortKeyPair) i.next();
            Term term = pair.getTerm();
            BigDecimal sortKey = pair.getSortKey();

            generateRootTerm(el, term, ids, sortKey);
        }
    }

    public static Element generateCategory(Element parent,
                                    Category cat,
                                    Set selected,
                                    BigDecimal sortKey) {
        Element el = parent.newChildElement("cms:category", CMS.CMS_XML_NS);

        el.addAttribute("id", XML.format(cat.getID()));
        el.addAttribute("name", cat.getName());
        el.addAttribute("description", cat.getDescription());
        el.addAttribute("isSelected", selected.contains(cat.getID()) ? "1" : "0");
        el.addAttribute("isAbstract", cat.isAbstract() ? "1" : "0");
        el.addAttribute("isEnabled", cat.isEnabled() ? "1" : "0");
        if (sortKey != null) {
            el.addAttribute("sortKey", sortKey.toString());
        }
        // sort order attribute added to every node so that we can 
        // correctly transform xml fragments returned by ajax 
        el.addAttribute("order", ContentSection.getConfig().getCategoryTreeOrder());

        StringBuffer path = new StringBuffer(parent.getAttribute("fullname"));
        if (path.length() > 0) path.append(" > ");
        path.append(cat.getName());
        el.addAttribute("fullname", path.toString());
		
	// need to uniquely identify each node in polyhierarchical trees 
	// so that expand/contract is applied to the correct node by 
	// javascript getElementByID function
	StringBuffer nodeID = new StringBuffer(parent.getAttribute("node-id"));
	if (nodeID.length() > 0) nodeID.append("-");
	nodeID.append(cat.getID());
	el.addAttribute("node-id", nodeID.toString());
		
        return el;
    }

    public static Element generateTerm(Element parent,
                             Term term,
                             Set selected,
                             BigDecimal sortKey) {
        Category cat = term.getModel();
        Element el = generateCategory(parent, cat, selected, sortKey);

        el.addAttribute("pid", term.getUniqueID().toString());
        el.addAttribute("domain", term.getDomain().getKey());
        return el;
    }
    
    private static void generateRootTerm(Element parent,
                             Term term,
                             Set selected,
                             BigDecimal sortKey) {
        Element el = generateTerm(parent, term, selected, sortKey);
        el.addAttribute("root","1");
    }
    
    public static void generateSubtree(Element parent, Category root) {
        DataCollection terms = SessionManager.getSession().retrieve(
                Term.BASE_DATA_OBJECT_TYPE);
        terms.addEqualsFilter("model.roTransParents.id", root.getID());
        terms.addEqualsFilter("model.parents.link.relationType", "child");

        Map children = new HashMap();
        while (terms.next()) {
            Term term = (Term) DomainObjectFactory.newInstance(terms
                    .getDataObject());
            BigDecimal parentID = (BigDecimal) terms.get("model.parents.id");

            List childList = (List) children.get(parentID);
            if (childList == null) {
                childList = new LinkedList();
                children.put(parentID, childList);
            }

            childList.add(new TermSortKeyPair(term, (BigDecimal) terms
                    .get("model.parents.link.sortKey")));
        }
        
        // XXX for isSelected
        Set ids = new HashSet();
        
        Element el = generateCategory(parent, root, ids, null);
        el.addAttribute("fullname", root.getName());
        el.addAttribute("node-id", root.getID().toString());
        el.addAttribute("order", ContentSection.getConfig().getCategoryTreeOrder());
	if (Aplaws.getAplawsConfig().ajaxExpandAllBranches()) {
	    //	recognisable attribute has to be in the XML for each snippet that is transformed,
	    // hence add it to the parent
	    el.addAttribute("expand",  "all" );
	}	
	
	List roots = (List) children.get(root.getID());
        if (null != roots) {
            Iterator i = roots.iterator();
            while (i.hasNext()) {
                TermSortKeyPair pair = (TermSortKeyPair) i.next();
                Term term = pair.getTerm();
                BigDecimal sortKey = pair.getSortKey();

                generateTermWithChildren(el, term, ids, sortKey, children);
            }
        }
    }
    
    private static void generateTermWithChildren(Element parent,
                     Term term,
                     Set selected,
                     BigDecimal sortKey,
                     Map children) {
        Category cat = term.getModel();
        Element el = generateCategory(parent, cat, selected, sortKey);

        el.addAttribute("pid", term.getUniqueID().toString());
        el.addAttribute("domain", term.getDomain().getKey());

        List c = (List) children.get(cat.getID());
        if (c != null) {
            Iterator i = c.iterator();
            while (i.hasNext()) {
                TermSortKeyPair pair = (TermSortKeyPair) i.next();
                Term child = pair.getTerm();
                BigDecimal childSortKey = pair.getSortKey();
                
	  	// either generate next level down, or get all levels below current
				               
                if (Aplaws.getAplawsConfig().ajaxExpandAllBranches()) {
		    generateTerm(el, child, selected, childSortKey);
                
                } else {
		    generateTermWithChildren(el, child, selected, childSortKey,
		         children);
                }
                
                
                
            }
        }
    }

    private static class TermSortKeyPair {
        private Term m_term;
        private BigDecimal m_sortKey;

        public TermSortKeyPair(Term term, BigDecimal sortKey) {
            m_term = term;
            m_sortKey = sortKey;
        }
        public Term getTerm() {
            return m_term;
        }
        public BigDecimal getSortKey() {
            return m_sortKey;
        }
    }
}
