package net.sourceforge.rtf.web.servlet;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;

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.springframework.context.ApplicationContext;

import net.sourceforge.rtf.RTFTemplate;
import net.sourceforge.rtf.document.RTFDocument;
import net.sourceforge.rtf.document.transformer.config.DigesterTransformerConfig;
import net.sourceforge.rtf.document.transformer.config.TransformerConfig;
import net.sourceforge.rtf.helper.RTFTemplateBuilder;
import net.sourceforge.rtf.template.IContext;
import net.sourceforge.rtf.web.RTFTemplateException;

/**
 * 
 * Abstract class RTFTemplate servlet to manage RTFTEmplate with Web Application.
 * Method to implement are : 
 * <ul>
 * 	<li><b>getRTFReader</b> : must return Reader of the RTF model source to use.</li>
 *  <li><b>putContext</b> : must return Reader of the RTF model source to use.</li>
 *  <li><b>getXMLFieldsAvailable</b> : must return XML fileds available 
 *  for the RTF model or null if there is not XML Fileds available.</li> 
 * </ul> 
 * @version 1.0.0 
 * @author <a href="mailto:angelo.zerr@gmail.com">Angelo ZERR</a>
 *
 */
public abstract class AbstractRTFTemplateServlet extends HttpServlet {

	public static final String RTF_CONTENT_TYPE = "application/rtf";
	
    /**
     * Map which contains transformed document cached.
     */
    private Map cachedTransformedDocumentMap = new HashMap();
    
    private ApplicationContext globalApplicationContext;
    
    public void init() {
        globalApplicationContext = getGlobalApplicationContext();
    }
    
    /**
     * Handles HTTP <code>GET</code> requests by calling
     * { #doRequest()}.
     */
    public void doGet( HttpServletRequest request, HttpServletResponse response )
        throws ServletException, IOException
    {
    	doRequest(request, response);
    }

    /**
     * Handles HTTP <code>POST</code> requests by calling
     * { #doRequest()}.
     */
    public void doPost( HttpServletRequest request, HttpServletResponse response )
        throws ServletException, IOException
    {
        doRequest(request, response);
    }
    

    /**
     *  Handles all requests (by default).
     *
     * @param request HttpServletRequest object containing client request
     * @param response HttpServletResponse object for the response
     * @throws ServletException the exception.
     * @throws IOException the exception.
     */
    protected void doRequest(HttpServletRequest request, HttpServletResponse response )
         throws ServletException, IOException
    {
    	try {
    		
            // Get spring configuration for the model.
            ApplicationContext applicationContext = getApplicationContext(request);
            if (applicationContext == null) {
                // Get global Spring configuration 
                applicationContext = globalApplicationContext;
            }
            
            // Test if RTFDocument must be un cached
            String cacheWitkKey = cacheWithKey(request);
            // Test if RTFDocument must be un cached
            String unCackeWitkKey = unCacheWithKey(request);
            if (unCackeWitkKey != null) {
                // the transformed document must be un cached.
                cachedTransformedDocumentMap.remove(unCackeWitkKey);
            }
            
            /**
             * 2. Get RTFtemplate builder
             */
            RTFTemplateBuilder builder = null;            
            if (applicationContext == null)
                builder = RTFTemplateBuilder.newRTFTemplateBuilder();
            else 
                builder = RTFTemplateBuilder.newRTFTemplateBuilder(applicationContext);
            
            /**
             * 3. Get RTFtemplate with Implementation
             */ 
            String rtfTemplateImpl = getRTFTemplateImpl(request);
            RTFTemplate rtfTemplate = (rtfTemplateImpl != null ? builder.
                newRTFTemplate(rtfTemplateImpl) : builder.newRTFTemplate()); 
            rtfTemplate.setGroupByPerPageBreak(getGroupByPerPageBreak(request));
            
            /**
             * 4. Put default format
             */
            putDefaultFormat(request, rtfTemplate);
            
            /**
             * 5. Create a common inner context - not required but showing how common context values can be re-used
             */
            IContext ctx = rtfTemplate.getTemplateEngine().newContext();
            putGlobalContext(request, ctx);
            
            /**
             * 6. Set the template
             */ 
            boolean isAlreadyCached = this.setTemplate(request, rtfTemplate, cacheWitkKey);
            
            /**
             * 8. Put Context
             */          
            putContext(request, rtfTemplate.getContext());
	        

	   
            /* 
             *   set the content type 
             */
            setContentType( request, response );
            
            String fileName = getFileNameOfContentDisposition(request);
            if (fileName != null) {
            	/* 
                 *   set the filename of content disposition
                 */
            	setContentDisposition(response, fileName);
            }
            
	        /**
             * 10. Write to servlet Output 
	         *     result of Merge data with RTF model
	         */
	        Writer writer = response.getWriter();
			rtfTemplate.merge( writer);
            
            if (cacheWitkKey != null &&  !isAlreadyCached) {
                cachedTransformedDocumentMap.put(cacheWitkKey, rtfTemplate.getTransformedDocument());
            }
			

    	}
    	catch (Exception e)
        {
            /*
             *  call the error handler to let the derived class
             *  do something useful with this failure.
             */
            error( request, response, e);
        }
    }

