/*******************************************************************************
 * Copyright (c) 2001, 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Jens Lukowski/Innoopract - initial renaming/restructuring
 *     
 *******************************************************************************/
package org.eclipse.wst.xml.core.internal.document;



import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionList;
import org.eclipse.wst.sse.core.internal.text.BasicStructuredDocument;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMElement;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMText;
import org.eclipse.wst.xml.core.internal.regions.DOMRegionContext;
import org.w3c.dom.Node;


class ReadOnlyController {

	class Span {
		int length;
		int offset;

		Span(int offset, int length) {
			this.offset = offset;
			this.length = length;
		}
	}

	private static ReadOnlyController fInstance;

	static synchronized ReadOnlyController getInstance() {
		if (fInstance == null) {
			fInstance = new ReadOnlyController();
		}
		return fInstance;
	}

	static private void lock(IStructuredDocument doc, int offset, int length, boolean canInsertBefore, boolean canInsertAfter) {
		if (doc == null) {
			return;
		}
		if (doc instanceof BasicStructuredDocument) {
			((BasicStructuredDocument)doc).makeReadOnly(offset, length, canInsertBefore, canInsertAfter);
		} else {
			doc.makeReadOnly(offset, length);
		}
	}

	static private void lock(IStructuredDocumentRegion node, boolean canInsertBefore, boolean canInsertAfter) {
		if (node == null) {
			return;
		}
		IStructuredDocument doc = node.getParentDocument();
		if (doc == null) {
			return;
		}
		if (doc instanceof BasicStructuredDocument) {
			((BasicStructuredDocument)doc).makeReadOnly(node.getStart(), node.getLength(), canInsertBefore, canInsertAfter);
		} else {
			doc.makeReadOnly(node.getStart(), node.getLength());
		}
	}

	static private void unlock(IStructuredDocumentRegion node) {
		if (node == null) {
			return;
		}
		IStructuredDocument doc = node.getParentDocument();
		if (doc == null) {
			return;
		}
		doc.clearReadOnly(node.getStart(), node.getLength());
	}

	private ReadOnlyController() {
		super();
	}

	private Span getDataSpan(IDOMNode node) {
		switch (node.getNodeType()) {
			case Node.ELEMENT_NODE :
				return getDataSpanForElement((IDOMElement) node);
			case Node.TEXT_NODE :
				return getDataSpanForText((IDOMText) node);
			default :
				return new Span(0, -1);
		}
	}

	private Span getDataSpanForElement(IDOMElement node) {
		IStructuredDocumentRegion docRegion = node.getStartStructuredDocumentRegion();
		if (docRegion == null || docRegion.getParentDocument() == null) {
			return new Span(0, -1);
		}

		ITextRegionList regions = docRegion.getRegions();
		if (regions == null) {
			return new Span(0, -1);
		}

		String startType;
		String endType;
		if (node.isCommentTag()) {
			startType = DOMRegionContext.XML_COMMENT_OPEN;
			endType = DOMRegionContext.XML_COMMENT_CLOSE;
		} else {
			startType = DOMRegionContext.XML_TAG_NAME;
			endType = DOMRegionContext.XML_TAG_CLOSE;
		}

		int startOffset = -1;
		int endOffset = -1;
		ITextRegion prevRegion = null;
		ITextRegion region;
		for (int i = 0; i < regions.size(); i++) {
			region = regions.get(i);
			String type = region.getType();
			if (type == startType) {
				startOffset = region.getEnd();
			} else if (type == endType && prevRegion != null) {
				endOffset = prevRegion.getTextEnd();
			}
			prevRegion = region;
		}

		if (0 <= startOffset && 0 <= endOffset) {
			return new Span(startOffset, endOffset - startOffset);
		} else {
			return new Span(0, -1);
		}
	}

	private Span getDataSpanForText(IDOMText node) {
		IStructuredDocumentRegion docRegion = ((NodeImpl) node).getStructuredDocumentRegion();
		if (docRegion == null) {
			return new Span(0, -1);
		}
		return new Span(0, docRegion.getLength());
	}

	/**
	 * This method is used from parent's setChildEditable()
	 * 
	 * case 1:<parent><node attr="value"/> <node2></parent>
	 * x####################x case 2:<parent><node attr="value"> <child>
	 * </child> </node> </parent> x###################? ?#######x (? :
	 * editable if node.isEditable() == true)
	 */
	void lockBoth(IDOMNode node) {
		if (node == null) {
			return;
		}

		IStructuredDocumentRegion flatNode;
		boolean canInsert = false;

		// end node (element)
		if (node.getNodeType() == Node.ELEMENT_NODE) {
			flatNode = node.getEndStructuredDocumentRegion();
			if (flatNode != null) {
				canInsert = node.isChildEditable();
				lock(flatNode, canInsert, false);
			}
		}
		// start node
		flatNode = node.getStartStructuredDocumentRegion();
		if (flatNode != null) {
			lock(flatNode, false, canInsert);
		}
	}

