package org.docx4j.utils;

import java.util.List;

import jakarta.xml.bind.Binder;
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBException;

import org.docx4j.XmlUtils;
import org.docx4j.jaxb.Context;
import org.docx4j.jaxb.JaxbValidationEventHandler;
import org.docx4j.jaxb.XPathBinderAssociationIsPartialException;
import org.docx4j.wml.P;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Node;

/**
 * Some users wish to be able to use an XPath on the result
 * of cloning a JAXB object.  This variant on XmlUtils.deepCopy 
 * allows that.
 * 
 * Note that if you use this object's deepCopy method more 
 * than once, the results returned by getJAXBNodesViaXPath
 * will only be on your last deepCopy.
 */
public class XPathAwareCloner {
	
	private static Logger log = LoggerFactory.getLogger(XPathAwareCloner.class);	
			
	/** Clone this JAXB object, using default JAXBContext. */ 
	public Object deepCopy(Object o) {		
		return deepCopy(o, Context.jc);		
	}	
	
	Object jaxbElement;
	
	/** Clone this JAXB object
	 * @param value
	 * @param jc
	 * @return
	 */
	public Object deepCopy(Object o, JAXBContext jc) {
		
		if (o==null) {
			throw new IllegalArgumentException("Can't clone a null argument");
		}
		
		try {
			// To be XPath aware, we need a binder.
			// But to unmarshall using a binder, we need to unmarshal a node.
			// So, our marshall should be to a W3C document
			org.w3c.dom.Document doc = XmlUtils.marshaltoW3CDomDocument(o, jc);
			
			// OK, unmarshall to binder
			binder = jc.createBinder();
			JaxbValidationEventHandler eventHandler = new JaxbValidationEventHandler();
			eventHandler.setContinue(false);
			binder.setEventHandler(eventHandler);
			jaxbElement =  binder.unmarshal( doc);
			
			//log.debug("Clone: " + XmlUtils.marshaltoString(jaxbElement, true, true));
			
			return jaxbElement;
		} catch (JAXBException ex) {
			throw new IllegalArgumentException(ex);
		}
	}
	

	private Binder<Node> binder;
	
	
	/**
	 * Enables synchronization between XML infoset nodes and JAXB objects 
	 * representing same XML document.
	 * 
	 * An instance of this class maintains the association between XML nodes
	 * of an infoset preserving view and a JAXB representation of an XML document. 
	 * Navigation between the two views is provided by the methods 
	 * getXMLNode(Object) and getJAXBNode(Object) .
	 * 
	 * In theory, modifications can be made to either the infoset preserving view or 
	 * the JAXB representation of the document while the other view remains
	 * unmodified. The binder ought to be able to synchronize the changes made in
	 * the modified view back into the other view using the appropriate
	 * Binder update methods, #updateXML(Object, Object) or #updateJAXB(Object).
	 * 
	 * But JAXB doesn't currently work as advertised .. access to this
	 * object is offered for advanced users on an experimental basis only.
	 */
	public Binder<Node> getBinder() {
		
		return binder;
	}
	
	/**
	 * Fetch JAXB Nodes matching an XPath (for example "//w:p").
	 * 
	 * If you have modified your JAXB objects (eg added or changed a 
	 * w:p paragraph), you need to update the association. The problem
	 * is that this can only be done ONCE, owing to a bug in JAXB:
	 * see https://jaxb.dev.java.net/issues/show_bug.cgi?id=459
	 * 
	 * So this is left for you to choose to do via the refreshXmlFirst parameter.   
	 * 
	 * @param xpathExpr
	 * @param refreshXmlFirst
	 * @return
	 * @throws JAXBException
	 * @throws XPathBinderAssociationIsPartialException 
	 */	
	public List<Object> getJAXBNodesViaXPath(String xpathExpr, boolean refreshXmlFirst) 
			throws JAXBException, XPathBinderAssociationIsPartialException {
		
		return XmlUtils.getJAXBNodesViaXPath(binder, jaxbElement, xpathExpr, refreshXmlFirst);
	}	
	
	/**
	 * @param args
	 * @throws JAXBException 
	 * @throws XPathBinderAssociationIsPartialException 
	 */
	public static void main(String[] args) throws JAXBException, XPathBinderAssociationIsPartialException {
		
	    String pString = "<w:p xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\">"
	    	      +"<w:r>"
	    	        +"<w:t xml:space=\"preserve\">Here is some text.</w:t>"
	    	      +"</w:r>"
	    	      +"<w:r>"
	    	        +"<w:rPr>"
	    	          +"<w:i/>"
	    	        +"</w:rPr>"
	    	        +"<w:t>An italic run.</w:t>"
	    	      +"</w:r>"
	    	      +"<w:r>"
	    	        +"<w:rPr>"
	    	          +"<w:i/>"
	    	        +"</w:rPr>"
	    	        +"<w:t xml:space=\"preserve\">"  +"</w:t>"
	    	      +"</w:r>"
	    	      +"<w:r>"
	    	        +"<w:t>More stuff.</w:t>"
	    	      +"</w:r>"
	    	      +"<w:r>"
	    	        +"<w:t xml:space=\"preserve\">"  +"</w:t>"
	    	      +"</w:r>"
	    	      +"<w:r>"
	    	        +"<w:rPr>"
	    	          +"<w:b/>"
	    	        +"</w:rPr>"
	    	        +"<w:t>More stuff.</w:t>"
	    	      +"</w:r>"
	    	      +"<w:r>"
	    	        +"<w:t xml:space=\"preserve\">" +"</w:t>"
	    	      +"</w:r>"
	    	      +"<w:r>"
	    	        +"<w:t xml:space=\"preserve\">The run we are seeking.</w:t>"
	    	      +"</w:r>"
	    	      +"<w:r>"
	    	        +"<w:rPr>"
	    	          +"<w:b/>"
	    	        +"</w:rPr>"
	    	        +"<w:t>More stuff.</w:t>"
	    	      +"</w:r>"
	    	      +"<w:r>"
	    	        +"<w:t xml:space=\"preserve\">" +"</w:t>"
	    	      +"</w:r>"
	    	      +"<w:r>"
	    	        +"<w:t>More stuff.</w:t>"
	    	      +"</w:r>"
	    	    +"</w:p>";
	    
	    P pIn = (P)XmlUtils.unmarshalString(pString);
	    
	    XPathAwareCloner cloner = new XPathAwareCloner();
	    P clonedP = (P)cloner.deepCopy(pIn);
	    
	    List<Object> results = cloner.getJAXBNodesViaXPath("//w:r[contains( w:t, 'seeking')]", false);
	    //List<Object> results = cloner.getJAXBNodesViaXPath("//w:r", false);
	    
	    int i=1;
	    for (Object result: results) {
	    	System.out.println("\n\r" + i + ": " + XmlUtils.marshaltoString(result, true, true));
	    	i++;
	    }
		

	}

}
