/* ======================================================================
   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.*;
import java.util.*;
import javax.servlet.*;

import org.apache.log4j.Logger;

import org.bodington.servlet.*;
import org.bodington.server.realm.Permission;
import org.bodington.server.resources.Resource;
import org.bodington.server.resources.UploadedFileSummary;
import org.bodington.server.*;
import org.bodington.util.BodingtonURL;

/**
 * Facility class for handling the structured document resource. The structured
 * document tool has the notion of <em>visible</em> and <em>invisible</em>
 * sections. When in <em>VIEW</em> mode only the visible sections can be seen.
 * When in <em>EDIT</em> the invisible sections can be seen too. This allows
 * for an author to work on a document and then reveal new sections when he or
 * she feels comfortable to do so.
 * @author Jon Maber
 * @author Alexis O'Connor
 */
public class DocumentFacility extends MediaDocumentFacility
	{
	/**
     * Helper class to bind a document section to it's index. The enclosing
     * facility class makes use of the uploaded file functionality by splitting
     * each document section into it's own uploaded file. Document sections can
     * be identified (and ordered) by the use of the {@link #getIndex()}
     * property. The underlying uploaded file is encapsulated via the 
     * {@link #getSummary()} property. 
     * @author Alexis O'Connor
     */
	private static class FileSectionData implements Comparable
    {
        private UploadedFileSummary summary;
	    private int[] index;
        private FileSectionData( UploadedFileSummary summary, int[] index )
        {
            this.summary = summary;
            this.index = index;
        }
        /**
         * Get the index of this document section. Section file names consist of
         * four parts, e.g. <code>1, 2, 5, 0</code> which facilitate ordering.
         * @return the index as a 4 element <code>int[]</code>.
         */
        int[] getIndex()
        {
            return index;
        }
        /**
         * Get the object which encapsulates the uploaded file.
         * @return the object which encapsulates the uploaded file.
         */
        UploadedFileSummary getSummary()
        {
            return summary;
        }
        
        public int compareTo( Object o )
            {
            int[] otherIndex = ((FileSectionData)o).getIndex();

            for ( int i = 0; i < index.length; i++ )
                {
                if ( index[i] == otherIndex[i] )
                    continue;

                return (index[i] > otherIndex[i]) ? 1 : -1;
                }

            return 0;
            }
    }
    private static Logger log = Logger.getLogger(DocumentFacility.class);
    private static final boolean defaultUTF8 = ("UTF-8").equals(System
        .getProperty("file.encoding"));

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

	public void insert( Request req, PrintWriter out, String command, String insertname )
		throws ServletException, IOException
		{
		log.debug( Thread.currentThread().getName() + " DocumentFacility insert()" );


		if ( out==null )
			{
			super.insert( req, out, command, insertname );
			return;
			}
		
		try
			{
			BuildingSession session;
			
			if ( command.equals( "menu" ) || command.equals( "editmenu" ) )
				{
				log.debug( Thread.currentThread().getName() + " DocumentFacility insert() menu" );
				session = req.getBuildingSession();
				menu( req, out, session, command.equals( "editmenu" )  );
				return;
				}

			if ( command.equals( "editpage" ) || command.equals( "saveinput" ) || command.equals( "newpage" ) 
                || command.equals( "sections" ) )
				{
	   		session = BuildingSessionManagerImpl.getSession( req.getResource() );
				if ( command.equals( "editpage" ) )
					{
					editpage( req, out, session  );
					return;
					}

				if ( command.equals( "saveinput" ) )
					{
					if ( out!=null )
						saveinput( req, out, session );
					return;
					}
					
                if ( command.equals( "newpage" ) )
                    {
                    if ( out!=null )
                        newpage( req, out, session );
                    return;
                    }
                
                if ( command.equals( "sections" ) )
                    {
                    if ( out!=null )
                        outputSections( req, out, session );
                    return;
                    }
				}

			}
		catch ( BuildingServerException bsex )
			{
			logException( out, "DocumentFacility", "insert", 
			    "A technical problem occurred.",
			    bsex );
			return;
			}

		log.debug( Thread.currentThread().getName() + " end of DocumentFacility insert()" );
			
		super.insert( req, out, command, insertname );
		}

	int[] parseFileName( UploadedFileSummary uf )
		{
		StringTokenizer tokenizer = new StringTokenizer( uf.getName(), "_." );
		int j;
		String bit;
		int[] index = new int[4];
		
		if ( tokenizer.countTokens()!=5 )
			return null;
			
		try
			{
			for ( j=0; j<4; j++ )
				{
				bit = tokenizer.nextToken();
				index[j] = Integer.parseInt( bit );
				}
			}
		catch ( Exception ex )
			{
			return null;
			}
				
		bit = tokenizer.nextToken();
		if ( bit==null )
			return null;
				
		if ( !bit.equalsIgnoreCase( "html" ) && !bit.equalsIgnoreCase( "htm" ) )
			return null;
			
		return index;
		}


	/**
     * Insert method responsible for rendering the links to the individual
     * document sections. This method will ensure that the links are in
     * numerical order.
     * @param req the request object.
     * @param out the object to write to.
     * @param session the current session.
     * @param edit if <code>true</code> the current mode is <em>EDIT</em>,
     *        otherwise it is <em>VIEW</em>.
     * @throws IOException if there is a problem with I/O.
     * @throws BuildingServerException if there is a general problem.
     */
    private void menu( Request req, PrintWriter out, BuildingSession session,
        boolean edit ) throws IOException, BuildingServerException
        {
        log.debug( "DocumentFacility.menu()" );

        UploadedFileSummary root = session.getUploadedFileSession()
            .getFileSummary( (String)null );
        if ( root == null )
            return;
        UploadedFileSummary[] files = session.getUploadedFileSession()
            .getFileAndDescendentSummaries( null, true );

        FileSectionData[] allSections = extractFileSectionData( root, files );
        Collections.sort( Arrays.asList( allSections ) );

        FileSectionData[] visible = extractSections( session, allSections,
            true );
        boolean isContent = false;
        
        if ( (edit && allSections.length > 0) || (!edit && visible.length > 0) )
            {
            out.println( "<H4>Contents</H4>" );
            if (!edit)
                {
                out.println("<a href=\"bs_template_collated.html\" target=viewer "
                    + "title=\"View this document as a single page\">View</a>"
                    + "&nbsp;this document as a single page.");
                }
            isContent = true;
            }

        // Only display any invisible sections when editing:
        if ( edit )
            {
            FileSectionData[] invisible = extractSections( session,
                allSections, false );
            if ( invisible.length > 0 )
                {
                out.println( "<H4>\"Invisible\" sections</H4>" );
                outputMenuTable( req, session, out, invisible, edit );
                }
            }

        // *Always* display any visible sections:
        if ( visible.length > 0 )
            {
            out.println( "<H4>\"Visible\" sections</H4>" );
            outputMenuTable( req, session, out, visible, edit );
            }
        
        if ( !edit && isContent )
            {
            out.println("<a href=\"bs_template_collated.html\" target=viewer "
                + "title=\"View this document as a single page\">View</a>"
                + "&nbsp;this document as a single page.");
            }
        }

    /**
     * Method to render the menu with links to each document section. This
     * method will enclose the links within an HTML table.
     * @param req the current request.
     * @param session the current session.
     * @param out the object to write to.
     * @param fsd an array of {@link FileSectionData} objects.
     * @param edit if <code>true</code> the current mode is <em>EDIT</em>,
     *        otherwise it is <em>VIEW</em>.
     * @throws BuildingServerException if there is a general problem.
     * @throws IOException if there is a problem with I/O.
     */
    private void outputMenuTable( Request req, BuildingSession session,
        PrintWriter out, FileSectionData[] fsd, boolean edit )
        throws BuildingServerException, IOException
        {
        out.println( "<TABLE>" );

        for ( int i = 0; i < fsd.length; i++ )
            {
            /*
             * Calculate & extract key values:
             */
            int[] index = fsd[i].getIndex();
            int level = depthOfIndex( index );

            String link;
            UploadedFileSummary summary = fsd[i].getSummary();
            if ( edit )
                {
                link = "<A TARGET=viewer HREF=\"" + req.absoluteURL()
                    + summary.getName() + "/bs_template_editdoc.html\">";
                }
            else
                {
                link = "<A TARGET=viewer HREF=\"" + req.absoluteURL()
                    + summary.getName() + "\">";
                }

            File page = session.getUploadedFileSession().getFile(
                summary.getUploadedFileId() );
            String title = extractHtmlTitle( page );

            /*
             * Render the key values:
             */
            out.print( "<TR><TD>" );
            out.println( "<H" + (3 + level) + ">" );
            out.print( index[0] );
            if ( level > 1 )
                out.print( "." + index[1] );
            if ( level > 2 )
                out.print( "." + index[2] );
            if ( level > 3 )
                out.print( "." + index[3] );
            out.println( "<H" + (3 + level) + ">" );
            out.print( "</TD><TD>" );
            out.println( "</H" + (3 + level) + ">" );
            out.print( link );
            out.print( title != null ? title : "Untitled Section" );
            out.print( "</A> " );
            out.println( "</H" + (3 + level) + ">" );
            out.print( "</TD></TR>" );
            }

        out.println( "</TABLE>" );
        }
    
    /**
     * Extract the title from the specified file. This method assumes that the
     * file represents an HTML file. The value returned will be any string
     * contained within the HTML <code>title</code> element.
     * @param page the file from which to extract the title.
     * @return a title from within the <code>file</code> parameter, or
     *         <code>null</code> if one could not be found.
     * @throws IOException if there is any problem reading the file.
     */
    private String extractHtmlTitle( File page ) throws IOException
        {
        String title = null;
        BufferedReader reader = new BufferedReader( new FileReader( page ) );
        boolean open_utf8 = defaultUTF8;
        String line = null;
        while ( (line = reader.readLine()) != null )
            {
            // if we find the utf8 char encoding declaration close the file
            // and reopen with utf8 encoding
            if ( !open_utf8 
                && line.equals( "<META http-equiv=\"Content-Type\" "
                    + "content=\"text/html; charset=utf-8\">" ) )
                {
                open_utf8 = true;
                reader.close();
                reader = new BufferedReader( new InputStreamReader(
                    new FileInputStream( page ), "UTF8" ) );
                }
            // read title if present
            if ( line.startsWith( "<TITLE>" ) && line.endsWith( "</TITLE>" ) )
                {
                title = line.substring( 7, line.length() - 8 );
                break;
                }
            }
        reader.close();
        title = title.trim();
        return title.length() > 0 ? title : null;
        }

    /**
     * Extract file section data pertaining to the document sections. Uploaded
     * files which are document sections, are files and direct children of the
     * resource. Therefore, this method is required to extract the names of
     * uploaded files that pertain to section file names. The value returned is
     * an array of file section data objects, which map the underlying uploaded
     * file to the document section index.
     * @param root the object representing the root of uploaded files for the
     *        current resource.
     * @param files an array of objects representing uploaded files that are
     *        immediate children for the current resource.
     * @return an array of file section data pertaining to the document sections.
     */
    private FileSectionData[] extractFileSectionData( UploadedFileSummary root,
        UploadedFileSummary[] files )
        {
        List list  = new Vector();
        for ( int i = 0; i < files.length; i++ )
            {
            if ( files[i].isFolder() )
                continue;
            // ignore file not in root folder
            if ( !files[i].getParentUploadedFileId().equals(
                root.getUploadedFileId() ) )
                continue;

            // ignore files with the wrong type of name
            int[] index = parseFileName( files[i] );
            if ( index == null )
                continue;
            
            list.add( new FileSectionData( files[i], index) );

            log.debug( "next record" );
            }

        return (FileSectionData[])list.toArray( new FileSectionData[list.size()] );
        }


	private void editpage( Request req, PrintWriter out, BuildingSession session )
		throws IOException, BuildingServerException
		{
		int i;
		int[] index;
		UploadedFileSummary ufpage;
		File page;
		String title, output, line;
		BufferedReader reader;
        EscapedHtmlWriter t_out;
		
	    t_out = new EscapedHtmlWriter( out );
		        
		if ( req.getTemplateParameterCount()<1 )
			return;

		ufpage = session.getUploadedFileSession().getFileSummary( (String)req.getTemplateParameter( 0 ) );
		if ( ufpage == null )
			{
			out.println( "The requested page was not found." );
			return;
			}

		index = parseFileName( ufpage );
		if ( index == null )
			{
			out.println( "The requested page had an invalid file name." );
			return;
			}

		page =  session.getUploadedFileSession().getFile( ufpage.getUploadedFileId() );
		if ( !page.exists() )
			{
			out.println( "The file corresponding to the page was not found." );
			return;
			}
		
		out.println( "Section Number<BR>" );
		out.println( "<INPUT SIZE=3 MAXLENGTH=3 NAME=ordinal_1 VALUE=\"" + index[0] + "\"> . " );
		out.println( "<INPUT SIZE=3 MAXLENGTH=3 NAME=ordinal_2 VALUE=\"" + index[1] + "\"> . " );
		out.println( "<INPUT SIZE=3 MAXLENGTH=3 NAME=ordinal_3 VALUE=\"" + index[2] + "\"> . " );
		out.println( "<INPUT SIZE=3 MAXLENGTH=3 NAME=ordinal_4 VALUE=\"" + index[3] + "\">" );
		if (index[0] == 0)
			out.println( "<SPAN>To make this section <B>visible</B>, increase the leading index.</SPAN>" );
		out.println( "<BR>" );

		// find title
		title = "Untitled Section";
		//open with default char encoding first
		reader = new BufferedReader( new InputStreamReader( new FileInputStream( page ) ) );
		boolean open_utf8=defaultUTF8;
		while ( (line = reader.readLine()) != null )
			{
			//if we find the utf8 char encoding declaration close the file
			//and reopen with utf8 encoding
			if ( !open_utf8 && line.equals("<META http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">" ) )
				{
				open_utf8 = true;
				reader.close();
				reader = new BufferedReader( new InputStreamReader( new FileInputStream( page ), "UTF8" ) );
				}
			//read title if present
			if ( line.startsWith("<TITLE>") && line.endsWith("</TITLE>") )
				{
				title = line.substring( 7, line.length()-8 );
				break;
				}
			}
		out.println( "Section Title<BR>" );
		out.print( "<INPUT NAME=title     VALUE=\"" );
		//actual value ouput with some encoding
		t_out.print( title.trim() );
		out.println( "\"><BR>" );

		//ready for content...
		out.print( "<SPAN CLASS=bs-textarea><TEXTAREA COLS=45 ROWS=15 NAME=html WRAP=SOFT>" );
		//skip through to body
		while ( (line = reader.readLine()) != null )
			{
			if ( line.startsWith("<BODY") )
				break;
			}
			
		//skip past heading
		while ( (line = reader.readLine()) != null )
			{
			if ( line.startsWith("<H3>") )
				break;
			}
		
		out.flush();
		
		//output through to end of body
		while ( (line = reader.readLine()) != null )
			{
			if ( line.startsWith( "</BODY>" ) )
				break;

		    // editable text has to be output with some
		    // encoding
			t_out.print( line );
            t_out.print( "\n" );
			}

		out.println( "</TEXTAREA></SPAN>" );
		
		reader.close();
		}

	private void saveinput( Request req, PrintWriter out, BuildingSession session )
		throws IOException, BuildingServerException
		{
		int[] index, newindex;
		UploadedFileSummary ufpage, otherufpage;
		File page;
		String title, output, line, newname;
		Writer writer;
		
		String fields[], values[];
		
		log.debug( Thread.currentThread().getName() + " saveinput" );
		
		if ( !BuildingContext.getContext().checkPermission( Permission.EDIT ) || !BuildingContext.getContext().checkPermission( Permission.UPLOAD ) )
			{
			out.println( "<PRE>You don't have access rights to save records in this location of the building. (requires edit and upload access rights.)</PRE>\n" );
			return;
			}

		if ( req.getTemplateParameterCount()<1 )
			return;

		ufpage = session.getUploadedFileSession().getFileSummary( req.getTemplateParameter( 0 ) );
		if ( ufpage == null )
			{
			out.println( "The requested page was not found." );
			return;
			}

		index = parseFileName( ufpage );
		if ( index == null )
			{
			out.println( "The requested page had an invalid file name." );
			return;
			}

		
		newindex=new int[4];
		fields = new String[6];
		fields[0]="ordinal_1";
		fields[1]="ordinal_2";
		fields[2]="ordinal_3";
		fields[3]="ordinal_4";
		fields[4]="title";
		fields[5]="html";
		values = new String[6];
		try
			{
			for ( int i=0; i<6; i++ )
				{
				values[i]=req.getParameter( fields[i] );
				if ( i<4 )
					newindex[i] = Integer.parseInt( values[i] );
				}
			}
		catch ( NumberFormatException nfex )
			{
			out.println( "Changes were not saved because you didn't enter a valid number in one or more of the index boxes. " );
			out.println( "Please backtrack and correct this." );
			return;
			}

		if ( fields[4] == null || fields[4].trim().length()==0 )		
			{
			out.println( "Changes were not saved because you didn't enter a section title. " );
			out.println( "Please backtrack and correct this." );
			return;
			}

		page =  session.getUploadedFileSession().getFile( ufpage.getUploadedFileId() );
		page = File.createTempFile( "strcdoc", "html" );

		log.debug( Thread.currentThread().getName() + " saveinput A" );

		writer = new OutputStreamWriter( new FileOutputStream( page ), "UTF8" );
		writer.write("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">");
		writer.write( "\r\n<HTML>\r\n<HEAD>" );
		writer.write( "\r\n<LINK HREF=\"bs_virtual_auto.css\" TYPE=\"text/css\"  REL=\"STYLESHEET\">" );
		writer.write( "\r\n<TITLE>" );
		writer.write( values[4] );
		writer.write( "</TITLE>\r\n</HEAD>\r\n<BODY>\r\n<H3>" );
		writer.write( values[4] );
		writer.write( "</H3>\r\n" );
		writer.write( values[5] );
		writer.write( "\r\n</BODY>\r\n</HTML>\r\n" );
		writer.close();

         session.getUploadedFileSession().transferFile( page.getAbsolutePath(), "/" + ufpage.getName(), "text/html" );
                
         newname = "" + newindex[0] + "_"+ newindex[1] + "_"+ newindex[2] + "_"+ newindex[3] + ".html";
         if ( !newname.equals( ufpage.getName() ) )
             {
             // the user wants to rename the file.
             otherufpage = session.getUploadedFileSession().getFileSummary( newname );
             if ( otherufpage != null )
                 {
                 out.println( "The index numbers you entered clash with another page - the old index numbers have been retained." );
                 newname = ufpage.getName();
                 }
             else
                 {
                 session.getUploadedFileSession().renameFile( ufpage.getName(), newname );
                 }
             }
		out.println( "<B>The page has been saved.</B>" );
		out.println( "<SCRIPT LANGUAGE=JavaScript>" );
		out.println( "top.buildmain.contents.location.reload()" );
		out.println( "</SCRIPT>" );
		}
	
	private void newpage( Request req, PrintWriter out, BuildingSession session )
		throws IOException, BuildingServerException
		{
		String name;
		int index[][];
		int i, x;
		
		log.debug( "DocumentFacility.menu()" );

		x=0;
		
		UploadedFileSummary root = session.getUploadedFileSession().getFileSummary( (String)null );
		if ( root!=null )
			{
			UploadedFileSummary[] files = session.getUploadedFileSession().getFileAndDescendentSummaries( null, true );
			
			//decide on a file name - 0.x.0.0
			index = new int[files.length][];
			for ( i=0; i<files.length; i++ )
				{
				if ( files[i].isFolder() )
					continue;
				//ignore file not in root folder
				if ( !files[i].getParentUploadedFileId().equals( root.getUploadedFileId() ) )
					continue;
				
				//ignore files with the wrong type of name
				index[i] = parseFileName( files[i] );
				if ( index[i] == null )
					continue;
					
				//ignore visible pages
				if ( index[i][0]!=0 )
					continue;
					
				if ( index[i][1]>=x )
					x = index[i][1]+1;
				}
			}
			
		name = "0_" + x + "_0_0.html";
	
		File file = BuildingContext.createTempFile( "docpage", ".html" );
		if ( file == null )
		    throw new BuildingServerException( "Unable to create a temporary file." );

		FileWriter writer = new FileWriter( file );
		writer.write("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">");
		writer.write( "\r\n<HTML>\r\n<HEAD>\r\n<LINK HREF=\"bs_virtual_auto.css\" TYPE=\"text/css\"  REL=\"STYLESHEET\">\r\n<TITLE>" );
		writer.write( "New Section" );
		writer.write( "</TITLE>\r\n</HEAD>\r\n<BODY>\r\n<H3>" );
		writer.write( "New Section" );
		writer.write( "</H3>\r\n" );
		writer.write( "<P></P>\r\n" );
		writer.write( "\r\n</BODY>\r\n</HTML>\r\n" );
		writer.close();

		session.getUploadedFileSession().transferFile( file.getAbsolutePath(), "/"+name, "text/html" );

		out.println( "<B>The new page has been created.</B>" );
		out.print( "<P>The new page has been given the title 'New Section' and index number 0." );
		out.print( x );
		out.println( ".0.0 in the contents page.  Click on that heading to enter " );
		out.println( "text and change its title and/or index number.</P>" );
		out.println( "<P><B>It will remain invisible to readers of your document " );
		out.println( "until you change the index number to 1.0.0.0 or higher.</B></P>" );
		out.println( "<SCRIPT LANGUAGE=JavaScript>" );
		out.println( "top.buildmain.contents.location.reload()" );
		out.println( "</SCRIPT>" );
		}

    /**
     * Extract sections from the specified arguments. This method will ensure
     * that the array returned only refers to valid files that actually exist.
     * The <code>visible</code> parameter specifies whether it is the visible
     * or invisible sections to be returned.
     * @param session the current session object.
     * @param fsd a sorted array of file section data objects.
     * @param visible indicates whether it is the visible or invisible sections
     *        to be returned.
     * @return a valid list of sorted uploaded file objects.
     * @throws BuildingServerException if there is some problem when dealing
     *         with the session.
     */
    private FileSectionData[] extractSections( BuildingSession session,
        FileSectionData[] fsd, boolean visible )
        throws BuildingServerException
        {
        List list = new Vector();
        for ( int i = 0; i < fsd.length; i++ )
            {
            int[] index = fsd[i].getIndex();
            if ( index == null )
                continue;

            if ( visible && index[0] < 1 )
                continue;

            if ( !visible && index[0] > 0 )
                break;

            File page = session.getUploadedFileSession().getFile(
                fsd[i].getSummary().getUploadedFileId() );
            if ( page == null || !page.exists() )
                continue;

            list.add( fsd[i] );
            }

        return (FileSectionData[])list.toArray( new FileSectionData[list.size()] );
        }
    
    /**
     * Insert method to output all the document sections in order.
     * @param req the current request.
     * @param out the object to write to.
     * @param session the current session.
     * @throws IOException if there is some problem with I/O.
     * @throws BuildingServerException if there is a general problem.
     */
    private void outputSections( Request req, PrintWriter out, BuildingSession session
        ) throws IOException, BuildingServerException
        {
        UploadedFileSummary root 
            = session.getUploadedFileSession().getFileSummary( (String)null );
        if ( root == null )
            return;
        UploadedFileSummary[] files 
            = session.getUploadedFileSession().getFileAndDescendentSummaries( 
                null, true );

        FileSectionData[] allSections = extractFileSectionData( root, files );
        Collections.sort( Arrays.asList( allSections ) );

        // We are only interested in the visible sections:
        FileSectionData[] sections = extractSections( session, allSections, true );
        if ( sections.length > 0 )
            for (int i = 0; i < sections.length; i++)
                outputDocumentSection( session, sections[i], out );
        else
            out.println( "There are currently no sections." );
        }
    
    /**
     * Output an individual document section.
     * @param session the current session.
     * @param fsd an object that encapsulates a document section.
     * @param out the object to write to.
     * @throws IOException if there is some problem with I/O.
     * @throws BuildingServerException if there is a general problem.
     */
    private void outputDocumentSection( BuildingSession session,
        FileSectionData fsd, PrintWriter out ) throws IOException,
        BuildingServerException
        {
        File file = session.getUploadedFileSession().getFile(
            fsd.getSummary().getUploadedFileId() );

        BufferedReader reader = new BufferedReader( new FileReader( file ) );
        boolean open_utf8 = defaultUTF8;
        String line = null;

        // Open the file and start scanning...
        while ( (line = reader.readLine()) != null )
            {
            // Re-open with UTF8 encoding if this is found to be used:
            if ( !open_utf8
                && line.equals( "<META http-equiv=\"Content-Type\" "
                    + "content=\"text/html; charset=utf-8\">" ) )
                {
                open_utf8 = true;
                reader.close();
                reader = new BufferedReader( new InputStreamReader(
                    new FileInputStream( file ), "UTF8" ) );
                continue;
                }

            // Break if we find an opening body tag:
            if ( line.startsWith( "<BODY" ) )
                break;
            }
        
        boolean isTitleFound = false;

        // Output everything we find until we reach a closing body tag:
        while ( (line = reader.readLine()) != null )
            {
            if ( line.startsWith( "</BODY>" ) )
                break;
            
            // Prefix the section header with index number:
            if ( !isTitleFound && line.startsWith( "<H3" ) )
                {
                line = line.substring( 4, line.length() - 5 );
                line = line.trim();
                line = line.length() > 0 ? line : "Untitled Section";
                String iString = indexToString( fsd.getIndex() );
                // Depth is always >= 1:
                int hValue = depthOfIndex( fsd.getIndex() ) + 2;
                out.println( "<H" + hValue + " id=" + iString + ">"
                    + iString + " " + line + "</H" + hValue + ">" );
                isTitleFound = true;
                continue;
                }
            
            out.println( line );
            }

        // Clean up:
        reader.close();
        }
    
    /**
     * Convert an index argument into a string. This will convert the index
     * argument into an easy to read form. More specifically, the elements
     * of the index will be seperated by the <code>.</code> character and
     * trailing zeroes following the first element will be removed.
     * @param index the argument to be converted.
     * @return a string representation of the argument.
     * @see FileSectionData#getIndex()
     */
    private String indexToString( int[] index )
        {
        int level = depthOfIndex( index );
        StringBuffer strBuf = new StringBuffer();
        strBuf.append( index[0] );
        if ( level > 1 )
            strBuf.append( "." ).append( index[1] );
        if ( level > 2 )
            strBuf.append( "." ).append( index[2] );
        if ( level > 3 )
            strBuf.append( "." ).append( index[3] );

        return strBuf.toString();
        }
        
    /**
     * Calculate the depth of the specified index. The depth of an index is
     * always at least <code>1</code>. The depth of an index refers to how
     * deep you can go within the index starting from the first element and find
     * an element which is greater than zero.
     * @param index the value whose depth is to be calculated.
     * @return the depth of the index.
     * @see FileSectionData#getIndex()
     * @see #indexToString(int[])
     */
    private int depthOfIndex( int[] index )
        {
        for ( int i = index.length - 1; i >= 0; i-- )
            if ( index[i] > 0 )
                return i + 1;
        return 1;
        }
	}

