/*
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 1999 Bull S.A.
 * Contact: jonas-team@objectweb.org
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * Initial developer(s): Helene Joanin
 * Contributor(s): ______________________________________.
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.objectweb.jonas_ejb.genic;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Vector;

import org.apache.velocity.VelocityContext;

import org.objectweb.jonas_ejb.deployment.api.BeanDesc;
import org.objectweb.jonas_ejb.deployment.api.EntityCmpDesc;
import org.objectweb.jonas_ejb.deployment.api.EntityDesc;
import org.objectweb.jonas_ejb.deployment.api.FieldDesc;
import org.objectweb.jonas_ejb.deployment.api.SessionDesc;
import org.objectweb.jonas_ejb.lib.BeanNaming;
import org.objectweb.jonas_ejb.lib.JavaType;


/**
 * This class allows to create the Velocity Context used to build the generated sources
 * with the Velocity templates.
 */
class VContextFactory {

    static VelocityContext create(BeanDesc dd, BeanNaming bn, int srcType) {

	String value = null;
	VelocityContext vc = new VelocityContext();

	vc.put("dd", dd);

	if (dd.getHomeClass() != null) {
	    value = dd.getHomeClass().getName();
	    vc.put("home", value);
	}

	if (dd.getLocalHomeClass() != null) {
	    value = dd.getLocalHomeClass().getName();
	    vc.put("localhome", value);
	}

	if (dd.getRemoteClass() != null) {
	    value = dd.getRemoteClass().getName();
	    vc.put("remote", value);
	}

	if (dd.getLocalClass() != null) {
	    value = dd.getLocalClass().getName();
	    vc.put("local", value);
	}

	if (bn.getFullWrpHomeName() != null) {
	    value = bn.getFullWrpHomeName();
	    vc.put("home_wrp", value);
	}

	if (bn.getFullWrpLocalHomeName() != null) {
	    value = bn.getFullWrpLocalHomeName();
	    vc.put("localhome_wrp", value);
	}

	if (bn.getFullWrpRemoteName() != null) {
	    value = bn.getFullWrpRemoteName();
	    vc.put("remote_wrp", value);
	}

	if (bn.getFullWrpLocalName() != null) {
	    value = bn.getFullWrpLocalName();
	    vc.put("local_wrp", value);
	}

	value = dd.getEjbClass().getName();
	vc.put("bean", value);

	value = bn.getFullDerivedBeanName();
	vc.put("bean_wrp", value);    // Same as "bean" if not a Entity CMP

	if (bn.getFullWrpHandleName() != null) {
	    value = bn.getFullWrpHandleName();
	    vc.put("handle", value);
	}


	if (dd instanceof EntityDesc) {
	    value = ((EntityDesc)dd).getPrimaryKeyClass().getName();
	    vc.put("pk", value);
	}

	switch(srcType) {
	case Source.HOME: 
	    value = BeanNaming.getPackageName(bn.getFullWrpHomeName());
	    vc.put("package", value);
	    value = bn.getWrpHomeName();
	    vc.put("class", value);
	    break;
	case Source.LOCAL_HOME:
	    value = BeanNaming.getPackageName(bn.getFullWrpLocalHomeName());
	    vc.put("package", value);
	    value = bn.getWrpLocalHomeName();
	    vc.put("class", value);
	    break;
	case Source.REMOTE:
	    value = BeanNaming.getPackageName(bn.getFullWrpRemoteName());
	    vc.put("package", value);
	    value = bn.getWrpRemoteName();
	    vc.put("class", value);
	    break;
	case Source.LOCAL:
	    value = BeanNaming.getPackageName(bn.getFullWrpLocalName());
	    vc.put("package", value);
	    value = bn.getWrpLocalName();
	    vc.put("class", value);
	    break;
	case Source.ENTITY_HANDLE:
	    value = BeanNaming.getPackageName(bn.getFullWrpHandleName());
	    vc.put("package", value);
	    value = bn.getWrpHandleName();
	    vc.put("class", value);
	    break;
	case Source.ENTITY_CMP_JDBC:
	    value = BeanNaming.getPackageName(bn.getFullDerivedBeanName());
	    vc.put("package", value);
	    value = bn.getDerivedBeanName();
	    vc.put("class", value);
	    break;
	}

	Vector prototypeMethodList = new Vector();
	Vector vcMethodList = new Vector();
	Method methods[] = null;
	switch(srcType) {
	case Source.HOME: 
	    /*
	     * Add in the methods list, the Home interface methods, except
	     * - methods (other than remove) defined in javax.ejb.EJBHome,
	     * - and overriding methods.
	     */
	    methods = dd.getHomeClass().getMethods();
	    for (int i=0; i < methods.length; i++) {
		Method method = methods[i];
		if (!method.getDeclaringClass().equals(javax.ejb.EJBHome.class)
		    || "remove".equals(method.getName())) {
		    String pMeth = convertMethod2String(method);
		    if (!prototypeMethodList.contains(pMeth)) {
			VcMethod vm = new VcMethod(method, dd.getMethodDesc(method), dd);
			vcMethodList.addElement(vm);
			prototypeMethodList.add(pMeth);
		    }
		}
	    }
	    break;
	case Source.LOCAL_HOME:
	    /*
	     * Add in the methods list, the LocalHome interface methods, except
	     * - methods (other than remove) defined in javax.ejb.EJBLocalHome,
	     * - and overriding methods.
	     */
	    methods = dd.getLocalHomeClass().getMethods();
	    for (int i=0; i < methods.length; i++) {
		Method method = methods[i];
		if (!method.getDeclaringClass().equals(javax.ejb.EJBLocalHome.class)
		    || "remove".equals(method.getName())) {
		    String pMeth = convertMethod2String(method);
		    if (!prototypeMethodList.contains(pMeth)) {
			VcMethod vm = new VcMethod(method, dd.getMethodDesc(method), dd);
			vcMethodList.addElement(vm);
			prototypeMethodList.add(pMeth);
		    }
		}
	    }
	    break;
	case Source.REMOTE:
	    /*
	     * Add in the methods list, the Remote interface methods, except
	     * - methods (other than remove) defined in javax.ejb.EJBObject,
	     * - and overriding methods.
	     */
	    methods = dd.getRemoteClass().getMethods();
	    for (int i=0; i < methods.length; i++) {
		Method method = methods[i];
		if (!method.getDeclaringClass().equals(javax.ejb.EJBObject.class)
		    || "remove".equals(method.getName())) {
		    String pMeth = convertMethod2String(method);
		    if (!prototypeMethodList.contains(pMeth)) {
			VcMethod vm = new VcMethod(method, dd.getMethodDesc(method), dd);
			vcMethodList.addElement(vm);
			prototypeMethodList.add(pMeth);
		    }
		}
	    }
	    break;
	case Source.LOCAL:
	    /*
	     * Add in the methods list, the Local interface methods, except
	     * - methods (other than remove) defined in javax.ejb.EJBLocalObject,
	     * - and overriding methods.
	     */
	    methods = dd.getLocalClass().getMethods();
	    for (int i=0; i < methods.length; i++) {
		Method method = methods[i];
		if (!method.getDeclaringClass().equals(javax.ejb.EJBLocalObject.class)
		    || "remove".equals(method.getName())) {
		    String pMeth = convertMethod2String(method);
		    if (!prototypeMethodList.contains(pMeth)) {
			VcMethod vm = new VcMethod(method, dd.getMethodDesc(method), dd);
			vcMethodList.addElement(vm);
			prototypeMethodList.add(pMeth);
		    }
		}
	    }
	    break;
	case Source.ENTITY_CMP_JDBC:
	    /*
	     * Add in the methods list, the Home and LocalHome interfaces methods, except
	     * - methods defined in javax.ejb.EJBHome and javax.ejb.EJBLocalHome and
	     * - and overriding methods.
	     * Same methods may be defined both in the Home interface and in the
	     * LocalHome interface. Don't add twice this method in the vcMethodList !!!
	     *
	     * Futhermore, in case of create() methods, methods added in the vcMethodList
	     * are the ejbCreate() associated bean's methods.
	     */
	    if (dd.getHomeClass() != null) {
		methods = dd.getHomeClass().getMethods();
		for (int i=0; i < methods.length; i++) {
		    Method method = methods[i];
		    if (!method.getDeclaringClass().equals(javax.ejb.EJBHome.class)) {
			String pMeth = convertMethod2String(method);
			if (!prototypeMethodList.contains(pMeth)) {
			    VcMethod vm =null;
			    if (method.getName().startsWith("create")) {
				Method beanMethod = getBeanMethod(method, dd.getEjbClass());
				vm = new VcMethod(beanMethod, dd.getMethodDesc(method), dd);
			    } else {
				vm = new VcMethod(method, dd.getMethodDesc(method), dd);
			    }
			    vcMethodList.addElement(vm);
			    prototypeMethodList.add(pMeth);
			}
		    }
		}
	    }
	    if (dd.getLocalHomeClass() != null) {
		methods = dd.getLocalHomeClass().getMethods();
		for (int i=0; i < methods.length; i++) {
		    Method method = methods[i];
		    if (!method.getDeclaringClass().equals(javax.ejb.EJBLocalHome.class)) {
			String pMeth = convertMethod2String(method);
			if (!prototypeMethodList.contains(pMeth)) {
			    VcMethod vm =null;
			    if (method.getName().startsWith("create")) {
				Method beanMethod = getBeanMethod(method, dd.getEjbClass());
				vm = new VcMethod(beanMethod, dd.getMethodDesc(method), dd);
			    } else {
				vm = new VcMethod(method, dd.getMethodDesc(method), dd);
			    }
			    vcMethodList.addElement(vm);
			    prototypeMethodList.add(pMeth);
			}
		    }
		}
	    }

	    /*
	     * Add the
	     * - setEntityContext(javax.ejb.EntityContext),
	     * - ejbLoad(),
	     * - ejbStore(),
	     * - ejbRemove()
	     * bean's methods.
	     */
	    try {
		Class [] params = {javax.ejb.EntityContext.class};
		Method beanMethod = dd.getEjbClass().getMethod("setEntityContext",
							       params);
		VcMethod vm = new VcMethod(beanMethod, null, dd);
		vcMethodList.addElement(vm);
	    } catch (Exception e) {
		throw new Error("setEntityContext(javax.ejb.EntityContext) method not defined in "
				+dd.getEjbClass().getName());
	    }
	    try {
		Method beanMethod = dd.getEjbClass().getMethod("ejbLoad", new Class[0]);
		VcMethod vm = new VcMethod(beanMethod, null, dd);
		vcMethodList.addElement(vm);
	    } catch (Exception e) {
		throw new Error("ejbLoad() method not defined in "
				+dd.getEjbClass().getName());
	    }
	    try {
		Method beanMethod = dd.getEjbClass().getMethod("ejbStore", new Class[0]);
		VcMethod vm = new VcMethod(beanMethod, null, dd);
		vcMethodList.addElement(vm);
	    } catch (Exception e) {
		throw new Error("ejbStore() method not defined in "
				+dd.getEjbClass().getName());
	    }
	    try {
		Method beanMethod = dd.getEjbClass().getMethod("ejbRemove", new Class[0]);
		VcMethod vm = new VcMethod(beanMethod, null, dd);
		vcMethodList.addElement(vm);
	    } catch (Exception e) {
		throw new Error("ejbRemove() method not defined in "
				+dd.getEjbClass().getName());
	    }
	    break;
	}
	vc.put("methodList", vcMethodList);

	Vector vcFieldList = new Vector();
	Vector vcFieldPkList = new Vector();
	Vector vcFieldNoPkList = new Vector();
	if ((srcType == Source.ENTITY_CMP_JDBC)
	    && (dd instanceof EntityCmpDesc)) {
	    EntityCmpDesc edd = (EntityCmpDesc)dd;
	    Field[] fieldsBean = dd.getEjbClass().getFields();
	    for (int i = 0; i<fieldsBean.length; i++) {
		Field f = fieldsBean[i];
		if (edd.hasCmpFieldDesc(f)) {
		    VcField vcf = new VcField(f, edd.getCmpFieldDesc(f));
		    vcFieldList.addElement(vcf);
		    if (edd.getCmpFieldDesc(f).isPrimaryKey()) {
			vcFieldPkList.addElement(vcf);
		    } else {
			vcFieldNoPkList.addElement(vcf);
		    }
		}
	    }
	}
	vc.put("fieldList", vcFieldList);
	vc.put("fieldPkList", vcFieldPkList);
	vc.put("fieldNoPkList", vcFieldNoPkList);

	// System.out.println("VelocityContext debug for '"+dd.getEjbName()
        // 		   +"' (type "+srcType+")");
	// System.out.println(toString(vc));

	return vc;
    }

