package org.docx4j.convert.out.pdf.viaXSLFO;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StringReader;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder;
import org.apache.commons.io.FileUtils;
import org.apache.fop.apps.Fop;
import org.apache.fop.apps.FopFactory;
import org.apache.fop.apps.MimeConstants;
import org.apache.log4j.Logger;
import org.docx4j.XmlUtils;
import org.docx4j.convert.out.Containerization;
import org.docx4j.convert.out.Converter;
import org.docx4j.convert.out.PageBreak;
import org.docx4j.convert.out.html.HtmlExporterNG2.EndnoteState;
import org.docx4j.convert.out.html.HtmlExporterNG2.FootnoteState;
import org.docx4j.convert.out.pdf.viaXSLFO.Conversion;
import org.docx4j.fonts.PhysicalFont;
import org.docx4j.fonts.PhysicalFonts;
import org.docx4j.fonts.fop.fonts.FontTriplet;
import org.docx4j.jaxb.Context;
import org.docx4j.model.PropertyResolver;
import org.docx4j.model.SymbolModel.SymbolModelTransformState;
import org.docx4j.model.TransformState;
import org.docx4j.model.listnumbering.Emulator.ResultTriple;
import org.docx4j.model.properties.Property;
import org.docx4j.model.properties.PropertyFactory;
import org.docx4j.model.properties.paragraph.Indent;
import org.docx4j.model.properties.paragraph.PBorderBottom;
import org.docx4j.model.properties.paragraph.PBorderTop;
import org.docx4j.model.properties.paragraph.PShading;
import org.docx4j.model.properties.run.Font;
import org.docx4j.model.structure.SectionWrapper;
import org.docx4j.model.structure.jaxb.ObjectFactory;
import org.docx4j.model.structure.jaxb.Sections;
import org.docx4j.model.structure.jaxb.Sections.Section;
import org.docx4j.model.table.TableModel.TableModelTransformState;
import org.docx4j.openpackaging.exceptions.Docx4JException;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
import org.docx4j.wml.CTPageNumber;
import org.docx4j.wml.CTSimpleField;
import org.docx4j.wml.NumberFormat;
import org.docx4j.wml.PPr;
import org.docx4j.wml.PPrBase.NumPr.Ilvl;
import org.docx4j.wml.RPr;
import org.docx4j.wml.Style;
import org.docx4j.wml.TcPr;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentFragment;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.Text;
import org.w3c.dom.traversal.NodeIterator;
import org.xml.sax.InputSource;

;

public class PclConversion extends /*org.docx4j.convert.out.pdf.viaXSLFO.*/Conversion {
	
         public PclConversion(WordprocessingMLPackage wordMLPackage) {
             super(wordMLPackage);
         }

