/*
 * Created on 18.09.2005
 */
package org.ganttproject.impex.htmlpdf;

import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import javax.imageio.ImageIO;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.apache.fop.apps.Driver;
import org.apache.fop.apps.FOPException;
import org.apache.fop.apps.Options;
import org.apache.fop.image.FopImageFactory;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.ganttproject.impex.htmlpdf.fonts.FontRecord;
import org.ganttproject.impex.htmlpdf.fonts.FontTriplet;
import org.ganttproject.impex.htmlpdf.fonts.JDKFontLocator;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;

import net.sourceforge.ganttproject.GanttExportSettings;
import net.sourceforge.ganttproject.IGanttProject;
import net.sourceforge.ganttproject.export.ExportException;
import net.sourceforge.ganttproject.export.Exporter;
import net.sourceforge.ganttproject.export.TaskVisitor;
import net.sourceforge.ganttproject.gui.UIFacade;
import net.sourceforge.ganttproject.gui.options.model.GPOptionGroup;
import net.sourceforge.ganttproject.language.GanttLanguage;
import net.sourceforge.ganttproject.resource.HumanResource;
import net.sourceforge.ganttproject.resource.ResourceManager;
import net.sourceforge.ganttproject.task.ResourceAssignment;
import net.sourceforge.ganttproject.task.Task;
import net.sourceforge.ganttproject.task.TaskManager;

public class ExporterToPDF extends ExporterBase implements Exporter {
    private static final String JPG_FORMAT_NAME = "jpg";
    private PDFStylesheet myStylesheet;

    public String getFileTypeDescription() {
        return GanttLanguage.getInstance().getText("impex.pdf.description");
    }

    public GPOptionGroup[] getSecondaryOptions() {
        return null;
    }

    public String getFileNamePattern() {
        return "pdf";
    }

    public String proposeFileExtension() {
        return "pdf";
    }

    public String[] getFileExtensions() {
        return new String[]{"pdf"};
    }

    protected Job[] createJobs(File outputFile, List resultFiles) {
        ExportState state = new ExportState(outputFile);
        Job generateGanttChart = createGenerateGanttChartJob(state);
        Job generateResourceChart = createGenerateResourcechartJob(state);
        Job initializeFOP = createFOPInitializationJob(state);
        Job runTransormation = createTransformationJob(state);
        return new Job[]{generateGanttChart, generateResourceChart,
                initializeFOP, runTransormation};
    }

    private Job createGenerateGanttChartJob(final ExportState state) {
        Job result = new ExportJob("generate gantt chart") {
            protected IStatus run(IProgressMonitor monitor) {
            	if (monitor.isCanceled()) {
            		Platform.getJobManager().cancel(ExporterBase.EXPORT_JOB_FAMILY);
            		return Status.CANCEL_STATUS;
            	}
                try {
                    RenderedImage ganttChartImage = getGanttChart().getRenderedImage(
                            new GanttExportSettings(true, true, true, true));
                    state.ganttChartImageFile = File.createTempFile(
                            "ganttchart", ".jpg");
                    ImageIO.write(ganttChartImage, JPG_FORMAT_NAME,
                            state.ganttChartImageFile);
                    monitor.worked(1);
                } catch (Exception e) {
                	cancel();
                	ExporterToPDF.this.getUIFacade().showErrorDialog(e);
                    return Status.CANCEL_STATUS;
                    
                } catch (OutOfMemoryError e) {
                	cancel();
                	ExporterToPDF.this.getUIFacade().showErrorDialog(e);
                    return Status.CANCEL_STATUS;
                }
                return Status.OK_STATUS;
            }

            
        };
        return result;

    }

    private Job createGenerateResourcechartJob(final ExportState state) {
        Job result = new ExportJob("Generate resource chart") {
            protected IStatus run(IProgressMonitor monitor) {
            	if (monitor.isCanceled()) {
            		Platform.getJobManager().cancel(ExporterBase.EXPORT_JOB_FAMILY);
            		return Status.CANCEL_STATUS;
            	}
                try {
                    RenderedImage resourceChartImage = getResourceChart().getRenderedImage(
                            new GanttExportSettings(true, true, true, true));
                    File outputFile = File.createTempFile("resourcechart",
                            ".jpg");
                    state.resourceChartImageFile = outputFile;
                    ImageIO.write(resourceChartImage, JPG_FORMAT_NAME,
                            outputFile);
                    monitor.worked(1);
                } catch (Exception e) {
                	cancel();
                	ExporterToPDF.this.getUIFacade().showErrorDialog(e);
                    return Status.CANCEL_STATUS;
                    
                } catch (OutOfMemoryError e) {
                	cancel();
                	ExporterToPDF.this.getUIFacade().showErrorDialog(e);
                    return Status.CANCEL_STATUS;
                }
                return Status.OK_STATUS;
            }
        };
        return result;
    }

