/*******************************************************************************
 * Copyright (c) 2008-2012 Red Hat, Inc.
 * Distributed under license by Red Hat, Inc. All rights reserved.
 * This program is 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:
 *     Red Hat, Inc. - initial API and implementation
 ******************************************************************************/ 
package org.jboss.tools.seam.text.ext.hyperlink;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMNode;
import org.jboss.tools.common.el.core.model.ELArgumentInvocation;
import org.jboss.tools.common.el.core.model.ELExpression;
import org.jboss.tools.common.el.core.model.ELInvocationExpression;
import org.jboss.tools.common.el.core.model.ELPropertyInvocation;
import org.jboss.tools.common.el.core.resolver.ElVarSearcher;
import org.jboss.tools.common.el.core.resolver.Var;
import org.jboss.tools.common.text.ext.hyperlink.AbstractHyperlinkPartitioner;
import org.jboss.tools.common.text.ext.hyperlink.HyperlinkRegion;
import org.jboss.tools.common.text.ext.hyperlink.IHyperLinkPartitionPriority;
import org.jboss.tools.common.text.ext.hyperlink.IHyperlinkPartitionRecognizer;
import org.jboss.tools.common.text.ext.hyperlink.IHyperlinkRegion;
import org.jboss.tools.common.text.ext.util.StructuredModelWrapper;
import org.jboss.tools.common.text.ext.util.Utils;
import org.jboss.tools.jst.web.ui.internal.text.ext.hyperlink.jsp.JSPRootHyperlinkPartitioner;
import org.jboss.tools.seam.core.ISeamContextVariable;
import org.jboss.tools.seam.core.ISeamMessages;
import org.jboss.tools.seam.core.ISeamProject;
import org.jboss.tools.seam.core.SeamCorePlugin;
import org.jboss.tools.seam.internal.core.el.SeamELCompletionEngine;
import org.jboss.tools.seam.text.ext.SeamExtPlugin;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.Text;

/**
 * @author Jeremy
 */
public class SeamBeanHyperlinkPartitioner extends AbstractHyperlinkPartitioner implements IHyperlinkPartitionRecognizer, IHyperLinkPartitionPriority { 
	public static final String SEAM_MESSAGES_BEAN_PARTITION = "org.jboss.tools.seam.text.ext.SEAM_MESSAGES_BEAN";

	/**
	 * @see com.ibm.sse.editor.hyperlink.AbstractHyperlinkPartitioner#parse(org.eclipse.jface.text.IDocument, com.ibm.sse.editor.extensions.hyperlink.IHyperlinkRegion)
	 */
	protected IHyperlinkRegion parse(IDocument document, int offset, IHyperlinkRegion superRegion) {
		StructuredModelWrapper smw = new StructuredModelWrapper();
		try {
			smw.init(document);
			Document xmlDocument = smw.getDocument();
			if (xmlDocument == null) return null;
			
			if (!recognize(document, offset, superRegion)) return null;
			IHyperlinkRegion r = getRegion(document, offset);
			if (r == null) return null;
			r = getWordRegion(document, offset);
			if (r == null) return null;
			
			Map<String, ISeamMessages> messages = findMessagesComponents(document, offset);

			if (messages != null && !messages.isEmpty()) {
				String axis = getAxis(document, offset);
				String contentType = superRegion.getContentType();
				String type = SEAM_MESSAGES_BEAN_PARTITION;
				
				return new HyperlinkRegion(r.getOffset(), r.getLength(), axis, contentType, type);
			}
			
			return null;
		} finally {
			smw.dispose();
		}
	}

	protected String getAxis(IDocument document, int offset) {
		return JSPRootHyperlinkPartitioner.computeAxis(document, offset) + "/";
	}
	
	public static IHyperlinkRegion getWordRegion (IDocument document, final int offset) {
		StructuredModelWrapper smw = new StructuredModelWrapper();
		try {
			smw.init(document);
			Document xmlDocument = smw.getDocument();
			if (xmlDocument == null) return null;
			
			Node n = Utils.findNodeForOffset(xmlDocument, offset);

			if (n == null || !(n instanceof Attr || n instanceof Text)) return null;

			int start = Utils.getValueStart(n);
			int end = Utils.getValueEnd(n);
			if(start < 0 || start > end || start > offset) return null;
			String attrText = document.get(start, end - start);

			StringBuffer sb = new StringBuffer(attrText);
			//find start of bean property
			int bStart = offset - start;
			while (bStart >= 0) { 
				if (!Character.isJavaIdentifierPart(sb.charAt(bStart))) {
					bStart++;
					break;
				}
			
				if (bStart == 0) break;
				bStart--;
			}
			// find end of bean property
			int bEnd = offset - start;
			while (bEnd < sb.length()) { 
				if (!Character.isJavaIdentifierPart(sb.charAt(bEnd)))
					break;
				bEnd++;
			}
			
			int propStart = bStart + start;
			int propLength = bEnd - bStart;
			
			if (propStart > offset || propStart + propLength < offset) return null;
			
			IHyperlinkRegion region = new HyperlinkRegion(propStart, propLength, null, null, null);
			return region;
		} catch (BadLocationException x) {
			SeamExtPlugin.getPluginLog().logError(x);
			return null;
		} finally {
			smw.dispose();
		}
	}