	/** Create a pcl version of the document, using XSL FO. 
	 * 
	 * @param os
	 *            The OutputStream to write the pcl to 
	 * @param settings
	 *            The configuration for the conversion 
	 * 
	 * */     
	@Override
	public void output(OutputStream os, PdfSettings settings) throws Docx4JException {

		// See http://xmlgraphics.apache.org/fop/0.95/embedding.html
		// (reuse if you plan to render multiple documents!)
		FopFactory fopFactory = FopFactory.newInstance();

		try {

			if (fopConfig == null) {

				DefaultConfigurationBuilder cfgBuilder = new DefaultConfigurationBuilder();
				String myConfig = "<fop version=\"1.0\"><strict-configuration>true</strict-configuration>"
                                    + "<renderers><renderer mime=\"application/vnd.hp-PCL\"></renderer></renderers></fop>";
//                                     + "<fonts>" + this.declareFonts() +
//                                     "</fonts></renderer></renderers></fop>";

				log.debug("\nUsing config:\n " + myConfig + "\n");

				// See FOP's PrintRendererConfigurator
				// String myConfig = "<fop
				// version=\"1.0\"><strict-configuration>true</strict-configuration>"
				// +
				// "<renderers><renderer mime=\"application/pdf\">" +
				// "<fonts><directory
				// recursive=\"true\">C:\\WINDOWS\\Fonts</directory>" +
				// "<auto-detect/>" +
				// "</fonts></renderer></renderers></fop>";

				fopConfig = cfgBuilder.build(new ByteArrayInputStream(myConfig
						.getBytes()));
			}

			fopFactory.setUserConfig(fopConfig);

			Fop fop = fopFactory.newFop(MimeConstants.MIME_PCL_ALT, os); // MIME_PCL_ALT matches mime type used in config above

			/*
			 * Based on the principle that we'll do all the smarts via extension
			 * functions which can take advantage of Java and docx4j's model of
			 * the package, all the XSLT needs is the main document part.
			 * 
			 * This means we can skip the step of generating a Flat OPC XML
			 * file.
			 */
			// Document domDoc = XmlPackage.getFlatDomDocument(wordMLPackage);
			// Document domDoc =
			// XmlUtils.marshaltoW3CDomDocument(wordMLPackage.getMainDocumentPart().getJaxbElement());

			// Containerization of borders/shading
			MainDocumentPart mdp = wordMLPackage.getMainDocumentPart();

			// Don't change the user's Document object; create a tmp one
			org.docx4j.wml.Document tmpDoc = XmlUtils.deepCopy(wordMLPackage
					.getMainDocumentPart().getJaxbElement());
			Containerization.groupAdjacentBorders(tmpDoc.getBody());
			PageBreak.movePageBreaks(tmpDoc.getBody());

			// log.info(XmlUtils.marshaltoString(mdp.getJaxbElement(), false));

			Sections sections = createSectionContainers(tmpDoc);
			Document domDoc = XmlUtils.marshaltoW3CDomDocument(sections,
					Context.jcSectionModel);

			log.debug(XmlUtils.marshaltoString(sections, false, Context.jcSectionModel));
			
			if (settings == null) {
				settings = new PdfSettings();
			}
			settings.setWmlPackage(wordMLPackage);
			boolean privateImageHandler = false;
			if (settings.getImageHandler() == null) {
				settings.setImageHandler(settings.getImageDirPath() != null ? 
						new PDFConversionImageHandler(settings.getImageDirPath(), true) : 
						new PDFConversionImageHandler());
				privateImageHandler = true;
			}
			

			// Resulting SAX events (the generated FO) must be piped through to
			// FOP
			Result result = new SAXResult(fop.getDefaultHandler());

			// Allow arbitrary objects to be passed to the converters.
			// The objects are assumed to be specific to a particular converter
			// (eg table),
			// so assume there will be one object implementing TransformState
			// per converter.
			HashMap<String, TransformState> modelStates = new HashMap<String, TransformState>();
			settings.getSettings().put("modelStates", modelStates);

			// Converter c = new Converter();
			Converter.getInstance().registerModelConverter("w:tbl",
					new TableWriter());
			Converter.getInstance().registerModelConverter("w:sym",
					new SymbolWriter());

			// By convention, the transform state object is stored by reference
			// to the
			// type of element to which its model applies
			modelStates.put("w:tbl", new TableModelTransformState());
			modelStates.put("w:sym", new SymbolModelTransformState());

			modelStates.put("footnoteNumber", new FootnoteState());
			modelStates.put("endnoteNumber", new EndnoteState());
			modelStates.put(PART_TRACKER, new PartTracker());
			modelStates.put(FIELD_TRACKER, new InField());

			Converter.getInstance().start(wordMLPackage);

			if (saveFO != null || log.isDebugEnabled()) {

				ByteArrayOutputStream intermediate = new ByteArrayOutputStream();
				Result intermediateResult = new StreamResult(intermediate);

				XmlUtils.transform(domDoc, xslt, settings.getSettings(), intermediateResult);

				String fo = intermediate.toString("UTF-8");
				log.debug(fo);

				if (saveFO != null) {
					FileUtils.writeStringToFile(saveFO, fo, "UTF-8");
					log.info("Saved " + saveFO.getPath());
				}

				Source src = new StreamSource(new StringReader(fo));

				Transformer transformer = XmlUtils.tfactory.newTransformer();
				transformer.transform(src, result);
			} else {

				XmlUtils.transform(domDoc, xslt, settings.getSettings(), result);
			}
			
			if (privateImageHandler) {
				//remove a locally created imageHandler in case the HtmlSettings get reused
				settings.getSettings().remove(PdfSettings.IMAGE_HANDLER);
			}

		} catch (Exception e) {
			throw new Docx4JException("FOP issues", e);
		} finally {
			// Clean-up
			try {
				os.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

	}
    

    /** private cruft that needs to be replicated here **/


    private static File saveFO;

       /**
         * Create a FOP font configuration for each font used in the
         * document.
         *
         * @return
         */
        private String declareFonts() {
               
                StringBuffer result = new StringBuffer();
                Map fontsInUse = wordMLPackage.getMainDocumentPart().fontsInUse();
                Iterator fontMappingsIterator = fontsInUse.entrySet().iterator();
                while (fontMappingsIterator.hasNext()) {
                    Map.Entry pairs = (Map.Entry)fontMappingsIterator.next();
                    if(pairs.getKey()==null) {
                        log.info("Skipped null key");
//                      pairs = (Map.Entry)fontMappingsIterator.next();
                        continue;
                    }
                   
                    String fontName = (String)pairs.getKey();               
                   
                    PhysicalFont pf = wordMLPackage.getFontMapper().getFontMappings().get(fontName);
                   
                    if (pf==null) {
                        log.error("Document font " + fontName + " is not mapped to a physical font!");
                        continue;
                    }
                   
                    String subFontAtt = "";
                    if (pf.getEmbedFontInfo().getSubFontName()!=null)
                        subFontAtt= " sub-font=\"" + pf.getEmbedFontInfo().getSubFontName() + "\"";
                   
                    result.append("<font embed-url=\"" +pf.getEmbeddedFile() + "\""+ subFontAtt +">" );
                        // now add the first font triplet
                            FontTriplet fontTriplet = (FontTriplet)pf.getEmbedFontInfo().getFontTriplets().get(0);
                            addFontTriplet(result, fontTriplet);
                    result.append("</font>" );
                   
                    // bold, italic etc
                    PhysicalFont pfVariation = PhysicalFonts.getBoldForm(pf);
                    if (pfVariation!=null) {
                            result.append("<font embed-url=\"" +pfVariation.getEmbeddedFile() + "\""+ subFontAtt +">" );
                        addFontTriplet(result, pf.getName(), "normal", "bold");
                            result.append("</font>" );
                    }
                    pfVariation = PhysicalFonts.getBoldItalicForm(pf);
                    if (pfVariation!=null) {
                            result.append("<font embed-url=\"" +pfVariation.getEmbeddedFile() + "\""+ subFontAtt +">" );
                        addFontTriplet(result, pf.getName(), "italic", "bold");
                            result.append("</font>" );
                    }
                    pfVariation = PhysicalFonts.getItalicForm(pf);
                    if (pfVariation!=null) {
                            result.append("<font embed-url=\"" +pfVariation.getEmbeddedFile() + "\""+ subFontAtt +">" );
                        addFontTriplet(result, pf.getName(), "italic", "normal");
                            result.append("</font>" );
                    }
                           
                }
               
                return result.toString();
               
        }
               
        private void addFontTriplet(StringBuffer result, FontTriplet fontTriplet) {
            result.append("<font-triplet name=\"" + fontTriplet.getName() + "\""
                                + " style=\"" + fontTriplet.getStyle() + "\""
                                + " weight=\"" + weightToCSS2FontWeight(fontTriplet.getWeight()) + "\""
                                                + "/>" );               
        }
        private void addFontTriplet(StringBuffer result, String familyName, String style, String weight) {
            result.append("<font-triplet name=\"" + familyName + "\""
                                + " style=\"" + style + "\""
                                + " weight=\"" + weight + "\""
                                                + "/>" );               
        }
       
        private String weightToCSS2FontWeight(int i) {
               
                if (i>=700) {
                        return "bold";
                } else {
                        return "normal";
                }
               
        }


       private Sections createSectionContainers(org.docx4j.wml.Document doc) {
                               
                ObjectFactory factory = new ObjectFactory();
               
                Sections sections = factory.createSections();
                Section section = factory.createSectionsSection();
                section.setName("s1"); // name must match fo master
               
                sections.getSection().add(section);
                                               
                //org.docx4j.wml.Document doc = (org.docx4j.wml.Document)wordMLPackage.getMainDocumentPart().getJaxbElement();
               
                int i = 2;
                for (Object o : doc.getBody().getEGBlockLevelElts() ) {
                       
                        if (o instanceof org.docx4j.wml.P) {
                                if (((org.docx4j.wml.P)o).getPPr() != null ) {
                                        org.docx4j.wml.PPr ppr = ((org.docx4j.wml.P)o).getPPr();
                                        if (ppr.getSectPr()!=null) {
                                                // According to the ECMA-376 2ed, if type is not specified, read it as next page
                                                // However Word 2007 sometimes treats it as continuous, and sometimes doesn't??                                         
                                               
                                                if ( ppr.getSectPr().getType()!=null
                                                                     && ppr.getSectPr().getType().getVal().equals("continuous")) {
                                                        // If its continuous, don't add a section
                                                } else {
                                                        section = factory.createSectionsSection();
                                                        section.setName("s" +i); // name must match fo master
                                                        sections.getSection().add(section);     
                                                        i++;
                                                }
                                        }
                                }                               
                        }
                        section.getAny().add( marshall(o) );
                                // TODO: since the section model knows nothing about WML,
                                // we have to marshall each object separately.
                                // To fix this, next time wml is generated, include the section model there!
                }
                return sections;                               
        }
   
        private Element marshall(Object o) {
               
                try {
                        org.w3c.dom.Document w3cDoc =
                                XmlUtils.marshaltoW3CDomDocument(o);
                       
                       
                                /* Force the RelationshipsPart to be marshalled using
                                 * the normal non-rels part NamespacePrefixMapper,
                                 * since otherwise (because we'd be using 2 namespace
                                 * prefix mappers?) we end up with errant xmlns="",
                                 * which is wrong and stops Word 2007 from loading the
                                 * document.
                                 *
                                 * Note that xmlPackage.xsd defines:
                                 *      <xsd:complexType name="CT_XmlData">
                                                <xsd:sequence>
                                                        <xsd:any processContents="skip" />
                                                </xsd:sequence>
                                 *
                                 * Note also that marshaltoString uses
                                 * just the normal non-rels part NamespacePrefixMapper,
                                 * so if/when this is marshalled again, that could
                                 * have been causing problems as well??
                                 */
                return w3cDoc.getDocumentElement();                     
                } catch (Exception e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                }                       
                return null;
               
        }
       

            private static DocumentFragment handleField(String instr, NodeIterator childResults) {
               
                try {
                       
            // Create a DOM builder and parse the fragment
                DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();       
                        Document document = factory.newDocumentBuilder().newDocument();
                       
                        //log.info("Document: " + document.getClass().getName() );
                       
                        if ( !instr.toLowerCase().contains( "page") ) {
                               
                                if (log.isDebugEnabled() ) {
                                        return message("no support for fields (except PAGE numbering)");
                                } else {
                                       
                                        // Try this
                                        Node foInlineElement = document.createElementNS("http://www.w3.org/1999/XSL/Format", "fo:inline");                     
                                        document.appendChild(foInlineElement);
                                       
                                        Node n = childResults.nextNode();
                                        XmlUtils.treeCopy( n,  foInlineElement );
                                       
                                        DocumentFragment docfrag = document.createDocumentFragment();
                                        docfrag.appendChild(document.getDocumentElement());
                                        return docfrag;                                 
                                }
                        }
                        // Its a PAGE numbering field
                       
                        /*
                         * For XSL FO page numbering, see generally
                         * http://www.dpawson.co.uk/xsl/sect3/N8703.html
                         *
                         * In summary,
                         *
                         * <fo:page-sequence master-name="blagh"
                         *                              format="i"
                         *                              initial-page-number="1"> ....
                         *
                         */
                        Node foPageNumber = document.createElementNS("http://www.w3.org/1999/XSL/Format",
                                        "fo:page-number");                     
                        document.appendChild(foPageNumber);
                                               
                        DocumentFragment docfrag = document.createDocumentFragment();
                        docfrag.appendChild(document.getDocumentElement());
                        return docfrag;
                                               
                } catch (Exception e) {
                        e.printStackTrace();
                        System.out.println(e.toString() );
                        log.error(e);
                }
       
        return null;
       
    }

	
}
    