    /**
     * Invoked when there is an error thrown in any part of doRequest() processing.
     * <br><br>
     * Default will send a simple HTML response indicating there was a problem.
     * 
     * @param request original HttpServletRequest from servlet container.
     * @param response HttpServletResponse object from servlet container.
     * @param cause  Exception that was thrown by some other part of process.
     * @throws ServletException the exception.
     * @throws IOException the exception.     
     */
    protected void error( HttpServletRequest request, HttpServletResponse response, Exception cause )
        throws ServletException, IOException
    {
        StringBuffer html = new StringBuffer();
        html.append("<html>");
        html.append("<title>Error</title>");
        html.append("<body bgcolor=\"#ffffff\">");
        html.append("<h2>RTFTemplateServlet: Error processing RTF edition</h2>");
        html.append("<pre>");
        String why = cause.getMessage();
        if (why != null && why.trim().length() > 0)
        {
            html.append(why);
            html.append("<br>");
        }

        StringWriter sw = new StringWriter();
        cause.printStackTrace( new PrintWriter( sw ) );

        html.append( sw.toString()  );
        html.append("</pre>");
        html.append("</body>");
        html.append("</html>");
        response.getOutputStream().print( html.toString() );
        
    }
    
    /**
     * Set content type to RTF.
     * @param request the request.
     * @param response the response.
     */
    protected void setContentType(HttpServletRequest request,
            HttpServletResponse response) {
    	response.setContentType(RTF_CONTENT_TYPE);
	}
    
    /**
     * Set FileName of Content Disposition
     * @param response the response.
     * @param fileName the fileName.
     */
    protected void setContentDisposition(HttpServletResponse response, String fileName) {
    	response.setHeader("Content-Disposition", "attachment; filename=\""+fileName+"\"");
    }
    
    /**
     * This method to implement must return Reader of the RTF model source to use.
     * @param request the request.
     * @return the result.
     * @throws Exception the exception.
     */
    protected Reader getRTFReader( HttpServletRequest request ) throws Exception {
        return null;
    }
    

    /**
     * This method to implement must return InputStream of the RTF model source to use.
     * @param request the request.
     * @return the result.
     * @throws Exception the exception.
     */
    protected InputStream getRTFInputStream( HttpServletRequest request ) throws Exception {
        return null;
    }
    
    /**
     * This method to implement must put context for the RTF model source to use.
     * @param request the request.
     * @param ctx the ctx.
     * @throws Exception the exception.
     */
    protected abstract void putContext(HttpServletRequest request, IContext ctx ) throws Exception;

    /**
     * This method to implement must return XML fileds available for the RTF model or null if there is not XML Fileds available.
     * @param request the request.
     * @return the result.
     * @throws Exception the exception.
     */
    protected abstract InputStream getXMLFieldsAvailable( HttpServletRequest request ) throws Exception; 
    
    /**
     * If this method is implemented, RTFTemplate use this TransformerConfig,
     * oterwise, RTFTemplate use default TransformerConfig.
     * @param request the request.
     * @return the result.
     */
    protected InputStream getTransformerConfig(HttpServletRequest request) {
    	return null;
    }
    
    /**
     * Return the real path of RTF Model.
     * @param request the request.
     * @param rtfModelFile the rtf Model File.
     * @return the result.
     */
    protected String getRealPathOfRTFModel(HttpServletRequest request , String rtfModelFile) {
    	return request.getRealPath("") +  rtfModelFile;
    }
    
    protected String getFileNameOfContentDisposition(HttpServletRequest request) {
    	return null;
    }
    
    protected String getRTFTemplateImpl(HttpServletRequest request) {
        return null;
    }
    
    protected void putDefaultFormat(HttpServletRequest request, RTFTemplate rtfTemplate) {
        
    }
    
    protected void putGlobalContext(HttpServletRequest request, IContext newContext) {
        
    }
    
    protected int getGroupByPerPageBreak(HttpServletRequest request) {
        return -1;
    }
    
    protected String cacheWithKey(HttpServletRequest request) {
        return null;
    }
    
    protected String unCacheWithKey(HttpServletRequest request) {
        return null;
    }
    
    /**
     * Return global Spring configuration for RTFTemplate
     * @return the result.
     */
    protected ApplicationContext getGlobalApplicationContext() {
        return null;
    }
    
    /**
     * Return Spring configuration for RTFTemplate
     * @param request the request.
     * @return the result.
     */
    protected ApplicationContext getApplicationContext(HttpServletRequest request) {
        return null;
    }
    
    private boolean setTemplate(HttpServletRequest request, RTFTemplate rtfTemplate, String cackeWitkKey) throws Exception {
        // test if RTF model is cached
        if (cackeWitkKey != null) {
            RTFDocument cachedTransformedDocument = (RTFDocument)cachedTransformedDocumentMap.get(cackeWitkKey);
            if (cachedTransformedDocument != null) {
                // Use cached Transformed Document
                rtfTemplate.setTransformedDocument(cachedTransformedDocument);
                return true;
            }
        }
        
        // there is no cache, or the template is not cached, 
        // load it
        /**
         * 1. Get RTF Reader or InputStream
         */
        InputStream rtfInputStream = null;
        Reader rtfReader = getRTFReader(request);
        if (rtfReader == null) {
            rtfInputStream = getRTFInputStream(request);
            if (rtfInputStream == null)
            throw new RTFTemplateException("Impossible to launch RTF edition. Method RTFTemplateServlet.getRTFReader or RTFTemplateServlet.getRTFInputStream must not return null.");
        }
        if(rtfReader != null)
            rtfTemplate.setTemplate(rtfReader);
        else
            rtfTemplate.setTemplate(rtfInputStream);
        
        /**
         * 9. Set XML fields available
         */
        InputStream xmlFieldsStream = getXMLFieldsAvailable(request);
        rtfTemplate.setXmlFields(xmlFieldsStream);
        
        /**
         * 7. Set Transformer Config
         */     
        InputStream transformerConfigStream = getTransformerConfig(request);
        if (transformerConfigStream != null) {
            TransformerConfig transformConfig = DigesterTransformerConfig.
                getTransformerConfig(transformerConfigStream);
            rtfTemplate.setTransformerConfig(transformConfig);
        }        
        return false;
    }
    
}