	public static IHyperlinkRegion getRegionPart(IDocument document, final int offset) {
		StructuredModelWrapper smw = new StructuredModelWrapper();
		try {
			smw.init(document);
			Document xmlDocument = smw.getDocument();
			if (xmlDocument == null) return null;
			
			Node n = Utils.findNodeForOffset(xmlDocument, offset);

			if (n == null || !(n instanceof Attr || n instanceof Text)) return null;

			int start = Utils.getValueStart(n);
			int end = Utils.getValueEnd(n);
			if(start < 0 || start > end || start > offset) return null;
			String attrText = document.get(start, end - start);

			StringBuffer sb = new StringBuffer(attrText);
			//find start of bean property
			int bStart = offset - start;
			while (bStart >= 0) { 
				if (!Character.isJavaIdentifierPart(sb.charAt(bStart)) &&
						sb.charAt(bStart) != '.' && sb.charAt(bStart) != '[' && sb.charAt(bStart) != ']') {
					bStart++;
					break;
				}
			
				if (bStart == 0) break;
				bStart--;
			}
			// find end of bean property
			int bEnd = offset - start;
			while (bEnd < sb.length()) { 
				if (!Character.isJavaIdentifierPart(sb.charAt(bEnd)))
					break;
				bEnd++;
			}
			
			int propStart = bStart + start;
			int propLength = bEnd - bStart;
			if (propStart > offset || propStart + propLength < offset) return null;
			
			return new HyperlinkRegion(propStart, propLength, null, null, null);
		} catch (BadLocationException x) {
			SeamExtPlugin.getPluginLog().logError(x);
			return null;
		} finally {
			smw.dispose();
		}
	}
	
	public static IHyperlinkRegion getRegion(IDocument document, final int offset) {
		StructuredModelWrapper smw = new StructuredModelWrapper();
		try {
			smw.init(document);
			Document xmlDocument = smw.getDocument();
			if (xmlDocument == null) return null;
			
			Node n = Utils.findNodeForOffset(xmlDocument, offset);

			if (n == null || !(n instanceof Attr || n instanceof Text)) return null;

			int start = 0;
			int end = document.getLength();
			if(n instanceof IDOMNode) {
				start = ((IDOMNode)n).getStartOffset();
				end = ((IDOMNode)n).getEndOffset();
			}

			//TODO do we have and need seam project here?
			SeamELCompletionEngine engine = new SeamELCompletionEngine();
			ELInvocationExpression tokens = engine.findExpressionAtOffset(document, offset, start, end);
			if (tokens == null /*|| tokens.size() == 0*/)
				return null; // No EL Operand found

			int propStart = tokens.getStartPosition();
			int propLength = tokens.getEndPosition() - propStart; 
			
			if (propStart > offset || propStart + propLength < offset) return null;
			
			IHyperlinkRegion region = new HyperlinkRegion(propStart, propLength);
			return region;
		} finally {
			smw.dispose();
		}
	}
	
	/**
	 * @see com.ibm.sse.editor.extensions.hyperlink.IHyperlinkPartitionRecognizer#recognize(org.eclipse.jface.text.IDocument, com.ibm.sse.editor.extensions.hyperlink.IHyperlinkRegion)
	 */
	public boolean recognize(IDocument document, int offset, IHyperlinkRegion region) {
		StructuredModelWrapper smw = new StructuredModelWrapper();
		try {
			smw.init(document);
			Document xmlDocument = smw.getDocument();
			if (xmlDocument == null) return false;
			
			Map<String, ISeamMessages> messages = findMessagesComponents(document, offset);
			if (messages != null && !messages.isEmpty()) {
				return true;
			}

			List<IJavaElement> javaElements = findJavaElements(document, offset);
			if (javaElements != null && !javaElements.isEmpty()) {
				return true;
			}
			
			return false;
		} finally {
			smw.dispose();
		}
	}

