/* ======================================================================
The Bodington System Software License, Version 1.0

Copyright (c) 2001 The University of Leeds.  All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

1.  Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.

2.  Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.

3.  The end-user documentation included with the redistribution, if any,
must include the following acknowledgement:  "This product includes
software developed by the University of Leeds
(http://www.bodington.org/)."  Alternately, this acknowledgement may
appear in the software itself, if and wherever such third-party
acknowledgements normally appear.

4.  The names "Bodington", "Nathan Bodington", "Bodington System",
"Bodington Open Source Project", and "The University of Leeds" must not be
used to endorse or promote products derived from this software without
prior written permission. For written permission, please contact
d.gardner@leeds.ac.uk.

5.  The name "Bodington" may not appear in the name of products derived
from this software without prior written permission of the University of
Leeds.

THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO,  TITLE,  THE IMPLIED WARRANTIES
OF QUALITY  AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO
EVENT SHALL THE UNIVERSITY OF LEEDS OR ITS CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
=========================================================

This software was originally created by the University of Leeds and may contain voluntary
contributions from others.  For more information on the Bodington Open Source Project, please
see http://bodington.org/

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

package org.bodington.servlet.facilities;

import org.apache.log4j.Logger;

import java.io.*;
import java.util.Properties;
import javax.servlet.ServletException;
import java.sql.Connection;

import org.bodington.server.*;
import org.bodington.server.realm.Permission;
import org.bodington.server.resources.*;
import org.bodington.servlet.Request;
import org.bodington.servlet.template.Template;
import org.bodington.util.TextUtils;

import java.net.URL;
import java.net.URLConnection;
import java.net.HttpURLConnection;

public class SentientLinkFacility extends Facility
{
    
  private static Logger log = Logger.getLogger(SentientLinkFacility.class);
  // Property names:
  private static final String SOAP_INTERFACE_URL = "Sentient.SOAPInterfaceURL";
  private static final String SOAP_SERVICE_URL = "Sentient.SOAPServiceURL";
  private static final String COLLEGE_ID = "Sentient.collegeID";
  private static final String SECURITY_CODE = "Sentient.securityCode";
  private static final String VLE_TYPE = "Sentient.VLEType";

  // SOAP constants:
  private static final String XML_DECLARATION = "<?xml version=\"1.0\" encoding=\"utf-8\"?>";

  private static final String SOAP_ENV_START = "<soap:Envelope"
				     + " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
				     + " xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\""
				     + " xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">";
  private static final String SOAP_ENV_END = "</soap:Envelope>";

  private static final String SOAP_BODY_START = "<soap:Body>";
  private static final String SOAP_BODY_END = "</soap:Body>";

  // SOAP variables, set from properties file:
  private static String SOAPInterfaceURL, SOAPServiceURL, collegeID, securityCode, VLEType;

  public SentientLinkFacility()
      throws BuildingServerException
  {
    log.debug("SentientLinkFacility constructor.");
    try
    {
      String bodhome = BuildingContext.getProperty("buildingservlet.context.root");
      File propsFile = new File( bodhome +File.separator+ "WEB-INF" + File.separator + "sentient.properties" );
      FileInputStream in = new FileInputStream( propsFile );

      log.info("Loading configuration from: "+ propsFile.getAbsoluteFile());
      
      Properties properties = new Properties();
      properties.load( in );
      SOAPServiceURL = properties.getProperty( SOAP_SERVICE_URL );
      SOAPInterfaceURL = SOAPServiceURL + properties.getProperty( SOAP_INTERFACE_URL );
      collegeID = properties.getProperty( COLLEGE_ID );
      securityCode = properties.getProperty( SECURITY_CODE );
      VLEType = properties.getProperty( VLE_TYPE );
      in.close();
      String message = "Sentient property settings: "
      +"SOAPServiceURL: " + SOAPServiceURL + "\r\n"
      +"SOAPInterfaceURL: " + SOAPInterfaceURL + "\r\n"
      +"collegeID: " + collegeID + "\r\n"
      +"securityCode: " + securityCode;
      log.debug( message );
      
    }
    catch (FileNotFoundException ex)
    {
      throw new BuildingServerException( ex.getMessage(), "Sentient properties file not found." );
    }
    catch (IOException ex)
    {
      throw new BuildingServerException( ex.getMessage(), "Error loading properties from Sentient properties file." );
    }
  }

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

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

    SentientLink original = (SentientLink)original_resource;
    SentientLink new_link = (SentientLink)new_resource;

    new_link.setUrl(original.getUrl());
  /** The original module would be editable in either place, so don't copy editable URL: */
    new_link.setEditUrl( "bs_template_error.html" );
    new_link.setNewWindow(original.isNewWindow());
