/* ======================================================================
   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 org.bodington.database.PrimaryKey;
import org.bodington.servlet.*;
import org.bodington.server.*;
import org.bodington.server.resources.*;
import org.bodington.servlet.template.Template;
import org.bodington.server.realm.Permission;
import org.bodington.util.BodingtonURL;
import org.bodington.util.html.HtmlFilterFactory;

import org.apache.log4j.Logger;

import java.io.*;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;


/**
 * Class that encapsulates the web-based user interface to a quicklink. The
 * session type associated with this class is {@link QuickLinkSession}. The
 * type of resource associated with this class is {@link QuickLink}.
 * @author Antony Corfield
 * @see QuickLink
 * @see QuickLinkSession
 */
public class QuickLinkFacility extends Facility
{
    private static Logger log = Logger.getLogger( QuickLinkFacility.class );

    public boolean canCopy( Resource resource )
    {
        return true;
    }

    public Resource newResource()
    {
        return new QuickLink();
    }

    public List initResource(HttpServletRequest req, Resource new_resource)
    {

        if (new_resource instanceof QuickLink)
        {
            QuickLink link = (QuickLink) new_resource;
            String url = "";
            link = (QuickLink) new_resource;

            url = req.getParameter("link");
            if (url == null || "".equals(url) || "http://".equals(url))
                return Collections.singletonList( "The URL for the link must be specified." );

            QuickLinkFacility.setLink(link, url, req);

            String newWindow = req.getParameter("new_window");
            if (newWindow != null && newWindow.equalsIgnoreCase("on"))// "true"??
                link.setNewWindow(true);
            else
                link.setNewWindow(false);

            link
                .setShowIntermediatePage(req.getParameter("intermediate_page") != null);

            link
                .setUseExistingFrameset(req.getParameter("existing_frameset") != null);
        }
        else
        {
            throw new IllegalArgumentException("Resource must be a QuickLink");
        }

        return super.initResource(req, new_resource);
    }

    public boolean initResource( Resource original_resource,
        Resource new_resource ) throws BuildingServerException
    {
        if ( !(new_resource instanceof QuickLink) )
            throw new BuildingServerException(
                "Error: The wrong type of resource was created." );

        QuickLink original = (QuickLink)original_resource;
        QuickLink new_link = (QuickLink)new_resource;
        new_link.setURL( original.getURL() );
        new_link.setResourceIdLink( original.getResourceIdLink() );
        new_link.setNewWindow( original.isNewWindow() );
        new_link.setShowIntermediatePage( original.isShowIntermediatePage() );
        new_link.setUseExistingFrameset( original.isUseExistingFrameset() );
        
        return true;
    }

    public List validate( Resource newResource ) throws BuildingServerException
    {
        List errors = new LinkedList();
        QuickLink quickLink = (QuickLink)newResource;
        String url = quickLink.getURL();
        if ( (url != null && url.length() > 0))
        {
            if ( ! HtmlFilterFactory.isGoodProtocol(url))
            {
                errors.add( "The Protocol isn't valid.");
            }
        }
        else if ( quickLink.getResourceIdLink() == null )
        {
            errors.add( "Must supply a url" );
        }
        errors.addAll(super.validate(newResource));
        return errors;
    }

    public void insert( Request req, PrintWriter out, String command,
        String insertname ) throws ServletException, IOException
    {
        log.debug( " QuickLinkFacility insert()" );

        try
        {
            BuildingSession session;
            QuickLinkSession link_session;
            QuickLink link;

            session = BuildingSessionManagerImpl.getSession( req.getResource() );
            if ( !(session instanceof QuickLinkSession) )
            {
                out.println( "<HR>Technical problem: unable to access "
                    + "appropriate tool session.<HR>" );
                return;
            }
            link_session = (QuickLinkSession)session;
            link = link_session.getQuickLink();

            if ( command.equalsIgnoreCase( "linkmodify" ) )
            {
                linkmodify( req, out, link );
                return;
            }

            if ( command.equalsIgnoreCase( "linkfield" ) )
            {
                linkfield( req, out, link, insertname );
                return;
            }
            
            if ( command.equalsIgnoreCase( "link" ) )
            {
                link( req, out, link );
                return;
            }

        }
        catch ( BuildingServerException bsex )
        {
            out.println( bsex.toString() );
            return;
        }

        super.insert( req, out, command, insertname );
    }

