/*
 * FOPPrinter.java
 *
 * Created on 25 de Maio de 2005, 15:43
 */

package siprp.util.fop;

import java.awt.print.Book;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.Serializable;
import java.util.Hashtable;

import javax.print.Doc;
import javax.print.DocFlavor;
import javax.print.DocPrintJob;
import javax.print.PrintException;
import javax.print.PrintService;
import javax.print.PrintServiceLookup;
import javax.print.ServiceUI;
import javax.print.SimpleDoc;
import javax.print.attribute.DocAttributeSet;
import javax.print.attribute.HashDocAttributeSet;
import javax.print.attribute.HashPrintRequestAttributeSet;
import javax.print.attribute.PrintRequestAttributeSet;
import javax.print.attribute.standard.Sides;
import javax.swing.JOptionPane;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamSource;

import org.apache.fop.apps.FOUserAgent;
import org.apache.fop.apps.Fop;
import org.apache.fop.apps.FopFactory;
import org.apache.fop.render.awt.AWTRenderer;


/**
 * Utilities to preview and print FO files in form of generated .fo files
 * 
 * @author lflores
 * @author João Neto
 */
public class FOPPrinter implements FOPPrinterConstants
{
	public FopFactory getFopfactory()
	{
		return fopfactory;
	}

	private static final Object LOCK = new Object();

	// private final Translator translator = new SecureResourceBundle( null );

	// private final AWTRenderer renderer = new AWTRenderer( translator );
	private final FopFactory fopfactory;
	private final FOUserAgent foUserAgent;
	private final TransformerFactory factory = TransformerFactory.newInstance();
	private static final DocFlavor flavor = DocFlavor.SERVICE_FORMATTED.PAGEABLE;

	private Transformer transformer = null;

	private static File userConfig = null;

	
	public static File getUserConfig()
	{
		return userConfig;
	}

	public static void setUserConfig(File userConfig)
	{
		FOPPrinter.userConfig = userConfig;
	}

	private static FOPPrinter fopPrinter = null;

	/** Creates a new instance of FOPPrinter */
	private FOPPrinter()
	{
		fopfactory = FopFactory.newInstance();
		if (userConfig != null)
		{
			try
			{
				fopfactory.setUserConfig(userConfig);
			} catch (Exception e)
			{
				System.err.println("Ignoring invalid user config file");
				e.printStackTrace();
			}
		}
		foUserAgent = new FOUserAgent(fopfactory);
		fopfactory.setStrictValidation(false);
		
		// driver.setLogger( new ConsoleLogger( ConsoleLogger.LEVEL_INFO ) );
	}

	public static FOPPrinter getFOPPrinter()
	{
		synchronized (LOCK)
		{
			if (fopPrinter == null)
			{
				fopPrinter = new FOPPrinter( );
			}
		}
		return fopPrinter;
	}
	
	
	/**
	 * Creates a .fo and prints it
	 * @param xslFile
	 * @param xmlBytes
	 ** @param preview
	 *            Flag to activate document preview before printing. If false,
	 *            the printing is immediately activated.
	 * @param printerDialog
	 *            Flag to activate the printer choosing dialog. If false, the
	 *            default or preferred printer is used.
	 * @throws Exception
	 */
	public void print(String xslFile, byte[] xmlBytes, boolean preview, boolean printerDialog ) throws Exception
	{
		InputStream input = getClass().getClassLoader().getResourceAsStream(xslFile);
		print(input, xmlBytes,preview,printerDialog);
	}
	
	/**
	 * Creates a .fo and prints it
	 * @param xslStream
	 * @param xmlBytes
	 ** @param preview
	 *            Flag to activate document preview before printing. If false,
	 *            the printing is immediately activated.
	 * @param printerDialog
	 *            Flag to activate the printer choosing dialog. If false, the
	 *            default or preferred printer is used.
	 * @throws Exception
	 */
	public void print(InputStream xslStream, byte[] xmlBytes, boolean preview, boolean printerDialog ) throws Exception
	{
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		FOPCreator.getFOPCreator().createFOfromXML(new ByteArrayInputStream(xmlBytes), xslStream, baos);
		printFO( new ByteArrayInputStream( baos.toByteArray() ), preview, printerDialog );
	}

	/**
	 * Prints a single .fo document.
	 * 
	 * @param fo
	 *            The generated fo document
	 * @param preview
	 *            Flag to activate document preview before printing. If false,
	 *            the printing is immediately activated.
	 * @param dialog
	 *            Flag to activate the printer choosing dialog. If false, the
	 *            default or preferred printer is used.
	 * @throws Exception
	 *             if the conversion fails
	 */
	public void printFO(InputStream fo, boolean preview, boolean dialog) throws Exception
	{
		printFO(new InputStream[]
		{ fo }, preview, dialog);
	}