    private Job createFOPInitializationJob(final ExportState state) {
        Job result = new ExportJob("Initializing FOP") {
            protected IStatus run(IProgressMonitor monitor) {
            	if (monitor.isCanceled()) {
            		Platform.getJobManager().cancel(ExporterBase.EXPORT_JOB_FAMILY);
            		return Status.CANCEL_STATUS;
            	}
                try {
                    Driver driver = new Driver();
                    driver.setRenderer(Driver.RENDER_PDF);
                    Options options = createOptions();
                    FopImageFactory.resetCache();
                    state.driver = driver;
                    monitor.worked(1);
                	//throw new RuntimeException("Moooo!!!!!");
                } catch (Exception e) {
                	cancel();
                	ExporterToPDF.this.getUIFacade().showErrorDialog(e);
                    return Status.CANCEL_STATUS;
                }
                return Status.OK_STATUS;
            }
        };
        return result;
    }

    private Job createTransformationJob(final ExportState state) {
        Job result = new ExportJob("Generating PDF") {
            protected IStatus run(IProgressMonitor monitor) {
            	if (monitor.isCanceled()) {
            		Platform.getJobManager().cancel(ExporterBase.EXPORT_JOB_FAMILY);
            		return Status.CANCEL_STATUS;
            	}
                assert myStylesheet!=null;
                OutputStream out = null;                
                try {
                    out = new FileOutputStream(state.outputFile);
                    state.driver.setOutputStream(out);
                    TransformerHandler stylesheetHandler = createHandler(myStylesheet
                            .getUrl().toString());
//                     SAXTransformerFactory factory = getTransformerFactory();
//                     TransformerHandler stylesheetHandler =
//                     factory.newTransformerHandler();
//                     Transformer transformer =
//                     stylesheetHandler.getTransformer();
//                     transformer.setOutputProperty(OutputKeys.ENCODING,
//                     "UTF-8");
//                     transformer.setOutputProperty(OutputKeys.INDENT, "yes");
//                     transformer.setOutputProperty(
//                     "{http://xml.apache.org/xslt}indent-amount", "4");

                    stylesheetHandler.setResult(new SAXResult(state.driver
                            .getContentHandler()));
//                     stylesheetHandler.setResult(new
//                     StreamResult(System.out));
                    exportProject(state, stylesheetHandler);
                    
                } catch (Exception e) {
                	cancel();
                	ExporterToPDF.this.getUIFacade().showErrorDialog(e);
                    return Status.CANCEL_STATUS;
                }
                finally {
                    monitor.worked(1);
                    if (out!=null) {
                        try {
                            out.flush();
                        	out.close();
                        }
                        catch(IOException e) {
                            getUIFacade().showErrorDialog(e);
                        }
                    }
                }
                return Status.OK_STATUS;
            }
        };
        return result;
    }

    private String i18n(String key) {
        String text = GanttLanguage.getInstance().getText(key);
        return GanttLanguage.getInstance().correctLabel(text);
    }
    
    protected void exportProject(ExportState state, TransformerHandler handler)
            throws SAXException, ExportException {
        DateFormat df = java.text.DateFormat.getDateTimeInstance(
                DateFormat.MEDIUM, DateFormat.MEDIUM, Locale.getDefault());
        handler.startDocument();
        AttributesImpl attrs = new AttributesImpl();
        addAttribute("xmlns:xsl", "http://www.w3.org/1999/XSL/Transform", attrs);
        addAttribute("xmlns:ganttproject", "http://ganttproject.sf.net/", attrs);
        addAttribute("version", "1.0", attrs);
        startElement("xsl:stylesheet", attrs, handler);
        // handler.startPrefixMapping("ganttproject",
        // "http://ganttproject.sf.net");
        addAttribute("xslfo-path", myStylesheet.getUrl().getPath(), attrs);
        startPrefixedElement("report", attrs, handler);
        addAttribute("xslfo-path", myStylesheet.getUrl().getPath(), attrs);
        addAttribute("title", i18n("ganttReport"), attrs);
        addAttribute("name", i18n("project"), attrs);
        addAttribute("nameValue", getProject().getProjectName(), attrs);
        addAttribute("organisation", i18n("organization"), attrs);
        addAttribute("organisationValue", getProject().getOrganization(), attrs);
        addAttribute("webLink", i18n("webLink"), attrs);
        addAttribute("webLinkValue", getProject().getWebLink(), attrs);
        addAttribute("currentDateTimeValue", df.format(new java.util.Date()),
                attrs);
        addAttribute("description", i18n("shortDescription"), attrs);
        startPrefixedElement("project", attrs, handler);
        textElement("descriptionValue", attrs, getProject().getDescription(),
                handler);
        endPrefixedElement("project", handler);
        writeCharts(state, handler);
        writeTasks(getProject().getTaskManager(), handler);
        writeResources(getProject().getHumanResourceManager(), handler);
        endPrefixedElement("report", handler);
        // handler.endPrefixMapping("ganttproject");
        endElement("xsl:stylesheet", handler);
        handler.endDocument();
    }

