package lu.tudor.santec.gecamed.esante.utils;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import lu.tudor.santec.gecamed.core.utils.FileUtils;
import lu.tudor.santec.gecamed.core.utils.ServerConfig;
import lu.tudor.santec.gecamed.patient.ejb.entity.beans.IncidentEntry;
import lu.tudor.santec.gecamed.patient.ejb.session.interfaces.IncidentManager;
import net.sf.jasperreports.engine.JRException;

//import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.log4j.Logger;
import org.artofsolving.jodconverter.OfficeDocumentConverter;
import org.artofsolving.jodconverter.document.DefaultDocumentFormatRegistry;
import org.artofsolving.jodconverter.document.DocumentFamily;
import org.artofsolving.jodconverter.document.DocumentFormat;
import org.artofsolving.jodconverter.document.DocumentFormatRegistry;
import org.artofsolving.jodconverter.office.DefaultOfficeManagerConfiguration;
import org.artofsolving.jodconverter.office.OfficeManager;
 

/**
 * Converts different type of incident entry contents to PDF/A format
 * 
 * @author donak
 * 
 * @version <br>
 *          $Log: GecamedToPdfConverterServer.java,v $
 *          Revision 1.4  2013-12-09 17:15:37  donak
 *          Bug-fixing for upload to eSanté platform
 *
 *          Revision 1.3  2013-11-15 15:44:32  donak
 *          Suppressed upload to eSanté DSP option for letters that have already been uploaded
 *          Added workaround for the filename-bug of incident entries of type letter (filename and original filename are swapped) which came into affect when converting to pdf/a
 *
 *          Revision 1.2  2013-11-14 18:17:17  donak
 *          Fixed versioning issue between JODConverter (Java 1.6) and JBOSS (Java 1.5). Server-side PDF ==> PDF/A conversion should work now.
 *          Fixed IncidentEntry getBinaryContent(). Now a call to this functions always provides the binary content of an incident entry, if any. function works on client and server side.
 *          Implemented function to update incident entry upload status w/o updating all bean properties.
 *          Fixed issue with retrieving default user inactivity logout delay from database
 *
 *          Revision 1.1  2013-11-12 12:48:22  donak
 *          Document upload:
 *          * conversion to pdf/a using open office has been moved to the server. OpenOffice 4 has to be located in the jboss work directory. ATTENTION: it still has to be evaluated if the license agreement dialog occurs when instance is started the first time on the server.
 *          * If document contains a description, the first forty characters of the description followed by three dots will be used as title instead of the filename
 *          * Upload of incident type letters has been fixed
 *          * upload for docx files has been added
 *
 *          Upload parameters:
 *          * database does now support storage of user dependent properties
 *          * The system will automatically remember the last chosen values for confidentiality, facility type, and speciality and propose them as default when the next document will be uploaded.
 *
 *          Inactivity Monitor:
 *          * the event mouse wheel scrolling is now taken into account for resetting the logoff timer
 *          * the logoff delay is now stored in the database. If the database does not contain this parameter, it will be created
 *
 *          General:
 *          * Optimized incident entry bean handling. Caching will now avoid copying the binary content and the generated pdf content of an incident entry as these elements should only be loaded when needed. Now it should be save to re-implement a proper getBinaryContent() handling.
 * Revision 1.8 2013-10-31 16:35:21 donak Adjusted formatting of date in default name for prescriptions in upload
 *          metadata dialog Additional fixes for inactivity monitor. Remember: card watchdog becomes only active after getAuthentificationCertificate() was
 *          called. (has to be re-called after watchdog fired). Additional fixes for 2 pass conversion to pdf/a format
 * 
 *          Revision 1.7 2013-10-31 14:52:31 donak Fix: Introduced 2 pass conversion for files that are generated from dynamic content (e.g. db). That way also
 *          prescriptions are converted to pdf/a instead of only pdf. Revision 1.6 2013-10-23 09:35:13 donak Undo conversion deactivation
 * 
 *          Revision 1.4 2013-10-22 17:09:49 donak User inactivity Watchdog (currently not enabled) Fix for pdf --> pdf/a conversion
 * 
 *          Revision 1.3 2013-10-18 16:58:04 donak Improved transformation performance Incident Entries are now transformed to pdf/a instead of pdf
 * 
 *          Revision 1.2 2013-10-17 14:54:30 donak Corrected upload handler for letters Defined incident entry dependent presets for upload metadata Bug fixing
 *          and documentation
 * 
 *          Revision 1.1 2013-10-16 14:37:04 donak Finished document uploading. Increased performance, more intuitive upload process including progress
 *          indicator Created a separate library-like class for IncidentEntry to pdf conversion
 * 
 */
