package org.plenix.jsp.language.webl;

import webl.util.*;
import webl.lang.*;
import webl.lang.expr.*;
import webl.util.*;

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

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


public class WebLProcessor extends LanguageProcessorImpl {
  // Members
  private Machine machine;

  // Constants
  static final String STARTUP_FILE = "Startup.webl";
  static final String PROPERTIES_FILE = "webl.properties";

  static final String FUNCTION_NAME = "service"; // Servlet function to be called in Jsp modules

  // Empty overriden methods
  public WebLProcessor() { super(); }
  public void compile(String baseName, String path) throws LanguageException { }
  public void unload(Object program) throws LanguageException { }

  // Program instance services
  public void init(String stageDirectory) throws LanguageException {
    // Set up the output log where errors during startup are reported
    Logger startupLog = new StringLog(true);
    Log.SetLogger(new ConsoleLog(false));        
        
    try {
      // WebL resource files should be located under PolyJsp's configuration directory
      FileLocator.AddSearchDirs(ResourceManager.getConfigurationDirectory());

      // WebL scripts will be located under PolyJsp's stage directory
      FileLocator.AddSearchDirs(stageDirectory);

      // load properties if present
      Properties props = new Properties(System.getProperties());
      InputStream in = FileLocator.Find(PROPERTIES_FILE);
      if (in != null) {
        props.load(new BufferedInputStream(in));
        System.setProperties(props);
      } 

      // Execute startup script if present
      String startupName = "";
      BufferedReader startupReader = null;

      in = FileLocator.Find(STARTUP_FILE);
      if (in != null) {
	startupName = STARTUP_FILE;
        startupReader = new BufferedReader(new AutoStreamReader(in, "", "UTF8"));
      }

      // Initialize global WebL machine
      this.machine = new Machine(startupName, startupReader, new String[0], 0, startupLog);

    } catch (JspException e) {
      throw new LanguageException(
        "WebL Processor: Error getting configuration directory. " +
	e.getMessage()
      );
    } catch (IOException e) {
      throw new LanguageException(
        "WebL Processor: Error while running startup script " + STARTUP_FILE + ": " +
	e.getMessage() + "\n" +
	startupLog.toString() // Report syntax errors
      );
    } catch (WebLException e) {
      throw new LanguageException(
        "WebL Processor: Exception while running Startup.webl, " +
	e.report()
      );
    }  	    
  }

  public Object load(String baseName, String path) throws LanguageException {
    return baseName;
  }

  public void execute(Object program, HttpServletRequest request, HttpServletResponse response)
    throws LanguageException
  {
    String moduleName = (String) program;

    try {
      Logger startupLog = new StringLog(false);
      startupLog.debugln("Module: " + moduleName + " Name: " + FUNCTION_NAME);

      Module module = this.machine.loadModule(moduleName, true, startupLog);

      if (module == null) {
        throw new LanguageException("Error loading WebL module " + moduleName + "\n" + startupLog.toString());
      }

      VarExpr functionVariable = module.importVariable(FUNCTION_NAME);

      if (functionVariable == null) {
        throw new LanguageException("No such exported variable: " + FUNCTION_NAME);
      }

      FunExpr functionExpression = (FunExpr) functionVariable.lookup(null);

      if (functionExpression.args.size() != 2) {
        throw new LanguageException("Exported function does not have the required 2 formal arguments");
      }

      Context newcontext = new Context(
        functionExpression.c,
	null,
	functionExpression.scope,
	Program.Str(moduleName + "_" + FUNCTION_NAME)
      );

      newcontext.binding[0] = BuildRequestObject(request);
      newcontext.binding[1] = BuildResponseObject(response);

      functionExpression.body.eval(newcontext);

      UpdateResponse(response, newcontext.binding[1]);
    } catch (Exception e) {
      error(response, e.toString());
      e.printStackTrace();
    }
  }

  public ObjectExpr BuildRequestObject(HttpServletRequest request) {
    ObjectExpr obj = new ObjectExpr();
    def(obj, "method", request.getMethod());
    def(obj, "requestURI", request.getRequestURI());
    def(obj, "servletPath", request.getServletPath());
    def(obj, "pathInfo", request.getPathInfo());
    def(obj, "pathTranslated", request.getPathTranslated());
    def(obj, "queryString", request.getQueryString());
    def(obj, "remoteUser", request.getRemoteUser());
    def(obj, "authType", request.getAuthType());

    def(obj, "remoteAddr", request.getRemoteAddr());
    def(obj, "remoteHost", request.getRemoteHost());
    def(obj, "scheme", request.getScheme());
    def(obj, "serverName", request.getServerName());
    obj.def("serverPort", Program.Int(request.getServerPort()));
    def(obj, "protocol", request.getProtocol());
    def(obj, "contentType", request.getContentType());

    ObjectExpr header = new ObjectExpr();
    Enumeration e = request.getHeaderNames();
    while (e.hasMoreElements()) {
      String n = (String)e.nextElement();
      String v = (String)request.getHeader(n);
      if (v == null) {
        header.def(n.toLowerCase(), Program.nilval);
      }
      else {
        header.def(n.toLowerCase(), Program.Str(v));
      }
    }
    obj.def("header", header);

    ObjectExpr param = new ObjectExpr();
    e = request.getParameterNames();
    while (e.hasMoreElements()) {
      String n = (String)e.nextElement();
      String[] V = request.getParameterValues(n);
      if (V.length > 1) {
        ListExpr L = new ListExpr();
        for (int i = 0; i < V.length; i++) {
          L = L.Append(Program.Str(V[i]));
        }
        param.def(n, L);
      } else {
        param.def(n, Program.Str(V[0]));
      }
    }
    obj.def("param", param);

    ObjectExpr cobj = new ObjectExpr();
    Cookie[] cookies = request.getCookies();
    if (cookies != null) {
      for(int i=0; i < cookies.length; i++) {
        Cookie c = cookies[i];
        def(cobj, c.getName(), c.getValue());
      }
    }
    obj.def("cookies", cobj);

    return obj;
  }

