/*
 * This file (PdfGenerator.java) is part of DebGen-ReportPrintLib  developed by ASTRID Research.
 * All rights reserved. Reproduction, modification, use or disclosure
 * to third parties without express authority is forbidden.
 *
 *  Copyright ASTRID Research, Hungary, 2010.
 *
 *  ASTRID Research
 *  Headquaters: Csapó utca 42. Debrecen H-4029 HUNGARY
 *  Phone: +36-52-533-173        Fax: +36-52-533-174
 *  Web: http://www.astrid.hu    E-mail: info@astrid.hu
 *
 * Project name: DEBGEN-Biobank-Project.
 * File last modified at 2010.02.01 19:53
 */
package hu.astrid.printxml.generator;

import hu.astrid.printxml.exception.PatientPrintException;
import hu.astrid.printxml.util.ReadFromXML;
import net.sf.jasperreports.engine.*;
import net.sf.jasperreports.engine.data.JRXmlDataSource;
import net.sf.jasperreports.engine.export.JRPdfExporter;
import net.sf.jasperreports.engine.util.JRProperties;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.*;
import java.net.URI;
import java.util.HashMap;
import java.util.Locale;

/**
 * Abstract class of PDF generators
 *
 * @author inagy
 * @param <T>
 */
public abstract class PdfGenerator<T> {

    /**
     * The Hungarian Locale
     */
    public static final Locale LOCALE_HUN = new Locale("hu");
    private Locale locale = LOCALE_HUN;
    // no need to "select" these fields from the xml document
    //private static final String RECORD_PATH = "analysis/biobankData/labels/sampleCode";
    /**
     * The target file (temp file)
     */
    protected File targetFile;
    protected String resourceName = "";

    /**
     * Default constructor
     */
    public PdfGenerator() {
    }

    /**
     * Creates a new PdfGenerator instance
     *
     * @param locale The locale of the pdf file
     */
    public PdfGenerator(Locale locale) {
        this.locale = locale;
    }

    /**
     * Returns the name of the jasper resource file, that will be used
     *
     * @return resourceName - The name of the jasper file starting with / and ending without the language code and the .jasper extension
     */
    public String getResourceName() {
        return resourceName;
    }

    /**
     * Sets the value of the .jasper resource file.
     *
     * @param resourceName The new name of the jasper file starting with / and ending without the language code and the .jasper extension
     */
    public void setResourceName(String resourceName) {
        this.resourceName = resourceName;
    }

    /**
     * Returns the generated PDF file as an URI
     *
     * @param input The input source
     * @return The pdf files URI
     * @throws PatientPrintException When the PDF generation isn't possible
     */
    public abstract URI getAsURI(T input) throws PatientPrintException;

    /**
     * Returns the generated PDF file as a Stream
     *
     * @param input The input source
     * @return The pdf files Stream
     * @throws PatientPrintException When the PDF generation isn't possible
     */
    public abstract InputStream getAsStream(T input) throws PatientPrintException;

    /**
     * Set the target targetFile where the pdf targetFile will be generated.
     * If targetFile is not set then the file will be generated to the temp directory.
     *
     * @param target Target file.
     */
    public void setTargetFile(File target) {
        targetFile = target;
    }

    /**
     * Returns the Target File
     *
     * @return the Target File
     */
    public File getTargetFile() {
        return targetFile;
    }

    /**
     * Generates a PDF File and returns the File object
     *
     * @param document The Document which contains the XML data
     * @return The PDF File
     * @throws PatientPrintException When the generation isn't possible.
     */
    public File generatePdfAsFile(Document document) throws PatientPrintException {
        if (document == null) {
            throw new IllegalArgumentException("Document cannot be null");
        } else if (!locale.getLanguage().equals("en") && !locale.getLanguage().equals("hu")) {
            throw new IllegalStateException("Only hungarian "
                    + "and english language is supported yet");
        }
        try {
            return generatePdf(document);
        } catch (PatientPrintException ex) {
            throw ex;
        } catch (Exception cause) {
            throw new PatientPrintException(cause);
        }
    }

