/* ======================================================================
   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.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Writer;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;

import javax.servlet.ServletException;

import org.apache.log4j.Logger;
import org.bodington.server.BuildingContext;
import org.bodington.server.BuildingServerException;
import org.bodington.server.BuildingSession;
import org.bodington.server.BuildingSessionManagerImpl;
import org.bodington.server.realm.Permission;
import org.bodington.server.resources.Resource;
import org.bodington.server.resources.SentientLink;
import org.bodington.server.resources.SentientLinkSession;
import org.bodington.servlet.Request;
import org.bodington.servlet.template.Template;

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 List postCreate( Request breq, Resource newResource )
      throws Exception
      {
      List errors = super.postCreate(breq, newResource);
      SentientLink link;
      String title, moduleCode, message;
      String url, editUrl;

      link = (SentientLink)newResource;

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

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

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

          // 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 errors;
      }
      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=\""+ 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)
          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");
                                }
  
  /**
   * Sentient Links can't be created any more.
   */
  public boolean isCreate()
  {
      return false;
  }

}