//    new_link.save();

    return true;
  }

/**
 * Using create() rather than initResource(), to create new Sentient module,
 * because the new resource's id is null until Facility calls ResourceTree.addResource().
 * New resource's id is needed for Sentient SOAP methods...
 */

  public boolean create( Request breq, Connection con, Resource new_resource )
      throws BuildingServerException
  {
    SentientLink link;
    String title, moduleCode, message;
    String url, editUrl;

    link = (SentientLink)new_resource;

    moduleCode = String.valueOf( link.getResourceId() );
    title = link.getTitle();

    try
    {
      message = checkModuleExists(collegeID, moduleCode);
      if ( !message.equals( "0" ) ) {
	message = "Sentient module already exists: " + message;
	throw new BuildingServerException( message );
      }

      message = createModule(collegeID, moduleCode, title, securityCode);
      if ( !message.equalsIgnoreCase("Created") ) {
	message = "Creation failed: " + message;
	throw new BuildingServerException( message );
      }

      // URLs are generated here, and then set as SentientLink properties:
      url = getURL(collegeID, moduleCode, "false", VLEType, securityCode);
      link.setUrl( url );

      editUrl = getURL(collegeID, moduleCode, "true", VLEType, securityCode);
      link.setEditUrl( editUrl );

      // set link to open in a new window if requested:
      if ( breq.getParameter( "new_window" ) != null )
	link.setNewWindow( true );

      link.save();
      return true;
    }
    catch (IOException ex)
    {
      throw new BuildingServerException( ex.getMessage(), "Creation of new Sentient module failed." );
    }

  }


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

    try
    {
      BuildingSession session;
      SentientLinkSession link_session;
      SentientLink link;

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

      if ( command.equalsIgnoreCase( "generatelink" ) )
      {
	// displays URL created dynamically using SOAP getURL() method.
	generateLink( out, insertname, link);
	return;
      }

      if ( command.equalsIgnoreCase( "link" ) )
	// gets URL saved as properties of SentientLink (not dynamically created).
      {
	if ( insertname.equalsIgnoreCase("url") )
	{
	  out.print( link.getUrl() );
	  return;
	}
	if ( insertname.equalsIgnoreCase("editurl") )
	{
	  out.print( link.getEditUrl() );
	  return;
	}
      }

      if ( command.equalsIgnoreCase( "window" ) )
      {
	if ( link.isNewWindow() )
	  out.print("checked");
	return;
      }

      /**
       * Called every time the page is loaded:
       * Parameters 'modify', 'update', and 'window' indicate which Submit button was pressed.
       */
      if ( command.equalsIgnoreCase( "linkmodify" ) )
      {
	if ( req.getParameter( "modify" ) != null )
	{
	  linkmodify( req, link );
	  return;
	}

	if ( req.getParameter( "update" ) != null )
	{
                    linkupdate(req, link);
	  return;
	}

	if ( req.getParameter( "window" ) != null )
	{
	  if ( req.getParameter( "new_window" ) != null)
	    link.setNewWindow( true );
	  else
	    link.setNewWindow( false );

	  link.save();
	  return;
	}

	return;
      }
    }
    catch ( BuildingServerException bsex )
    {
      logException( out, "SentientLinkFacility", "insert()", bsex.getMessage(), bsex);
      return;
    }

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

  private void generateLink(  PrintWriter out, String insertname, SentientLink link )
  //	    throws IOException
  {
    String moduleCode, url;
    boolean editable = false;

    moduleCode = link.getResourceId().toString();
    url = "Error in dynamic link creation";

    if ( insertname.equalsIgnoreCase("editurl"))
      editable = true;

    try
    {
      url = getURL(collegeID, moduleCode, String.valueOf(editable), VLEType, securityCode);
    }
    catch (IOException ex)
    {
      log.debug( "Dynamic generation of Sentient Link failed: "+ ex.getMessage() );
    }

    out.println( url );

    return;
  }

  private void linkmodify( Request req, SentientLink link )
      throws BuildingServerException
  {
    String url, editUrl;

    url = req.getParameter( "url" );
    if ( url != null ) {
      url = url.trim();
      link.setUrl( url );
      link.save();
      return;
    }

    editUrl = req.getParameter( "editurl" );
    if ( editUrl != null ) {
      editUrl = editUrl.trim();
      link.setEditUrl( editUrl );
      link.save();
      return;
    }

  }

    private void linkupdate(Request req, SentientLink link)
      throws BuildingServerException
  {
    String url, editUrl;
    String moduleCode = link.getResourceId().toString();

    try
    {
      if ( req.getParameter( "url" ) != null ) {
	url = getURL(collegeID, moduleCode, "false", VLEType, securityCode);
	link.setUrl( url );
	link.save();
	return;
      }

      if ( req.getParameter( "editurl" ) != null ) {
	editUrl = getURL(collegeID, moduleCode, "true", VLEType, securityCode);
	link.setEditUrl( editUrl );
	link.save();
	return;
      }
    }
    catch (IOException ex)
    {
      throw new BuildingServerException( ex.getMessage(), "Updating the Sentient link failed." );
    }
  }

  private String checkCollegeExists(String collegeID)
      throws IOException
  {
    ByteArrayOutputStream bout = new ByteArrayOutputStream();
    Writer wout = new OutputStreamWriter(bout);

    wout.write( XML_DECLARATION );
    wout.write( SOAP_ENV_START );
    wout.write( SOAP_BODY_START );

    wout.write("<CheckCollegeExists xmlns=\"" + SOAPServiceURL + "\">");
    wout.write("<pintIdentifier>" + collegeID + "</pintIdentifier>");
    wout.write("</CheckCollegeExists>");

    wout.write( SOAP_BODY_END );
    wout.write( SOAP_ENV_END );

    wout.flush();
    wout.close();

    // Create the connection:
    URL url = new URL( SOAPInterfaceURL );
    URLConnection connection = url.openConnection();
    HttpURLConnection httpConn = (HttpURLConnection) connection;

    sendSOAPMessage( "CheckCollegeExists", httpConn, bout );
    String strRes = getSOAPResponse( "CheckCollegeExistsResult", httpConn );

    return strRes;
  }

  private String checkModuleExists(String collegeID, String moduleCode)
      throws IOException
  {
    ByteArrayOutputStream bout = new ByteArrayOutputStream();
    Writer wout = new OutputStreamWriter(bout);

    wout.write( XML_DECLARATION );

    wout.write( SOAP_ENV_START );
    wout.write( SOAP_BODY_START );

    wout.write("<CheckModuleExists xmlns=\"" + SOAPServiceURL + "\">");
    wout.write("<pintIdentifier>" + collegeID + "</pintIdentifier>");
    wout.write("<pstrModuleCode>" + moduleCode + "</pstrModuleCode>");
    wout.write("</CheckModuleExists>");

    wout.write( SOAP_BODY_END );
    wout.write( SOAP_ENV_END );

    wout.flush();
    wout.close();

    // Create the connection:
    URL url = new URL( SOAPInterfaceURL );
    URLConnection connection = url.openConnection();
    HttpURLConnection httpConn = (HttpURLConnection) connection;

    sendSOAPMessage( "CheckModuleExists", httpConn, bout );
    String strRes = getSOAPResponse( "CheckModuleExistsResult", httpConn );

    return strRes;
  }

  private String createModule(String collegeID, String moduleCode, String moduleTitle, String securityCode)
      throws IOException
  {
    ByteArrayOutputStream bout = new ByteArrayOutputStream();
    Writer wout = new OutputStreamWriter(bout);

    wout.write( XML_DECLARATION );

    wout.write( SOAP_ENV_START );
    wout.write( SOAP_BODY_START );

    wout.write("<CreateModule xmlns=\"" + SOAPServiceURL + "\">");
    wout.write("<pintIdentifier>" + collegeID + "</pintIdentifier>");
    wout.write("<pstrModuleCode>" + moduleCode + "</pstrModuleCode>");
    wout.write("<pstrTitle>" + moduleTitle + "</pstrTitle>");
    wout.write("<pstrSecurity>" + securityCode + "</pstrSecurity>");
    wout.write("</CreateModule>");

    wout.write( SOAP_BODY_END );
    wout.write( SOAP_ENV_END );

    wout.flush();
    wout.close();

    // Create the connection:
    URL url = new URL( SOAPInterfaceURL );
    URLConnection connection = url.openConnection();
    HttpURLConnection httpConn = (HttpURLConnection) connection;

    sendSOAPMessage( "CreateModule", httpConn, bout );
    String strRes = getSOAPResponse( "CreateModuleResult", httpConn );

    return strRes;

  }

  private String getURL(String collegeID, String moduleCode, String admin, String VLEType, String securityCode)
      throws IOException
  {
    ByteArrayOutputStream bout = new ByteArrayOutputStream();
    Writer wout = new OutputStreamWriter(bout);

    wout.write( XML_DECLARATION );

    wout.write( SOAP_ENV_START );
    wout.write( SOAP_BODY_START );

    wout.write("<GetURL xmlns=\"" + SOAPServiceURL + "\">");
    wout.write("<pintIdentifier>" + collegeID + "</pintIdentifier>");
    wout.write("<pstrModuleCode>" + moduleCode + "</pstrModuleCode>");
    wout.write("<pbooAdmin>" + admin + "</pbooAdmin>");
    wout.write("<pintVLE>" + VLEType + "</pintVLE>");
    wout.write("<pstrSecurity>" + securityCode + "</pstrSecurity>");
    wout.write("</GetURL>");

    wout.write( SOAP_BODY_END );
    wout.write( SOAP_ENV_END );

    wout.flush();
    wout.close();

    // Create the connection:
    URL url = new URL( SOAPInterfaceURL );
    URLConnection connection = url.openConnection();
    HttpURLConnection httpConn = (HttpURLConnection) connection;

    sendSOAPMessage( "GetURL", httpConn, bout );
    String strRes = getSOAPResponse( "GetURLResult", httpConn );

    return strRes;
  }


  private void sendSOAPMessage( String methodName, HttpURLConnection httpConn, ByteArrayOutputStream bout )
      throws IOException
  {
    String SOAPAction = SOAPServiceURL + methodName;

    byte[] b = bout.toByteArray();

    // Set the appropriate HTTP parameters:
    httpConn.setRequestProperty( "Content-Length",
				 String.valueOf( b.length ) );
    httpConn.setRequestProperty("Content-Type","text/xml; charset=utf-8");
    httpConn.setRequestProperty("SOAPAction",SOAPAction);
    httpConn.setRequestMethod( "POST" );
    httpConn.setDoOutput(true);
    httpConn.setDoInput(true);

    OutputStream out = null;
    try
    {
      out = httpConn.getOutputStream();
    }
    catch (IOException ex)
    {
      throw new IOException( "Connection to Sentient service failed.");
    }
    out.write( b );
    out.close();
  }

  private String getSOAPResponse( String responseName, HttpURLConnection httpConn )
      throws IOException
  {

    InputStreamReader isr = new InputStreamReader(httpConn.getInputStream());
    BufferedReader in = new BufferedReader(isr);

    String inputLine;

    ByteArrayOutputStream aout = new ByteArrayOutputStream();
    while ((inputLine = in.readLine()) != null){
      aout.write(inputLine.getBytes(),0,inputLine.length());
    }
    in.close();

    String fullResponse = aout.toString();
    int iStart = fullResponse.indexOf( responseName );
    int iEnd = fullResponse.indexOf( "/" + responseName );
    iStart = iStart + responseName.length() + 1;
    iEnd = iEnd -1;
    String strRes = fullResponse.substring(iStart,iEnd);

    return( strRes );
  }

  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, target = null, content;
                            String link, link_target;
                            boolean italic=false;  // should really use a redefinable style

                            boolean manage = false;
                            boolean deny = false;

                            java.util.Date openDate, closeDate;
                            String available_date;

                            link = ((SentientLink)resource).getUrl();
                            if ( ( (SentientLink) resource).isNewWindow())
                              link_target = "_blank";
                            else
                              link_target = "menu";

                            manage = resource.checkPermission( Permission.MANAGE );
                                href = ( !state.rootless && depth==1  )?null:
                                    ( breq.getContextPath() +breq.getServletPath() + resource.getFullName() );
                                target = "_top";
                                // 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 ( link != null )
                                {
            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=\"Icon for " + 
                                    (TextUtils.beginsWithVowel(
                                        resource.getResourceTypeName()) ? "an" : "a") 
                                    + " " + resource.getResourceTypeName() + ".\" 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)
          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)
        {
            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
        {
                                if ( href != null )
                                    out.print( href );
                                  else
                                    out.print( "{untitled item}" );
        }

                            if ( italic )
                                out.print( "</i>" );

        if (link != 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( target );
                                    out.print( "\"" );
                                    }
                                out.print( " href=\"" );
                                out.print( href );
                                out.print( "\" alt=\"Manage SentientLink.\">  &gt;</a>" );
            // closing tag for manage link
                                }

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

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

                            // files have no further content in the node.
                            // but resources have descriptions or introductions

                            // amendment - if big icons files have an empty content section
                            // to take up space next to icon and prevent next (floating)
                            // icon coming up next to current one.

                                    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("<br>" + markup);

                                    out.print( "</div>" );
//        out.print("<div class=\"clearer\">&nbsp;</div>\n");
                                }

}