	public static List<IJavaElement> findJavaElements(IDocument document, int offset) {
		StructuredModelWrapper smw = new StructuredModelWrapper();
		try {
			smw.init(document);
			Document xmlDocument = smw.getDocument();
			if (xmlDocument == null) return null;
			
			IHyperlinkRegion r = getRegion(document, offset);
			if (r == null) return null;

			String propText = document.get(r.getOffset(), r.getLength());
			IFile file = smw.getFile();
			IProject project = (file == null ? null : file.getProject());

			ISeamProject seamProject = SeamCorePlugin.getSeamProject(project, true);
			if (seamProject == null)
				return null;

			SeamELCompletionEngine engine = new SeamELCompletionEngine();

			String prefix = propText;
			ELExpression expr = engine.parseOperand(prefix);
			if (!(expr instanceof ELInvocationExpression))
				return null; // No EL Operand found
			expr.getModel().shift(r.getOffset() - expr.getFirstToken().getStart());
			
			List<IJavaElement> javaElements = null;
			try {
				javaElements = engine.getJavaElementsForELOperandTokens(seamProject, file, (ELInvocationExpression)expr);
			} catch (StringIndexOutOfBoundsException e) {
				SeamExtPlugin.getPluginLog().logError(e);
				return null;
			} catch (BadLocationException e) {
				SeamExtPlugin.getPluginLog().logError(e);
				return null;
			}

			//Do not need it, vars handled in getJavaElementsForELOperandTokens
			if (javaElements == null || javaElements.isEmpty()) {
				// Try to find a local Var (a pair of variable-value attributes)
				ElVarSearcher varSearcher = new ElVarSearcher(file, engine);
				// Find a Var in the EL 
				int start = expr.getStartPosition();
				int end = expr.getEndPosition();
				
				if (expr.getText().length() == 0)
					return null;
				
				List<Var> allVars= varSearcher.findAllVars(file, start);
				Var var = varSearcher.findVarForEl(expr.getText(), null, allVars, true);
				if (var == null) {
					// Find a Var in the current offset assuming that it's a node with var/value attribute pair
					var = varSearcher.findVar(file, start);
				}
				if (var == null)
					return null;

				String resolvedValue = var.getValue();
				if (resolvedValue == null || resolvedValue.length() == 0) 
						return null;
				if (resolvedValue.startsWith("#{") || resolvedValue.startsWith("${"))
					resolvedValue = resolvedValue.substring(2);
				if (resolvedValue.endsWith("}"))
					resolvedValue = resolvedValue.substring(0, resolvedValue.lastIndexOf("}"));
				
				// Replace the Var with its resolved value in tokens (Var is always the first token)
				StringBuffer elText = new StringBuffer();
				elText.append(resolvedValue);
				String app = expr.getText();
				int i = app.indexOf('.');
				app = app.substring(i + 1);
				elText.append('.').append(app);
				
				javaElements = engine.getJavaElementsForExpression(
						seamProject, file, elText.toString(), offset);
			}
			return javaElements;
		} catch (BadLocationException x) {
			SeamExtPlugin.getPluginLog().logError(x);
			return null;
		} finally {
			smw.dispose();
		}
	}
	
