/* AbstrTraversal.java
 * Bryan Chadwick :: 2007
 * Abstract traversal function */

package edu.neu.ccs.demeterf.perform;

import edu.neu.ccs.demeterf.Control;
import java.lang.reflect.*;
import java.util.ArrayList;
import edu.neu.ccs.demeterf.util.Option;
import edu.neu.ccs.demeterf.util.Util;
import edu.neu.ccs.demeterf.lib.List;


public abstract class AbstTraversal{
    protected Control control;

    /** Create a Traversal that goes Everywhere */
    public AbstTraversal(){ this(Control.everywhere()); }
    /** Create a Traversal with Selective edge/field Bypassing */
    public AbstTraversal(Control c){ control = c; }
    
    /** Do the Traversal... No traversal arguments */
    public <Ret> Ret traverse(Object o){ return this.<Ret>traverse(o, Option.none()); }

    /** Do the Traversal... With a traversal argument */
    public <Ret> Ret traverse(Object o, Object a){ return this.<Ret>traverse(o, Option.some(a)); }

    /** Do the Traversal... With an ML like Option argument (Some/None)*/
    protected <Ret> Ret traverse(Object o, Option arg){
        Object result = null;
        Class<?> c = o.getClass();
        boolean hasArg = arg.some();
        
        List<Field> fl = Util.getFuncFields(c);
        if(control.isBuiltIn(c))
            result = applyBuilder(Util.addArg(new Object[]{o},arg),true);
        else{
            if(c.isArray())
                result = traverseArray((Object[])o, arg);
            else{
                ArrayList<Object> ret = new ArrayList<Object>();
                ret.add(o);
                
                for(Field f : fl){
                    //if(!control.ignore(c, f.getName()))
                        try{
                            f.setAccessible(true);
                            Object tret = f.get(o);
                            Option farg = (hasArg)?applyAugment(new Object[]{o, arg.get()},f):arg;
                            if(!control.skip(c, f.getName()))
                                tret = this.<Object>traverse(tret, farg);
                            ret.add(tret);
                        }catch(Exception e){ throw (RuntimeException)e; }
                }
                if(hasArg)ret.add(arg.get());
                result = applyBuilder(ret.toArray(),false);
            }
        }
        return (Ret)result;
    }
    /** Traverses each element of an Array, and combines the results into
     *    an array with an element type of the 'closest' common supertype
     *    of all returned values */
    protected Object traverseArray(Object[] o, Option arg){
        ArrayList<Object> ret = new ArrayList<Object>();
        Class<?> c = Object.class; 
        for(int i = 0; i < o.length; i++){
            Object t = traverse(o[i], arg); 
            Class<?> tc = t.getClass();
            ret.add(t);
            if(i == 1)c = tc;
            else 
                while(!c.isAssignableFrom(tc))
                    c = c.getSuperclass();
        }
        return ret.toArray((Object[])Array.newInstance(c, 0));
    }
    /** Apply the Augmentor to the Argument at this Object before
     *    traversing the given field. */
    protected Option applyAugment(Object o[], Field f){
        return Option.some(applyAugment(o, f.getDeclaringClass(), f.getName()));
    }
    
    /** Apply the Builder to this list of 'fields' */    
    protected abstract Object applyBuilder(Object o[], boolean prim);
    /** Apply the Augmentor the the traversal argument at this object
     *   before traversing the given 'field' */
    protected abstract Object applyAugment(Object o[], Class<?> pc, String fn);
}