public class GecamedToPdfConverterServer {

	private static Logger logger = Logger.getLogger(GecamedToPdfConverterServer.class.getName());
	
	public static OfficeManager officeManager = null;
	private static OfficeDocumentConverter converter = null;
	private static OfficeDocumentConverter pdfConverter = null;
	// describes the pdf/a format
	private static final int PDFX1A2001 = 1;
	public static final String EXT_PDF = "pdf";



	/**
	 * Converts different incident entry payloads to pdf.<br>
	 * Currently supported formats are: Prescription, PDF (nothing is done in this case), OpenDocument, RTF, HTML, Word (old and new format), Excel (old and new
	 * format), PowerPoint (old and new format), and Flash.
	 * 
	 * @param incidentEntry
	 *            The incident entry of which the content should be converted to pdf/a
	 * @return The content of the incident entry in pdf/a format
	 * @throws IOException
	 *             If the incident entry data could not be found or if there is no appropriate access to the file system (e.g. privileges)
	 * @throws JRException
	 *             If the incident entry data could not be converted to pdf/a (e.g. unsuported format or missing open office installation)
	 */
	public static byte[] convert (IncidentEntry incidentEntry) throws Exception {

		String filename = null;
		String fileType = null;
		Object data = null;

		// prescription has first to be transformed to pdf
		if (IncidentManager.PRESCRIPTION.equals(incidentEntry.getEntryType().getName())) {
			// pdfs that have been generated just for export do not possess a filename. Thus a generic one is used
			fileType = EXT_PDF;
		} else if ((filename = incidentEntry.getFileName()) != null) {
//			// special treatment needed for incident entries of type letter
			if(IncidentManager.LETTER.equals(incidentEntry.getEntryType().getName())){
//				// due to a GECAMed bug the values of filename and original filename a swapped
				filename = incidentEntry.getOriginalFilename();
			}
			// this part handles all kind of file types (OpenDocument, PDF, RTF, HTML, Word, Excel, PowerPoint, and Flash).
			fileType = FilenameUtils.getExtension(filename).toLowerCase();
//			System.out.println("\n\n\nFilename is \""+filename+"\"\n\n\n");
//			System.out.println("\n\n\nOriginal filename is \""+incidentEntry.getOriginalFilename()+"\"\n\n\n");
			if(fileType==null || fileType.length()==0){
				// occurs if incident entry type is not supported
				throw new IOException("The \"" + incidentEntry.getEntryType().getName() + "\" has an invalid filename \""+filename+"\", which is missing a valid filename extension");
			}
			
		} else{
			// occurs if incident entry type is not supported
			throw new IOException("Incident entry type \"" + incidentEntry.getEntryType().getName() + "\" is currently not supported.");
		}

		// create a source file for the transformer
		File source = createTempFile("toBeConverted", fileType, incidentEntry);
		// and also a destination file for the generated pdf
		File destination = createTempFile("createdPDFA", EXT_PDF, null);
		
		try {
			// if open office has not yet been started, start it first as a service
			if (officeManager == null) {
				String openOfficeLocation = ServerConfig.getOpenOfficeDir();
				
				try {
					officeManager = new DefaultOfficeManagerConfiguration()
							.setOfficeHome(openOfficeLocation)
							.buildOfficeManager();
				} catch (IllegalArgumentException e) {
					throw new OpenOfficeException("No OpenOffice installation could be found at the expected path \""+openOfficeLocation+"\" at the GECAMed-server.", openOfficeLocation);
				}
				// and create a special converter for pdf documents (open office needs the pdf extension from oracle for this)
				DocumentFormatRegistry formatRegistry = new DefaultDocumentFormatRegistry();
				formatRegistry.getFormatByExtension("pdf").setInputFamily(DocumentFamily.DRAWING);
				pdfConverter = new OfficeDocumentConverter(officeManager, formatRegistry);
				// create a converter for all document types but pdf
				converter = new OfficeDocumentConverter(officeManager);

				// establish connection to open office
				officeManager.start();

				// and also assure that it will be stopped again if GECAMed is shut down
				Runtime.getRuntime().addShutdownHook(new Thread("close open office connection") {
					public void run() {
						// abandon the connection to open office at GECAMed shutdown, if one has been established
						if (officeManager != null) {
							officeManager.stop();
						}
					}
				});
			}

			// convert the document
			if (!EXT_PDF.equals(fileType)) {
				// this is for non-pdf source formats
				converter.convert(source, destination, toFormatPDFA());
			} else {
				// special treatment is needed for pdf ==> pdf/a conversion
				pdfConverter.convert(source, destination, toFormatPDFA_DRAW());
			}

			// obtain the pdf/a version of the incident entry content
			data = org.apache.commons.io.FileUtils.readFileToByteArray(destination);
		} catch (Exception e) {
			logger.error("Error while converting to PDF/A", e);
			throw e;
		} finally {
			// delete temp files
			FileUtils.deleteFile(source);	
			FileUtils.deleteFile(destination);	
			// and remove the temporary binary content of a prescription
			if (IncidentManager.PRESCRIPTION.equals(incidentEntry.getEntryType().getName())) {
				incidentEntry.setBinaryContent(null);
			}
		}
		
		
		return (byte[]) data;
	}


