package functional;

import java.lang.reflect.Array;
import java.util.Stack;

/**
 * Some simple functions that are helpful when working with arrays.
 * 
 * @author Peter Goodman
 */
public class ArrayUtils {
    
    /**
     * Return a generic array of type T[] from a Stack<T>.
     */
    @SuppressWarnings("unchecked")
    static protected <T> T[] stack_to_array(Stack<T> elm_stack) {
        
        if(!elm_stack.isEmpty()) {
            return elm_stack.toArray(
                (T[]) Array.newInstance(
                    elm_stack.firstElement().getClass(),
                    elm_stack.size()
                )
            );
        }
        
        return (T[]) pack();
    }
    
    /**
     * Pack items of a given type into an array.
     * @param <T>
     * @param items
     * @return
     */
    static public <T> T[] pack(T ... items) {
        return items;
    }
    
    /**
     * Map a function onto an array of items and return the mapped array.
     * 
     * @param <I> input type
     * @param <O> output type
     * @param elms
     * @param fn
     * @param new_elms
     * @return
     */
    static public <I,O> O[] map(I[] old_elms, F<I,O> fn) {
        Stack<O> elm_stack = new Stack<O>();
        if(null != old_elms) {
            for(I elm : old_elms)
                elm_stack.push(fn.call(elm));
        }
        return stack_to_array(elm_stack);
    }
    
    /**
     * Filter elements from an array using a predicate and return the filtered
     * array.
     */
    static public <T> T[] filter(T[] old_elms, P<T> fn) {
        Stack<T> elm_stack = new Stack<T>();
        if(null != old_elms) {
            for(T elm : old_elms) {
                if(fn.call(elm))
                    elm_stack.push(elm);
            }
        }
        return stack_to_array(elm_stack);
    }
    
    /**
     * Flatten a two-dimensional array into a one-dimensional array.
     * 
     * @param <T> type of elements in the array
     * @param elms
     * @param new_elms
     * @return
     */
    static public <T> T[] flatten(T[][] old_elms) {
        Stack<T> elm_stack = new Stack<T>();
        if(null != old_elms) {
            for(T[] sub_elms : old_elms) {
                for(T elm : sub_elms)
                    elm_stack.push(elm);
            }
        }
        return stack_to_array(elm_stack);
    }
    
    /**
     * Reduce an array.
     * @param <I>
     * @param <O>
     * @param old_elms
     * @param initial_val
     * @param fn
     * @return
     */
    static public <I,O> O reduce(I[] old_elms, O initial_val, F2<I,O,O> fn) {
        O reduced_val = initial_val;
        if(null != old_elms) {
            for(I elm : old_elms)
                reduced_val = fn.call(elm, reduced_val);
        }
        return reduced_val;
    }
}