	public static Map<String, ISeamMessages> findMessagesComponents(IDocument document, int offset) {
		StructuredModelWrapper smw = new StructuredModelWrapper();
		try {
			smw.init(document);
			Document xmlDocument = smw.getDocument();
			if (xmlDocument == null) return null;
			
			IHyperlinkRegion r = getRegion(document, offset);
			if (r == null) return null;
			
			String propText = document.get(r.getOffset(), r.getLength());
			
			
			IFile file = smw.getFile();
			IProject project = (file == null ? null : file.getProject());

			ISeamProject seamProject = SeamCorePlugin.getSeamProject(project, true);
			if (seamProject == null)
				return null;

			SeamELCompletionEngine engine= new SeamELCompletionEngine();

			String prefix = propText;
			ELExpression exp = engine.parseOperand(prefix);
			if (exp == null)
				return null; // No EL Operand found
			
			Map<ELInvocationExpression, List<ISeamContextVariable>> map = new HashMap<ELInvocationExpression, List<ISeamContextVariable>>();

			exp.getModel().shift(r.getOffset() - exp.getFirstToken().getStart());

			if (	!(exp instanceof ELInvocationExpression) &&
					!(exp instanceof ELPropertyInvocation) && 
					!(exp instanceof ELArgumentInvocation))
				return null;
			
			String propertyName = null;
			if (exp instanceof ELPropertyInvocation) {
				propertyName = ((ELPropertyInvocation)exp).getMemberName();
			} else if (exp instanceof ELArgumentInvocation) {
				propertyName = Utils.trimQuotes(
						((ELArgumentInvocation)exp).getArgument().getArgument().getText());
			}
			
			if (propertyName == null)
				return null;
			
//			ScopeType scope = SeamELCompletionEngine.getScope(seamProject, file);

			ELInvocationExpression expr = (ELInvocationExpression)exp;
			
			ELInvocationExpression left = expr;

			if (expr.getLeft() != null) {
				while (left != null) {
					List<ISeamContextVariable> resolvedVars = new ArrayList<ISeamContextVariable>();
					resolvedVars = engine.resolveVariables(seamProject, file, left,
							left == expr, true);
					if (resolvedVars != null && !resolvedVars.isEmpty()) {
						map.put(left, resolvedVars);
						break;
					}
					left = (ELInvocationExpression) left.getLeft();
				}
			}

			// At the moment map contains the resolved EL parts mapped to their vars
			// And now we need to extract SeamXmlFactory vars to the real ones 
			// and filter out all non-SeamMessagesComponent vars
			// Next we need to map the SeamMessagesComponent vars to properties
			Map<String, ISeamMessages> messages = new HashMap<String, ISeamMessages>();
			if (map != null && !map.isEmpty()) {
				for (ELInvocationExpression l : map.keySet()) {
					List<ISeamContextVariable> variables = map.get(l);
					for (ISeamContextVariable variable : variables) {
						ISeamMessages messagesVariable = SeamELCompletionEngine.getSeamMessagesComponentVariable(variable);
						if (messagesVariable != null) {
							messages.put(propertyName, messagesVariable);
						}
					}
				}
			}

			return messages;
		} catch (BadLocationException x) {
			SeamExtPlugin.getPluginLog().logError(x);
			return null;
		} finally {
			smw.dispose();
		}
	}

	public static IHyperlinkRegion getMessagesPropertyRegion(IDocument document, int offset) {
		StructuredModelWrapper smw = new StructuredModelWrapper();
		try {
			smw.init(document);
			Document xmlDocument = smw.getDocument();
			if (xmlDocument == null) return null;
			
			IHyperlinkRegion r = getRegion(document, offset);
			if (r == null) return null;
			
			String propText = document.get(r.getOffset(), r.getLength());
			
			IFile file = smw.getFile();
			IProject project = (file == null ? null : file.getProject());

			ISeamProject seamProject = SeamCorePlugin.getSeamProject(project, true);
			if (seamProject == null)
				return null;

			SeamELCompletionEngine engine= new SeamELCompletionEngine();

			String prefix = propText;
			ELExpression exp = engine.parseOperand(prefix);
			if (exp == null)
				return null; // No EL Operand found
			
			Map<ELInvocationExpression, List<ISeamContextVariable>> map = new HashMap<ELInvocationExpression, List<ISeamContextVariable>>();

			exp.getModel().shift(r.getOffset() - exp.getFirstToken().getStart());

			if (	!(exp instanceof ELInvocationExpression) &&
					!(exp instanceof ELPropertyInvocation) && 
					!(exp instanceof ELArgumentInvocation))
				return null;
			
			String propertyName = null;
			if (exp instanceof ELPropertyInvocation) {
				propertyName = ((ELPropertyInvocation)exp).getMemberName();
				
				int start = ((ELPropertyInvocation)exp).getLastToken().getStart();
				int length = ((ELPropertyInvocation)exp).getLastToken().getLength();
				
				return new HyperlinkRegion(start, length); 
			} else if (exp instanceof ELArgumentInvocation) {
				propertyName = Utils.trimQuotes(
						((ELArgumentInvocation)exp).getArgument().getArgument().getText());
				int start = ((ELArgumentInvocation)exp).getArgument().getArgument().getStartPosition();
				int length = ((ELArgumentInvocation)exp).getArgument().getArgument().getEndPosition() - 
						((ELArgumentInvocation)exp).getArgument().getArgument().getStartPosition();
				
				return new HyperlinkRegion(start, length); 
			}
			
			return null;
		} catch (BadLocationException x) {
			SeamExtPlugin.getPluginLog().logError(x);
			return null;
		} finally {
			smw.dispose();
		}
	}

	public int getPriority() {
		return 0; // to be first
	}
}