    /**
     * Creates a DOM Document from an InputStream
     *
     * @param is The InputStream of the input xml file
     * @return The parsed DOM Document object
     * @throws DOMException                 When DOM fails
     * @throws ParserConfigurationException When the parser can't be configured
     * @throws SAXException                 When the xml isn't valid
     * @throws IOException                  When the file has problems
     */
    protected Document createDocument(InputStream is) throws DOMException, ParserConfigurationException, SAXException, IOException {
        /*
         * 
         * BUG SOLVED, the following commented lines no more needed
         * 
         */

////////        /*
////////         * The content of the inputStream must be copied because the jasperReport can't
////////         * use DOM parsed and validated xml Documents.
////////         * The validated xml Document will be change during validation.
////////         * if the document is not validated during parse() then jasperReport accepts the xml
////////         * Because of these had I have to do the parsing twice.
////////         *
////////         */
////////        //TODO: solve this problem
        DocumentBuilderFactory factory = createDocumentBuilderFactory();
        DocumentBuilder builder = createDocumentBuilder(factory);


        /*
         *
         * BUG SOLVED, the following commented lines no more needed
         *
         */

////////        //byte[] inputStreamAsByteArray = inputStreamToArray(is);
////////        /*
////////          TODO: Megoldani a validációs problémát.
////////        A probléma jelenleg úgy van kezelve, hogy a paraméterül kapott inputStream-ből
////////        két streamet csinánuk és az egyiket validáljuk, a másikat meg visszaadjuk a jaspernek, hogy
////////        tudjon nem validált xml-lel dolgozni
////////         */
////////        //builder.parse(new ByteArrayInputStream(inputStreamAsByteArray));
////////        //return DocumentBuilderFactory.newInstance()
////////        //        .newDocumentBuilder().parse(new ByteArrayInputStream(inputStreamAsByteArray));
        return builder.parse(is);
    }

    /**
     * Creates a DOM Document from an URI
     *
     * @param uri The URI of the input xml file
     * @return The parsed DOM Document object
     * @throws ParserConfigurationException When the parser can't be configured
     * @throws SAXException                 When the xml isn't valid
     * @throws IOException                  When the file has problems
     */
    protected Document createDocument(URI uri) throws ParserConfigurationException, SAXException, IOException {
        return createDocument(uri.toURL().openStream());
    }

