/*******************************************************************************
 * Copyright (c) 2004, 2008 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.wst.css.ui.internal.contentassist;



import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
import org.eclipse.wst.css.core.internal.provisional.adapters.ICSSModelAdapter;
import org.eclipse.wst.css.core.internal.provisional.document.ICSSDocument;
import org.eclipse.wst.css.core.internal.provisional.document.ICSSModel;
import org.eclipse.wst.css.core.internal.provisional.document.ICSSNode;
import org.eclipse.wst.css.ui.internal.templates.TemplateContextTypeIdsCSS;
import org.eclipse.wst.html.core.internal.htmlcss.StyleAdapterFactory;
import org.eclipse.wst.sse.core.StructuredModelManager;
import org.eclipse.wst.sse.core.internal.provisional.INodeAdapter;
import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
import org.eclipse.wst.sse.ui.internal.contentassist.ContentAssistUtils;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode;
import org.eclipse.wst.xml.ui.internal.contentassist.XMLContentAssistUtilities;
import org.eclipse.wst.xml.ui.internal.util.SharedXMLEditorPluginImageHelper;

public class CSSContentAssistProcessor implements IContentAssistProcessor {

	private int fDocumentOffset = 0;
	private char fQuote = 0;
	private CSSTemplateCompletionProcessor fTemplateProcessor = null;

	/**
	 * Return a list of proposed code completions based on the specified
	 * location within the document that corresponds to the current cursor
	 * position within the text-editor control.
	 * 
	 * @param documentPosition
	 *            a location within the document
	 * @return an array of code-assist items
	 */
	public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int documentPosition) {

		IndexedRegion indexedNode = ContentAssistUtils.getNodeAt(viewer, documentPosition + fDocumentOffset);
		IDOMNode xNode = null;
		IDOMNode parent = null;
		CSSProposalArranger arranger = null;
		boolean isEmptyDocument = false;
		// If there is a selected region, we'll need to replace the text
		ITextSelection selection = (ITextSelection) viewer.getSelectionProvider().getSelection();
		boolean selected = (selection != null && selection.getText() != null && selection.getText().trim().length() > 0);
		
		// bail if we couldn't get an indexed node
		// if(indexedNode == null) return new ICompletionProposal[0];
		if (indexedNode instanceof IDOMNode) {
			xNode = (IDOMNode) indexedNode;
			parent = (IDOMNode) xNode.getParentNode();
		}
		// need to get in here if there in the no 0 region <style>|</style>
		// case
		if ((xNode != null) && xNode.getNodeName().equalsIgnoreCase(HTML40Namespace.ElementName.STYLE)) {
			// now we know the cursor is in a <style> tag w/out region
			IStructuredModel cssModel = getCSSModel(xNode);
			if (cssModel != null) {
				// adjust offsets for embedded style
				int offset = documentPosition;
				int pos = 0;
				IndexedRegion keyIndexedNode = cssModel.getIndexedRegion(pos);
				if (keyIndexedNode == null) {
					keyIndexedNode = (IndexedRegion) ((ICSSModel) cssModel).getDocument();
				}
				arranger = new CSSProposalArranger(pos, (ICSSNode) keyIndexedNode, offset, (char) 0, selected);
			}
		} else if ((parent != null) && parent.getNodeName().equalsIgnoreCase(HTML40Namespace.ElementName.STYLE)) {
			// now we know the cursor is in a <style> tag with a region
			// use the parent because that will be the <style> tag
			IStructuredModel cssModel = getCSSModel(parent);
			if (cssModel != null) {
				// adjust offsets for embedded style
				int offset = indexedNode.getStartOffset();
				int pos = documentPosition - offset;
				IndexedRegion keyIndexedNode = cssModel.getIndexedRegion(pos);
				if (keyIndexedNode == null) {
					keyIndexedNode = (IndexedRegion) ((ICSSModel) cssModel).getDocument();
				}
				arranger = new CSSProposalArranger(pos, (ICSSNode) keyIndexedNode, offset, (char) 0, selected);
			}
		} else if (indexedNode instanceof IDOMNode) {
			// get model for node w/ style attribute
			IStructuredModel cssModel = getCSSModel((IDOMNode) indexedNode);
			if (cssModel != null) {
				IndexedRegion keyIndexedNode = cssModel.getIndexedRegion(documentPosition - fDocumentOffset);
				if (keyIndexedNode == null) {
					keyIndexedNode = (IndexedRegion) ((ICSSModel) cssModel).getDocument();
				}
				if (keyIndexedNode instanceof ICSSNode) {
					// inline style for a tag, not embedded
					arranger = new CSSProposalArranger(documentPosition, (ICSSNode) keyIndexedNode, fDocumentOffset, fQuote, selected);
				}
			}
		} else if (indexedNode instanceof ICSSNode) {
			// when editing external CSS using CSS Designer, ICSSNode is
			// passed.
			ICSSDocument cssdoc = ((ICSSNode) indexedNode).getOwnerDocument();
			if (cssdoc != null) {
				IStructuredModel cssModel = cssdoc.getModel();
				if (cssModel != null) {
					IndexedRegion keyIndexedNode = cssModel.getIndexedRegion(documentPosition - fDocumentOffset);
					if (keyIndexedNode == null) {
						keyIndexedNode = (IndexedRegion) ((ICSSModel) cssModel).getDocument();
					}
					if (keyIndexedNode instanceof ICSSNode) {
						// inline style for a tag, not embedded
						arranger = new CSSProposalArranger(documentPosition, (ICSSNode) keyIndexedNode, fDocumentOffset, fQuote, selected);
					}
				}
			}
		} else if ((indexedNode == null) && isViewerEmpty(viewer)) {
			isEmptyDocument = true;
			// the top of empty CSS Document
			IStructuredModel cssModel = null;
			try {
				cssModel = StructuredModelManager.getModelManager().getExistingModelForRead(viewer.getDocument());
				if (cssModel instanceof ICSSModel) {
					IndexedRegion keyIndexedNode = cssModel.getIndexedRegion(documentPosition - fDocumentOffset);
					if (keyIndexedNode == null) {
						keyIndexedNode = (IndexedRegion) ((ICSSModel) cssModel).getDocument();
					}
					if (keyIndexedNode instanceof ICSSNode) {
						// inline style for a tag, not embedded
						arranger = new CSSProposalArranger(documentPosition, (ICSSNode) keyIndexedNode, fDocumentOffset, fQuote);
					}
				}
			} finally {
				if (cssModel != null)
					cssModel.releaseFromRead();
			}
		}

		ICompletionProposal[] proposals = new ICompletionProposal[0];
		if (arranger != null) {
			fDocumentOffset = 0;
			proposals = arranger.getProposals();

			ICompletionProposal[] newfileproposals = new ICompletionProposal[0];
			ICompletionProposal[] anyproposals = new ICompletionProposal[0];
			// add template proposals
			if (getTemplateCompletionProcessor() != null) {
				if (isEmptyDocument) {
					getTemplateCompletionProcessor().setContextType(TemplateContextTypeIdsCSS.NEW);
					newfileproposals = getTemplateCompletionProcessor().computeCompletionProposals(viewer, documentPosition);
				}
				getTemplateCompletionProcessor().setContextType(TemplateContextTypeIdsCSS.ALL);
				anyproposals = getTemplateCompletionProcessor().computeCompletionProposals(viewer, documentPosition);
			}

			// add end tag if parent is not closed
			ICompletionProposal endTag = XMLContentAssistUtilities.computeXMLEndTagProposal(viewer, documentPosition, indexedNode, HTML40Namespace.ElementName.STYLE, SharedXMLEditorPluginImageHelper.IMG_OBJ_TAG_GENERIC); 

			// add the additional proposals
			int additionalLength = newfileproposals.length + anyproposals.length;
			additionalLength = (endTag != null) ? ++additionalLength : additionalLength;
			if (additionalLength > 0) {
				ICompletionProposal[] plusOnes = new ICompletionProposal[proposals.length + additionalLength];
				int appendPos = proposals.length;
				// add end tag proposal
				if (endTag != null) {
					System.arraycopy(proposals, 0, plusOnes, 1, proposals.length);
					plusOnes[0] = endTag;
					++appendPos;
				} else {
					System.arraycopy(proposals, 0, plusOnes, 0, proposals.length);
				}
				// add items in newfileproposals
				for (int i = 0; i < newfileproposals.length; ++i) {
					plusOnes[appendPos + i] = newfileproposals[i];
				}
				// add items in anyproposals
				appendPos = appendPos + newfileproposals.length;
				for (int i = 0; i < anyproposals.length; ++i) {
					plusOnes[appendPos + i] = anyproposals[i];
				}
				proposals = plusOnes;
			}
		}
		return proposals;
	}

	/**
	 * Returns true if there is no text or it's all white space, otherwise
	 * returns false
	 * 
	 * @param treeNode
	 * @param textViewer
	 * @return boolean
	 */
	private boolean isViewerEmpty(ITextViewer textViewer) {
		boolean isEmpty = false;
		String text = textViewer.getTextWidget().getText();
		if ((text == null) || ((text != null) && text.trim().equals(""))) //$NON-NLS-1$
			isEmpty = true;
		return isEmpty;
	}

	/**
	 * Get CSSModel for an indexed node
	 * 
	 * @param indexedNode
	 * @return IStructuredModel
	 */
	// private IStructuredModel getCSSModel(IndexedRegion indexedNode) {
	// if (indexedNode == null) return null;
	// Node node = (Node)indexedNode;
	// INodeNotifier notifier = (INodeNotifier)node.getParentNode();
	// if (notifier == null) return null;
	// INodeAdapter adapter =
	// StyleAdapterFactory.getInstance().adapt(notifier);
	// if (adapter == null || !(adapter instanceof CSSModelAdapter)) return
	// null;
	// CSSModelAdapter modelAdapter = (CSSModelAdapter)adapter;
	// return modelAdapter.getModel();
	// }
	/**
	 * Returns the CSSmodel for a given XML node.
	 * 
	 * @param element
	 * @return IStructuredModel
	 */
	private IStructuredModel getCSSModel(IDOMNode element) {
		if (element == null)
			return null;
		INodeAdapter adapter = StyleAdapterFactory.getInstance().adapt(element);
		if ((adapter == null) || !(adapter instanceof ICSSModelAdapter))
			return null;
		ICSSModelAdapter modelAdapter = (ICSSModelAdapter) adapter;
		return modelAdapter.getModel();
	}

	/**
	 * Returns information about possible contexts based on the specified
	 * location within the document that corresponds to the current cursor
	 * position within the text viewer.
	 * 
	 * @param viewer
	 *            the viewer whose document is used to compute the possible
	 *            contexts
	 * @param documentPosition
	 *            an offset within the document for which context information
	 *            should be computed
	 * @return an array of context information objects or <code>null</code>
	 *         if no context could be found
	 */
	public org.eclipse.jface.text.contentassist.IContextInformation[] computeContextInformation(org.eclipse.jface.text.ITextViewer viewer, int documentOffset) {
		return null;
	}

	/**
	 * Returns the characters which when entered by the user should
	 * automatically trigger the presentation of possible completions.
	 * 
	 * @return the auto activation characters for completion proposal or
	 *         <code>null</code> if no auto activation is desired
	 */
	public char[] getCompletionProposalAutoActivationCharacters() {
		return null;
	}

	/**
	 * Returns the characters which when entered by the user should
	 * automatically trigger the presentation of context information.
	 * 
	 * @return the auto activation characters for presenting context
	 *         information or <code>null</code> if no auto activation is
	 *         desired
	 */
	public char[] getContextInformationAutoActivationCharacters() {
		return null;
	}

	/**
	 * Returns a validator used to determine when displayed context
	 * information should be dismissed. May only return <code>null</code> if
	 * the processor is incapable of computing context information.
	 * 
	 * @return a context information validator, or <code>null</code> if the
	 *         processor is incapable of computing context information
	 */
	public org.eclipse.jface.text.contentassist.IContextInformationValidator getContextInformationValidator() {
		return null;
	}

	/**
	 * Return the reason why computeProposals was not able to find any
	 * completions.
	 * 
	 * @return an error message or null if no error occurred
	 */
	public String getErrorMessage() {
		return null;
	}

	/**
	 * Insert the method's description here. Creation date: (2001/05/22
	 * 10:37:05)
	 * 
	 * @param offset
	 *            int
	 */
	public void setDocumentOffset(int offset) {
		fDocumentOffset = offset;
	}

	/**
	 * 
	 * @param quote
	 *            char
	 */
	public void setQuoteCharOfStyleAttribute(char quote) {
		fQuote = quote;
	}

	private CSSTemplateCompletionProcessor getTemplateCompletionProcessor() {
		if (fTemplateProcessor == null) {
			fTemplateProcessor = new CSSTemplateCompletionProcessor();
		}
		return fTemplateProcessor;
	}
}