    private void link( Request request, PrintWriter out, QuickLink link )
        throws BuildingServerException
    {
        out.print( "<a href=\"" );
        out.print( getLink( link, request, false ) + "\" ");
        String target = null;
        if ( link.isNewWindow() )
            target = "_new";
        else if ( link.isUseExistingFrameset() )
            target = "manage";
        else
            target = "_top";
        out.print( "target=" + target );
        
        out.print( ">" + link.getTitle() + "</a>" );
    }

    private void linkfield( Request req, PrintWriter out, QuickLink link,
        String name ) throws IOException
    {

        if ( name.equalsIgnoreCase( "url" ) )
        {
            String url = QuickLinkFacility.getLink( link, req, false );
            if ( url == null || url.length() == 0 )
            {
                out.println( "No URL has been specified for this QuickLink!" );
                return;
            }
            out.print( "<INPUT SIZE=\"32\" NAME=\"link\" VALUE=\"" );
            out.print( url );
            out.println( "\">" );
        }

        if ( name.equalsIgnoreCase( "window" ) )
        {
            out.print( "<INPUT TYPE=\"CHECKBOX\" NAME=\"new_window\" " );
            if ( link.isNewWindow() ) 
                out.print( "CHECKED=\"true\"" );

            out.println( " />" );
        }
        
        if ( name.equalsIgnoreCase( "intermediate" ) )
        {
            out.print( "<INPUT TYPE=\"CHECKBOX\" NAME=\"intermediate_page\" " );
            if ( link.isShowIntermediatePage() ) 
                out.print( "CHECKED=\"true\"" );

            out.println( " />" );
        }
        
        if ( name.equalsIgnoreCase( "frameset" ) )
        {
            out.print( "<INPUT TYPE=\"CHECKBOX\" NAME=\"existing_frameset\" " );
            if ( link.isUseExistingFrameset() ) 
                out.print( "CHECKED=\"true\"" );

            out.println( " />" );
        }
 
        return;
    }

    private void linkmodify( Request req, PrintWriter out, QuickLink link )
        throws IOException, BuildingServerException
    {
        String url;
        boolean isBodURL;
        url = req.getParameter( "link" );
        if ( url == null || (url = url.trim()).length() == 0 )
        {
            out.println( "<HR>You must specify the URL<HR>" );
            return;
        }
        
        if ( ! HtmlFilterFactory.isGoodProtocol(url))
        {
          out.print( "<HR>The Protocol isn't valid.<HR>");
          return;
        }

        isBodURL = QuickLinkFacility.setLink( link, url, req );

        String newWindow = req.getParameter( "new_window" );
        if ( newWindow != null && newWindow.equalsIgnoreCase( "on" ) )
            link.setNewWindow( true );
        else
            link.setNewWindow( false );
        
        link.setShowIntermediatePage( 
            req.getParameter("intermediate_page") != null );
        
        link.setUseExistingFrameset(  
            req.getParameter("existing_frameset") != null );

        link.save();

        out.print( "<CENTER><P>The requested changes were stored.</P></CENTER>" );
        if ( isBodURL )
            out.print( "<P>This QuickLink points to a resource within this "
                + "site." );
        else
            out.print( "<P>This QuickLink points to an external website." );
        // TODO doesn't work with relative links:
        out.print( " You can test the link by clicking on the URL below (this "
            + "opens in a new window for test purposes only)"
                + "<BR><A HREF=\"" + url + "\" TARGET=_new>" + url + "</P>" );
        return;
    }

