using System;
using genlib = System.Collections.Generic;
using edu.neu.ccs.demeterf.control;
using edu.neu.ccs.demeterf.lib;
using edu.neu.ccs.demeterf.util;
using System.Reflection;

namespace edu.neu.ccs.demeterf{
    public abstract class AbstTraversal{
        public static string BuilderMethod = "combine";
        public static string AugmentorMethod = "update";
        
        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 eb){ control = eb; }
    
        /** Do the Traversal... No arguments */
        public virtual Ret traverse<Ret>(Object o){ return (Ret)traverse<Object>(o, Option.none()); }

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

        static int indentAmt = 0;
        static String indent(){ return indent(indentAmt); }
        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 virtual Ret traverse<Ret>(Object o, Option arg){
            indentAmt++;
            Type t = o.GetType();
            bool hasArg = arg.some();
            Object result = null;

            List<FieldInfo> fs = Util.getFuncFields(t);
            if(control.isBuiltIn(t)){
                result = applyBuilder(Util.addArg(new Object[]{o},arg),true);
            }else{
                if(t.IsArray){
                    Option narg = (hasArg)?applyAugment(new Object[]{o, arg.get()},null):arg;
                    result = traverseArray((Object[])o, narg);
                }else{
                    genlib.List<Object> ret = new genlib.List<Object>();
                    ret.Add(o);
                    
                    foreach(FieldInfo f in fs){
                            Object tret = f.GetValue(o);
                            if(tret == null){
                                // Throw an error

                                if(!Util.allowNull)Util.nullFieldError(f);
                                return default(Ret);
                            }else{
                                Option farg = (hasArg)?applyAugment(new Object[]{o, arg.get()},f):arg;
                                if(!control.skip(t, f.Name))
                                    tret = traverse<Object>(tret, farg);
                            }
                            ret.Add(tret);
                    }
                    if(hasArg)ret.Add(arg.get());
                    result = applyBuilder(ret.ToArray(),false);
                }
            }
            indentAmt--;
            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 valuse */
        protected virtual Object traverseArray(Object[] o, Option arg){
            genlib.List<Object> ret = new genlib.List<Object>();
            Type t = Util.objectType; 
            for(int i = 0; i < o.Length; i++){
                Object el = traverse<Object>(o[i], arg); 
                Type tel = el.GetType();
                ret.Add(el);
                if(i == 1)t = tel;
                else 
                    while(!t.IsAssignableFrom(tel))
                        t = t.BaseType;
            }
            Object[] retArr = ret.ToArray();
            Object[] typedArr = (Object[])(Array.CreateInstance(t, 0));
            for(int i = 0; i < retArr.Length; i++)
                typedArr[i] = retArr[i];
            return typedArr;
        }

        // Apply the Augmentor the the Argument at this Object

        protected Option applyAugment(Object[] o, FieldInfo f){
            return Option.some(applyAugment(o, f.DeclaringType, f.Name));
        }
        
        /** Apply the Builder to this list of 'Fields' */    
        protected abstract Object applyBuilder(Object[] o, bool leaf);
        /** Apply the Augmentor the the Argument at this Object */
        protected abstract Object applyAugment(Object[] o, Type pc, String fn);
    }
}