/*
 * LingPipe v. 3.9
 * Copyright (C) 2003-2010 Alias-i
 *
 * This program is licensed under the Alias-i Royalty Free License
 * Version 1 WITHOUT ANY WARRANTY, without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the Alias-i
 * Royalty Free License Version 1 for more details.
 *
 * You should have received a copy of the Alias-i Royalty Free License
 * Version 1 along with this program; if not, visit
 * http://alias-i.com/lingpipe/licenses/lingpipe-license-1.txt or contact
 * Alias-i, Inc. at 181 North 11th Street, Suite 401, Brooklyn, NY 11211,
 * +1 (718) 290-9170.
 */

package com.aliasi.demo.framework;

import com.aliasi.util.Streams;

import com.aliasi.xml.SAXWriter;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;

import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.servlet.ServletException;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUpload;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.fileupload.servlet.ServletRequestContext;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;

/**
 * The <code>DemoServlet</code> class provides a servlet interface
 * to streaming demos.  The demo servlet presents demos in three
 * ways: web form, file upload and web service, each with its own
 * method of transmitting property information.
 *
 * <p>See the Web demo instructions for more information on using
 * this class.</p>
 *
 * @author  Bob Carpenter
 * @version 2.3.0
 * @since   LingPipe2.3
 */
public class DemoServlet extends HttpServlet {

    static final long serialVersionUID = 6980095913648079890L;

    private StreamDemo mDemo;

    /**
     * Construct a demo servlet.
     */
    public DemoServlet() { }

    /**
     * Initialize the demo servlet.  This is the initialization
     * called by the servlet container.  It constructs the
     * demo using reflection given the parameters.
     */
    public void init() throws ServletException {
        String demoClass = getInitParameter("demoConstructor");
        String demoConstructorArgs
            = getInitParameter("demoConstructorArgs");

        try {
            mDemo
                = Constants
                .constructDemo(demoClass,
                               demoConstructorArgs);
        } catch (IllegalArgumentException e) {
            String msg = "Error constructing demo="
                + e;
            throw new ServletException(e);
        } catch (ClassCastException e) {
            String msg = "Could not convert class=" + demoClass
                + " to instance of StreamDemo.";
            throw new ServletException(e);
        }
    }

    /**
     * Destroys this demo by null-ing out the resources used.
     */
    public void destroy() {
        mDemo = null;
    }

    /**
     * Handles <code>GET</code> requests by deferring them
     * to <code>POST</code> requests.
     *
     * @param request Servlet equest.
     * @param response Servlet response.
     * @throws IOException If there is an underlying I/O error.
     * @throws ServletException If there is an underlying servlet
     * error.
     */
    public void doGet(HttpServletRequest request,
                      HttpServletResponse response)
        throws IOException, ServletException {

        doPost(request,response);
    }

    /**
     * Handles <code>POST</code> requests.  First it determines what
     * type of demo is involved by looking at the path information.
     * If the extension is <code>/textInput.html</code>, it generates
     * the input form for the text input demo.  If the extension is
     * <code>/fileInput.html</code>, it generates the input form for
     * file upload.  If neither extension is present, it generates
     * the output for the demo, based on the properties determined
     * from the input.
     *
     * @param request Servlet equest.
     * @param response Servlet response.
     * @throws IOException If there is an underlying I/O error.
     * @throws ServletException If there is an underlying servlet
     * error.
     */
    public void doPost(HttpServletRequest request,
                       HttpServletResponse response)
        throws IOException, ServletException {

        String pathInfo = request.getPathInfo();
        if (pathInfo.equals("/response.xml")) {
            generateOutput(request,response);
        } else if (pathInfo.equals("/textInput.html")) {
            generateInputForm(request,response,true);
        } else if (pathInfo.equals("/fileInput.html")) {
            generateInputForm(request,response,false);
        }
    }

