/*******************************************************************************
 * Copyright (c) 2007 Exadel, Inc. and 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:
 *     Exadel, Inc. and Red Hat, Inc. - initial API and implementation
 ******************************************************************************/ 
package org.jboss.tools.jsf.model.handlers;

import java.util.*;

import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IPath;
import org.eclipse.jface.text.source.ISourceViewer;
import org.jboss.tools.common.model.*;
import org.jboss.tools.common.model.filesystems.XFileObject;
import org.jboss.tools.common.model.util.EclipseResourceUtil;
import org.jboss.tools.common.meta.*;
import org.jboss.tools.jsf.JSFModelPlugin;
import org.jboss.tools.jsf.model.pv.JSFProjectBean;
import org.jboss.tools.jsf.project.JSFNature;
import org.jboss.tools.jsf.web.JSFWebProject;
import org.jboss.tools.jsf.web.pattern.JSFUrlPattern;
import org.jboss.tools.jst.web.project.WebProject;
import org.jboss.tools.jst.web.tld.TaglibData;
import org.jboss.tools.jst.web.tld.VpeTaglibManager;
import org.jboss.tools.jst.web.tld.VpeTaglibManagerProvider;
import org.jboss.tools.jst.web.tld.model.TLDUtil;

public class JSPAdopt implements XAdoptManager {

    public boolean isAdoptable(XModelObject target, XModelObject object) {
        if(!isAcceptableTarget(target)) return false;
        return isAdoptableProperty(object) ||
		       isAdoptablePropertyReference(object) || 
               isAdoptablePage(object) ||
               isAdoptableFile(object) ||
               isAdoptableMapEntry(object);
    }

    public void adopt(XModelObject target, XModelObject object, java.util.Properties p) throws XModelException {
        if(isAdoptableProperty(object)) adoptProperty(target, object, p);
		else if(isAdoptablePropertyReference(object)) adoptPropertyReference(target, object, p);
		else if(isAdoptablePage(object)) adoptPage(target, object, p);
        else if(isAdoptableFile(object)) adoptFile(target, object, p);
		else if(isAdoptableMapEntry(object)) adoptMapEntry(target, object, p);
    }

    static String PAGE_TARGET = ".FileJSP.FileHTML.FileXHTML.FacesConfig."; //$NON-NLS-1$
    static String PAGE_ENTITY = ".FileJSP.FileXHTML."; //$NON-NLS-1$

	private boolean isAcceptableTarget(XModelObject target) {
		String entity = "." + target.getModelEntity().getName() + "."; //$NON-NLS-1$ //$NON-NLS-2$
		return PAGE_TARGET.indexOf(entity) >= 0;
	}

    protected boolean isAdoptableProperty(XModelObject object) {
        return object.getModelEntity().getName().startsWith("JSFManagedProperty"); //$NON-NLS-1$
    }

    public void adoptProperty(XModelObject target, XModelObject object, Properties p) {
        if(p == null) return;
        int c = getPos(p);
        if(c < 0) return;
		String bean = object.getParent().getAttributeValue("managed-bean-name"); //$NON-NLS-1$
        String name = object.getAttributeValue("property-name"); //$NON-NLS-1$
        String start = "#{" + bean + "." + name + "}"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
        p.setProperty("start text", start); //$NON-NLS-1$
    }

	protected boolean isAdoptablePage(XModelObject object) {
		String entity = "." + object.getModelEntity().getName() + "."; //$NON-NLS-1$ //$NON-NLS-2$
		if(PAGE_ENTITY.indexOf(entity) < 0  || !EclipseResourceUtil.hasNature(object.getModel(), JSFNature.NATURE_ID)) return false;
		String path = WebProject.getInstance(object.getModel()).getPathInWebRoot(object);
		return path != null;
	}

	public void adoptPage(XModelObject target, XModelObject object, Properties p) {
		if(p == null) return;
		String res = WebProject.getInstance(object.getModel()).getPathInWebRoot(object);
		if(res == null) return;
		if(applyPattern(p)) {
			JSFUrlPattern pattern = JSFWebProject.getInstance(object.getModel()).getPatternLoader().getUrlPattern();
			res = pattern.getJSFUrl(res);
		}
        int pos = getPos(p);
		if(res.startsWith("/") && pos >= 0 && isInsideResponseRedirect(p.getProperty("text"), pos)) { //$NON-NLS-1$ //$NON-NLS-2$
			res = res.substring(1);
		}
		p.setProperty("start text", res); //$NON-NLS-1$
		p.setProperty("end text", ""); //$NON-NLS-1$ //$NON-NLS-2$
	}
	
	static String NO_JSF_URL = 
		"+include+jsp:include+jsp:directive.include+ui:include+ui:composition+ui:decorate+s:decorate+"; //$NON-NLS-1$
	static Map<String, String> PREFIXES = new HashMap<String, String>();
	{
		PREFIXES.put("http://jboss.com/products/seam/taglib", "s"); //$NON-NLS-1$ //$NON-NLS-2$
		PREFIXES.put("http://java.sun.com/jsf/facelets", "ui"); //$NON-NLS-1$ //$NON-NLS-2$
	}
	
