/* ======================================================================
   Parts Copyright 2006 University of Leeds, Oxford University, University of the Highlands and Islands.

   Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

====================================================================== */

package org.bodington.servlet.facilities;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;

import javax.xml.transform.Templates;

import org.apache.log4j.Logger;
import org.bodington.server.BuildingContext;
import org.bodington.server.BuildingServerException;
import org.bodington.server.realm.PassPhrase;
import org.bodington.server.realm.Permission;
import org.bodington.server.realm.User;
import org.bodington.server.resources.FeedCache;
import org.bodington.server.resources.FeedResource;
import org.bodington.server.resources.Resource;
import org.bodington.server.resources.ResourceTreeManager;
import org.bodington.servlet.FacilityList;
import org.bodington.servlet.Request;
import org.bodington.servlet.template.Template;
import org.bodington.xml.DOMFactory;
import org.bodington.xml.XMLSerializer;
import org.bodington.xml.XMLTransformer;
import org.w3c.dom.Document;

/**
 * @author buckett
 */
public abstract class FeedFacility extends Facility
{
    private static Logger log = Logger.getLogger(FeedFacility.class);
    private static String TRANSFORM_STYLESHEET = "/org/bodington/xsl/feedTransform.xsl";
    private static String PRIVATE_STEM =  "http://hcdt1.oucs.ox.ac.uk/pebble/";
    private Templates stylesheetTemplate;
    private String statusMessage;
    
    public boolean canCopy( Resource resource )
    {
        return true;
    }
    
    protected boolean displayCurrentSettings(String command, FeedResource resource, PrintWriter out)
    {
        // returns boolean if command was acted upon.
        if (command.equalsIgnoreCase("inline"))
        {
            if (resource.isDisplayInline()) out.print("checked");
            return true;
        }
    
        if (command.equalsIgnoreCase("image"))
        {
            if (resource.isDisplayImage()) out.print("checked");
            return true;
        }
    
        if (command.equalsIgnoreCase("title"))
        {
            if (resource.isDisplayTitle()) out.print("checked");
            return true;
        }
    
        if (command.equalsIgnoreCase("description"))
        {
            if (resource.isDisplayDescription()) out.print("checked");
            return true;
        }
    
        if (command.equalsIgnoreCase("author"))
        {
            if (resource.isDisplayAuthor()) out.print("checked");
            return true;
        }
    
        if (command.equalsIgnoreCase("allowlinks"))
        {
            if (resource.isAllowTitleLinks()) out.print("checked");
            return true;
        }
    
        if (command.equalsIgnoreCase("date"))
        {
            if (resource.isDisplayDate()) out.print("checked");
            return true;
        }
    
        if (command.equalsIgnoreCase("maxnumber"))
        {
            out.print(resource.getMaxNumberOfItems());
            return true;
        }
    
        if (command.equalsIgnoreCase("itemtitles"))
        {
            if (resource.isDisplayItemTitles()) out.print("checked");
            return true;
        }
    
        if (command.equalsIgnoreCase("itemauthors"))
        {
            if (resource.isDisplayItemAuthors()) out.print("checked");
            return true;
        }
    
        if (command.equalsIgnoreCase("content"))
        {
            if (resource.isDisplayItemContent()) out.print("checked");
            return true;
        }
    
        if (command.equalsIgnoreCase("excerpt"))
        {
            if (resource.isDisplayExcerpt()) out.print("checked");
            return true;
        }
    
        if (command.equalsIgnoreCase("itemdate"))
        {
            if (resource.isDisplayItemDate()) out.print("checked");
            return true;
        }
        
        if (command.equalsIgnoreCase("itemseparator"))
        {
            out.print(resource.getItemSeparationTag());
            return true;
        }
    
        if (command.equalsIgnoreCase(getURLParameter()))
        {
            out.print(resource.getURL());
            return true;
        }
    
        return false;
    }

