/*******************************************************************************
 * Copyright (c) 2004, 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.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import org.eclipse.wst.css.core.internal.CSSCorePlugin;
import org.eclipse.wst.css.core.internal.metamodel.CSSMMDescriptor;
import org.eclipse.wst.css.core.internal.metamodel.CSSMMFunction;
import org.eclipse.wst.css.core.internal.metamodel.CSSMMNode;
import org.eclipse.wst.css.core.internal.metamodel.CSSMMNumber;
import org.eclipse.wst.css.core.internal.metamodel.CSSMMProperty;
import org.eclipse.wst.css.core.internal.metamodel.CSSMMUnit;
import org.eclipse.wst.css.core.internal.metamodel.util.CSSFunctionID;
import org.eclipse.wst.css.core.internal.metamodel.util.CSSMetaModelUtil;
import org.eclipse.wst.css.core.internal.parserz.CSSRegionContexts;
import org.eclipse.wst.css.core.internal.preferences.CSSCorePreferenceNames;
import org.eclipse.wst.css.core.internal.provisional.document.ICSSNode;
import org.eclipse.wst.css.core.internal.provisional.document.ICSSPrimitiveValue;
import org.eclipse.wst.css.core.internal.provisional.document.ICSSStyleDeclItem;
import org.eclipse.wst.css.core.internal.util.CSSUtil;
import org.eclipse.wst.css.core.internal.util.RegionIterator;
import org.eclipse.wst.css.ui.internal.image.CSSImageType;
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.w3c.dom.css.CSSFontFaceRule;

class CSSProposalGeneratorForDeclarationValue extends CSSProposalGenerator {

	private static final String IMPORTANT = "!important"; //$NON-NLS-1$
	private boolean fUseUpperCase;
	private boolean fAppendSemiColon;

	/**
	 * CSSProposalGeneratorForDeclarationValue constructor comment.
	 * 
	 */
	CSSProposalGeneratorForDeclarationValue(CSSContentAssistContext context) {
		super(context);
		fUseUpperCase = CSSCorePlugin.getDefault().getPluginPreferences().getInt(CSSCorePreferenceNames.CASE_PROPERTY_VALUE) == CSSCorePreferenceNames.UPPER;
	}

	/**
	 *  
	 */
	private void addFunction(List candidates, CSSMMFunction prop) {
		String text = prop.toString();
		if (!isMatch(text)) {
			return;
		}

		int cursorPos = 0;
		StringBuffer buf = new StringBuffer();
		if (prop.getName().equals(CSSFunctionID.F_URI)) {
			StringAndOffset sao = generateURI();
			buf.append(sao.fString);
			cursorPos = sao.fOffset;
		} else {
			buf.append(prop.toString());
			cursorPos = buf.length();
			StringAndOffset sao = generateParenthesis();
			buf.append(sao.fString);
			cursorPos += sao.fOffset;
		}

		text = buf.toString();
		text = (fUseUpperCase) ? text.toUpperCase() : text.toLowerCase();

		CSSCACandidate item = new CSSCACandidate();
		item.setReplacementString(text);
		item.setCursorPosition(cursorPos);
		item.setDisplayString(text);
		item.setMMNode(prop);
		item.setImageType(CSSImageType.VALUE_FUNCTION);
		appendSemiColon(item);
		candidates.add(item);
	}

	/**
	 *  
	 */
	private void addNumber(List candidates, CSSMMNumber prop) {
		String fullText = fContext.getTextToReplace();
		// skip number
		int unitIndex = -1;
		for (int i = 0; i < fullText.length(); i++) {
			if (Character.isDigit(fullText.charAt(i))) {
				unitIndex = i + 1;
			} else {
				break;
			}
		}

		String unitSubText = ""; //$NON-NLS-1$
		String numSubText = ""; //$NON-NLS-1$
		if (0 <= unitIndex) {
			numSubText = fullText.substring(0, unitIndex);
			if (unitIndex < fullText.length()) {
				unitSubText = fullText.substring(unitIndex);
			}
		} else {
			unitSubText = fullText;
		}

		Iterator i = prop.getDescendants();
		while (i.hasNext()) {
			CSSMMUnit unit = (CSSMMUnit) i.next();
			String unitString = unit.getUnitString();
			if ((0 < unitSubText.length() && unitString.indexOf(unitSubText) != 0) || (0 < numSubText.length() && unitString.equals("#"))) { //$NON-NLS-1$
				continue;
			}

			String text = numSubText + unitString;
			text = (fUseUpperCase) ? text.toUpperCase() : text.toLowerCase();
			CSSCACandidate item = new CSSCACandidate();
			item.setReplacementString(text);
			if (0 < numSubText.length() || text.equals("#")) { //$NON-NLS-1$
				item.setCursorPosition(text.length());
			} else {
				item.setCursorPosition(0);
			}
			item.setDisplayString(text);
			item.setImageType(CSSImageType.VALUE_NUMBER);
			appendSemiColon(item);
			candidates.add(item);
		}
	}

	/**
	 *  
	 */
	private void checkSemiColon() {
		fAppendSemiColon = false;

		ITextRegion targetRegion = fContext.getTargetRegion();
		if (targetRegion != null && targetRegion.getType() != CSSRegionContexts.CSS_DECLARATION_DELIMITER) {
			// find trailing ":" or ";"
			// if ":" before ";" is found, add ";"
			RegionIterator iterator = fContext.getRegionIterator();
			IStructuredDocumentRegion container = iterator.getStructuredDocumentRegion();
			while (iterator.hasNext()) {
				ITextRegion region = iterator.next();
				if (iterator.getStructuredDocumentRegion() != container) {
					break;
				}
				if (region.getType() == CSSRegionContexts.CSS_DECLARATION_SEPARATOR) {
					fAppendSemiColon = true;
					break;
				}
			}
			if (!fAppendSemiColon) {
				// second chance:
				// leading IStructuredDocumentRegion is not ";"
				IStructuredDocumentRegion nextStructuredDocumentRegion = CSSUtil.findNextSignificantNode(container);
				if (CSSUtil.getStructuredDocumentRegionType(nextStructuredDocumentRegion) != CSSRegionContexts.CSS_DECLARATION_DELIMITER) {
					fAppendSemiColon = true;
				}
			}
		}
	}

	/**
	 *  
	 */
	private void appendSemiColon(CSSCACandidate item) {
		if (fAppendSemiColon) {
			String replacementString = item.getReplacementString();
			item.setReplacementString(replacementString + ";"); //$NON-NLS-1$
			int cursorPosition = item.getCursorPosition();
			if (replacementString.length() <= cursorPosition) {
				// cursorpos is tail of string
				cursorPosition++;
				item.setCursorPosition(cursorPosition);
			}
		}
	}

	/**
	 *  
	 */
	private void addSemiColon(List candidates) {
		ICSSNode targetNode = fContext.getTargetNode();
		if (targetNode instanceof ICSSStyleDeclItem) {
			ICSSNode firstChild = targetNode.getFirstChild();
			if (firstChild == null) {
				return;
			}
			if (firstChild instanceof IndexedRegion) {
				int startOffset = ((IndexedRegion) firstChild).getStartOffset();
				if (fContext.getCursorPos() <= startOffset) {
					return;
				}
			}
		}

		boolean bAddCloser = false;

		ITextRegion targetRegion = fContext.getTargetRegion();
		if (targetRegion != null && targetRegion.getType() != CSSRegionContexts.CSS_DECLARATION_DELIMITER) {
			// find trailing ":" or ";"
			// if ":" before ";" is found, add ";"
			RegionIterator iterator = fContext.getRegionIterator();
			IStructuredDocumentRegion container = iterator.getStructuredDocumentRegion();
			while (iterator.hasNext()) {
				ITextRegion region = iterator.next();
				if (iterator.getStructuredDocumentRegion() != container) {
					break;
				}
				if (region.getType() == CSSRegionContexts.CSS_DECLARATION_SEPARATOR) {
					bAddCloser = true;
					break;
				}
			}
			if (!bAddCloser) {
				// second chance:
				// leading IStructuredDocumentRegion is not ";"
				IStructuredDocumentRegion nextStructuredDocumentRegion = CSSUtil.findNextSignificantNode(container);
				if (CSSUtil.getStructuredDocumentRegionType(nextStructuredDocumentRegion) != CSSRegionContexts.CSS_DECLARATION_DELIMITER) {
					bAddCloser = true;
				}
			}
		}

		if (bAddCloser) {
			CSSCACandidate item = new CSSCACandidate();
			String text = fContext.getTextToReplace() + ";";//$NON-NLS-1$
			item.setReplacementString(text);
			item.setCursorPosition(text.length());
			item.setDisplayString(";");//$NON-NLS-1$
			item.setImageType(null);
			candidates.add(item);
		}
	}

	/**
	 *  
	 */
	private void addString(List candidates, String text) {
		if (!isMatch(text)) {
			return;
		}

		text = (fUseUpperCase) ? text.toUpperCase() : text.toLowerCase();

		CSSCACandidate item = new CSSCACandidate();
		item.setReplacementString(text);
		item.setCursorPosition(text.length());
		item.setDisplayString(text);
		item.setImageType(CSSImageType.VALUE_STRING);
		appendSemiColon(item);
		candidates.add(item);
	}

	private void addImportant(List candidates) {
		ICSSNode targetNode = fContext.getTargetNode();
		while (targetNode instanceof ICSSPrimitiveValue) {
			targetNode = targetNode.getParentNode();
		}
		if (!(targetNode instanceof ICSSStyleDeclItem)) {
			return;
		}
		// 1. has no priority region
		// 2. cursor position is after of last child
		// 3. normal isMatch method
		String priority = ((ICSSStyleDeclItem) targetNode).getPriority();
		if (priority == null || priority.length() == 0) {
			ICSSNode lastChild = targetNode.getLastChild();
			if (lastChild instanceof IndexedRegion) {
				int startOffset = ((IndexedRegion) lastChild).getStartOffset();
				//	int endOffset = ((IndexedRegion)lastChild).getEndOffset();
				if (startOffset < fContext.getCursorPos() && isMatch(IMPORTANT)) {
					CSSCACandidate item = new CSSCACandidate();
					item.setReplacementString(IMPORTANT);
					item.setCursorPosition(IMPORTANT.length());
					item.setDisplayString(IMPORTANT);
					item.setImageType(CSSImageType.VALUE_STRING);
					appendSemiColon(item);
					candidates.add(item);
				}
			}
		}
	}

	/**
	 * getCandidates method comment.
	 */
	protected Iterator getCandidates() {
		List candidates = new ArrayList();

		checkSemiColon(); // check should add semi-colon or not

		String name = getPropertyName();
		if (name != null) {
			CSSMetaModelUtil util = new CSSMetaModelUtil(fContext.getMetaModel());
			Iterator i = Collections.EMPTY_LIST.iterator();
			if (isFontFaceRule()) {
				CSSMMDescriptor desc = util.getDescriptor(name);
				if (desc != null) {
					i = desc.getValues();
				}
			} else {
				CSSMMProperty prop = util.getProperty(name);
				if (prop != null) {
					i = prop.getValues();
				}
			}
			while (i.hasNext()) {
				CSSMMNode val = (CSSMMNode) i.next();
				String valueType = val.getType();
				if (valueType == CSSMMNode.TYPE_KEYWORD) {
					addString(candidates, val.toString());
				} else if (valueType == CSSMMNode.TYPE_NUMBER) {
					addNumber(candidates, (CSSMMNumber) val);
				} else if (valueType == CSSMMNode.TYPE_FUNCTION) {
					addFunction(candidates, (CSSMMFunction) val);
				}
			}
		}

		addImportant(candidates);
		addSemiColon(candidates);

		return candidates.iterator();
	}

	/**
	 * @return java.lang.String
	 */
	private String getPropertyName() {
		ICSSNode targetNode = fContext.getTargetNode();
		while (targetNode instanceof ICSSPrimitiveValue) {
			targetNode = targetNode.getParentNode();
		}
		if (targetNode instanceof ICSSStyleDeclItem) {
			return ((ICSSStyleDeclItem) targetNode).getPropertyName();
		} else {
			return null;
		}
	}

	/**
	 *  
	 */
	private boolean isFontFaceRule() {
		ICSSNode targetNode = fContext.getTargetNode();
		while (targetNode instanceof ICSSPrimitiveValue) {
			targetNode = targetNode.getParentNode();
		}
		if (targetNode instanceof ICSSStyleDeclItem) {
			targetNode = targetNode.getParentNode(); // get Declaration
			if (targetNode != null) {
				// inline style has no rule node
				targetNode = targetNode.getParentNode(); // get rule
			}
		}
		return (targetNode instanceof CSSFontFaceRule);
	}
}
