/*******************************************************************************
 * Copyright (c) 2010 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 java.util.Arrays;
import java.util.List;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.wst.css.core.internal.Logger;
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.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.core.internal.provisional.text.IStructuredDocumentRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion;
import org.eclipse.wst.sse.ui.contentassist.CompletionProposalInvocationContext;
import org.eclipse.wst.sse.ui.contentassist.ICompletionProposalComputer;
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;

/**
 * <p>Completion computer for CSS</p>
 */
public class CSSCompletionProposalComputer implements ICompletionProposalComputer {
	/**
	 * @see org.eclipse.wst.sse.ui.contentassist.ICompletionProposalComputer#computeCompletionProposals(org.eclipse.wst.sse.ui.contentassist.CompletionProposalInvocationContext, org.eclipse.core.runtime.IProgressMonitor)
	 */
	public List computeCompletionProposals(CompletionProposalInvocationContext context, IProgressMonitor monitor) {
		ITextViewer viewer = context.getViewer();
		int documentPosition = context.getInvocationOffset();
		
		IndexedRegion indexedNode = ContentAssistUtils.getNodeAt(viewer, documentPosition);
		IDOMNode xNode = null;
		IDOMNode parent = null;
		CSSProposalArranger arranger = null;

		// If there is a selected region, we'll need to replace the text
		ITextSelection selection = (ITextSelection) viewer.getSelectionProvider().getSelection();

		// 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, selection.getLength(), (char) 0);
			}
		} 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, selection.getLength(), (char) 0);
			}
		} else if (indexedNode instanceof IDOMNode) {
			IDOMNode domNode = ((IDOMNode)indexedNode);
			// get model for node w/ style attribute
			IStructuredModel cssModel = getCSSModel(domNode);
			if (cssModel != null) {
				// adjust offsets for embedded style
				int textRegionStartOffset = getTextRegionStartOffset(domNode, documentPosition);
				int pos = documentPosition - textRegionStartOffset;
				
				char quote = (char) 0;
				try {
					quote = context.getDocument().get(textRegionStartOffset, 1).charAt(0);
				} catch (BadLocationException e) {
					Logger.logException("error getting quote character", e);
				}
				
				//get css indexed region
				IndexedRegion cssIndexedNode = cssModel.getIndexedRegion(pos);
				if (cssIndexedNode == null) {
					cssIndexedNode = (IndexedRegion) ((ICSSModel) cssModel).getDocument();
				}
				if (cssIndexedNode instanceof ICSSNode) {
					// inline style for a tag, not embedded
					arranger = new CSSProposalArranger(pos, (ICSSNode) cssIndexedNode, textRegionStartOffset, selection.getLength(), quote);
				}
			}
		} 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);
					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, 0, selection.getLength(), (char)0);
					}
				}
			}
		} else if ((indexedNode == null) && ContentAssistUtils.isViewerEmpty(viewer)) {
			// 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);
					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, 0, (char)0);
					}
				}
			} finally {
				if (cssModel != null)
					cssModel.releaseFromRead();
			}
		}

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

			ICompletionProposal[] newfileproposals = new ICompletionProposal[0];
			ICompletionProposal[] anyproposals = new ICompletionProposal[0];

			// 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 Arrays.asList(proposals);
	}

	/**
	 * @see org.eclipse.wst.sse.ui.contentassist.ICompletionProposalComputer#computeContextInformation(org.eclipse.wst.sse.ui.contentassist.CompletionProposalInvocationContext, org.eclipse.core.runtime.IProgressMonitor)
	 */
	public List computeContextInformation(
			CompletionProposalInvocationContext context,
			IProgressMonitor monitor) {
		// TODO Auto-generated method stub
		return null;
	}

	/**
	 * @see org.eclipse.wst.sse.ui.contentassist.ICompletionProposalComputer#getErrorMessage()
	 */
	public String getErrorMessage() {
		// TODO Auto-generated method stub
		return null;
	}
	
	/**
	 * @see org.eclipse.wst.sse.ui.contentassist.ICompletionProposalComputer#sessionStarted()
	 */
	public void sessionStarted() {
		//default is to do nothing
	}

	/**
	 * @see org.eclipse.wst.sse.ui.contentassist.ICompletionProposalComputer#sessionEnded()
	 */
	public void sessionEnded() {
		//default is to do nothing
	}
	
	/**
	 * Returns the CSSmodel for a given XML node.
	 * 
	 * @param element
	 * @return IStructuredModel
	 */
	private static 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();
	}
	
	/**
	 * <p>Get the start offset of the text region containing the given document position</p>
	 * 
	 * @param domNode {@link IDOMNode} containing the document position
	 * @param documentPosition the document relative position to get the start
	 * offset of the containing {@link ITextRegion} for
	 * @return start offset of the {@link ITextRegion} containing the given document position
	 */
	private static int getTextRegionStartOffset(IDOMNode domNode, int documentPosition) {
		IStructuredDocumentRegion structRegion = domNode.getFirstStructuredDocumentRegion();
		return structRegion.getStartOffset(structRegion.getRegionAtCharacterOffset(documentPosition));
	}
}