    protected void updateDisplayInline(Request req, FeedResource resource)
    {
        if (req.getParameter("inline") != null && !resource.isDisplayInline())
        {
            resource.setDisplayInline(true);
            resource.setUnsaved();
        }
        else if (req.getParameter("inline") == null
            && resource.isDisplayInline())
        {
            resource.setDisplayInline(false);
            resource.setUnsaved();
        }
    }

    protected void updateMaxNumberOfItems(Request req, FeedResource resource)
    {
        if (req.getParameter("maxnumber") != null)
        {
            String maxNumber = req.getParameter("maxnumber");
            if (!resource.getMaxNumberOfItems().equals(maxNumber)) try
            {
                int newMax = Integer.parseInt(maxNumber); // check that it's a
                // number.
                resource.setMaxNumberOfItems(maxNumber);
                resource.setUnsaved();
            }
            catch (NumberFormatException ex)
            {
                //        do nothing... // TODO display error message?
            }
        }
    }

    protected void updateDisplayImage(Request req, FeedResource resource)
    {
        if (req.getParameter("image") != null && !resource.isDisplayImage())
        {
            resource.setDisplayImage(true);
            resource.setUnsaved();
        }
        else if (req.getParameter("image") == null && resource.isDisplayImage())
        {
            resource.setDisplayImage(false);
            resource.setUnsaved();
        }
    }

    protected void updateDisplayTitle(Request req, FeedResource resource)
    {
        if (req.getParameter("title") != null && !resource.isDisplayTitle())
        {
            resource.setDisplayTitle(true);
            resource.setUnsaved();
        }
        else if (req.getParameter("title") == null && resource.isDisplayTitle())
        {
            resource.setDisplayTitle(false);
            resource.setUnsaved();
        }
    }

    protected void updateDisplayDescription(Request req, FeedResource resource)
    {
        if (req.getParameter("description") != null
            && !resource.isDisplayDescription())
        {
            resource.setDisplayDescription(true);
            resource.setUnsaved();
        }
        else if (req.getParameter("description") == null
            && resource.isDisplayDescription())
        {
            resource.setDisplayDescription(false);
            resource.setUnsaved();
        }
    }

    protected void updateDisplayAuthor(Request req, FeedResource resource)
    {
        if (req.getParameter("author") != null && !resource.isDisplayAuthor())
        {
            resource.setDisplayAuthor(true);
            resource.setUnsaved();
        }
        else if (req.getParameter("author") == null
            && resource.isDisplayAuthor())
        {
            resource.setDisplayAuthor(false);
            resource.setUnsaved();
        }
    }

    protected void updateAllowTitleLinks(Request req, FeedResource resource)
    {
        if (req.getParameter("allowlinks") != null
            && !resource.isAllowTitleLinks())
        {
            resource.setAllowTitleLinks(true);
            resource.setUnsaved();
        }
        else if (req.getParameter("allowlinks") == null
            && resource.isAllowTitleLinks())
        {
            resource.setAllowTitleLinks(false);
            resource.setUnsaved();
        }
    }

    protected void updateDisplayDate(Request req, FeedResource resource)
    {
        if (req.getParameter("date") != null && !resource.isDisplayDate())
        {
            resource.setDisplayDate(true);
            resource.setUnsaved();
        }
        else if (req.getParameter("date") == null && resource.isDisplayDate())
        {
            resource.setDisplayDate(false);
            resource.setUnsaved();
        }
    }

    protected void updateDisplayItemTitles(Request req, FeedResource resource)
    {
        if (req.getParameter("itemtitles") != null
            && !resource.isDisplayItemTitles())
        {
            resource.setDisplayItemTitles(true);
            resource.setUnsaved();
        }
        else if (req.getParameter("itemtitles") == null
            && resource.isDisplayItemTitles())
        {
            resource.setDisplayItemTitles(false);
            resource.setUnsaved();
        }
    }