	/**
	 * Prints an array of .fo documents.
	 * 
	 * @param fo
	 *            The array of generated fo documents
	 * @param preview
	 *            Flag to activate document preview before printing. If false,
	 *            the printing is immediately activated.
	 * @param dialog
	 *            Flag to activate the printer choosing dialog. If false, the
	 *            default or preferred printer is used.
	 * @throws Exception
	 *             if the conversion fails
	 */
	public void printFO(InputStream fo[], boolean preview, boolean dialog) throws Exception
	{
		printFO(fo, preview, dialog, null);
	}

	/**
	 * Prints a single .fo document with printing options.
	 * 
	 * @param fo
	 *            The generated fo document
	 * @param preview
	 *            Flag to activate document preview before printing. If false,
	 *            the printing is immediately activated.
	 * @param dialog
	 *            Flag to activate the printer choosing dialog. If false, the
	 *            default or preferred printer is used.
	 * @param options
	 *            Printing options
	 * @see FOPPrinterConstants
	 * @throws Exception
	 *             if the conversion fails
	 */
	public void printFO(InputStream fo, boolean preview, boolean dialog,
			Hashtable<Object, Serializable> options) throws Exception
	{
		printFO(new InputStream[]
		{ fo }, preview, dialog, options);
	}

	/**
	 * Prints an array of .fo documents with printing options.
	 * 
	 * @param fo
	 *            The array of generated fo documents
	 * @param preview
	 *            Flag to activate document preview before printing. If false,
	 *            the printing is immediately activated.
	 * @param dialog
	 *            Flag to activate the printer choosing dialog. If false, the
	 *            default or preferred printer is used.
	 * @param options
	 *            Printing options
	 * @see FOPPrinterConstants
	 * @throws Exception
	 *             if the conversion fails
	 */
	public void printFO(InputStream fo[], boolean preview, boolean dialog,
			Hashtable<Object, Serializable> options) throws Exception
	{
		if (options == null)
		{
			options = new Hashtable<Object, Serializable>();
		}
		String defaultPrinter = System.getProperty("evutils.defaultprinter");
		if (defaultPrinter != null)
		{
			options.put(FOP_PREFERRED_PRINTER_NAME, defaultPrinter);
		}
		if (fo.length > 1 && preview)
		{
			throw new Exception("Can't have preview with multi document print");
		}
		if (transformer == null)
		{
			transformer = factory.newTransformer();
		}
		// long tic = System.currentTimeMillis();

		AWTRenderer renderer = new AWTRenderer();

		foUserAgent.setRendererOverride(renderer);

		if (!preview)
		{
			renderer.setPreviewDialogDisplayed(false);
		}
		renderer.setUserAgent(foUserAgent);

		Result res[] = new Result[fo.length];
		for (int i = 0; i < fo.length; ++i)
		{
			Source src = new StreamSource(fo[i]);
			Fop fop = fopfactory.newFop(foUserAgent);
			res[i] = new SAXResult(fop.getDefaultHandler());
			transformer.transform(src, res[i]);
		}

		if (!preview)
		{
			Book book = new Book();
			for (int i = 0; i < renderer.getNumberOfPages(); ++i)
			{
				book.append(renderer.getPrintable(i), renderer.getPageFormat(i));
			}
			PrintRequestAttributeSet pras = new HashPrintRequestAttributeSet();

			PrintService service = chooseService(dialog, options, pras);
			if (service != null)
			{
				// PrintJobAttributeSet pjas = new HashPrintJobAttributeSet();
				DocAttributeSet das = new HashDocAttributeSet();
				// das.add( new NumberUp( 2 ) );
				Doc doc = new SimpleDoc(book, flavor, das);
				DocPrintJob job = service.createPrintJob();
				try
				{
					job.print(doc, pras);
				}
				catch (PrintException pe)
				{
					pe.printStackTrace();
				}
			}
		}
	}