	void lockData(IDOMNode node) {
		if (node == null) {
			return;
		}

		Span span = getDataSpan(node);
		if (0 <= span.length) {
			lock(node.getModel().getStructuredDocument(), node.getStartOffset() + span.offset, span.length, false, false);
		}
	}

	/**
	 * lock itself and all descendants
	 */
	void lockDeep(IDOMNode node) {
		if (node == null) {
			return;
		}

		int offset = node.getStartOffset();
		int length = node.getEndOffset() - offset;

		boolean canInsert = true;
		IDOMNode parent = (IDOMNode) node.getParentNode();
		if (parent != null && !parent.isChildEditable()) {
			canInsert = false;
		}
		lock(node.getStructuredDocument(), offset, length, canInsert, canInsert);
	}

	/**
	 * This method is used from parent's setChildEditable()
	 * 
	 * case 1:<parent><node attr="value"/> <node2></parent> x######x x##x
	 * case 2:<parent><node attr="value"> <child></child> </node> </parent>
	 * x######x x#? ?#######x (? : editable if node.isEditable() == true)
	 */
	void lockNode(IDOMNode node) {
		if (node == null) {
			return;
		}
		if (!node.isDataEditable()) {
			lockBoth(node);
			return;
		}

		IStructuredDocumentRegion flatNode;
		boolean canInsert = false;

		// end node (element)
		if (node.getNodeType() == Node.ELEMENT_NODE) {
			flatNode = node.getEndStructuredDocumentRegion();
			if (flatNode != null) {
				canInsert = node.isChildEditable();
				lock(flatNode, canInsert, false);
			}
		}
		// start node
		flatNode = node.getStartStructuredDocumentRegion();
		if (flatNode != null) {
			Span span = getDataSpan(node);
			if (0 <= span.length) {
				IStructuredDocument structuredDocument = flatNode.getParentDocument();
				int offset, length;
				offset = flatNode.getStart();
				length = span.offset;
				lock(structuredDocument, offset, length, false, false);
				offset = offset + span.offset + span.length;
				length = flatNode.getEnd() - offset;
				lock(structuredDocument, offset, length, false, canInsert);
			} else {
				lock(flatNode, false, canInsert);
			}
		}
	}

	private void unlock(IStructuredDocument doc, int offset, int length) {
		if (doc == null) {
			return;
		}
		doc.clearReadOnly(offset, length);
	}

	void unlockBoth(IDOMNode node) {
		if (node == null) {
			return;
		}

		IStructuredDocumentRegion flatNode;
		// start node
		flatNode = node.getStartStructuredDocumentRegion();
		if (flatNode != null) {
			unlock(flatNode);
		}
		// end node
		flatNode = node.getEndStructuredDocumentRegion();
		if (flatNode != null) {
			unlock(flatNode);
		}
	}

	void unlockData(IDOMNode node) {
		if (node == null) {
			return;
		}

		Span span = getDataSpan(node);
		if (0 <= span.length) {
			unlock(node.getModel().getStructuredDocument(), span.offset, span.length);
		}
	}

	void unlockDeep(IDOMNode node) {
		if (node == null) {
			return;
		}

		int offset = node.getStartOffset();
		int length = node.getEndOffset() - offset;

		unlock(node.getStructuredDocument(), offset, length);
	}

	void unlockNode(IDOMNode node) {
		if (node == null) {
			return;
		}

		IStructuredDocumentRegion flatNode;
		// end node
		if (node.getNodeType() == Node.ELEMENT_NODE) {
			flatNode = node.getEndStructuredDocumentRegion();
			if (flatNode != null) {
				unlock(flatNode);
			}
		}

		// start node
		flatNode = node.getStartStructuredDocumentRegion();
		if (flatNode != null) {
			if (node.isDataEditable()) {
				unlock(flatNode);
			} else {
				Span span = getDataSpan(node);
				if (span.length <= 0) {
					unlock(flatNode);
				} else {
					IStructuredDocument structuredDocument = flatNode.getParentDocument();
					int offset, length;
					offset = flatNode.getStart();
					length = span.offset - offset;
					unlock(structuredDocument, offset, length);
					offset = span.offset + span.length;
					length = flatNode.getEnd() - span.offset;
					unlock(structuredDocument, offset, length);
				}
			}
		}
	}
}