    /**
     * Generates a PDF document from the DOM Document parameter
     *
     * @param document The parsed DOM Document
     * @return The File of the PDF document
     * @throws JRException           When the Jasper fails
     * @throws PatientPrintException When the conversion is failed
     * @throws IOException           When IO error occurs
     */
    protected File generatePdf(Document document) throws JRException, PatientPrintException, IOException {
        if (document == null) {
            throw new IllegalArgumentException("Document cannot be null");
        } else if (!locale.getLanguage().equals("en") && !locale.getLanguage().equals("hu")) {
            throw new IllegalStateException("Only hungarian "
                    + "and english language is supported yet");
        }
        JRXmlDataSource jasperXmlDataSource = null;
        InputStream jasper = null;
        File userTargetFile = getTargetFile();
        File generatedPdf = (userTargetFile == null ? generateTempFile() : userTargetFile);
        FileOutputStream fo = null;
        try {
            // pass the document root here, so no template parts will be repeated unexpectedly
            jasperXmlDataSource = new JRXmlDataSource(document);
            String resource = this.resourceName + locale.getLanguage().toUpperCase() + ".jasper";
            jasper = getClass().getResourceAsStream(resource);
            JasperPrint print = JasperFillManager.fillReport(jasper, new HashMap<Object, Object>(), jasperXmlDataSource);
            fo = new FileOutputStream(generatedPdf);
            JRExporter exporter = new JRPdfExporter();
            exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, fo);
            exporter.setParameter(JRExporterParameter.JASPER_PRINT, print);
            JRProperties.setProperty("net.sf.jasperreports.default.pdf.encoding", "ISO-8859-2");
            exporter.exportReport();
            fo.flush();
        } finally {
            try {
                jasper.close();
            } catch (Exception e) {
                //IGNORED
            }
            try {
                fo.close();
            } catch (Exception e) {
                //IGNORED
            }
        }
        return generatedPdf;
    }

    /**
     * Return a new File instance to store the created .pdf file. This name must be unique, method is
     * synchronized to garantee the unique name, that is generated with System.currentTimeMillis() method.
     * To use this method, TMP, TEMP or java.io.tmpdir environment variable must be set in the operating system.
     *
     * @return A suitable file to the generated .pdf file.
     * @throws PatientPrintException PatientPrintException is thrown when no temp directory is not configured.
     */
    public static synchronized File generateTempFile() throws PatientPrintException {
        String tempDir = System.getenv("TMP");
        //Query TEMP directory of the user or operating system
        if (tempDir == null || tempDir.isEmpty()) {
            tempDir = System.getenv("TEMP");
        }
        if (tempDir == null || tempDir.isEmpty()) {
            tempDir = System.getProperty("java.io.tmpdir");
        }
        if (tempDir == null || tempDir.isEmpty()) {
            throw new PatientPrintException("No temp directory set in the used operating system!");
        }
        String filename = tempDir + "/report" + System.currentTimeMillis() + ".pdf";
        return new File(filename);
    }

    /**
     * Returns the value of the patients named property
     *
     * @param d The Document of the patients data
     * @return True if the patient is named and false otherwise
     */
    protected boolean isItNamedPatient(Document d) {
        Node nameNode = ReadFromXML.getNode(d.getFirstChild(), "named");
        return nameNode != null;
    }

    /**
     * Creates a new DocumentBuilder instance with the default error handler
     *
     * @param builderFactory The builder factory, which is used to this process.
     * @return builder
     * @throws ParserConfigurationException When the parser couldn't be configured.
     */
    private DocumentBuilder createDocumentBuilder(DocumentBuilderFactory builderFactory) throws ParserConfigurationException {
        DocumentBuilder builder = builderFactory.newDocumentBuilder();
        builder.setErrorHandler(new ErrorHandler() {

            public void warning(SAXParseException exception) throws SAXException {
                throw exception;
            }

            public void error(SAXParseException exception) throws SAXException {
                throw exception;
            }

            public void fatalError(SAXParseException exception) throws SAXException {
                throw exception;
            }
        });
        return builder;
    }

    /**
     * Creates a new DocumentBuilderFactory with or without the validating feature
     *
     * @return builderFactory
     * @throws IllegalArgumentException Probably never, but I could be wrong, when I set a wrong attribute.
     */
    protected DocumentBuilderFactory createDocumentBuilderFactory() throws IllegalArgumentException {
        DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
        builderFactory.setNamespaceAware(true);
        builderFactory.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaLanguage", "http://www.w3.org/2001/XMLSchema");
        builderFactory.setValidating(true);
        return builderFactory;
    }

    /*
     *
     * BUG SOLVED, the following commented lines no more needed
     *
     */
//////    /**
//////     * Creates a byte array from an inputStream
//////     *
//////     * @param inputStream The stream
//////     * @return The byte array of the stream data
//////     * @throws IOException When IO error occurs
//////     */
//////    protected byte[] inputStreamToArray(InputStream inputStream) throws IOException {
//////        StringBuilder strBuilder = new StringBuilder();
//////        Reader reader = new InputStreamReader(inputStream);
//////        //DONE:  add buffer
//////        try {
//////            char[] c = new char[1024];
//////            int i;
//////            while ((i = reader.read(c)) != -1) {
//////                strBuilder.append(c, 0, i);
//////            }
//////            return strBuilder.toString().getBytes();
//////
//////        } finally {
//////            try {
//////                reader.close();
//////            } catch (Exception ex) {
//////                //IGNORED
//////            }
//////            try {
//////                inputStream.close();
//////            } catch (Exception ex) {
//////                //IGNORED
//////            }
//////        }
//////    }
}
