package org.plenix.jsp;

import com.kav.xsl.*;
import java.io.*;
import java.util.*;
import javax.servlet.http.*;
import org.w3c.dom.*;

import org.plenix.jsp.parser.*;
import org.plenix.jsp.language.*;

class JspScript
{
  private File jspFile;
  private File translatedFile;

  private Object compiledProgram;
  private LanguageProcessor language;

/** This is a script cache used to lookup already 
  * loaded Jsp scripts*/
  private static Hashtable scripts = new Hashtable();
  private static XSLFormatter formatter = new XSLFormatter();

  protected static JspScript getScript(String fileName)
  {
    synchronized (scripts)
    {
      return (JspScript) scripts.get(fileName);
    }
  }

  protected JspScript(File theFile)
    throws JspException, IOException, LanguageException, FileNotFoundException, ParseException
  {
    synchronized (scripts)
    {
      this.jspFile = theFile;
      String fileName = jspFile.getCanonicalPath();
  
      if (scripts.get(fileName) != null)
      {
        throw new JspException("Duplicate script: " + fileName);
      }
  
      // Locate translated file & determine language
      long lastModified = -1L;
      Enumeration enumeration = LanguageLoader.languages();
      String baseName = Util.getNormalizedName(fileName);
  
      while (enumeration.hasMoreElements())
      {
        LanguageProcessor candidateLanguage = (LanguageProcessor) enumeration.nextElement();
        String sourceExtension = candidateLanguage.getSourceExtension();
        File candidateFile = new File(ResourceManager.getStageDirectory() + File.separator + baseName + "." + sourceExtension);
  
        if (candidateFile.isFile() && candidateFile.canRead())
        {
          if (lastModified < candidateFile.lastModified())
  	{
  	  String objectExtension = candidateLanguage.getObjectExtension();
  
  	  if (objectExtension == null)
  	  {
  	    this.language = candidateLanguage;
              this.translatedFile = candidateFile;
  	  }
  	  else
  	  {
  	    String objectFileName = ResourceManager.getStageDirectory() + File.separator + baseName;
  
  	    if (objectExtension.length() > 0)
  	    {
  	      objectFileName += "." + objectExtension;
  	    }
  
  	    File objectFile = new File(objectFileName);

  	    if (objectFile.isFile() && objectFile.canRead())
  	    {
  	      this.language = candidateLanguage;
                this.translatedFile = candidateFile;
  	    }
  	  }
  	}
        }
      }

      // New script: translate, compile and load
      if (this.language == null)
      {
        refresh();
      }
      else
      {
        // Existing script: just load
        this.compiledProgram = this.language.load(baseName, ResourceManager.getStageDirectory());
      }

      scripts.put(fileName, this);
    }
  }

  protected void execute(HttpServletRequest request, HttpServletResponse response)
    throws JspException, IOException, LanguageException, FileNotFoundException, ParseException
  {
    if (this.language == null)
    {
      throw new JspException("Undefined language for script '" + this.jspFile.getCanonicalPath() + "'");
    }

    // Check modification times 
    if (this.jspFile.lastModified() > this.translatedFile.lastModified())
    {
      this.language.unload(compiledProgram);
      refresh();
    }

    this.language.execute(this.compiledProgram, request, response);
  }

  private void refresh()
    throws IOException, FileNotFoundException, ParseException, LanguageException, JspException
  {
    // Compile Jsp script
    String jspScriptName = this.jspFile.getCanonicalPath();
    FileReader jspReader = new FileReader(jspScriptName);
    Parser parser = new Parser(jspReader);
    parser.parse(jspScriptName);

    // Get language
    String languageName = parser.getLanguage();
    this.language = LanguageLoader.getLanguage(languageName);
    if (this.language == null)
    {
      throw new JspException("Unsupported language: " + languageName);
    }

    // Get language/version stylesheet
    String versionName = parser.getVersion();
    XSLStylesheet stylesheet = language.getStylesheet(versionName);
    if (stylesheet == null)
    {
      throw new JspException("Unsupported JSP version: " + versionName);
    }

    // Translate Jsp script to target language
    XSLProcessor xslProcessor = new XSLProcessor();
    NodeList nodeList = xslProcessor.process(parser.getDocument(), stylesheet);

    String baseName = Util.getNormalizedName(jspScriptName);

    // Create subdirectories as needed
    String directoryName = ResourceManager.getStageDirectory() + File.separator + Util.getDirectoryName(baseName);
    File directory = new File(directoryName);
    directory.mkdirs();

    String sourceExtension = language.getSourceExtension();
    String translatedScriptName = ResourceManager.getStageDirectory() + File.separator + baseName + "." + sourceExtension;

    FileWriter writer = new FileWriter(translatedScriptName);
    // synchronized(formatter) ?
    formatter.process(nodeList, new PrintWriter(writer));

    this.translatedFile = new File(translatedScriptName);

    this.language.compile(baseName, ResourceManager.getStageDirectory());
    this.compiledProgram = this.language.load(baseName, ResourceManager.getStageDirectory());
  }

  protected static Enumeration scripts()
  {
    return scripts.elements();
  }

  protected void unload()
    throws LanguageException
  {
    if (language != null && compiledProgram != null)
    {
      language.unload(compiledProgram);
    }
  }
}