	/**
	 * Creates a temporary file used either as source or destination for the conversion of an incident entry file to pdf format
	 * 
	 * @param fileTitle
	 *            The name of the file without extension - the resulting filename will be unique
	 * @param fileType
	 *            The file extension indicating the file type (e.g. pdf, doc, or odt)
	 * @param document
	 *            (OPTIONAL) the binary data that will be written to the file or the incident entry containing the data that will be written to the temporary
	 *            file
	 * @return The temporary file either empty or, if provided, containing the data of the incident entry file or null if a file with content should be created
	 *         but the content could not be found
	 * @throws IOException
	 *             If data is neither null, a byte array, or an incident entry or if the temporary file could not be created
	 */
	private static File createTempFile(String fileTitle, String fileType, Object document) throws IOException {
		File file = FileUtils.createTempFile(fileTitle+"." + fileType);
		if (document != null) {
			byte[] data = null;

			if (document instanceof IncidentEntry) {
				// an incident entry has been provided try to load the data
//				data = ((IncidentEntry) document).getBinaryContent();
				try
				{
//					data = FileOpener.loadBinary((IncidentEntry) document);
					data = ((IncidentEntry) document).getBinaryContent();
				}
//				catch (IOException e)
//				{
//					throw e;
//				}
				catch (Exception e)
				{
					logger.error("Error while trying to load data of document.", e);
					return null;
				}
				if(data==null || data.length==0){
					// get document type and set the first character to upper case
					String entryType = Character.toUpperCase(((IncidentEntry)document).getEntryType().getName().charAt(0)) + ((IncidentEntry)document).getEntryType().getName().substring(1);
					// explain the problem a little bit more in detail
					String errorDetails = new StringBuilder(500).append("\nUnable to transform the document \"" + fileTitle + '.' + fileType + "\".\n")
							.append(entryType).append(" data of incident entry ").append(((IncidentEntry)document).getEntryTypeId()).append(" is not available.").toString();

					// indicate error to the user
					throw new IOException(errorDetails);
				}
			} else if (document instanceof byte[]) {
				// data is already available thus it can be used without any further effort
				data = (byte[]) document;
			} else {
				// explain the problem a little bit more in detail
				String errorDetails = new StringBuilder(500).append("\nUnable to transform the document \"" + fileTitle + '.' + fileType + "\".\n")
						.append("The incident data type \"").append(document.getClass().getName()).append("\" is neither expected nor supported.").toString();

				// indicate error to the user
				throw new IOException(errorDetails);
			}

			// write the document to the temp file
			FileUtils.writeFile(data, file);
		}

		return file;
	}

	/**
	 * This DocumentFormat must be used when converting from document (not pdf) to pdf/a For some reason "PDF/A-1" is called "SelectPdfVersion" internally;
	 * maybe they plan to add other PdfVersions later.
	 */
	private static DocumentFormat toFormatPDFA() {
		DocumentFormat format = new DocumentFormat("PDF/A", "pdf", "application/pdf");
		Map<String, Object> properties = new HashMap<String, Object>();
		properties.put("FilterName", "writer_pdf_Export");

		Map<String, Object> filterData = new HashMap<String, Object>();
		filterData.put("SelectPdfVersion", PDFX1A2001);
		properties.put("FilterData", filterData);

		format.setStoreProperties(DocumentFamily.TEXT, properties);

		return format;
	}

	/**
	 * This DocumentFormat must be used when converting from pdf to pdf/a For some reason "PDF/A-1" is called "SelectPdfVersion" internally; maybe they plan to
	 * add other PdfVersions later.
	 */
	private static DocumentFormat toFormatPDFA_DRAW() {
		DocumentFormat format = new DocumentFormat("PDF/A", "pdf", "application/pdf");
		Map<String, Object> properties = new HashMap<String, Object>();
		properties.put("FilterName", "draw_pdf_Export");

		Map<String, Object> filterData = new HashMap<String, Object>();
		filterData.put("SelectPdfVersion", PDFX1A2001);
		properties.put("FilterData", filterData);

		format.setStoreProperties(DocumentFamily.DRAWING, properties);

		return format;
	}
	

}