  public ObjectExpr BuildResponseObject(HttpServletResponse response) {
    ObjectExpr obj = new ObjectExpr();
    obj.def("statusCode", Program.Int(200));
    obj.def("statusMsg", Program.Str("OK"));
    obj.def("result", Program.Str("empty page"));
    obj.def("mimeType", Program.Str("text/html"));
    obj.def("cookies", new ObjectExpr());
    obj.def("header", new ObjectExpr());
    return obj;
  }

  public void UpdateResponse(HttpServletResponse response, Expr o) throws FormatException, IOException {
    if (o instanceof ObjectExpr) {
      ObjectExpr obj = (ObjectExpr)o;
      response.setStatus((int)getInt(obj, "statusCode"), getStr(obj, "statusMsg"));

      // set headers
      ObjectExpr header = getObj(obj, "header");
      Enumeration e = header.EnumKeys();
      while (e.hasMoreElements()) {
        Expr n = (Expr)e.nextElement();
        response.setHeader(n.print(), header.get(n).print());
      }

      // set cookies
      ObjectExpr t = getObj(obj, "cookies");
      e = t.getContent();
      while (e.hasMoreElements()) {
        Expr n = (Expr)e.nextElement();
        response.addCookie(MakeCookie(n, t.get(n)));
      }

      response.setContentType(getStr(obj, "mimeType"));
      PrintWriter out = response.getWriter();
      out.print(getStr(obj, "result"));
    }
  }

  private Cookie MakeCookie(Expr name, Expr o) throws FormatException {
    if (o != null && o instanceof ObjectExpr) {
      ObjectExpr obj = (ObjectExpr)o;
      Cookie C = new Cookie(name.print(), getStr(obj, "value"));
      C.setComment(getStr(obj, "comment"));
      C.setDomain(getStr(obj, "domain"));
      C.setPath(getStr(obj, "path"));
      C.setMaxAge((int)getInt(obj, "maxage"));
      C.setVersion((int)getInt(obj, "version"));
      return C;
    } else {
      throw new FormatException("Cookies need to be of type object; got instead " + o);
    }
  }

  private void def(ObjectExpr obj, String fld, String val) {
    if (val == null) {
      obj.def(fld, Program.nilval);
    }
    else {
      obj.def(fld, Program.Str(val));
    }
  }

  private String getStr(ObjectExpr obj, String fld) throws FormatException {
    Expr val = obj.get(fld);
    if (val == null) {
      throw new FormatException("Expected an object field called " + fld + " in " + obj);
    } else if (val == Program.nilval) {
      return "";
    }
    else if (val instanceof StringExpr) {
      return ((StringExpr)val).val();
    }
    else {
      return val.print();
    }
  }

  private long getInt(ObjectExpr obj, String fld) throws FormatException {
    Expr val = obj.get(fld);
    if (val == null) {
      throw new FormatException("Expected an object field called " + fld + " in " + obj);
    }
    else if (val instanceof IntExpr) {
      return ((IntExpr)val).val;
    } else {
      throw new FormatException("Expected an object field called " + fld + " of type int in " + obj);        
    }
  }

  private ObjectExpr getObj(ObjectExpr obj, String fld) throws FormatException {
    Expr val = obj.get(fld);
    if (val == null) {
      throw new FormatException("Expected an object field called " + fld + " in " + obj);
    }
    else if (val instanceof ObjectExpr) {
      return (ObjectExpr)val;
    } else {
      throw new FormatException("Expected an object field called " + fld + " of type object in " + obj);        
    }
  }

  private boolean getBool(ObjectExpr obj, String fld) throws FormatException {
    Expr val = obj.get(fld);
    if (val == null) {
      throw new FormatException("Expected an object field called " + fld + " in " + obj);
    }
    else if (val instanceof BooleanExpr) {
      return val == Program.trueval;
    } else {
      throw new FormatException("Expected an object field called " + fld + " of type bool in " + obj);        
    }
  }

  void error(HttpServletResponse response, String msg) throws LanguageException {
    try {
      response.setContentType("text/html");
      response.setStatus(500, "Internal Server Error");
  
      PrintWriter out = response.getWriter();
      out.println("<HEAD>");
      out.println("  <TITLE>PolyJsp WebL Error</TITLE>");
      out.println("</HEAD>");
      out.println("<BODY BGCOLOR='#ffffff'>");
      out.println("  <CENTER>");
      out.println("    <H1><FONT COLOR='red'>PolyJsp WebL Internal Server Error</FONT></H1>");
      out.println("  </CENTER>");
      out.print("<PRE>");
      out.print(msg);
      out.println("<PRE>");
      out.println("</BODY>");
      out.close();
  
      Log.debugln("[Error: " + msg + "]");
    }
    catch (IOException e) {
      throw new LanguageException("IOException: " + e.getMessage());
    }
  }
}

class FormatException extends Exception {
  public FormatException(String msg) {
    super(msg);
  }
}