    protected void updateDisplayItemAuthors(Request req, FeedResource resource)
    {
        if (req.getParameter("itemauthors") != null
            && !resource.isDisplayItemAuthors())
        {
            resource.setDisplayItemAuthors(true);
            resource.setUnsaved();
        }
        else if (req.getParameter("itemauthors") == null
            && resource.isDisplayItemAuthors())
        {
            resource.setDisplayItemAuthors(false);
            resource.setUnsaved();
        }
    }

    protected void updateDisplayItemContent(Request req, FeedResource resource)
    {
        if (req.getParameter("content") != null
            && !resource.isDisplayItemContent())
        {
            resource.setDisplayItemContent(true);
            resource.setUnsaved();
        }
        else if (req.getParameter("content") == null
            && resource.isDisplayItemContent())
        {
            resource.setDisplayItemContent(false);
            resource.setUnsaved();
        }
    }

    protected void updateDisplayExcerpt(Request req, FeedResource resource)
    {
        if (req.getParameter("excerpt") != null && !resource.isDisplayExcerpt())
        {
            resource.setDisplayExcerpt(true);
            resource.setUnsaved();
        }
        else if (req.getParameter("excerpt") == null
            && resource.isDisplayExcerpt())
        {
            resource.setDisplayExcerpt(false);
            resource.setUnsaved();
        }
    }

    protected void updateDisplayItemDate(Request req, FeedResource resource)
    {
        if (req.getParameter("date") != null && !resource.isDisplayItemDate())
        {
            resource.setDisplayItemDate(true);
            resource.setUnsaved();
        }
        else if (req.getParameter("date") == null
            && resource.isDisplayItemDate())
        {
            resource.setDisplayItemDate(false);
            resource.setUnsaved();
        }
    }

    protected void updateItemSeparationTag(Request req, FeedResource resource)
    {
        if (req.getParameter("itemseparator") != null)
        {
            String separationTag = req.getParameter("itemseparator");
            if (!separationTag.equals(resource.getItemSeparationTag()))
            {
                resource.setItemSeparationTag(separationTag);
                resource.setUnsaved();
            }
        }
    }

    protected void updateURL(Request req, FeedResource resource)
    throws BuildingServerException
    {
        if (req.getParameter(getURLParameter()) != null)
        {
            String url = req.getParameter(getURLParameter());
            try 
            {
                if (denyAccess(new URL(url)))
                {
                    throw new BuildingServerException("You are not allowed access to this URL");
                }
            } 
            catch (MalformedURLException mue)
            {
                throw new BuildingServerException("<strong>The URL you supplied does not appear to be valid</strong>");
            }
            
            if (!url.equals(resource.getURL()))
            {
                resource.setURL(url);
                resource.setUnsaved();
            }
        }
    }

    protected void resourceMenuItem(Resource resource, Request breq, PrintWriter out, ResourceMenuOutputState state, int depth, int highlight) throws IOException, BuildingServerException
    {
        FeedResource newsfeed = (FeedResource) resource;
        if (newsfeed.isDisplayInline())
            displayInline(newsfeed, breq, out, state, depth, highlight);
        else
            displayLinked(newsfeed, breq, out, state, depth, highlight);
    }

    private void displayInline(FeedResource resource, Request breq, PrintWriter out, ResourceMenuOutputState state, int depth, int highlight) throws IOException, BuildingServerException
    {
        boolean manage;
        int level;
        String href, target, content;
    
        content = transformFeed(resource);
    
        manage = resource.checkPermission(Permission.MANAGE);
        href = (!state.rootless && depth == 1) ? null : (breq.getContextPath()
            + breq.getServletPath() + resource.getFullName());
        target = "_top";
    
        //    if ( !resource.checkPermission( Permission.VIEW ) ) // else : just
        // don't see it at all!
        //    {
    
        level = ((depth - 1) % 10) + 1;
    
        // span is used to get everything on one line in browsers that
        // lack CSS but CSS redfines as block.
        out.print("<span class=\"" + state.css_class_prefix + "_node_content");
        if (depth == 1) out.print("_without_stalk");
        out.print("_lev");
        out.print(level);
        out.print("_hl");
        out.print(highlight);
        out.print("\">");
    
        if (href != null && manage)
        {
            out.print("<a");
            if (target != null)
            {
                out.print(" target=\"");
                out.print(target);
                out.print("\"");
            }
            out.print(" href=\"");
            out.print(href);
            out.print("\" title=\"Manage "+ getFacilityName()+ ".\"><div style=\"font-size: x-small\"><sup>Manage</sup></div></a>");
        }
    
        out.print(content);
        // TODO Do we want to have timed release?
        //String markup = timedResourceText(resource);
        //if (markup != null)
        //    out.print("<br>" + markup);
    
        out.print("</span>");
    
    }