    void generateOutput(HttpServletRequest request,
                        HttpServletResponse response)
        throws IOException, ServletException {

        InputStream in = null;
        OutputStream out = null;

        try {
            response.setContentType(mDemo.responseType());
            out = response.getOutputStream();


            @SuppressWarnings("unchecked") // bad inherited API from commons
            Properties properties
                = mapToProperties((Map<String,String[]>)request.getParameterMap());

            String reqContentType = request.getContentType();

            if (reqContentType == null
                || reqContentType.startsWith("text/plain")) {

                properties.setProperty("inputType","text/plain");
                String reqCharset = request.getCharacterEncoding();
                if (reqCharset != null)
                    properties.setProperty("inputCharset",reqCharset);
                in = request.getInputStream();

            } else if (reqContentType
                       .startsWith("application/x-www-form-urlencoded")) {

                String codedText = request.getParameter("inputText");
                byte[] bytes = codedText.getBytes("ISO-8859-1");
                in = new ByteArrayInputStream(bytes);

            } else if (ServletFileUpload
                       .isMultipartContent
                       (new ServletRequestContext(request))) {

                FileItemFactory factory = new DiskFileItemFactory();
                ServletFileUpload uploader = new ServletFileUpload(factory);
                @SuppressWarnings("unchecked") // bad commons API
                List<FileItem> items = (List<FileItem>) uploader.parseRequest(request);
                Iterator<FileItem> it = items.iterator();
                while (it.hasNext()) {
                    log("found item");
                    FileItem item = it.next();
                    if (item.isFormField()) {
                        String key = item.getFieldName();
                        String val = item.getString();
                        properties.setProperty(key,val);
                    } else {
                        byte[] bytes = item.get();
                        in = new ByteArrayInputStream(bytes);
                    }
                }

            } else {
                System.out.println("unexpected content type");
                String msg = "Unexpected request content"
                    + reqContentType;
                throw new ServletException(msg);
            }
            mDemo.process(in,out,properties);
        } catch (FileUploadException e) {
            throw new ServletException(e);
        } finally {
            Streams.closeInputStream(in);
            Streams.closeOutputStream(out);
        }
    }

