/*******************************************************************************
 * Contributors:
 * Steven Spungin <steven@spungin.tv> - Bug 431735, Bug 391089
 *******************************************************************************/

package org.eclipse.e4.tools.emf.ui.internal.common.xml;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.eclipse.e4.tools.emf.ui.common.IModelResource;
import org.eclipse.e4.tools.emf.ui.internal.common.component.tabs.empty.E;
import org.eclipse.e4.ui.internal.workbench.E4XMIResource;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource.Diagnostic;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.FindReplaceDocumentAdapter;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.IRegion;
import org.xml.sax.InputSource;

public class EMFDocumentResourceMediator {
	private IModelResource modelResource;
	private Document document;
	private boolean updateFromEMF;
	private List<Diagnostic> errorList = new ArrayList<>();
	private Runnable documentValidationChanged;

	public EMFDocumentResourceMediator(final IModelResource modelResource) {
		this.modelResource = modelResource;
		document = new Document();
		document.addDocumentListener(new IDocumentListener() {

			@Override
			public void documentChanged(DocumentEvent event) {
				if (updateFromEMF) {
					return;
				}

				final String doc = document.get();
				final E4XMIResource res = new E4XMIResource();
				try {
					res.load(new InputSource(new StringReader(doc)), null);
					modelResource.replaceRoot(res.getContents().get(0));
					errorList.clear();
					if (documentValidationChanged != null) {
						documentValidationChanged.run();
					}
				} catch (final IOException e) {
					errorList = res.getErrors();
					if (documentValidationChanged != null) {
						documentValidationChanged.run();
					}

				}
			}

			@Override
			public void documentAboutToBeChanged(DocumentEvent event) {

			}
		});
		updateFromEMF();
	}

	public void setValidationChangedCallback(Runnable runnable) {
		documentValidationChanged = runnable;
	}

	public List<Diagnostic> getErrorList() {
		return Collections.unmodifiableList(errorList);
	}

	public void updateFromEMF() {
		try {
			updateFromEMF = true;
			document.set(toXMI(modelResource.getRoot().get(0)));
		} finally {
			updateFromEMF = false;
		}
	}

	public Document getDocument() {
		return document;
	}

	private String toXMI(EObject root) {
		final E4XMIResource resource = (E4XMIResource) root.eResource();
		// resource.getContents().add(EcoreUtil.copy(root));
		final StringWriter writer = new StringWriter();
		try {
			resource.save(writer, null);
		} catch (final IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return writer.toString();
	}

	/**
	 * @return The region for the start tag of the EObject, or null if not
	 *         found.
	 */
	public IRegion findStartTag(EObject object) {
		if (object == null) {
			return null;
		}
		final E4XMIResource root = (E4XMIResource) modelResource.getRoot().get(0).eResource();
		final String xmiId = root.getID(object);

		final FindReplaceDocumentAdapter find = new FindReplaceDocumentAdapter(document);
		IRegion region;
		try {
			// TODO This will not work if the element has '<' or '>' in an
			// attribute value
			region = find.find(0, "<.*?" + xmiId + ".*?>", true, true, false, true); //$NON-NLS-1$ //$NON-NLS-2$
			return region;
		} catch (final BadLocationException e) {
			return null;
		}
	}

	/**
	 *
	 * @return The region for the start of the text, or null if not found or the
	 *         text is empty.
	 */
	public IRegion findText(String text, int startOffset) {
		if (E.isEmpty(text)) {
			return null;
		}

		final FindReplaceDocumentAdapter find = new FindReplaceDocumentAdapter(document);
		IRegion region;
		try {
			region = find.find(startOffset, text, true, true, false, false);
			return region;
		} catch (final BadLocationException e) {
			return null;
		}
	}

}