    private void displayLinked(Resource resource, Request breq, PrintWriter out, ResourceMenuOutputState state, int depth, int highlight) throws IOException, BuildingServerException
    {
        Facility facility;
        Template template;
        String href = null, manage_href, icon_src = null, title = null, target = null, manage_target, content;
        boolean italic = false; // should really use a redefinable style
    
        boolean manage = false;
        boolean deny = false;
    
        manage = resource.checkPermission(Permission.MANAGE);
        href = (!state.rootless && depth == 1) ? null : (breq.getContextPath()
            + breq.getServletPath() + resource.getFullName());
        target = "_top";
    
        manage_href = href + "bs_template_manage.html";
        manage_target = "menu";
    
        // using getTemplateGifUrl doesn't work for descendant resources - need
        // to
        // reference proper resource.
        facility = state.fl.get(new Integer(resource.getHttpFacilityNo()));
        if (facility != null)
        {
            template = Template.get(facility.facilityname, resource
                .getImplicitHttpUIStyle(), resource.getResourceId(),
                state.big_icons ? "icon.gif" : "iconsmall.gif");
            if (template != null)
                icon_src = breq.getContextPath() + state.gif_url
                    + template.getUrl() + state.colour_code;
        }
        title = resource.getTitle();
        if (!resource.checkPermission(Permission.VIEW))
        {
            if (state.anon)
                // anonymous users are allowed to see link because
                // they get a message about logging in if they enter
                italic = true;
            else
            {
                // logged in but no access - don't give link
                href = null;
                deny = true;
            }
        }
    
        // heading level for title line matches depth but there are only
        // six heading levels in HTML so stop there.
        int hlevel = (depth > 6) ? 6 : depth;
        int level = ((depth - 1) % 10) + 1;
    
        // title in heading with appropriate class attribute
        out.print("          <h");
        out.print(hlevel);
    
        out.print(" class=\"" + state.css_class_prefix + "_node_title_lev");
        out.print(level);
        out.print("\">");
    
        // optional link around whole title (inc. icon)
        // changed to 2 separate links for icon and text
        // (and a third for the manage link)
        if (href != null)
        {
            out.print("<a"); // link for icon
            if (target != null)
            {
                out.print(" target=\"");
                out.print(target);
                out.print("\"");
            }
            out.print(" href=\"");
            out.print(href);
            out.print("\">");
        }
        // <altAndTitle> --->>>
        out.print("<img alt=\""+ iconName(resource)+ "\" src=\"");
        // <<<--- <altAndTitle>
        out.print(icon_src);
        out.print("\" ");
    
        if (depth == 1)
            out.print("class=\"" + state.css_class_prefix
                + "_node_icon_without_stalk\" />");
        else
            out.print("class=\"" + state.css_class_prefix
                + "_node_icon_no_expander\" />");
    
        if (href != null) out.print("</a>"); // closing tag for icon link
    
        // span is used to get everything on one line in browsers that
        // lack CSS but CSS redfines as block.
        out.print("<span class=\"" + state.css_class_prefix + "_node_content");
        if (depth == 1) out.print("_without_stalk");
        out.print("_lev");
        out.print(level);
        out.print("_hl");
        out.print(highlight);
        out.print("\">");
    
        if (href != null)
        {
            out.print("<a"); // tag for text link
            if (target != null)
            {
                out.print(" target=\"");
                out.print(target);
                out.print("\"");
            }
            out.print(" href=\"");
            out.print(href);
            out.print("\">");
        }
    
        if (italic) out.print("<i>");
    
        if (title != null)
        {
            out.print(title);
        }
    
        else
        {
            if (href != null)
                out.print(href);
            else
                out.print("{untitled item}");
        }
    
        if (italic) out.print("</i>");
    
        if (href != null) out.print("</a>"); // closing tag for text link
    
        if (href != null && manage)
        {
            out.print("&nbsp;<a"); // tag for manage link
            if (target != null)
            {
                out.print(" target=\"");
                out.print(manage_target);
                out.print("\"");
            }
            out.print(" href=\"");
            out.print(manage_href);
            out.print("\" title=\"Manage "+ getFacilityName()+ 
            		".\"><span style=\"font-size: x-small\"><sup>Manage</sup></span></a>");// closing tag for manage link
        }
    
        out.print("</span>");
    
        out.print("</h"); // end of node_title
        out.print(hlevel);
        out.println(">");
    
        if (!state.rootless && depth == 1)
            content = resource.getIntroduction();
        else
            content = resource.getDescription();
        if (deny)
        {
            content = "<em>"
                + "You are not included on the access list for this item.</em>"
                + " <br />" + content;
        }
    
        content = (content == null) ? "" : content;
    
        out.print("<div class=\"" + state.css_class_prefix + "_node_content");
        if (depth == 1) out.print("_without_stalk");
        out.print("_lev");
        out.print(level);
        out.print("_hl");
        out.print(highlight);
        out.print("\">");
    
        out.print(content);
        
        String markup = timedResourceText(resource);
        if (markup != null)
            out.print("<div>" + markup + "</div>");
    
        out.print("</div>");
        out.print("<div class=\"clearer\">&nbsp;</div>\n");
    }

