/*
 * Copyright (C) 2003-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.cms.dispatcher;

import com.arsdigita.caching.CacheTable;
import com.arsdigita.cms.CMSExcursion;
import com.arsdigita.cms.ContentItem;
import com.arsdigita.cms.ContentSection;
import com.arsdigita.cms.ContentSectionServlet;
import com.arsdigita.cms.ContentType;
import com.arsdigita.cms.ItemCollection;
import com.arsdigita.cms.Template;
import com.arsdigita.dispatcher.Dispatcher;
import com.arsdigita.dispatcher.DispatcherHelper;
import com.arsdigita.dispatcher.RequestContext;
import com.arsdigita.persistence.Filter;
import com.arsdigita.util.Assert;
import com.arsdigita.web.Web;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;

/**
 * This is the dispatcher for content-sections. It maintains a
 * ContentItem-to-Template cache Code that modifies a published
 * ContentItem's template must update the cache in this class by
 * calling the appropriate cache methods.
 *
 * @author bche@redhat.com
 */
public class ContentItemDispatcher implements Dispatcher {

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

    //the cache table for mapping content items to template JSP's
    private static CacheTable s_cache =
        new CacheTable("ContentItemDispatcherCache");
    //cache for the template resolver
    public static Map s_templateResolverCache =
        Collections.synchronizedMap(new HashMap());

    protected ItemXML m_itemXML;

    //     public static final String TEMPLATE_ROOT =
    //        "/packages/content-section/templates";
    //     public static final String DEFAULT_ITEM_TEMPLATE =
    //        "/default/item.jsp";
    //     public static final String DEFAULT_FOLDER_TEMPLATE =
    //        "/default/folder.jsp";

    public ContentItemDispatcher() {
        m_itemXML = new ItemXML();
    }

    /**
     * @see com.arsdigita.dispatcher.Dispatcher#dispatch
     *      (HttpServletRequest, HttpServletResponse, RequestContext)
     */
    public void dispatch
        (final HttpServletRequest request, final HttpServletResponse response,
         final RequestContext actx)
        throws IOException, ServletException {
        Boolean bXMLMode =
            (Boolean)request.getAttribute(ContentSectionServlet.XML_MODE);
        if (bXMLMode != null && bXMLMode.booleanValue())  {
            //if this is XML mode, then use itemXML
            m_itemXML.dispatch(request, response, actx);
        } else {
            //this is normal dispatching

            //get the Content Item
            //final ContentItem item = (ContentItem) request.getAttribute
            //    (ContentSectionServlet.CONTENT_ITEM);
            final ContentItem item = getContentItem(request);
            //get the Content Section
            final ContentSection section =
                (ContentSection) Web.getContext().getApplication();

            Assert.assertNotNull(item);

            //get the item's template
            final String sTemplateURL = getTemplatePath(item, request, actx);

            //dispatch to the template
            CMSExcursion excursion = new CMSExcursion() {
                public void excurse()
                    throws ServletException, IOException {
                    setContentSection(section);
                    setContentItem(item);

                    DispatcherHelper.setRequestContext(request, actx);
                    DispatcherHelper.forwardRequestByPath
                        (sTemplateURL, request, response);
                }
            };
            excursion.run();
        }
    }

    /**
     * Fetches the content item from the request attributes.
     *
     * @param request The HTTP request
     * @return The content item
     * @pre ( request != null )
     */
    public static ContentItem getContentItem(HttpServletRequest request) {
        return (ContentItem) request.getAttribute
            (ContentSectionServlet.CONTENT_ITEM);
    }

    //synchronize access to the cache

    private static synchronized void cachePut(BigDecimal contentItemID,
                                              String sTemplatePath) {
        s_cache.put(contentItemID, sTemplatePath);
    }

    private static synchronized void cacheRemove(BigDecimal contentItemID) {
        s_cache.remove(contentItemID);
    }

    /**
     * Method cacheRemove.  Removes the cached template path for the
     * contentItem item
     * @param item
     */
    public static void cacheRemove(ContentItem item) {
        if (item == null) {
            return;
        }
        if (s_log.isDebugEnabled()) {
            s_log.debug("removing cached entry for item " + item.getName() +
                        " with ID " + item.getID());
        }
        s_cache.remove(item.getID());
    }

    /**
     * Method cachePut.  Maps the ContentItem item to the
     * template t in the cache
     * @param item
     * @param t
     */
    public static void cachePut(ContentItem item, Template t) {
        ContentSection section = item.getContentSection();
        String sPath = getTemplatePath(section, t);

        //only cache live items
        if (item == null ||
            item.getVersion().compareTo(ContentItem.LIVE) != 0) {
            return;
        }

        if (s_log.isDebugEnabled()) {
            s_log.debug("updating mapping for item " + item.getName() +
                        " with ID " + item.getID() +
                        " in section " + section.getName() +
                        " of type " + item.getContentType().getLabel() +
                        " to template " + sPath);
        }

        cachePut(item.getID(), sPath);
    }