    /*
     * Convert a method to a string depending on its name and its parameters list.
     */
    private static String convertMethod2String(Method meth) {
        String value = new String(meth.getName());
        Class[] params = meth.getParameterTypes();
        value = value + "_" + params.length;
        for (int p=0; p<params.length; p++) {
            value = value + "_" + JavaType.getName(params[p]);
        }
        return(value);
    }


    /*
     * Return the associated bean's method of the given interface's method, ie
     * - ejbRemove(..) for [home|object]remove(..)
     * - ejbCreateXxx(..) for home.createXxx(..)
     * - ejbFindXxx(..) for home.findXxx(..)
     * - ejbHomeXxx(..) for home.xxx(..)
     * - xxx(..) for object.xxx(..)
     */
    private static Method getBeanMethod(Method method, Class bean) {

	String methodName = method.getName();
	Method beanMethod;
	boolean isMethodHome =
	    javax.ejb.EJBHome.class.isAssignableFrom(method.getDeclaringClass()) ||
	    javax.ejb.EJBLocalHome.class.isAssignableFrom(method.getDeclaringClass());

	String beanMethodName = methodName;
	if ("remove".equals(methodName)) {
	    beanMethodName = "ejbRemove";
	} else if (isMethodHome) {
	    if (methodName.startsWith("create") || methodName.startsWith("find")) {
		beanMethodName = "ejb"+BeanNaming.firstToUpperCase(methodName);
	    } else {
		beanMethodName = "ejbHome"+BeanNaming.firstToUpperCase(methodName);
	    }
	}

	try {
	    beanMethod = bean.getMethod(beanMethodName, method.getParameterTypes());
	} catch (Exception e) {
	    throw new Error("No associated bean's method for the interface method "+method.toString());
	}
	return beanMethod;
    }

    static String toString(VelocityContext vc) {
	StringBuffer ret = new StringBuffer();
	Object keys[] = vc.internalGetKeys();
	for (int i=0; i < keys.length; i++) {
	    String key = (String)keys[i];
	    Object value = vc.internalGet(key);
	    if (i>0) {
		ret.append("\n");
	    }
	    ret.append("  key = "+key+"; value = "+value);
	}
	return(ret.toString());
    }

}