    protected String getFrameDisplay(FeedResource resource)
    {
        if (resource.isDisplayInline())
        { // TODO keep this? (loads manage page instead of displaying blank
            // page...)
            String redirect = "<SCRIPT LANGUAGE=JavaScript>"
                + "top.buildmain.feed.location.href=\"./bs_template_manage.html\""
                + "</SCRIPT>";
            return redirect;
        }
    
        return transformFeed(resource);
    }

    /**
     * Fomat the supplied feed with HTML.
     * @param resource A FeedResource object to format.
     * @return A String HTML rendering of the Feed.
     */
    public String transformFeed(FeedResource resource)
    {
        // Displays XML feed transformed to HTML.
        // If a problem occurs, and display is set to:
        // inline - nothing displayed at all.
        // not inline - an error message is displayed.
    
        try
        {
            FeedCache feedCache = resource.getFeedCache();
            feedCache.update();
            return transformFeed(new ByteArrayInputStream(feedCache
                .getData().getBytes()), resource.loadTransformParams());
        }
        catch (Exception ex)
        {
            setStatusMessage("Error in parsing/transforming XML to HTML: "
                + ex.getMessage());
        }
    
        log.error(getStatusMessage());
    
        if (!resource.isDisplayInline()) return getStatusMessage();
    
        return "";
    }
    
    protected String transformFeed(InputStream data, Hashtable paramMap) 
    throws BuildingServerException
    {
        Document doc = DOMFactory.parse(data);
        StringWriter writer = new StringWriter();
        
        // TODO Should be able to use a re-useable template
        // instead of loading the stylesheet every time, but it doesn't
        // work...
        
        InputStream stylesheet = getClass().getResourceAsStream(
            BuildingContext.getProperty("blog.xslt", TRANSFORM_STYLESHEET));
        XMLSerializer.transformOutput(doc, stylesheet, paramMap, writer);
        
        return writer.toString();
    }

    private String getStatusMessage()
    {
        if (statusMessage != null) return statusMessage;
    
        return "No status message available.";
    }