    /**
     * Method cachePut.  Maps all the content items of ContentType
     * type and in ContentSection section that don't have their
     * own templates to the template t in the cache
     * @param section
     * @param type
     * @param t
     */
    public static void cachePut(ContentSection section,
                                ContentType type,
                                Template t) {
        s_log.debug("updating cache for section " + section.getName() +
                    " and type " + type.getLabel());

        //get all the items in the section
        ItemCollection items = section.getItems();

        //filter items by content type
        BigDecimal typeID = type.getID();
        Filter filter = items.addFilter("type.id = :typeID");
        filter.set("typeID", typeID);

        //get only live items
        Filter liveFilter =
            items.addFilter("version = '" + ContentItem.LIVE + "'");

        //filter out content items in ContentSection section of
        //ContentType type with a template for the "public" context
        Filter itemsFilter = items.addNotInSubqueryFilter
            ("id", "com.arsdigita.cms.ItemsWithTemplateMapping");
        itemsFilter.set("sectionId", section.getID());
        itemsFilter.set("typeId", type.getID());

        //TODO: FILTER OUT CONTENT ITEMS IN THIS SECTION OF THIS TYPE
        //WITH A TEMPLATE FOR THE "PUBLIC" CONTEXT
        /*
         * select items.item_id
         * from cms_items items, cms_item_template_map map
         * where items.item_id = map.item_id
         * and use_context = 'public'
         * and items.version = 'live'
         * and items.section_id = :section_id
         * and items.type_id = :type_id
         */

        synchronized(s_cache) {
            //update the cache for all items
            while (items.next()) {
                cachePut(items.getContentItem(), t);
            }
        }
    }

    private static String getTemplatePath(ContentSection section,
                                          Template template) {
        //the template path is
        //    TEMPLATE_ROOT/[content-section-name]/[template-path]
        final String sep = java.io.File.separator;
        String sPath = ContentSection.getConfig().getTemplateRoot() +
            sep + section.getName() + sep + template.getPath();
        return sPath;
    }

    private static void updateTemplateCache(ContentSection section,
                                            ContentItem item,
                                            String sTemplatePath) {
        //use the live version of the item for the cache
        item = item.getLiveVersion();
        s_log.debug("updating mapping for item " + item.getName() +
                    " with ID " + item.getID() +
                    " in section " + item.getContentSection().getName() +
                    " of type " + item.getContentType().getLabel() +
                    " to template " + sTemplatePath);
        cachePut(item.getID(), sTemplatePath);
    }

    private String cacheGet(BigDecimal key) {
        return (String)s_cache.get(key);
    }

    private String getTemplatePath(ContentItem item, HttpServletRequest req,
                                   RequestContext ctx) {
	//check if the template path is cached
        //BigDecimal id = item.getID();
	//String sPath = cacheGet(id);

	//return from cache
	// current cache scheme doesn't work when there are
	//multiple templates per item, as would happen with
	// multiple template contexts or in the case of
	//category item resolution, more than one category for
	//the item.
	//if (sPath != null) {
	//s_log.debug("returning template path from cache");
	//	return sPath;
	//}

	//s_log.debug("template path not in cache, so fecthing");

	//template is not in the cache, so retrieve it and place it in
	//the cache
	String sPath = fetchTemplateURL(item, req, ctx);
	//cachePut(id, sPath);

	return sPath;
    }

    /**
     * Fetches the URL of a template for an item. The returned URL is
     * relative to the webapp context.
     */
    public String fetchTemplateURL(ContentItem item,
                                   HttpServletRequest request,
                                   RequestContext actx) {
        if (s_log.isDebugEnabled()) {
            s_log.debug("fetching URL for item " + item.getName() +
                        " with ID " + item.getID());
        }

        ContentSection section = item.getContentSection();
        String templateURL = getTemplateResolver(section).getTemplate
            (section, item, request);

        if (s_log.isDebugEnabled()) {
            s_log.debug("templateURL is " + templateURL);
        }
        return templateURL;

    }

    /**
     * Fetches the TemplateResolver for a content section. Checks cache first.
     *
     * @param section The content section
     * @return The TemplateResolver associated with the content section
     */
    public  TemplateResolver getTemplateResolver(ContentSection section) {
        String name = section.getName();
        TemplateResolver ir =
            (TemplateResolver) s_templateResolverCache.get(name);

        if (ir == null) {
            ir = section.getTemplateResolver();
            s_templateResolverCache.put(name, ir);
        }

        return ir;
    }


}
