package org.apache.slide.projector.engine;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;

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

import org.apache.commons.contract.Processor;
import org.apache.commons.contract.Result;
import org.apache.commons.contract.descriptor.ParameterDescriptor;
import org.apache.slide.projector.Projector;
import org.apache.slide.projector.application.ApplicationManager;
import org.apache.slide.projector.constraints.ConstraintsManager;
import org.apache.slide.projector.context.HttpContext;
import org.apache.slide.projector.context.ProjectorContext;
import org.apache.slide.projector.processor.ConfigurationException;
import org.apache.slide.projector.processor.SimpleProcessor;
import org.apache.slide.projector.processor.core.ExceptionRenderer;
import org.apache.slide.projector.util.StreamHelper;
import org.apache.slide.projector.value.Streamable;
import org.apache.slide.projector.value.URI;
import org.apache.slide.projector.value.URIValue;

public class ProcessServlet extends HttpServlet {
    private static Logger logger = Logger.getLogger(ProcessServlet.class.getName());
    public final static URIValue DEFAULT_EXCEPTION_PROCESSOR = new URIValue("exception.html");
    private final static Random random = new Random(System.currentTimeMillis()); 
    private final static String allowedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    
    public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    	ApplicationManager.getInstance();
    	boolean sessionCreated = request.getSession(false) == null;
    	ProjectorContext context = new HttpContext(request, response);
        logger.log(Level.INFO, "Request uri=" + request.getRequestURL());
        logger.log(Level.INFO, "Context path=" + request.getContextPath());
        logger.log(Level.INFO, "Servlet path=" + request.getServletPath());
        URI uri = new URIValue(request.getRequestURI().substring(request.getContextPath().length()+request.getServletPath().length()+1));
        try {
            Result result;
            Processor processor;
            try {
                logger.log(Level.FINE, "Processing started with URI=" + uri);
                Map requestParameters = request.getParameterMap();
                if ( requestParameters.containsKey(Projector.PROCESS_ID_PARAMETER) ) {
                	context.setProcessId(requestParameters.get(Projector.PROCESS_ID_PARAMETER).toString());
                } else {
                    StringBuffer processIdBuffer = new StringBuffer(Projector.PROCESS_ID_LENGTH);
                	for ( int i = 0; i < Projector.PROCESS_ID_LENGTH; i++) {
                    	processIdBuffer.append(allowedChars.charAt(random.nextInt(allowedChars.length())));
                	}
                	context.setProcessId(processIdBuffer.toString());
                }
                processor = ProcessorManager.getInstance().getProcessor(uri);
                Scheduler.getInstance();
                Scheduler.setContext(context);
            	if ( sessionCreated ) {
            		// new session
                    Scheduler.getInstance().launchSessionJobs(context);
            	}
                Scheduler.getInstance().launchRequestJobs(context);
                if ( ProcessorManager.getInstance().getProcessorDescriptor(uri).isBookmark() ) {
                    context.setBookmark(uri);
                }
                ParameterDescriptor[] parameterDescriptors = processor.getParameterDescriptors();
                result = ProcessorManager.process(processor, new HashMap(requestParameters), context);
            } catch ( Exception exception ) {
                logger.log(Level.SEVERE, "Exception while processing processor with URI="+uri+"", exception);
                processor = ProcessorManager.getInstance().getProcessor(DEFAULT_EXCEPTION_PROCESSOR);
                Map exceptionParameters = new HashMap();
                exceptionParameters.put(ExceptionRenderer.EXCEPTION, exception);
                result = ProcessorManager.process(processor, exceptionParameters, context);
            }
            Object presentableResource = ((HttpContext)context).getPresentableResource();
            if (presentableResource == null) {
                // check for 'output' resource in processor result
                presentableResource = result.getResultEntries().get(SimpleProcessor.OUTPUT);
            }
            if ( presentableResource != null ) {
                if ( presentableResource instanceof Streamable ) {
                    response.setContentType(ConstraintsManager.getInstance().getContentType(presentableResource)+"; charset=UTF-8");
                    response.setContentLength(((Streamable)presentableResource).getContentLength());
                    StreamHelper.copy(((Streamable)presentableResource).getInputStream(), response.getOutputStream());
                    if (!((Streamable)presentableResource).isDocument()) {
                        logger.log(Level.FINE, "You received a fragment on top level, no complete document!");
                    }
                }
            } else {
                logger.log(Level.SEVERE, "No renderable resource composed!");
            }
        } catch (Exception e) {
            logger.log(Level.SEVERE, "Exception while processing request", e);
        } finally {
            Scheduler.setContext(null);
        }
    }
    
    public void init(ServletConfig config) {
    	String filename = config.getInitParameter("configuration");
    	try {
			Projector.configure(new FileInputStream(filename));
		} catch (ConfigurationException e) {
            logger.log(Level.SEVERE, "Invalid projector configuration. Projector not started!", e);
		} catch (FileNotFoundException e) {
            logger.log(Level.SEVERE, "Projector configuration file projector.xml not found. Projector not started!", e);
		}
    }
}