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

package edu.neu.ccs.demeterf;

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


/** Abstract Traversal with Control. Concrete traversals
 *   ({@link edu.neu.ccs.demeterf.Traversal Traversal} and 
 *   {@link edu.neu.ccs.demeterf.parallel.ParTraversal ParTraversal})
 *   complete the implementation, providing sequential and parallel traversal of
 *   data structures respectively.
 */
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)); }

    /** Indentation amount for nested comments */
    protected static int indent = 0;
    /** Computes the indentation string for nested comments */
    protected static String indent(){ return indent(indent); }
    /** Computes an indentation string of <tt>i</tt> spaces. */
    protected static String indent(int i){ if(i==0)return ""; return " "+indent(i-1); }

    /** Do the Traversal... With an ML like Option argument (Some/None)*/
    protected <Ret> Ret traverse(Object o, Option arg){
        indent++;
        Object result = null;
        if(o == null){
            if(!Util.allowNull)Util.nullError();
            return 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);
                            if(tret == null){
                                // Throw an error
                                if(!Util.allowNull)Util.nullFieldError(f);
                            }else{
                                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);
            }
        }
        indent--;
        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);
}