    protected void resourceMenuItem( Resource resource, Request breq,
        PrintWriter out, ResourceMenuOutputState state, int depth, int highlight )
        throws IOException, BuildingServerException
    {
        Facility facility;
        Template template;
        String href = null, icon_src = null, title = null, content;
        String link, link_target;

        boolean manage = false;
        boolean italic = false;  // should really use a redefinable style
        boolean deny = false;

        QuickLink quickLink = (QuickLink) resource;
        link = QuickLinkFacility.getLink( quickLink, breq, false );
        if ( quickLink.isShowIntermediatePage() )
            link_target = "_top";
        else if ( quickLink.isNewWindow() )
            link_target = "_new";
        else // default!
            link_target = "_top";
            
        manage = resource.checkPermission( Permission.MANAGE );
        href = (!state.rootless && depth == 1) ? null : (breq.getContextPath()
            + breq.getServletPath() + resource.getFullName());
        
        // href is the target of the quicklink or it's URL:
        link = quickLink.isShowIntermediatePage() || quickLink.isUseExistingFrameset()
            ? href : getLink( quickLink, breq, false );
        
        
        // 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;
                // Setting this means that the actual URL is not leaked and 
                // the user can be presented with the login invitation page.
                link = href;
            }
            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 ( link != null && !deny )
        {
            out.print( "<a" ); // link for icon
            if ( link_target != null )
            {
                out.print( " target=\"" );
                out.print( link_target );
                out.print( "\"" );
            }
            out.print( " href=\"" );
            out.print( link );
            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 ( link != null && !deny ) 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 ( link != null && !deny )
        {
            out.print( "<a" ); // tag for text link
            if ( link_target != null )
            {
                out.print( " target=\"" );
                out.print( link_target );
                out.print( "\"" );
            }
            out.print( " href=\"" );
            out.print( link );
            out.print( "\">" );
        }

        if (italic)
            out.print("<I>");
        
        if ( title != null )
            out.print( title );
        else
            out.print( "{untitled item}" );
        
        if (italic)
            out.print("</I>");

        if ( link != null && !deny ) out.print( "</a>" ); // closing tag for
                                                            // text link

        if ( href != null && manage )
        {
            out.print( "&nbsp;<a " ); // tag for manage link
            out.print( "href=\"" + href + "manage/bs_template_index.html\" " ); 
            out.print( "target=\"_top\" title=\"Manage QuickLink.\">" );
            out.print("<span style=\"font-size: x-small\"><sup>Manage</sup></span></a>" ); 
        }

        out.print( "</span>" );

        out.print( "</h" ); // end of node_title
        out.print( hlevel );
        out.println( ">" );

        if ( !state.rootless && depth == 1 )// TODO ??
            content = resource.getIntroduction();
        else
            content = resource.getDescription();

        if ( deny )
        {
            content = "<em>"
                + "You are not included on the access list for this item.</em>"
                + " <br />";
            /* TODO: show description? */
        }

        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" );
    }

    /**
     * Associate the quicklink with the specified URL. This method tries to
     * ascertain whether or not this link is an internal one.
     * @param quickLink the quicklink object we are trying to set.
     * @param url the URL that is the target of the quicklink.
     * @param request the request object.
     * @return <code>true</code> if the <code>link</code> parameter refers
     *         to an internal resource, otherwise <code>false</code>.
     * @see QuickLink#isBodURL()
     */
    public static boolean setLink( QuickLink quickLink, String url,
        HttpServletRequest request ) 
    {
        PrimaryKey id;
        Resource res;
        UploadedFile ulf;
        BodingtonURL bodUrl = new BodingtonURL( request );

        if ( (res = bodUrl.getResource( url )) != null )
        {
            quickLink.setResourceIdLink( res.getResourceId() );
            quickLink.setURL( null );
            return true;
        }

        if ( (ulf = bodUrl.getUploadedFile( url )) != null )
        {
            quickLink.setResourceIdLink( ulf.getPrimaryKey() );
            quickLink.setURL( null );
            return true;
        }

        quickLink.setURL( url );
        quickLink.setResourceIdLink( null );
        return false;
    }

    /**
     * Get the URL associated with the specified quicklink. If the URL can not
     * be calculated, then an empty string is returned.
     * @param quickLink The QuickLink we are trying to build the URL for.
     * @param request The Request object that making this call.
     * @param check if this is <code>true</code> then we check that the
     *        desination of this link hasn't been deleted.
     * @return the URL associated with the specified quicklink.
     */
    public static String getLink( QuickLink quickLink, Request request,
        boolean check )
    {
        Resource res;
        UploadedFile ulf;
        String description, link = null;
        String msg = " (The resource this links to may have been deleted)";
        int i;
        BodingtonURL bodUrl = new BodingtonURL( request );

        if ( quickLink.getResourceIdLink() == null ) 
            return quickLink.getURL();

        // check == true; returns null if resource has been deleted
        link = bodUrl.getResourceUrl( quickLink.getResourceIdLink(), check );
        if ( link == null )

            // check == true; returns null if UploadedFile has been deleted
            link = bodUrl.getUploadedFileUrl( quickLink.getResourceIdLink(),
                check );
        try
        {
            description = quickLink.getDescription();
            if ( link == null )
            {
                i = description.indexOf( msg );
                if ( i < 0 )
                    quickLink.setDescription( quickLink.getDescription() + msg );
                return "";
            }
            i = description.indexOf( msg );
            if ( i > -1 )
            {
                description = description.substring( 0, i );
                quickLink.setDescription( description );
            }
            return link;
        }
        catch ( BuildingServerException ex )
        {
            return "";
        }
    }
    
