/*******************************************************************************
 * This file is part of GECAMed.
 * 
 * GECAMed is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License (L-GPL) as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * GECAMed is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License (L-GPL)
 * along with GECAMed.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * GECAMed is Copyrighted by the Centre de Recherche Public Henri Tudor (http://www.tudor.lu)
 * (c) CRP Henri Tudor, Luxembourg, 2008
 *******************************************************************************/
package lu.tudor.santec.gecamed.labo.utils;

import java.awt.image.BufferedImage;
import java.awt.print.PrinterJob;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.Locale;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import lu.tudor.santec.gecamed.core.utils.Logger;
import lu.tudor.santec.gecamed.core.utils.entitymapper.Entity2XMLMapper;
import lu.tudor.santec.gecamed.labo.ejb.entity.beans.Result;
import lu.tudor.santec.gecamed.office.ejb.entity.beans.Office;
import lu.tudor.santec.i18n.Translatrix;

import org.apache.fop.apps.FOPException;
import org.apache.fop.apps.FOUserAgent;
import org.apache.fop.apps.Fop;
import org.apache.fop.apps.FopFactory;
import org.apache.fop.apps.MimeConstants;
import org.apache.fop.render.awt.AWTRenderer;
import org.apache.fop.render.print.PrintRenderer;
import org.apache.log4j.Level;
import org.w3c.dom.Document;

