/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with this
 * work for additional information regarding copyright ownership. The ASF
 * licenses this file to You 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.apache.stanbol.enhancer.benchmark.impl;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.LinkedList;
import java.util.List;
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.clerezza.rdf.core.Graph;
import org.apache.clerezza.rdf.core.serializedform.Serializer;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.LineIterator;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.stanbol.enhancer.benchmark.Benchmark;
import org.apache.stanbol.enhancer.benchmark.BenchmarkParser;
import org.apache.stanbol.enhancer.servicesapi.Chain;
import org.apache.stanbol.enhancer.servicesapi.ChainManager;
import org.apache.stanbol.enhancer.servicesapi.ContentItemFactory;
import org.apache.stanbol.enhancer.servicesapi.EnhancementJobManager;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.tools.generic.EscapeTool;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.http.HttpService;
import org.osgi.service.http.NamespaceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/** Front-end servlet for the benchmark module */
@Component(immediate=true, metatype=false)
@SuppressWarnings("serial")
public class BenchmarkServlet extends HttpServlet {
    
    private final Logger log = LoggerFactory.getLogger(getClass());
    
    @Reference
    private HttpService httpService;
    
    @Reference
    private BenchmarkParser parser;
    
    @Reference
    private EnhancementJobManager jobManager;

    @Reference
    private ContentItemFactory ciFactory;
    
    @Reference
    private Serializer graphSerializer;
    /**
     * Needed to lookup active enhancement changes as parsed by {@link ChainState}
     */
    @Reference
    private ChainManager chainManager;
   
    public static final String PARAM_CONTENT = "content";
    private static final String PARAM_CHAIN = "chain";
    
    public static final String DEFAULT_MOUNT_PATH = "/benchmark";
    public static final String DEFAULT_BENCHMARK = "default.txt";
    public static final String EXAMPLE_BENCHMARKS_RESOURCE_ROOT = "/examples";
    
    @Property(value=DEFAULT_MOUNT_PATH)
    public static final String MOUNT_PATH_PROPERTY = "mount.path";

    private String mountPath; 
    private final VelocityEngine velocity = new VelocityEngine();
    
    // Formatter for benchmark graphs
    public static class GraphFormatter {
        private final Serializer serializer;
        
        GraphFormatter(Serializer s) {
            serializer = s;
        }
        
        public String format(Graph g, String mimeType) throws UnsupportedEncodingException {
            final ByteArrayOutputStream bos = new ByteArrayOutputStream();
            serializer.serialize(bos, g, mimeType);
            return bos.toString("UTF-8");
        }
    };

    /** Register with HttpService when activated */
    public void activate(ComponentContext ctx) throws ServletException, NamespaceException {
        mountPath = (String)ctx.getProperties().get(MOUNT_PATH_PROPERTY);
        if(mountPath == null) {
            mountPath = DEFAULT_MOUNT_PATH;
        }
        if(mountPath.endsWith("/")) {
            mountPath = mountPath.substring(mountPath.length() - 1);
        }
        
        httpService.registerServlet(mountPath, this, null, null);
        log.info("Servlet mounted at {}", mountPath);
        
        final Properties config = new Properties();
        config.put("class.resource.loader.description", "Velocity Classpath Resource Loader");
        config.put("class.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
        config.put("resource.loader","class");
        velocity.init(config);
    }
    
    public void deactivate(ComponentContext ctx) {
        httpService.unregister(mountPath);
    }
    
    @Override
    public String getServletInfo() {
        return "Apache Stanbol Enhancer Benchmarks";
    }

    private VelocityContext getVelocityContext(HttpServletRequest request, String pageTitle) {
        final VelocityContext ctx = new VelocityContext();
        ctx.put("title", getServletInfo() + " - " + pageTitle);
        ctx.put("contextPath", request.getContextPath());
        ctx.put("cssPath", request.getContextPath() + mountPath + "/benchmark.css");
        ctx.put("esc", new EscapeTool());
        return ctx;
    }
    
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) 
    throws ServletException, IOException {
        
        final String path = request.getPathInfo() == null ? "" : request.getPathInfo(); 
        if(path.endsWith(".css")) {
            // Serve our css
            final Template t = velocity.getTemplate("/velocity/benchmark.css");
            response.setContentType("text/css");
            response.setCharacterEncoding("UTF-8");
            t.merge(getVelocityContext(request, null), response.getWriter());
            
        } else if(path.length() < 2){
            // No benchmark specified -> redirect to default
            response.sendRedirect(getExampleBenchmarkPath(request, DEFAULT_BENCHMARK));
            
        } else {
            // Benchmark input form pre-filled with selected example
            final Template t = velocity.getTemplate("/velocity/benchmark-input.html");
            final VelocityContext ctx = getVelocityContext(request, "Benchmark Input");
            ctx.put("formAction", request.getContextPath() + mountPath);
            ctx.put("benchmarkText", getBenchmarkText(path)); 
            ctx.put("benchmarkPaths", getExampleBenchmarkPaths(request)); 
            ctx.put("currentBenchmarkPath", path); 
            response.setContentType("text/html");
            response.setCharacterEncoding("UTF-8");
            t.merge(ctx, response.getWriter());
        }
    }
    
    private String getExampleBenchmarkPath(HttpServletRequest request, String name) {
        return request.getContextPath() + mountPath + "/" + name;
    }
    
    private List<String> getExampleBenchmarkPaths(HttpServletRequest request) throws IOException {
        // TODO how to enumerate bundle resources?
        final String list = getBenchmarkText("/LIST.txt");
        final LineIterator it = new LineIterator(new StringReader(list));
        final List<String> result = new LinkedList<String>();
        while(it.hasNext()) {
            result.add(getExampleBenchmarkPath(request, it.nextLine()));
        }
        return result;
    }

    /** Return example benchmark text from our class resources */
    private String getBenchmarkText(String path) throws IOException {
        final InputStream is = getClass().getResourceAsStream(EXAMPLE_BENCHMARKS_RESOURCE_ROOT + path);
        if(is == null) {
            return "";
        }
        
        try {
            return IOUtils.toString(is);
        } finally {
            is.close();
        }
    }
    
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
    throws ServletException, IOException {
        final String content = request.getParameter(PARAM_CONTENT);
        if(content == null) {
            throw new ServletException("Missing " + PARAM_CONTENT + " parameter");
        }
        String chainName = request.getParameter(PARAM_CHAIN);
        final Template t = AccessController.doPrivileged(new PrivilegedAction<Template>() {
            @Override
            public Template run() {
                return velocity.getTemplate("/velocity/benchmark-results.html");
            }
        });
        final VelocityContext ctx = getVelocityContext(request, "Benchmark Results");
        ctx.put("contentItemFactory", ciFactory);
        ctx.put("jobManager", jobManager);
        List<? extends Benchmark> benchmarks = parser.parse(new StringReader(content));
        if(chainName != null && !chainName.isEmpty()){
            Chain chain = chainManager.getChain(chainName);
            if(chain == null){
                response.setStatus(404);
                PrintWriter w = response.getWriter();
                w.println("Unable to perform benchmark on EnhancementChain '"
                    +chainName+"' because no chain with that name is active!");
                IOUtils.closeQuietly(w);
                return;
            }
            for(Benchmark benchmark : benchmarks){
                benchmark.setChain(chain);
            }
        }
        ctx.put("benchmarks", benchmarks);
        ctx.put("graphFormatter", new GraphFormatter(graphSerializer));
        
        response.setContentType("text/html");
        response.setCharacterEncoding("UTF-8");
        t.merge(ctx, response.getWriter());
    }
}