    private void writeCharts(ExportState state, TransformerHandler handler)
            throws SAXException {
        AttributesImpl attrs = new AttributesImpl();
        addAttribute("title", i18n("ganttChart"), attrs);
        addAttribute("src", state.ganttChartImageFile.getAbsolutePath(), attrs);
        startPrefixedElement("ganttchart", attrs, handler);
        endPrefixedElement("ganttchart", handler);

        addAttribute("title", i18n("resourcesChart"), attrs);
        addAttribute("src", state.resourceChartImageFile.getAbsolutePath(),
                attrs);
        startPrefixedElement("resourceschart", attrs, handler);
        endPrefixedElement("resourceschart", handler);
    }

    private void writeTasks(TaskManager taskManager,
            final TransformerHandler handler) throws ExportException,
            SAXException {
        AttributesImpl attrs = new AttributesImpl();
        addAttribute("xslfo-path", "", attrs);
        addAttribute("title", i18n("tasksList"), attrs);
        addAttribute("name", i18n("name"), attrs);
        addAttribute("begin", i18n("start"), attrs);
        addAttribute("end", i18n("end"), attrs);
        addAttribute("milestone", i18n("meetingPoint"), attrs);
        addAttribute("progress", "%", attrs);
        addAttribute("assigned-to", "Res.", attrs);
        addAttribute("notes", i18n("notes"), attrs);
        startPrefixedElement("tasks", attrs, handler);
        TaskVisitor visitor = new TaskVisitor() {
            AttributesImpl myAttrs = new AttributesImpl();
            protected String serializeTask(Task t, int depth) throws Exception {
                startPrefixedElement("task", myAttrs, handler);
                textElement("name", myAttrs, t.getName(), handler);
                textElement("begin", myAttrs, t.getStart().toString(), handler);
                textElement("end", myAttrs, t.getEnd().toString(), handler);
                textElement("milestone", myAttrs, Boolean.valueOf(
                        t.isMilestone()).toString(), handler);
                textElement("progress", myAttrs, String.valueOf(t
                        .getCompletionPercentage()), handler);

                StringBuffer usersS = new StringBuffer();
                ResourceAssignment[] assignments = t.getAssignments();
                if (assignments.length > 0) {
                    for (int j = 0; j < assignments.length; j++) {
                        usersS.append(assignments[j].getResource().getName()
                                + "\n");
                    }
                } else {
                    usersS.append("&#160;");
                }
                textElement("assigned-to", myAttrs, usersS.toString(), handler);
                textElement("notes", myAttrs, ((t.getNotes() == null || t
                        .getNotes().length() == 0) ? " " : t.getNotes()),
                        handler);
                if (t.getColor()!=null) {
                    textElement("color", myAttrs, getHexaColor(t.getColor()),
                            handler);
                }
                endPrefixedElement("task", handler);
                return "";
            }
        };
        try {
            visitor.visit(taskManager);
        } catch (Exception e) {
            throw new ExportException("Failed to write tasks", e);
        }
        endPrefixedElement("tasks", handler);
    }

    private void writeResources(ResourceManager resourceManager,
            TransformerHandler handler) throws SAXException {
        AttributesImpl attrs = new AttributesImpl();
        addAttribute("title", i18n("resourcesList"), attrs);
        addAttribute("name", i18n("colName"), attrs);
        addAttribute("role", i18n("colRole"), attrs);
        addAttribute("mail", i18n("colMail"), attrs);
        addAttribute("phone", i18n("colPhone"), attrs);
        startPrefixedElement("resources", attrs, handler);
        {
            List resources = resourceManager.getResources();

            // String
            // []function=RoleManager.Access.getInstance().getRoleNames();
            for (int i = 0; i < resources.size(); i++) {
                HumanResource p = (HumanResource) resources.get(i);
                startPrefixedElement("resource", attrs, handler);
                textElement("name", attrs, p.getName(), handler);
                textElement("role", attrs, p.getRole().getName(), handler);
                textElement("mail", attrs, p.getMail(), handler);
                textElement("phone", attrs, p.getPhone(), handler);
                endPrefixedElement("resource", handler);
            }

        }
        endPrefixedElement("resources", handler);

    }

