package gov.va.med.cds.template.generated;

import gov.va.med.cds.clinicaldata.ClinicalRecordInterface;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.StringReader;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.ValidationEvent;
import javax.xml.bind.ValidationEventHandler;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.util.StreamReaderDelegate;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.DocumentResult;

public class JaxBMarshallerUnmarshaller implements JaxBMarshallerUnmarshallerInterface {

	Map<String, JAXBContext> marshallUnmarshallContexts = new HashMap<String, JAXBContext>();

	public Element marshalToXmlElement(Object data, Class<?> clazz) throws Exception {

		// JAXBContext context = JAXBContext.newInstance(clazz);
		JAXBContext context = getMarshalUnmarshallContext(clazz);
		Marshaller marshaller = context.createMarshaller();
		marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

		DocumentResult dr = new DocumentResult();
		marshaller.marshal(data, dr);
		Document doc = dr.getDocument();

		return doc.getRootElement();
	}

	private JAXBContext getMarshalUnmarshallContext(Class<?> clazz) throws JAXBException {
		JAXBContext context = null;
		synchronized (marshallUnmarshallContexts) {
			context = marshallUnmarshallContexts.get(clazz.getCanonicalName());
			if (context == null) {
				context = JAXBContext.newInstance(clazz);
				marshallUnmarshallContexts.put(clazz.getCanonicalName(), context);
			}
		}
		return context;
	}

	public ClinicalRecordInterface unmarshal(Element clinicalRecord, Class<?> clazz) throws Exception {

		// JAXBContext jaxBContext = JAXBContext.newInstance(clazz);
		JAXBContext jaxBContext = getMarshalUnmarshallContext(clazz);
		Unmarshaller unmarshaller = jaxBContext.createUnmarshaller();

		// String clinicalRecordString = clinicalRecord.asXML();
		// StringReader reader = new StringReader(clinicalRecordString);

		// unmarshaller.setEventHandler(new MyValidationEventHandler());//debug
		// code
		XMLStreamReader reader = createXMLReader(clinicalRecord);

		return (ClinicalRecordInterface) unmarshaller.unmarshal(reader);

	}

	protected XMLStreamReader createXMLReader(Element clinicalRecord) throws Exception {
		String clinicalRecordString = clinicalRecord.asXML();
		InputStream is = new ByteArrayInputStream(clinicalRecordString.getBytes(Charset.forName("UTF-8")));
		XMLInputFactory xmlInputFactory = XMLInputFactory.newFactory();
		xmlInputFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
		xmlInputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false);
		XMLStreamReader xr = xmlInputFactory.createXMLStreamReader(is);

		return new XMLReaderNamespaceIgnored(xr);
	}

	class XMLReaderNamespaceIgnored extends StreamReaderDelegate {

		public XMLReaderNamespaceIgnored(XMLStreamReader reader) {
			super(reader);
		}

		@Override
		public String getAttributeNamespace(int arg0) {
			return "";
		}

		@Override
		public String getNamespaceURI() {
			return "";
		}

	}

	// debug code
	class MyValidationEventHandler implements ValidationEventHandler {

		public boolean handleEvent(ValidationEvent event) {
			System.out.println("\nEVENT");
			System.out.println("SEVERITY: " + event.getSeverity());
			System.out.println("MESSAGE: " + event.getMessage());
			System.out.println("LINKED EXCEPTION: " + event.getLinkedException());
			System.out.println("LOCATOR");
			System.out.println("   LINE NUMBER: " + event.getLocator().getLineNumber());
			System.out.println("   COLUMN NUMBER: " + event.getLocator().getColumnNumber());
			System.out.println("   OFFSET: " + event.getLocator().getOffset());
			System.out.println("   OBJECT: " + event.getLocator().getObject());
			System.out.println("   NODE: " + event.getLocator().getNode());
			System.out.println("   URL: " + event.getLocator().getURL());
			return true;

		}
	}

}