    /**
     * Indicates whether the implicit link should open in a new window. This
     * method is intended to be called from templates.
     * @param request the current request.
     * @return <code>true</code> if the implicit link should open in a new 
     * window, otherwise <code>false</code>. 
     */
    public boolean isNewWindow( Request request )
    {
        try
        {
            BuildingSession session;
            QuickLinkSession link_session;
            QuickLink link;

            session = BuildingSessionManagerImpl.getSession( 
                request.getResource() );
            link_session = (QuickLinkSession)session;
            link = link_session.getQuickLink();
            return link.isNewWindow();
        }
        catch ( BuildingServerException bsex )
        {
        }
        return false;
    }
    
    /**
     * Indicates whether an intermediate page should be shown to the user. This
     * method is intended to be called from templates.
     * @param request the current request.
     * @return <code>true</code> if an intermediate page should be shown to
     *         the user, otherwise <code>false</code>.
     */
    public boolean isShowIntermediatePage( Request request )
    {
        try
        {
            BuildingSession session;
            QuickLinkSession link_session;
            QuickLink link;

            session = BuildingSessionManagerImpl.getSession( 
                request.getResource() );
            link_session = (QuickLinkSession)session;
            link = link_session.getQuickLink();
            return link.isShowIntermediatePage();
        }
        catch ( BuildingServerException bsex )
        {
        }
        return false;
    }
    
    /**
     * Indicates whether the existing frameset should be used. If 
     * <code>true</code>, then the target (<code>href</code>) of the quicklink
     * will be rendered within the right-hand frame. 
     * <p>
     * <em>NOTE: This method is intended to be called from templates.</em>
     * @param request the current request.
     * @return <code>true</code> if the existing frameset should be used, 
     * otherwise <code>false</code>.
     */
    public boolean isUseExistingFrameset( Request request )
    {
        try
        {
            BuildingSession session;
            QuickLinkSession link_session;
            QuickLink link;

            session = BuildingSessionManagerImpl.getSession( 
                request.getResource() );
            link_session = (QuickLinkSession)session;
            link = link_session.getQuickLink();
            return link.isUseExistingFrameset();
        }
        catch ( BuildingServerException bsex )
        {
        }
        return false;
    }
    
    /**
     * Get the target of the implicit resource. This method is intended to be
     * called from templates.
     * @param request the current request.
     * @return the target of the implicit resource.
     */
    public String getTarget( Request request )
    {
        try
        {
            BuildingSession session;
            QuickLinkSession link_session;
            QuickLink link;

            session = BuildingSessionManagerImpl.getSession( 
                request.getResource() );
            link_session = (QuickLinkSession)session;
            link = link_session.getQuickLink();
            return getLink( link, request, false );
        }
        catch ( BuildingServerException bsex )
        {
        }
        return "";
    }
    
    public boolean isManageRequest( Request request )
    {
        return request.getTemplateParameterCount() == 1
            && "manage".equals( request.getTemplateParameter( 0 ) );
    }

    protected boolean checkModifyTemplateParameters( Request request, PrintWriter out )
    {
        if ( isManageRequest( request ))
            return true;
        
        return super.checkModifyTemplateParameters( request, out );
    }
    
    protected boolean checkDeleteTemplateParameters( Request request, PrintWriter out )
    {
        if ( isManageRequest( request ))
            return true;
        
        return super.checkDeleteTemplateParameters( request, out );
    }
    
    public boolean isResourceAtDefaultURL()
    {
        return false;
    }

    public String fileParameter( Request request )
    {
        /*
         * NOTE: overriding this method enables management of uploaded files to
         * circumvent the use of 'manage' as a template parameter!
         */
        String s = super.fileParameter( request );
        
        if ( !"/manage".equals( s ))
            return s;
        else
            try
            {
                // Check for a child called 'manage':
                if ( request.getBuildingSession().getUploadedFileSession().getFileSummary( s ) == null )
                    return "";
            }
            catch ( BuildingServerException e ) {}
            
            return s;
    }
}
