/* ======================================================================
   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.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(" response.disableCaching();");
	    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;
    }
    
}