    private void setStatusMessage(String message)
    {
        if (message != null) statusMessage = message;
    }

    /**
     * Check if the current use should have access to the URL.
     * It currently checks against the user in the current context. 
     * The check is to see if the beginning of the URL matches the restricted
     * URL if it does then check that the initials of the user match the URL path.
     * @param url The URL against which are performing the check.
     * @return True if the user should be denyed access.
     */
    protected boolean denyAccess(URL url)
    {
        String username;
        try
        {
            PassPhrase passPhrase = PassPhrase.findPassPhrase(
                (User) BuildingContext.getContext().getUser()
            );
            if (passPhrase == null || passPhrase.getUserName() == null)
            {
                return false;
            }
            username = passPhrase.getUserName();
        }
        catch (BuildingServerException e)
        {
            return false;
        }
        URL privateStem;
        try
        {
            privateStem = new URL(BuildingContext.getProperty("blog.private",
                PRIVATE_STEM));
        }
        catch (MalformedURLException mue)
        {
            return false;
        }
    
        if (privateStem.getProtocol().equals(url.getProtocol()) && 
                        privateStem.getHost().equals(url.getHost()) &&
                        privateStem.getPort() == url.getPort())
        {
            if (url.getPath().startsWith(privateStem.getPath()) && 
                (url.getPath().substring(privateStem.getPath().length())).startsWith(username+"/"))
                return false;
            else
                return true;
        }
        return false;
    }

    private void createStylesheetTemplate(String stylesheetPath) throws BuildingServerException
    {
        InputStream stylesheet = getClass().getResourceAsStream(stylesheetPath);
        stylesheetTemplate = XMLTransformer.createTemplate(stylesheet);
    }

    /**
     * Used to define what the string is that should be used as the URL paramter in subclasses.
     * @return Returns the URL parameter that is used.
     */
    abstract protected  String getURLParameter();

    /**
     * Used to define what the string is that should be used as the Display Name in subclasses.
     * @return Returns the Facility name that should be displayed to the user.
     */
    abstract protected String getFacilityName();

    
    /**
     * Check that it is ok to create this resource.
     * @return A list of errors encountered when creating this resource. 
     */
    public List validate( Resource newResource ) throws BuildingServerException
	{
        List errors = super.validate(newResource);
        try
        {
            URL url = new URL(((FeedResource) newResource).getURL());
            if (denyAccess(url))
            {
                errors = new ArrayList(errors);
                errors.add("You don't have permission to access that URL");   
            }
        }
        catch (MalformedURLException e)
        {
            errors = new ArrayList(errors);
            errors.add("URL doesn't seem to be valid.");
        }
        return errors;
	}
    
    /**
     * This attempts to render a feed. Can be called in any template 
     * to render a feed on that page. Development of this was done
     * to allow RSS in the login page.
     * The output of this call should be valid XML and so shouldn't break
     * page rendering but may contain evil code from the RSS feed.
     * @param out The PrintWriter to write the rendered feed to.
     * @param resourceUrl The bodington resource to display
     */
    public static void displayFeed(PrintWriter out, String resourceUrl)
    {
        Resource resource = null;
        try
        {
            resource = ResourceTreeManager.getInstance().findResource(resourceUrl);
        }
        catch (BuildingServerException e)
        {
            log.error("Problem find resource: "+ resourceUrl, e);
        }
        if (resource instanceof FeedResource)
        {
            FeedResource feedResource = (FeedResource)resource;
            FeedFacility  feedFacility = (FeedFacility)FacilityList.getFacilities().get(new Integer(feedResource.getHttpFacilityNo()));
            out.write(feedFacility.transformFeed(feedResource));
        }
        else
        {
            out.println("<h3>No News</h3>");
            out.println(resourceUrl+ "  does not appear to be a FeedResource.");
        }
    }
    
    public boolean isResourceAtDefaultURL()
    {
        return false;
    }
}