public class ResultRenderer
	{
	private Result			m_Result;
	private Document		m_XMLResult;
	private Transformer 	m_Transformer;		
	private FopFactory      m_FOPFactory;	
	
	private Office			m_Office;
	
	private static File	  m_TempDirectory = new File (System.getProperty("java.io.tmpdir"));

	private static Logger m_Logger = new Logger (ResultRenderer.class);
	
//---------------------------------------------------------------------------
//***************************************************************************
//* Constants	                                                            *
//***************************************************************************
//---------------------------------------------------------------------------

	public static final int	c_None			= 0;
	public static final int	c_XMLFormat		= 1;
	public static final int	c_HTMLFormat	= 2;
	public static final int	c_PDFFormat		= 3;
	public static final int c_ImageFormat	= 4;
	public static final int c_PrintFormat	= 5;
	
	private static final String c_NoTemplate 	= "";
	private static final String c_HTMLTemplate 	= "detailed_html";
	private static final String c_FOPTemplate 	= "detailed_fo";
	
    private static final String m_Templates [] =  { c_NoTemplate,
    												c_NoTemplate,
    												c_HTMLTemplate,
    												c_FOPTemplate,
    												c_FOPTemplate,
    												c_FOPTemplate};
		
	private static final int	c_BufferSize  = 4096;
	
	private static final String c_XSLPath = "/lu/tudor/santec/gecamed/labo/repository/xsl/";
	
//---------------------------------------------------------------------------
//***************************************************************************
//* Constructor	                                                            *
//***************************************************************************
//---------------------------------------------------------------------------

public ResultRenderer (Result p_Result)
	{
	m_Result    = p_Result;	
	m_XMLResult = null;
	m_Office    = null;
	
	this.setupXSL ("html_common.xsl");	
	this.setupXSL ("fo_common.xsl");	
	}
	
//---------------------------------------------------------------------------
//***************************************************************************
//* Primitives	                                                            *
//***************************************************************************
//---------------------------------------------------------------------------

private File setupXSL (String p_XSLFileName)
	{
	File				l_XSLFile;
	
	l_XSLFile = new File (m_TempDirectory.getPath() + File.separator + p_XSLFileName);
	if (!l_XSLFile.canRead()) this.copyXSLToTempDirectory(l_XSLFile);	
	return l_XSLFile;
	}

//---------------------------------------------------------------------------

private void copyXSLToTempDirectory (File p_XSLFile)
	{
	InputStream			l_XSLResource;
	FileOutputStream	l_XSLStream;
	byte[]				l_Buffer;
	int					l_BytesRead;
					
	l_Buffer = new byte [c_BufferSize];

	try	{
		l_XSLResource = ResultParser.class.getResourceAsStream(c_XSLPath + p_XSLFile.getName());
		l_XSLStream = new FileOutputStream (p_XSLFile.getPath());
		
		do	{
			l_BytesRead = l_XSLResource.read(l_Buffer, 0, c_BufferSize);
			if (l_BytesRead > 0) l_XSLStream.write(l_Buffer, 0, l_BytesRead);
			}
		while (l_BytesRead > 0);
		
		l_XSLStream.close();
		l_XSLResource.close();
		p_XSLFile.deleteOnExit();
		}
	catch (Exception p_Exception)
		{
		m_Logger.log(Level.FATAL, "Failed to copy into temp directory " + p_XSLFile.getPath(),p_Exception);
		}		
	}

//---------------------------------------------------------------------------

private File getXSLTemplate (int p_Template)
	{
	String	l_TemplateFileName;	
	Locale	l_CurrentLocale;
	File	l_TemplateFile = null;
	
	if ((p_Template > c_None) && (p_Template <= m_Templates.length))	
		{
		l_TemplateFileName = m_Templates [p_Template];
		l_CurrentLocale    = Translatrix.getLocale();	
		
		if (l_TemplateFileName.length() > 0)
			{
			l_TemplateFileName += "_" + l_CurrentLocale.getLanguage () + "_" + 
										l_CurrentLocale.getCountry () + ".xsl";
		
			l_TemplateFile = this.setupXSL (l_TemplateFileName);
			}
		}
	
	return l_TemplateFile;
	}

//---------------------------------------------------------------------------

private Document generateXML (Result p_Result)
	{
	DocumentBuilderFactory	l_Factory;
	DocumentBuilder			l_Builder;
	Document				l_Document = null;
	Entity2XMLMapper		l_Mapper;
	
	try {
		l_Factory = DocumentBuilderFactory.newInstance();
		l_Builder = l_Factory.newDocumentBuilder();
      
		l_Document = (Document)l_Builder.newDocument();
		
		l_Mapper = new Entity2XMLMapper (l_Document, false);
	
		l_Mapper.mapSingle 	  (p_Result.getLaboratory(), "/lu/tudor/santec/gecamed/labo/mapping/Laboratory.properties");	
		l_Mapper.mapRepeating (p_Result.getLaboratory().getAddresses(), "/lu/tudor/santec/gecamed/labo/mapping/LaboratoryAddress.properties");
		l_Mapper.mapRepeating (p_Result.getContactPersons(), "/lu/tudor/santec/gecamed/labo/mapping/Contact.properties");
		l_Mapper.mapSingle    (p_Result.getPatient(), "/lu/tudor/santec/gecamed/labo/mapping/Patient.properties");	
		l_Mapper.mapSingle    (p_Result.getPatient().getBillingAddress(), "/lu/tudor/santec/gecamed/labo/mapping/PatientAddress.properties");
		l_Mapper.mapSingle    (p_Result.getPrescriber(), "/lu/tudor/santec/gecamed/labo/mapping/Physician.properties");	
		
		if ((p_Result.getPrescriber() != null) && (p_Result.getPrescriber().getPhysicianAddress() != null))
			{
			l_Mapper.mapSingle    (p_Result.getPrescriber().getPhysicianAddress(), "/lu/tudor/santec/gecamed/labo/mapping/PhysicianAddress.properties");	
			}	
		else if (m_Office != null)
			{
			l_Mapper.mapSingle    (m_Office.getLatestOfficeAddress(), "/lu/tudor/santec/gecamed/labo/mapping/PhysicianAddress.properties");		
			}
		
		l_Mapper.mapSingle    (p_Result, "/lu/tudor/santec/gecamed/labo/mapping/Result.properties");	
			
		l_Document = l_Mapper.getXMLDocument();
		}
	catch(ParserConfigurationException p_Exception) 
		{
		m_Logger.log (Level.ERROR,"Caught an exception while generating XML!",p_Exception);    
		}
	
	return l_Document;
	}
	
//---------------------------------------------------------------------------

private FopFactory getFopFactory ()
	{
	if (m_FOPFactory == null) m_FOPFactory = FopFactory.newInstance();

	return m_FOPFactory;
	}

//---------------------------------------------------------------------------

private FOUserAgent newUserAgent (FopFactory p_Factory)
	{
	FOUserAgent l_UserAgent;
		
	l_UserAgent = p_Factory.newFOUserAgent();
	l_UserAgent.setTitle("LaboResult/" + m_Result.getLaboratory().getName() + "/" + m_Result.getReference());
	l_UserAgent.setProducer ("GECAMed");
	l_UserAgent.setCreationDate (new Date());
	
	return l_UserAgent;
	}

//---------------------------------------------------------------------------
//***************************************************************************
//* Class Body	                                                            *
//***************************************************************************
//---------------------------------------------------------------------------

public void setTemplate (int p_Template)
    {
    TransformerFactory      l_Factory       = null;
    FileInputStream    		l_InputStream   = null;
    StreamSource            l_XSLSource     = null;
    File					l_XSLFile		= null; 
    
    l_XSLFile = this.getXSLTemplate(p_Template);
    if (l_XSLFile == null) return;
    
    try {
        l_InputStream = new FileInputStream (l_XSLFile);
        l_XSLSource   = new StreamSource (l_InputStream,m_TempDirectory.getPath() + File.separator);
        l_Factory     = TransformerFactory.newInstance();
        m_Transformer = l_Factory.newTransformer(l_XSLSource);
         }
    catch (Exception p_Exception)
        {
        m_Logger.log (Level.WARN,"Caught an exception while setting XSL Template!",p_Exception);    
        }
    }

//---------------------------------------------------------------------------

public void setOffice (Office p_Office)
	{
	m_Office = p_Office;	
	}

//---------------------------------------------------------------------------

public byte[] renderAsXML ()
	{
	Source					l_DOMSource		= null;
	ByteArrayOutputStream   l_OutputStream  = null;  
	Transformer 			l_Transformer	= null;
	
	if (m_XMLResult == null) m_XMLResult = this.generateXML (m_Result);
	
	l_DOMSource = new DOMSource (m_XMLResult);
	l_OutputStream = new ByteArrayOutputStream ();
		
	StreamResult l_Output = new StreamResult (l_OutputStream);		
	
	try	{
		l_Transformer = TransformerFactory.newInstance().newTransformer();
        l_Transformer.setOutputProperty(OutputKeys.INDENT, "yes");
		l_Transformer.transform(l_DOMSource, l_Output);      
		}
	catch (TransformerException p_Exception)
		{
		m_Logger.log (Level.ERROR,"Failed to render result as HTML!",p_Exception); 	
		}
		
    return l_OutputStream.toByteArray();
	}

//---------------------------------------------------------------------------

public byte[] renderAsHTML ()
	{
	Source					l_DOMSource		= null;
	ByteArrayOutputStream   l_OutputStream  = null;  
	
	if (m_Transformer == null) return null;
	
	if (m_XMLResult == null) m_XMLResult = this.generateXML (m_Result);
	
	l_DOMSource = new DOMSource (m_XMLResult);
	l_OutputStream = new ByteArrayOutputStream ();
		
	StreamResult l_Output = new StreamResult (l_OutputStream);		
	
	try	{
		m_Transformer.transform(l_DOMSource, l_Output);      
		}
	catch (TransformerException p_Exception)
		{
		m_Logger.log (Level.ERROR,"Failed to render result as HTML!",p_Exception); 	
		}
		
    return l_OutputStream.toByteArray();
	}

//---------------------------------------------------------------------------

public byte[] renderAsPDF ()
	{
	Source					l_DOMSource		= null;
	ByteArrayOutputStream   l_OutputStream  = null;  
	SAXResult				l_SAXResult		= null;
	FopFactory				l_FOPFactory;
	Fop						l_FOPProcessor;
	FOUserAgent				l_UserAgent;
	
	if (m_Transformer == null) return null;
		
	if (m_XMLResult == null) m_XMLResult = this.generateXML (m_Result);
	
	l_DOMSource = new DOMSource (m_XMLResult);
	l_OutputStream = new ByteArrayOutputStream ();
	
	try	{
		l_FOPFactory = this.getFopFactory();
		l_FOPFactory.setStrictValidation(false);
		l_UserAgent = this.newUserAgent(l_FOPFactory);	
		l_FOPProcessor = l_FOPFactory.newFop(MimeConstants.MIME_PDF, l_UserAgent, l_OutputStream);
        
        // Resulting SAX events (the generated FO) must be piped through to FOP
        
        l_SAXResult = new SAXResult (l_FOPProcessor.getDefaultHandler());
    
        //Start XSLT transformation and FOP processing
       
        m_Transformer.transform (l_DOMSource, l_SAXResult);      
 		}
	catch (FOPException p_Exception)
		{
		m_Logger.log (Level.ERROR,"Failed to render result as PDF!",p_Exception); 	
		}
	catch (TransformerException p_Exception)
		{
		m_Logger.log (Level.ERROR,"Failed to render result as PDF!",p_Exception); 	
		}
	finally 
		{
		try	{
			l_OutputStream.close();
			}
		catch (IOException p_Exception)
			{		
			//Can be safely ignored.
			}
		}

    return l_OutputStream.toByteArray();
	}

//---------------------------------------------------------------------------

public BufferedImage[] renderAsImage ()
	{
	Source					l_DOMSource		= null;
	SAXResult				l_SAXResult		= null;
	FopFactory				l_FOPFactory;
	Fop						l_FOPProcessor;
	FOUserAgent				l_UserAgent;
	AWTRenderer 			l_Renderer;
	BufferedImage[]			l_Images		= null;
	int						l_CurrentPage;
	
	if (m_Transformer == null) return null;
		
	if (m_XMLResult == null) m_XMLResult = this.generateXML (m_Result);
	
	l_DOMSource = new DOMSource (m_XMLResult);
	
	try	{
		l_FOPFactory = this.getFopFactory();
		l_FOPFactory.setStrictValidation(false);
		l_UserAgent = this.newUserAgent(l_FOPFactory);	

//		l_Renderer = new AWTRenderer ();
		l_Renderer = new AWTRenderer (l_UserAgent);
//		l_Renderer.setPreviewDialogDisplayed (false);
		l_Renderer.setScaleFactor(1);
        
		l_UserAgent.setRendererOverride (l_Renderer);            
//		l_Renderer.setUserAgent (l_UserAgent);
		
		
		l_FOPProcessor = l_FOPFactory.newFop(l_UserAgent);
       
        l_SAXResult = new SAXResult (l_FOPProcessor.getDefaultHandler());
         
        //Start XSLT transformation and FOP processing
       
        m_Transformer.transform (l_DOMSource, l_SAXResult);      
 		
        l_Images = new BufferedImage[l_Renderer.getNumberOfPages()];
        for (l_CurrentPage = 0; l_CurrentPage < l_Images.length; l_CurrentPage++)
        	{
        	l_Images[l_CurrentPage] = l_Renderer.getPageImage(l_CurrentPage);
        	}
        }
	catch (FOPException p_Exception)
		{
		m_Logger.log (Level.ERROR,"Failed to render result as Image!",p_Exception); 	
		}
	catch (TransformerException p_Exception)
		{
		m_Logger.log (Level.ERROR,"Failed to render result as Image!",p_Exception); 	
		}
	
	return l_Images;
	}

//---------------------------------------------------------------------------

public void print (boolean p_WithDialog)
	{
	Source					l_DOMSource		= null;
	SAXResult				l_SAXResult		= null;
	FopFactory				l_FOPFactory;
	Fop						l_FOPProcessor;
	FOUserAgent				l_UserAgent;
	PrintRenderer			l_Renderer;
	PrinterJob 				l_PrintJob;
	
	if (m_Transformer == null) return;
		
	if (m_XMLResult == null) m_XMLResult = this.generateXML (m_Result);
	
	l_DOMSource = new DOMSource (m_XMLResult);
	
	l_PrintJob = PrinterJob.getPrinterJob();	
	if (p_WithDialog) 
		{
		if (l_PrintJob.printDialog())
			{
			try	{
				l_FOPFactory = this.getFopFactory();
				l_FOPFactory.setStrictValidation(false);
				l_UserAgent = this.newUserAgent(l_FOPFactory);	
		
//				l_Renderer = new PrintRenderer (l_PrintJob);
				l_Renderer = new PrintRenderer (l_UserAgent);
				l_UserAgent.setRendererOverride (l_Renderer);            
//		        l_Renderer.setUserAgent (l_UserAgent);
				
				l_FOPProcessor = l_FOPFactory.newFop(l_UserAgent);
		       
		        l_SAXResult = new SAXResult (l_FOPProcessor.getDefaultHandler());
		       
		        m_Transformer.transform (l_DOMSource, l_SAXResult);      
		        }
			catch (FOPException p_Exception)
				{
				m_Logger.log (Level.ERROR,"Failed to print result!",p_Exception); 	
				}
			catch (TransformerException p_Exception)
				{
				m_Logger.log (Level.ERROR,"Failed to print result!",p_Exception); 	
				}
			}
		}
	}

//---------------------------------------------------------------------------
//***************************************************************************
//* End of Class                                                            *
//***************************************************************************
//---------------------------------------------------------------------------
	}