    private void generateInputForm(HttpServletRequest request,
                                   HttpServletResponse response,
                                   boolean isTextField)
        throws IOException, ServletException {

        OutputStream out = null;
        try {
            response.setContentType("text/html");
            out = response.getOutputStream();
            String charset
                = mDemo.defaultProperties()
                .getProperty("Input Character Encoding");
            if (charset == null) {
                charset = "UTF-8";
            }
            SAXWriter saxWriter = new SAXWriter(out,charset,XHTML_MODE);
            saxWriter.setDTDString(XHTML_DTD_STRING);
            saxWriter.startDocument();
            Attributes htmlAtts
                = SAXWriter
                .createAttributes("xmlns","http://www.w3.org/1999/xhtml",
                                  "xml:lang","en");
            saxWriter.characters("\n");
            saxWriter.startSimpleElement("html",htmlAtts);

            saxWriter.characters("\n");
            saxWriter.startSimpleElement("head");
            saxWriter.characters("\n");
            saxWriter.startSimpleElement("title");
            saxWriter.characters(mDemo.title());
            saxWriter.endSimpleElement("title");

            saxWriter.characters("\n");
            Attributes httpAtts
                = SAXWriter
                .createAttributes("http-equiv",
                                  "Content-type",
                                  "content",
                                  "application/xhtml+xml; charset=" + charset);
            saxWriter.startEndSimpleElement("meta",httpAtts);


            saxWriter.characters("\n");
            saxWriter.startSimpleElement("style","type","text/css");
            saxWriter.characters("\n");
            saxWriter.characters(CSS_STYLE);
            saxWriter.characters("\n");
            saxWriter.endSimpleElement("style");
            saxWriter.characters("\n");
            saxWriter.endSimpleElement("head");

            saxWriter.characters("\n");
            saxWriter.startSimpleElement("body");

            saxWriter.characters("\n");
            saxWriter.startSimpleElement("div","id","header");

            Attributes logoAtts
                = SAXWriter
                .createAttributes("href","http://alias-i.com",
                                  "id","logo");
            saxWriter.characters("\n");
            saxWriter.startSimpleElement("a",logoAtts);
            Attributes imgAtts
                = SAXWriter
                .createAttributes("src","http://alias-i.com/lingpipe/web/img/logo-small.gif",
                                  "alt","Alias-i Logo");
            saxWriter.startSimpleElement("img",imgAtts);
            saxWriter.endSimpleElement("img");
            saxWriter.endSimpleElement("a");

            saxWriter.characters("\n");
            saxWriter.startSimpleElement("h1","id","pageTitle");
            saxWriter.characters(mDemo.title());
            saxWriter.endSimpleElement("h1");

            saxWriter.characters("\n");
            saxWriter.endSimpleElement("div"); // id=header

            saxWriter.characters("\n");
            saxWriter.startSimpleElement("h2","id","poweredBy");
            saxWriter.characters("Powered by ");
            saxWriter.startSimpleElement("a","href","http://alias-i.com/lingpipe");
            saxWriter.characters("LingPipe");
            saxWriter.endSimpleElement("a");
            saxWriter.endSimpleElement("h2");


            String[] lines = mDemo.description().split("\n");
            if (lines.length > 0) {
                saxWriter.characters("\n");
                saxWriter.startSimpleElement("div","id","description");
                for (int i = 0; i < lines.length; ++i) {
                    saxWriter.characters("\n");
                    saxWriter.startSimpleElement("p");
                    if (i == 0) {
                        saxWriter.startSimpleElement("b");
                        saxWriter.characters("This Demo:");
                        saxWriter.endSimpleElement("b");
                    }
                    saxWriter.characters(" ");
                    saxWriter.characters(lines[i]);
                    saxWriter.endSimpleElement("p");
                }
                saxWriter.characters("\n");
                saxWriter.endSimpleElement("div");
            }

            String actionPath =
                request.getContextPath()
                + request.getServletPath()
                + "/response.xml";

            Attributes formAtts
                = isTextField
                ? SAXWriter.createAttributes("method","post",
                                             "action",actionPath)
                : SAXWriter.createAttributes("method","post",
                                             "action",actionPath,
                                             "enctype","multipart/form-data");
            saxWriter.characters("\n");
            saxWriter.startSimpleElement("form",formAtts);

            saxWriter.characters("\n");
            saxWriter.startSimpleElement("div","id","license");
            saxWriter.characters("\n");
            saxWriter.startSimpleElement("p");
            saxWriter.characters("\n");
            saxWriter.startSimpleElement("b");
            saxWriter.characters("Terms of Use:");
            saxWriter.endSimpleElement("b");
            saxWriter.characters(" This demo is for evaluation purposes only.");
            saxWriter.characters(" Please do not overload our demo server. ");
            saxWriter.startSimpleElement("a","href",
                                         "http://alias-i.com/lingpipe/web/contact.html");
            saxWriter.characters("Contact Alias-i");
            saxWriter.endSimpleElement("a");
            saxWriter.characters(" about other uses.");
            saxWriter.endSimpleElement("p");
            saxWriter.endSimpleElement("div"); // license

            saxWriter.characters("\n");
            saxWriter.startSimpleElement("div","id","input");
            saxWriter.characters("\n");
            saxWriter.startSimpleElement("h4");
            saxWriter.characters((isTextField ? "Text" : "File") + " Input");
            saxWriter.endSimpleElement("h4");

            if (isTextField) {
                Attributes textAreaAtts
                    = SAXWriter
                    .createAttributes("name","inputText",
                                      "rows","12",
                                      "cols","60");
                saxWriter.characters("\n");
                saxWriter.startEndSimpleElement("textarea",textAreaAtts);

            } else {
                Attributes fileUploadAtts
                    = SAXWriter
                    .createAttributes("type","file",
                                      "name","inputFile",
                                      "size","50%",
                                      "id","fileUpload");
                saxWriter.characters("\n");
                saxWriter.startEndSimpleElement("input",fileUploadAtts);
            }


            saxWriter.characters("\n");
            saxWriter.startEndSimpleElement("br");

            Attributes inputAtts
                = isTextField
                ? SAXWriter
                  .createAttributes("type","submit",
                                    "value","Submit Text",
                                    "id","submitButton",
                                    "title","Click to analyze input text.")
                : SAXWriter
                .createAttributes("type","submit",
                                  "value","Submit File",
                                  "id","submitButton",
                                  "title",
                                  "Click to upload and analyze selected file.");
            saxWriter.characters("\n");
            saxWriter.startEndSimpleElement("input",inputAtts);

            saxWriter.characters("\n");
            saxWriter.startSimpleElement("span","id","switch");
            saxWriter.characters("(or switch to:");
            if (isTextField) {
                saxWriter.startSimpleElement("a","href","fileInput.html");
                saxWriter.characters("File Input Form");
            } else {
                saxWriter.startSimpleElement("a","href","textInput.html");
                saxWriter.characters("Text Input Form");
            }
            saxWriter.endSimpleElement("a");
            saxWriter.characters(")");
            saxWriter.endSimpleElement("span");

            if (!isTextField) {
                saxWriter.startEndSimpleElement("br");
                saxWriter.startEndSimpleElement("br");
                saxWriter.startEndSimpleElement("br");
                saxWriter.startEndSimpleElement("br");
                saxWriter.startEndSimpleElement("br");
                saxWriter.startEndSimpleElement("br");
                saxWriter.startEndSimpleElement("br");
                saxWriter.startEndSimpleElement("br");
                saxWriter.startEndSimpleElement("br");
            }


            saxWriter.characters("\n");
            saxWriter.endSimpleElement("div");

            saxWriter.characters("\n");
            saxWriter.startSimpleElement("div","id","params");

            Map<String,String[]> propertyDeclarations = mDemo.propertyDeclarations();
            Iterator<Map.Entry<String,String[]>> entryIt 
                = propertyDeclarations.entrySet().iterator();
            while (entryIt.hasNext()) {
                Map.Entry<String,String[]> entry = entryIt.next();
                String key = entry.getKey();
                String[] vals = entry.getValue();
                String toolTip = mDemo.propertyToolTips().get(key);
                saxWriter.characters("\n");
                saxWriter.startSimpleElement("h4");
                saxWriter.characters(key);
                saxWriter.endSimpleElement("h4");
                if (vals.length > 1) {
                    Attributes selectAtts
                        = (toolTip != null)
                        ? SAXWriter.createAttributes("name",key,
                                                     "title",toolTip)
                        : SAXWriter.createAttributes("name",key);
                    saxWriter.characters("\n");
                    saxWriter.startSimpleElement("select",selectAtts);
                    for (int i = 0; i < vals.length; ++i) {
                        saxWriter.characters("\n");
                        saxWriter.startSimpleElement("option","value",vals[i]);
                        saxWriter.characters(vals[i]);
                        saxWriter.endSimpleElement("option");
                    }
                    saxWriter.characters("\n");
                    saxWriter.endSimpleElement("select");
                } else {
                    Attributes paramInputAtts
                        = (vals.length == 1)
                        ? SAXWriter.createAttributes("type","text",
                                                     "name",key,
                                                     "value",vals[0])
                        : SAXWriter.createAttributes("type","text",
                                                     "name",key);
                    saxWriter.characters("\n");
                    saxWriter.startEndSimpleElement("input",paramInputAtts);
                }
            }

            saxWriter.characters("\n");
            saxWriter.endSimpleElement("div");

            saxWriter.characters("\n");
            saxWriter.endSimpleElement("form");


            saxWriter.characters("\n");
            saxWriter.startSimpleElement("div","id","footInstructions");
            saxWriter.characters("\n");
            saxWriter.startSimpleElement("div","id","instructions");
            saxWriter.characters("\n");
            saxWriter.startSimpleElement("h3");
            saxWriter.characters("General Web Demo Instructions");
            saxWriter.endSimpleElement("h3");

            saxWriter.characters("\n");
            saxWriter.startSimpleElement("ul");
            writeItems(GENERAL_WEB_INSTRUCTIONS,saxWriter);
            if (isTextField)
                writeItems(TEXT_WEB_INSTRUCTIONS,saxWriter);
            else
                writeItems(FILE_WEB_INSTRUCTIONS,saxWriter);

            saxWriter.characters("\n");
            saxWriter.endSimpleElement("ul");

            saxWriter.startSimpleElement("h3");
            saxWriter.characters("For more info: ");
            saxWriter.startSimpleElement("a","href","http://alias-i.com/web/lingpipe/demos.html");
            saxWriter.characters("Complete LingPipe Demo Instructions");
            saxWriter.endSimpleElement("a");
            saxWriter.endSimpleElement("h3");

            saxWriter.characters("\n");
            saxWriter.endSimpleElement("div"); // instructions

            Map<String,String> modelToResource = mDemo.modelToResource();
            Map<String,String> tutorialToUrl = mDemo.tutorialToUrl();

            if ((modelToResource.size() > 0)
                || (tutorialToUrl.size() > 0)) {

                saxWriter.characters("\n");
                saxWriter.startSimpleElement("div","id","resources");

                saxWriter.characters("\n");
                saxWriter.startSimpleElement("h3");
                saxWriter.characters("Related LingPipe Resources");
                saxWriter.endSimpleElement("h3");

                if (tutorialToUrl.size() > 0) {
                    saxWriter.characters("\n");
                    saxWriter.startSimpleElement("h4");
                    saxWriter.characters("Tutorials");
                    saxWriter.endSimpleElement("h4");

                    saxWriter.characters("\n");
                    saxWriter.startSimpleElement("ul");
                    Iterator<Map.Entry<String,String>> it = tutorialToUrl.entrySet().iterator();
                    while (it.hasNext()) {
                        Map.Entry<String,String> entry = it.next();
                        String tutorialName = entry.getKey();
                        String tutorialUrl = entry.getValue();
                        saxWriter.characters("\n");
                        saxWriter.startSimpleElement("li");
                        saxWriter.startSimpleElement("a","href",tutorialUrl);
                        saxWriter.characters(tutorialName);
                        saxWriter.endSimpleElement("a");
                        saxWriter.endSimpleElement("li");
                    }
                    saxWriter.characters("\n");
                    saxWriter.endSimpleElement("ul");
                }

                if (modelToResource.size() > 0) {
                    saxWriter.characters("\n");
                    saxWriter.startSimpleElement("h4");
                    saxWriter.characters("Models");
                    saxWriter.endSimpleElement("h4");

                    saxWriter.characters("\n");
                    saxWriter.startSimpleElement("ul");
                    Iterator<Map.Entry<String,String>> it = modelToResource.entrySet().iterator();
                    while (it.hasNext()) {
                        Map.Entry<String,String> entry = it.next();
                        String modelName = entry.getKey();
                        String modelResourcePath = entry.getValue();
                        saxWriter.characters("\n");
                        saxWriter.startSimpleElement("li");
                        saxWriter.startSimpleElement("a","href","../resource" + modelResourcePath);
                        saxWriter.characters(modelName);
                        saxWriter.endSimpleElement("a");
                        saxWriter.endSimpleElement("li");
                    }
                    saxWriter.characters("\n");
                    saxWriter.endSimpleElement("ul");
                }

                saxWriter.characters("\n");
                saxWriter.endSimpleElement("div"); // resources
            }

            saxWriter.characters("\n");
            saxWriter.endSimpleElement("div"); // footInstructions


	    //Allows Google Analytics to track site usage
	    saxWriter.characters("\n");
	    saxWriter.startSimpleElement("script","type","text/javascript");
	    saxWriter.characters("\n");
	    saxWriter.characters(
				 "var gaJsHost = ((\'https:\' == document.location.protocol) ? \'https://ssl.\' : \'http://www.\');\n" 
				+ "document.write(unescape(\'%3Cscript src=\'\' + gaJsHost + \'google-analytics.com/ga.js\' type=\'text/javascript\'%3E%3C/script%3E\'));\n");
	    saxWriter.endSimpleElement("script");
	    saxWriter.characters("\n");
	    saxWriter.startSimpleElement("script","type","text/javascript");
	    saxWriter.characters("\n");
	    saxWriter.characters("try var pageTracker = _gat._getTracker(\'UA-15123726-1\'); pageTracker._trackPageview(); } catch(err) {}\n");

	    saxWriter.endSimpleElement("script");
	    saxWriter.characters("\n");			

            saxWriter.characters("\n");
            saxWriter.endSimpleElement("body");

            saxWriter.characters("\n");
            saxWriter.endSimpleElement("html");
            saxWriter.endDocument();
        } catch (SAXException e) {
            throw new ServletException(e);
        } finally {
            Streams.closeOutputStream(out);
        }
    }