	public static PrintService chooseService(boolean dialog, Hashtable<Object, Serializable> options,
			PrintRequestAttributeSet pras)
	{
		if (options.containsKey(FOP_DUPLEX_TYPE))
		{
			pras.add((Sides) options.get(FOP_DUPLEX_TYPE));
		}
		PrintService printService[] = PrintServiceLookup.lookupPrintServices(flavor, pras);
		PrintService defaultService = PrintServiceLookup.lookupDefaultPrintService();

		boolean hasMandatory = options.containsKey(FOP_MANDATORY_PRINTER_NAME);
		boolean hasPreferred = options.containsKey(FOP_PREFERRED_PRINTER_NAME);
		String mandatoryName = (String) options.get(FOP_MANDATORY_PRINTER_NAME);
		String preferredName = (String) options.get(FOP_PREFERRED_PRINTER_NAME);
		PrintService mandatory = null;
		PrintService preferred = null;

		if (hasPreferred || hasMandatory)
		{
			for (int n = 0; n < printService.length; n++)
			{
				if (hasMandatory)
				{
					if (printService[n].getName().equals(mandatoryName))
					{
						mandatory = printService[n];
						break;
					}
				}
				else if (hasPreferred)
				{
					if (printService[n].getName().equals(preferredName))
					{
						preferred = printService[n];
						break;
					}
				}
			}
		}
		// long toc = System.currentTimeMillis();
		// System.err.println( "PRINT FO INNER: " + ( toc - tic ) );
		PrintService service;
		if (!dialog && !hasMandatory && !hasPreferred)
		{
			service = defaultService;
		}
		else if (hasMandatory)
		{
			if (mandatory == null)
			{
				JOptionPane.showMessageDialog(null, "A impressora " + mandatoryName
						+ " n\u00e3o est\u00e1 dispon\u00EDvel.", "Erro...",
						JOptionPane.ERROR_MESSAGE);
				service = null;
			}
			else
			{
				service = mandatory;
			}
		}
		else if ((!dialog) && hasPreferred && preferred != null)
		{

			// pras.add((Sides) options.get(FOP_DUPLEX_TYPE));
			service = preferred;

		}
		else if (hasPreferred && preferred != null)
		{

			// pras.add((Sides) options.get(FOP_DUPLEX_TYPE));
			service = ServiceUI.printDialog(null, 200, 200, printService, preferred, flavor, pras);

		}
		else
		{
			if (printService == null || printService.length == 0)
			{
				JOptionPane.showMessageDialog(null,
						"N\u00e3o existem impressoras dispon\u00edveis neste computador.", "Erro",
						JOptionPane.ERROR_MESSAGE);
				service = null;
			}
			else
			{
				service = ServiceUI.printDialog(null, 200, 200, printService, defaultService,
						flavor, pras);
			}
		}
		return service;
	}

	// private static PreviewDialog createPreviewDialog(FOUserAgent foUserAgent)
	// {
	// final PreviewDialog frame = new PreviewDialog(foUserAgent);
	// frame.validate();
	// frame.addWindowListener(new WindowAdapter()
	// {
	// public void windowClosed(WindowEvent we)
	// {
	// frame.setVisible(false);
	// }
	// });
	// // center window
	// Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
	// frame.setSize(screenSize);
	// Dimension frameSize = frame.getSize();
	//
	// /*
	// * if( frameSize.height > screenSize.height ) { frameSize.height =
	// * screenSize.height; } if( frameSize.width > screenSize.width ) {
	// * frameSize.width = screenSize.width; }
	// */
	// frame.setLocation((screenSize.width - frameSize.width) / 2,
	// (screenSize.height - frameSize.height) / 2);
	// return frame;
	// }

	// private PreviewDialog viewFO(InputStream fo) throws IOException,
	// FOPException,
	// TransformerException
	// {
	// PreviewDialog frame = createPreviewDialog(foUserAgent);
	// frame.setVisible(true);
	// // foUserAgent.getRendererOverride().s
	//		
	//		
	// renderer.setProgressListener(frame);
	// renderer.setComponent(frame);
	// frame.progress("A Preparar ...");
	// return frame;
	// }

	public static void main(String arg[]) throws Exception
	{
//		PipedInputStream pis = new PipedInputStream();
//		final PipedOutputStream pos = new PipedOutputStream(pis);
//		final FOPCreator creator = FOPCreator.getFOPCreator();
		System.out.println("Creating FO");

		// JFileChooser chooser = new JFileChooser();
		// chooser.setDialogTitle("Select XML data file:");
		// int returnVal = chooser.showOpenDialog(null);
		// String dataFile = null;
		// if (returnVal == JFileChooser.APPROVE_OPTION)
		// {
		// dataFile = chooser.getSelectedFile().getAbsolutePath();
		// }
		// chooser.setDialogTitle("Select XSL-FO template file:");
		// returnVal = chooser.showOpenDialog(null);
		// String templateFile = null;
		// if (returnVal == JFileChooser.APPROVE_OPTION)
		// {
		// templateFile = chooser.getSelectedFile().getAbsolutePath();
		// System.out.println(chooser.getSelectedFile().getAbsolutePath());
		// }

		// final String DATA = dataFile;
		// final String TEMPLATE = templateFile;

//		final String DATA = "/home/jneto/Desktop/boletim.xml";
//		final String TEMPLATE = "/home/jneto/Desktop/boletim.xsl";
//		Thread t = new Thread()
//		{
//			public void run()
//			{
//				try
//				{
//					creator.createFOfromXML(new FileInputStream(DATA),
//							new FileInputStream(TEMPLATE), pos);
//					System.out.println("FO created");
//				} catch (Exception ex)
//				{
//					ex.printStackTrace();
//				}
//			}
//		};
//		t.start();
//		t.wait(2);
		System.out.println("Starting to print");
		// Hashtable<Object, Serializable> options = new Hashtable<Object,
		// Serializable>();
		// options.put(FOP_DUPLEX_TYPE,
		// javax.print.attribute.standard.Sides.TUMBLE);
		// new FOPPrinter().printFO(pis, false, true, options);
		new FOPPrinter().printFO(new FileInputStream("/home/jneto/Desktop/carta1.fo"), true,
				true);
		System.out.println("DONE");
	}
}