	boolean applyPattern(Properties p) {
		if(p == null) return true;
		String tag = p.getProperty("context:tagName"); //$NON-NLS-1$
		if(tag == null) return true;
		int q = tag.indexOf(':');
		if(q >= 0) {
			String dp = tag.substring(0, q);
			ISourceViewer sv = (ISourceViewer)p.get("viewer"); //$NON-NLS-1$
			String uri = getURI(sv, dp);
			if(uri != null) {
				String dp1 = PREFIXES.get(uri);
				if(dp1 != null && !dp1.equals(dp)) {
					tag = dp1 + tag.substring(q);
				}
			}
		}
		if(NO_JSF_URL.indexOf("+" + tag + "+") >= 0) return false;		 //$NON-NLS-1$ //$NON-NLS-2$
		return true;
	}
	private static String getURI(ISourceViewer viewer, String prefix) {
		VpeTaglibManager tldManager = null;
		if((tldManager == null) && (viewer instanceof VpeTaglibManagerProvider)) {
			tldManager = ((VpeTaglibManagerProvider)viewer).getTaglibManager();
			if(tldManager != null) {
				List list = tldManager.getTagLibs();
				for (int i = 0; i < list.size(); i++) {
					TaglibData data = (TaglibData)list.get(i);
					if(prefix.equals(data.getPrefix())) {
						return data.getUri();
					}
				}
			}			
		}
		return null;
	}

    protected boolean isAdoptableFile(XModelObject object) {
    	if(XFileObject.FILE != object.getFileType()) return false;
    	if(TLDUtil.isTaglib(object)) return false;
    	IResource c = (IResource)object.getAdapter(IResource.class);
    	if(c == null) return false;
    	String webroot = WebProject.getInstance(object.getModel()).getWebRootLocation();
    	if(webroot == null) return false;
    	IPath path = c.getLocation();
    	// if this happens it means that something is wrong 
    	// with reference to IResource in object or its parent
    	// that may need attention.
    	if(path == null || path.toString() == null) return false;
    	String f = path.toString().replace('\\', '/').toLowerCase();
    	webroot = webroot.replace('\\', '/').toLowerCase();
        return f.startsWith(webroot);
    }

    public void adoptFile(XModelObject target, XModelObject object, Properties p) {
        if(p == null) return;
		String webroot = WebProject.getInstance(object.getModel()).getWebRootLocation();
		IResource c = (IResource)object.getAdapter(IResource.class);
		String f = c.getLocation().toString().replace('\\', '/');
		String res = f.substring(webroot.length());
        p.setProperty("start text", res); //$NON-NLS-1$
        p.setProperty("end text", ""); //$NON-NLS-1$ //$NON-NLS-2$
    }

	protected boolean isAdoptablePropertyReference(XModelObject object) {
		String entity = "." + object.getModelEntity().getName() + "."; //$NON-NLS-1$ //$NON-NLS-2$
		return ".JSFProjectBeanProperty.JSFProjectBeanMethod.".indexOf(entity) >= 0; //$NON-NLS-1$
	}

	public void adoptPropertyReference(XModelObject target, XModelObject object, Properties p) {
		if(p == null) return;
        int c = getPos(p);
		if(c < 0) return;
		String s = object.getAttributeValue("name"); //$NON-NLS-1$
		XModelObject o = object;
		while(o != null && isAdoptablePropertyReference(o)) {
			o = o.getParent();
			if(o != null) {
				String part = o.getAttributeValue("name"); //$NON-NLS-1$
				if(o instanceof JSFProjectBean) {
					XModelObject[] list = ((JSFProjectBean)o).getBeanList();
					if(list.length > 1) {
						part = SelectBeanSupport.run((JSFProjectBean)o);
						if(part == null) return;
					}
				}
				s = part + "." + s; //$NON-NLS-1$
			}
		}
		String start = "#{" + s + "}"; //$NON-NLS-1$ //$NON-NLS-2$
		p.setProperty("start text", start); //$NON-NLS-1$
	}
	
	int getPos(Properties p) {
		int c = -1;
		if(p == null) return -1;
		String s = p.getProperty("pos"); //$NON-NLS-1$
		if(s == null || s.trim().length() == 0) return c;
		try {
			c = Integer.parseInt(s.trim());
		} catch (NumberFormatException e) {
			JSFModelPlugin.getPluginLog().logError(e);
		}
		return c;
	}
	
	public boolean isAdoptableMapEntry(XModelObject object) {
		return "JSFMapEntry".equals(object.getModelEntity().getName()); //$NON-NLS-1$
	}

	public void adoptMapEntry(XModelObject target, XModelObject object, Properties p) {
		String key = object.getAttributeValue("key"); //$NON-NLS-1$
		XModelObject g = object.getParent().getParent();
		String entity = g.getModelEntity().getName();
		String start = null;
		if("JSFManagedBean".equals(entity) || "JSFManagedBean20".equals(entity)) { //$NON-NLS-1$
			String bean = g.getAttributeValue("managed-bean-name"); //$NON-NLS-1$
			start = "#{" + bean + "." + key + "}"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		} else if("JSFManagedProperty".equals(entity)) { //$NON-NLS-1$
			XModelObject h = g.getParent();
			String bean = h.getAttributeValue("managed-bean-name"); //$NON-NLS-1$
			String property = g.getAttributeValue("property-name"); //$NON-NLS-1$
			start = "#{" + bean + "." + property + "." + key + "}"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
		}
		if(start != null) {
			p.setProperty("start text", start); //$NON-NLS-1$
		}
	}

	static boolean isInsideResponseRedirect(String text, int off) {
		if(off < 0) return false;
		String START = "response.sendRedirect(\""; //$NON-NLS-1$
		String END = "\")"; //$NON-NLS-1$
		int i = 0;
		while(i < text.length() && i < off) {
			int i1 = text.indexOf(START, i);
			if(i1 < 0 || i1 + START.length() > off) return false;
			int i2 = text.indexOf(END, i1 + START.length());
			if(i2 < 0 || i2 >= off) return true;
			i = i2 + END.length();
		}
		return false;
	}
}