    static void writeItems(String text, SAXWriter saxWriter)
        throws SAXException {

        String[] paragraphs = text.split("\n");
        for (int i = 0; i < paragraphs.length; ++i) {
            saxWriter.characters("\n");
            saxWriter.startSimpleElement("li");
            saxWriter.characters("\n");
            saxWriter.characters(paragraphs[i]);
            saxWriter.characters("\n");
            saxWriter.endSimpleElement("li");
        }
    }

    private Properties mapToProperties(Map<String,String[]> map) {
        Properties reqProperties
            = new Properties(mDemo.defaultProperties());
        Iterator<Map.Entry<String,String[]>> it = map.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry<String,String[]> entry = it.next();
            String key = entry.getKey();
            String[] vals = entry.getValue();
            if (vals == null || vals.length == 0) continue;
            reqProperties.setProperty(key,vals[0]);
        }
        return reqProperties;
    }

    static final boolean XHTML_MODE = true;

    private static final String XHTML_DTD_STRING
        = "<!DOCTYPE html"
        + " PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\""
        + " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">";


    private static final String GENERAL_WEB_INSTRUCTIONS
        =  "Set the web browser's character encoding based on the encoding of text to be submitted (Use browser menu=View, submenu=Encoding)."
        + "\n"
        + "Set the input character encoding to match the actual encoding of the input bytes."
        + "\n"
        + "Set the input type selection to match the type of the input, either plain text, HTML or XML."
        + "\n"
        + "Set the output character encoding to any value; it need not match the input character set or browser.";

    private static final String TEXT_WEB_INSTRUCTIONS
        = "Cut-and-paste or enter text in the specified character encoding."
        + "\n"
        + "To analyze a file, first switch to the file input form, the link for which is next to the submit button.";

    private static final String FILE_WEB_INSTRUCTIONS
        = "Files may be analyzed by entering an absolute path to them in the file input field."
        + " They may also be selected by clicking on the browse button and opening the desired file."
        + " This will write its absolute path into the input field."
        + " Use the submit button to upload the file for analysis."
        + "\n"
        + "To analyze direct text input, first switch to the text input form, the link for which is next to the submit button.";


    private static final String CSS_STYLE
        = "* { margin: 0; padding: 0; font-family: verdana, arial, sans-serif; background-color: #DDD; border: none; font-size: 1em; }"
        + "\n"
        + "#header {background-color: #272651; padding: 0; color: #272651; margin: 0 0 1px 0; border: 15px solid #DDD; border-top: 1em solid #DDD; border-left: 1.15em solid #DDD; border-bottom: 1px solid #DDD; }"
        + "\n"
        + "h1#pageTitle { font-size: 1.5em; color: #EEE; background-color: #272651; padding: 0.75em 0 .75em .333em; }"
        + "\n"
        + "a#logo { float: right; display: inline; padding: 1em 1em 0 0; background-color: #272651; }"
        + "\n"
        + "h2 { font-size: 1em; }"
        + "\n"
        + "h2#poweredBy { float: right; font-size: 0.8em; font-weight: normal; background-color: #272651; color: #EEE; margin: 0; padding: 1em 1.5em; border: 15px solid #DDD; border-top: none }"
        + "\n"
        + "h2#poweredBy a { font-size: 1.5em; font-weight: normal; background-color: #272651; color: #F7EB00; }"
        + "\n"
        + "h3 { font-size: 1em; margin: 1.0em; }"
        + "\n"
        + "h4 { font-size: 0.8em; margin: .25em 0; }"
        + "\n"
        + "#description p { width: 100%; margin-bottom: 0.25em; padding: 0.5em; background-color: #EEE; border: 1px solid #888; font-size: 0.75em;  overflow:hidden }"
        + "\n"
        + "#description p b { background-color: #EEE; }"
        + "\n"
        + "#description { width: 30em; padding: 1.15em 0.75em 1.15em 1.15em; line-height: 140%; }"
        + "\n"
        + "#license { clear: both; width: 30em; padding: 0em 0.75em 1.15em 1.15em; line-height: 140%; }"
        + "\n"
        + "#license p { width: 100%; margin-bottom: 0.25em; padding: 0.75em 0.5em; background-color: #EEE; border: 1px solid #888; font-size: 0.75em;  overflow:hidden }"
        + "\n"
        + "#license p b { background-color: #EEE; }"
        + "\n"
        + "#license p a { background-color: #EEE; }"
        + "\n"
        + "#input { float: left; width: 30em; padding: 1.15em .75em 1.15em 1.15em; }"
        + "\n"
        + "#input textarea { width: 100%; margin-bottom: 0.25em; padding: 0.5em; background-color: #FFF; border: 1px solid #888; font-size: 0.8em;  overflow:hidden }"
        + "\n"
        + "#input #fileUpload { margin-bottom: 0.25em; padding: 0.5em; background-color:#FFF; border: 1px solid black; font-size: 0.8em; }"
        + "\n"
        + "#input #submitButton { float: left; font-size: 0.8em; font-weight: bold; padding: 0.5em; margin-bottom: 1.5em; background-color: #FFF; border: 1px solid #888; }"
        + "\n"
        + "#switch { font-weight: normal; font-size: .8em; float: right; text-align: right; }"
        + "\n"
        + "#params { padding-top: 1.75em; padding-left: 3em; margin-left: 30em; }"
        + "\n"
        + "#params h4 { margin: 1em 0 .1em 0; padding: 0; }"
        + "\n"
        + "#params select { font-size: 0.9em;  background-color: #EEE; border: 1px solid #333; padding: 0;  }"
        + "\n"
        + "#params input { font-size: 0.9em;  background-color: #EEE; border: 1px solid #888; }"
        + "\n"
        + "#footInstructions { clear: both; float: left; width: 30em; padding: 1.15em 0.75em 1.15em 1.15em; }"
        + "\n"
        + "#instructions { width: 100%; margin-bottom: 0.25em; padding: 0.5em; background-color: #EEE; border: 1px solid #888; font-size: 0.8em;  overflow:hidden }"
        + "\n"
        + "#instructions h3 { font-weight: bold; margin: 0em 1em 0 0.5em; background: #EEE; padding-top: 0.75em; }"
        + "\n"
        + "#instructions ul { margin: 0 1.5em 0.5em 2.0em; background: #EEE; }"
        + "\n"
        + "#instructions ul li { margin: 0.75em 0 0 0; background: #EEE; }"
        + "\n"
        + "#resources { width: 100%; margin-bottom: 0.25em; margin-top: 1em; padding: 0.5em; background-color: #EEE; border: 1px solid #888; font-size: 0.8em;  overflow:hidden }"
        + "\n"
        + "#resources h3 { font-size: 1em; background: #EEE; margin: 0; padding: 0.5em 0.5em; }"
        + "\n"
        + "#instructions a { background: #EEE; }"
        + "\n"
        + "#resources h4 { font-size: 0.9em;  margin: 0; padding: 0.5em 0.5em 0 0.5em; background-color: #EEE; }"
        + "\n"
        + "#resources ul { margin: 0 1.5em 0.5em 2.0em; background: #EEE; }"
        + "\n"
        + "#resources ul li { margin: 0.5em 0 0 0; background: #EEE; }"
        + "\n"
        + "#resources ul li a { background: #EEE; font-size: 0.9em; }"
        + "\n"
        ;

}