    private Options createOptions() throws ExportException {
        JDKFontLocator locator = new JDKFontLocator();
        FontRecord[] fontRecords = locator.getFontRecords();
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        StreamResult output = new StreamResult(outputStream);
        try {
            TransformerHandler handler = getTransformerFactory()
                    .newTransformerHandler();
            handler.setResult(output);
            // just for nifty debugging :)
            // handler.getTransformer().setOutputProperty(OutputKeys.INDENT,
            // "yes");
            createConfiguration(handler, fontRecords);
        } catch (TransformerConfigurationException e) {
            throw new ExportException("Failed to create FOP options", e);
        } catch (SAXException e) {
            throw new ExportException("Failed to create FOP options", e);
        }
        Options result;
        try {
            result = new Options(new ByteArrayInputStream(outputStream
                    .toByteArray()));
        } catch (FOPException e) {
            throw new ExportException("Failed to create FOP options", e);
        }
        return result;
    }

    private void createConfiguration(TransformerHandler handler,
            FontRecord[] fontRecords) throws SAXException {
        AttributesImpl attrs = new AttributesImpl();
        handler.startDocument();
        handler.startElement("", "configuration", "configuration", attrs);
        handler.startElement("", "fonts", "fonts", attrs);

        for (int i = 0; i < fontRecords.length; i++) {
            FontRecord nextRecord = fontRecords[i];
            attrs.clear();
            attrs.addAttribute("", "metrics-file", "metrics-file", "CDATA",
                    nextRecord.getMetricsLocation().toString());
            attrs.addAttribute("", "kerning", "kerning", "CDATA", "yes");
            attrs.addAttribute("", "embed-file", "embed-file", "CDATA",
                    nextRecord.getFontLocation().getPath());
            handler.startElement("", "font", "font", attrs);
            writeTriplets(handler, nextRecord.getFontTriplets());
            handler.endElement("", "font", "font");
        }
        handler.endElement("", "fonts", "fonts");
        handler.endElement("", "configuration", "configuration");
        handler.endDocument();
    }

    private void writeTriplets(TransformerHandler handler,
            FontTriplet[] fontTriplets) throws SAXException {
        AttributesImpl attrs = new AttributesImpl();
        for (int i = 0; i < fontTriplets.length; i++) {
            FontTriplet next = fontTriplets[i];
            attrs.clear();
            attrs.addAttribute("", "name", "name", "CDATA", next.getName());
            attrs.addAttribute("", "style", "style", "CDATA", next.isItalic()
                    ? "italic"
                    : "normal");
            attrs.addAttribute("", "weight", "weight", "CDATA", next.isBold()
                    ? "bold"
                    : "normal");
            handler.startElement("", "font-triplet", "font-triplet", attrs);
            handler.endElement("", "font-triplet", "font-triplet");
        }
    }

    private static class ExportState {
        final File outputFile;
        public ExportState(File outputFile) {
            this.outputFile = outputFile;
        }
        Driver driver;
        File ganttChartImageFile;
        File resourceChartImageFile;
    }
    protected void setSelectedStylesheet(Stylesheet stylesheet) {
        myStylesheet = (PDFStylesheet) stylesheet;
    }

    protected String getStylesheetOptionID() {
        return "impex.pdf.stylesheet";
    }
    
    protected Stylesheet[] getStylesheets() {
        StylesheetFactoryImpl factory = new StylesheetFactoryImpl() {
            protected Stylesheet newStylesheet(URL resolvedUrl,
                    String localizedName) {
                return new PDFStylesheetImpl(resolvedUrl, localizedName);
            }
        };
        return (Stylesheet[]) factory.createStylesheets(PDFStylesheet.class);
    }

    private class PDFStylesheetImpl extends StylesheetImpl
            implements
                PDFStylesheet {

        PDFStylesheetImpl(URL stylesheetURL, String localizedName) {
            super(stylesheetURL, localizedName);
        }

    }

    private static String getHexaColor(java.awt.Color color) {
        StringBuffer out = new StringBuffer();
        out.append("#");
        if (color.getRed() <= 15) {
            out.append("0");
        }
        out.append(Integer.toHexString(color.getRed()));
        if (color.getGreen() <= 15) {
            out.append("0");
        }
        out.append(Integer.toHexString(color.getGreen()));
        if (color.getBlue() <= 15) {
            out.append("0");
        }
        out.append(Integer.toHexString(color.getBlue()));

        return out.toString();
    }

}
