/* ======================================================================
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.template;


import java.io.*;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Vector;
import java.util.Hashtable;
import java.util.Enumeration;

import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.bodington.server.BuildingContext;
import org.bodington.database.PrimaryKey;


import com.sun.tools.javac.Main;



/**
 * The legacy template compiler. This class takes a file containing the 
 * HTML template and converts it into java code to be compiled. 
 */
public class LegacyTemplate
implements CharStream
{
	    /*
	public File       file;
	public long       file_date;
	public long 	  last_checked;
	//public String     html;
	public String    facility;
	public Integer    style;
	public String     name;
	private String	  url;
	public PrimaryKey resource_id;
	     
	public boolean redirected=false;
	     */
    
    
    //Vector actions;
    
    
    Reader reader=null;
    StringBuffer token_buffer;
    int offset, token_offset;
    boolean end_token;
    
    
    
    
    
    public LegacyTemplate() //File f , String url, String cname, String fac, Integer s, PrimaryKey res, String n )
    {
    }
    
    private int javaCompileStatus = XmlTemplate.JAVA_COMPILE_SUCCESS;
    
    /**
     * Get the value of the java compile status. This is the value returned by
     * the java compiler (javac) when it last successfully compiled the
     * template. A value equal to {@link XmlTemplate#JAVA_COMPILE_SUCCESS}
     * indicates that the template compiled successfully.
     * @return the value of the java compile status.
     * @see XmlTemplate#JAVA_COMPILE_SUCCESS
     */
    public int getJavaCompileStatus()
    {
        return javaCompileStatus;
    }
    
    /**
     * Compile the legacy template.
     * @param source the template to be compiled.
     * @param directory the directory where the java source code and the binary
     *        class file should be output.
     * @param class_name the name of the template binary class file.
     * @param class_path the CLASSPATH to be used by the java compiler.
     * @return the return value of the java compiler. 
     * @throws IOException if there is some kind of problem with java
     *         compilation.
     * @throws Exception if there is some other problem (most likely configuring
     *         the java compiler).
     */
    int compile( File source, File directory, String class_name, String class_path )
    throws IOException, Exception
    {
    // NOTE: this method needs to be accessible from XmlTemplate. That is why
    // package / default access is correct.    
	int c;
	TemplateParser parser;
	Token token;
	Hashtable attributes=null;
	String name=null;
	TemplateSegment segment;
	Vector actions;
	StringBuffer output=new StringBuffer();
	offset=0;
	token_offset=0;
	end_token=false;
	
	token_buffer = new StringBuffer();
	
	try
	{
	    if ( !source.exists() )
	    {
		return javaCompileStatus;
	    }
	    
	    reader = new FileReader( source );
	    
	    actions = new Vector();
	    parser = new TemplateParser( this );
	    
	    do
	    {
		token = parser.getNextToken();
		
		switch ( token.kind )
		{
		    case TemplateParserConstants.STAGO:
			// may need to output the start tag
			// but might be part of <BUILDING> tag
			output.append( token.toString() );
			break;
			
		    case TemplateParserConstants.BUILDING:
			attributes = new Hashtable();
			name = null;
			//is there more than just the '<' in the output buffer?
			if ( output.length()>1 )
			{
			    // if so, need to put output action in list
			    // minus the '<' that is part of the <BUILDING> tag
			    output.setLength( output.length()-1 );
			    segment = new TemplateSegment();
			    segment.action = segment.ACTION_OUTPUT;
			    segment.attributes = new Hashtable();
			    segment.attributes.put( "text", output.toString() );
			    actions.addElement( segment );
			}
			// now put insert action into list
			segment = new TemplateSegment();
			segment.action = segment.ACTION_INSERT;
			segment.attributes = attributes;
			actions.addElement( segment );
			output.setLength( 0 );
			break;
			
		    case TemplateParserConstants.BIMG:
			attributes = new Hashtable();
			name = null;
			//is there more than just the '<' in the output buffer?
			if ( output.length()>1 )
			{
			    // if so, need to put output action in list
			    // minus the '<' that is part of the <BUILDING> tag
			    output.setLength( output.length()-1 );
			    segment = new TemplateSegment();
			    segment.action = segment.ACTION_OUTPUT;
			    segment.attributes = new Hashtable();
			    segment.attributes.put( "text", output.toString() );
			    actions.addElement( segment );
			}
			// now put insert image action into list
			segment = new TemplateSegment();
			segment.action = segment.ACTION_INSERT_IMG;
			segment.attributes = attributes;
			actions.addElement( segment );
			output.setLength( 0 );
			break;
			
		    case TemplateParserConstants.BODY:
			attributes = new Hashtable();
			name = null;
			//is there more than just the '<' in the output buffer?
			if ( output.length()>1 )
			{
			    // if so, need to put output action in list
			    // minus the '<' that is part of the <BUILDING> tag
			    output.setLength( output.length()-1 );
			    segment = new TemplateSegment();
			    segment.action = segment.ACTION_OUTPUT;
			    segment.attributes = new Hashtable();
			    segment.attributes.put( "text", output.toString() );
			    actions.addElement( segment );
			}
			// now put insert image action into list
			segment = new TemplateSegment();
			segment.action = segment.ACTION_INSERT_BODY;
			segment.attributes = attributes;
			actions.addElement( segment );
			output.setLength( 0 );
			break;
			
		    case TemplateParserConstants.A_NAME:
		    case TemplateParserConstants.AI_NAME:
		    case TemplateParserConstants.ABD_NAME:
			// remember name of attribute
			name = token.toString().toLowerCase();
			break;
			
		    case TemplateParserConstants.A_EQ:
		    case TemplateParserConstants.AI_EQ:
		    case TemplateParserConstants.ABD_EQ:
			break;
			
		    case TemplateParserConstants.CDATA:
		    case TemplateParserConstants.CIDATA:
		    case TemplateParserConstants.CBDDATA:
			// store value of attribute in list
			if ( name!=null && attributes!=null )
			{
			    String attvalue = token.toString();
			    if ( attvalue.startsWith( "\"" ) )
				attvalue = attvalue.substring( 1 );
			    if ( attvalue.endsWith( "\"" ) )
				attvalue = attvalue.substring( 0, attvalue.length()-1 );
			    attributes.put( name, attvalue );
			}
			break;
			
		    case TemplateParserConstants.BTAGC:
		    case TemplateParserConstants.BITAGC:
		    case TemplateParserConstants.BDTAGC:
			break;
			
		    case TemplateParserConstants.EOF:
			// if there is anything left to output put it in list
			if ( output.length()>0 )
			{
			    segment = new TemplateSegment();
			    segment.action = segment.ACTION_OUTPUT;
			    segment.attributes = new Hashtable();
			    segment.attributes.put( "text", output.toString() );
			    actions.addElement( segment );
			}
			break;
			
		    default:
			output.append( token.toString() );
			break;
		}
	    }
	    while ( token.kind != TemplateParserConstants.EOF );
	    
	    
	    reader.close();
	    
	    
	    // now iterate the segments and output a java source file
	    String java_file_name = class_name.replace( '.', File.separatorChar ) + ".java";
	    File java_file = new File( directory, java_file_name );
	    
	    File parent_dir = java_file.getParentFile();
	    if ( !parent_dir.exists() )
		parent_dir.mkdirs();
	    
	    PrintWriter java_writer =
	    new PrintWriter( new OutputStreamWriter(
	    new FileOutputStream( java_file ), "US-ASCII" ) );
	    
	    String unqual_class_name;
	    if ( class_name.indexOf( '.' ) > 0 )
	    {
		unqual_class_name = class_name.substring( class_name.lastIndexOf( '.' )+1 );
		java_writer.print( "package " );
		java_writer.print( class_name.substring( 0, class_name.lastIndexOf( '.' ) ) );
		java_writer.println( ";" );
	    }
	    else
		unqual_class_name = class_name;

	    java_writer.println( "import org.bodington.servlet.*;" );
	    java_writer.println( "import org.bodington.servlet.template.*;" );
	    java_writer.println( "import java.io.*;" );
	    java_writer.println( "import javax.servlet.*;" );
	    java_writer.println( "import javax.servlet.http.*;" );

	    java_writer.println( "public class " + unqual_class_name );
	    java_writer.println( " extends LegacyTemplateProcessor" );
	    java_writer.println( "{" );
	    java_writer.println( "   public " + unqual_class_name + "()" );
	    java_writer.println( "       {" );
	    java_writer.println( "       }" );
	    java_writer.println( "   public void process( Request request, Response response )" );
	    java_writer.println( "       throws IOException, ServletException" );
	    java_writer.println( "       {" );
            java_writer.println( "response.setContentType(\"text/html; charset=utf-8\");" );
	    java_writer.println( "PrintWriter writer=response.getWriter();" );

	    java_writer.println( "java.util.Hashtable legacy_template_attributes;" );
	    
	    String text;
	    for ( int i=0; i< actions.size(); i++ )
	    {
		segment = (TemplateSegment)actions.elementAt( i );
		switch ( segment.getAction() )
		{
		    case TemplateSegment.ACTION_OUTPUT:
			text = (String)segment.getAttribute( "text" );
			if ( text != null )
			{
			    java_writer.println( "if ( !request.isSwitchedOff() )" );
			    java_writer.println( "writer.print( " );
			    compileStringLiteral( java_writer, text );
			    java_writer.println( " );" );
			    java_writer.println( "writer.flush();" );
			}
			break;
			
		    case TemplateSegment.ACTION_INSERT:
			// must still call interactive items even if
			// output is switched off because some interactive
			// items are used to switch output back on!
			compileAttributes( java_writer, segment );
			java_writer.println( "if ( request.isSwitchedOff() )" );
			java_writer.println( "{" );
			java_writer.println( "insertInteractive( request, null, legacy_template_attributes );" );
			java_writer.println( "}" );
			java_writer.println( "else" );
			java_writer.println( "{" );
			java_writer.println( "insertInteractive( request, writer, legacy_template_attributes );" );
			java_writer.println( "}" );
			java_writer.println( "writer.flush();" );
			break;
			
		    case TemplateSegment.ACTION_INSERT_IMG:
			compileAttributes( java_writer, segment );
			java_writer.println( "if ( !request.isSwitchedOff() )" );
			java_writer.println( "{" );
			java_writer.println( "insertModifiedTag( request, writer, legacy_template_attributes, \"IMG\", \"SRC\" );" );
			java_writer.println( "writer.flush();" );
			java_writer.println( "}" );
			break;
			
		    case TemplateSegment.ACTION_INSERT_BODY:
			compileAttributes( java_writer, segment );
			java_writer.println( "if ( !request.isSwitchedOff() )" );
			java_writer.println( "{" );
			java_writer.println( "insertModifiedTag( request, writer, legacy_template_attributes, \"BODY\", \"BACKGROUND\" );" );
			java_writer.println( "writer.flush();" );
			java_writer.println( "}" );
			break;
		}
	    }

	    java_writer.println( "}" );
	    java_writer.println( "}" );
	    
	    java_writer.close();

	    String[] args = null;
	    args = new String[]
	    {
		//"-encoding", "UTF-8",
		"-classpath", class_path,
		"-sourcepath", directory.getAbsolutePath(),
		"-d", directory.getAbsolutePath(),
        "-source", "1.4",
		java_file.getAbsolutePath()
	    };
	
        javaCompileStatus = Main.compile(args);
	    
	}
	
	finally 
	{
		if ( reader!=null )
		    reader.close();
    token_buffer = null;
	}
    return javaCompileStatus;
    }
    
    
    
    private void compileAttributes( PrintWriter writer, TemplateSegment segment )
    throws IOException
    {
	writer.println( "legacy_template_attributes = new java.util.Hashtable();" );
	Hashtable table = segment.getAttributeTable();
	Enumeration enumeration = table.keys();
	String key, value;
	
	while ( enumeration.hasMoreElements() )
	{
	    key = (String)enumeration.nextElement();
	    value = (String)table.get( key );
	    
	    writer.print( "legacy_template_attributes.put( " );
	    compileStringLiteral( writer, key );
	    writer.print( ", " );
	    compileStringLiteral( writer, value );
	    writer.println( " );" );
	}
	
    }
    
    
    private void compileStringLiteral( PrintWriter writer, String literal )
    throws IOException
    {
	int l, i, k;
	String hex;
	
	l = literal.length();
	writer.print( "\"" );
	for ( k=0; k<l; k++ )
	{
	    if ( literal.charAt( k ) == '\n' )
	    {
		writer.print( "\\n" );
	    }
	    else if ( literal.charAt( k ) == '\r' )
	    {
		writer.print( "\\r" );
	    }
	    else if ( literal.charAt( k ) == '\\' )
	    {
		writer.print( "\\\\" );
	    }
	    else if ( literal.charAt( k ) == '"' )
	    {
		writer.print( "\\\"" );
	    }
	    else if ( literal.charAt( k ) < ' ' || literal.charAt( k ) > '~' )
	    {
		hex = Integer.toHexString( (int)literal.charAt( k ) );
		writer.print( "\\u" );
		for ( i=hex.length(); i<4; i++ )
		    writer.print( "0" );
		writer.print( hex );
	    }
	    else
	    {
		writer.print( literal.charAt( k ) );
	    }
	}
	writer.print( "\"" );
    }
    
    
    
    
    
    /**
     * Returns the next character from the selected input.  The method
     * of selecting the input is the responsibility of the class
     * implementing this interface.  Can throw any java.io.IOException.
     */
    public char readChar()
    throws java.io.IOException
    {
	int n;
	char c;
	
	
	// is the next char already buffered?
	if ( (offset-token_offset) < token_buffer.length() )
	{
	    // if so get it from buffer
	    c = token_buffer.charAt( offset-token_offset );
	}
	else
	{
	    //otherwise read it from the file
	    n = reader.read();
	    if ( n<0 )
		throw new EOFException( "End of template." );
	    c = (char)n;
	    //store char in case parser wants to "unread" it later.
	    token_buffer.append( (char)c );
	}
	
	// push offset on to point at next char
	// to return
	offset++;
	
	return c;
    }
    
    /**
     * Returns the column position of the character last read.
     * @deprecated
     * @see #getEndColumn
     */
    public int getColumn()
    {
	return offset-1;
    }
    
    /**
     * Returns the line number of the character last read.
     * @deprecated
     * @see #getEndLine
     */
    public int getLine()
    {
	return 1;
    }
    
    /**
     * Returns the column number of the last character for current token (being
     * matched after the last call to BeginTOken).
     */
    public int getEndColumn()
    {
	return offset-1;
    }
    
    /**
     * Returns the line number of the last character for current token (being
     * matched after the last call to BeginTOken).
     */
    public int getEndLine()
    {
	return 1;
    }
    
    /**
     * Returns the column number of the first character for current token (being
     * matched after the last call to BeginTOken).
     */
    public int getBeginColumn()
    {
	return token_offset;
    }
    
    /**
     * Returns the line number of the first character for current token (being
     * matched after the last call to BeginTOken).
     */
    public int getBeginLine()
    {
	return 1;
    }
    
    /**
     * Backs up the input stream by amount steps. Lexer calls this method if it
     * had already read some characters, but could not use them to match a
     * (longer) token. So, they will be used again as the prefix of the next
     * token and it is the implemetation's responsibility to do this right.
     */
    public void backup(int amount)
    {
	offset-=amount;
    }
    
    /**
     * Returns the next character that marks the beginning of the next token.
     * All characters must remain in the buffer between two successive calls
     * to this method to implement backup correctly.
     */
    public char BeginToken()
    throws java.io.IOException
    {
	int spare = token_buffer.length() - (offset - token_offset);
	for ( int i=0; i<spare; i++ )
	    token_buffer.setCharAt( i , token_buffer.charAt( (offset - token_offset)+i ) );
	token_buffer.setLength( spare );
	token_offset=offset;
	return this.readChar();
    }
    
    /**
     * Returns a string made up of characters from the marked token beginning
     * to the current buffer position. Implementations have the choice of returning
     * anything that they want to. For example, for efficiency, one might decide
     * to just return null, which is a valid implementation.
     */
    public String GetImage()
    {
	if ( (offset - token_offset) == token_buffer.length() )
	    return token_buffer.toString();
	
	return token_buffer.toString().substring( 0, offset - token_offset );
    }
    
    /**
     * Returns an array of characters that make up the suffix of length 'len' for
     * the currently matched token. This is used to build up the matched string
     * for use in actions in the case of MORE. A simple and inefficient
     * implementation of this is as follows :
     *
     *   {
     *      String t = GetImage();
     *      return t.substring(t.length() - len, t.length()).toCharArray();
     *   }
     */
    public char[] GetSuffix(int len)
    {
	String t = GetImage();
	return t.substring(t.length() - len, t.length()).toCharArray();
    }
    
    /**
     * The lexer calls this function to indicate that it is done with the stream
     * and hence implementations can free any resources held by this class.
     * Again, the body of this function can be just empty and it will not
     * affect the lexer's operation.
     */
    public void Done()
    {
	offset = -1;
	token_offset = 0;
	end_token=true;
	token_buffer = null;
    }
    
}
