package org.reactfx;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.BinaryOperator;
import java.util.function.Consumer;

public abstract class ListHelper<T> {

    public static <T> ListHelper<T> add(ListHelper<T> listHelper, T elem) {
        if(listHelper == null) {
            return new SingleElemHelper<>(elem);
        } else {
            return listHelper.add(elem);
        }
    }

    public static <T> ListHelper<T> remove(ListHelper<T> listHelper, T elem) {
        if(listHelper == null) {
            return listHelper;
        } else {
            return listHelper.remove(elem);
        }
    }

    public static <T> void forEach(ListHelper<T> listHelper, Consumer<T> f) {
        if(listHelper != null) {
            listHelper.forEach(f);
        }
    }

    public static <T> Optional<T> reduce(ListHelper<T> listHelper, BinaryOperator<T> f) {
        if(listHelper == null) {
            return Optional.empty();
        } else {
            return listHelper.reduce(f);
        }
    }

    public static <T, U> U reduce(ListHelper<T> listHelper, U unit, BiFunction<U, T, U> f) {
        if(listHelper == null) {
            return unit;
        } else {
            return listHelper.reduce(unit, f);
        }
    }

    public static <T> boolean isEmpty(ListHelper<T> listHelper) {
        return listHelper == null;
    }

    public static <T> int size(ListHelper<T> listHelper) {
        if(listHelper == null) {
            return 0;
        } else {
            return listHelper.size();
        }
    }

    private ListHelper() {
        // private constructor to prevent subclassing
    };

    protected abstract ListHelper<T> add(T elem);

    protected abstract ListHelper<T> remove(T elem);

    protected abstract void forEach(Consumer<T> f);

    protected abstract Optional<T> reduce(BinaryOperator<T> f);

    protected abstract <U> U reduce(U unit, BiFunction<U, T, U> f);

    protected abstract int size();


    private static class SingleElemHelper<T> extends ListHelper<T> {
        private final T elem;

        public SingleElemHelper(T elem) {
            this.elem = elem;
        }

        @Override
        protected ListHelper<T> add(T elem) {
            return new MultiElemHelper<>(this.elem, elem);
        }

        @Override
        protected ListHelper<T> remove(T elem) {
            if(Objects.equals(this.elem, elem)) {
                return null;
            } else {
                return this;
            }
        }

        @Override
        protected void forEach(Consumer<T> f) {
            f.accept(elem);
        }

        @Override
        protected Optional<T> reduce(BinaryOperator<T> f) {
            return Optional.of(elem);
        }

        @Override
        protected <U> U reduce(U unit, BiFunction<U, T, U> f) {
            return f.apply(unit, elem);
        }

        @Override
        protected int size() {
            return 1;
        }
    }

    private static class MultiElemHelper<T> extends ListHelper<T> {
        private final List<T> elems = new ArrayList<>();

        @SafeVarargs
        public MultiElemHelper(T... elems) {
            this.elems.addAll(Arrays.asList(elems));
        }

        @Override
        protected ListHelper<T> add(T elem) {
            elems.add(elem);
            return this;
        }

        @Override
        protected ListHelper<T> remove(T elem) {
            elems.remove(elem);
            switch(elems.size()) {
                case 0: return null;
                case 1: return new SingleElemHelper<>(elems.get(0));
                default: return this;
            }
        }

        @SuppressWarnings("unchecked")
        @Override
        protected void forEach(Consumer<T> f) {
            for(Object elem: elems.toArray()) {
                f.accept((T) elem);
            }
        }

        @Override
        protected Optional<T> reduce(BinaryOperator<T> f) {
            return elems.stream().reduce(f);
        }

        @Override
        protected <U> U reduce(U unit, BiFunction<U, T, U> f) {
            U u = unit;
            for(T elem: elems) {
                u = f.apply(u, elem);
            }
            return u;
        }

        @Override
        protected int size() {
            return elems.size();
        }
    }
}

@@ -259,16 +259,55 @@
 onsumer;
+%0Aimport java.util.function.IntFunction;
 %0A%0Apublic
@@ -1415,32 +1415,278 @@
        %7D%0A    %7D%0A%0A
+    public static %3CT%3E T%5B%5D toArray(ListHelper%3CT%3E listHelper, IntFunction%3CT%5B%5D%3E allocator) %7B%0A        if(listHelper == null) %7B%0A            return allocator.apply(0);%0A        %7D else %7B%0A            return listHelper.toArray(allocator);%0A        %7D%0A    %7D%0A%0A
     public stati
@@ -2340,24 +2340,89 @@
  T, U%3E f);%0A%0A
+    protected abstract T%5B%5D toArray(IntFunction%3CT%5B%5D%3E allocator);%0A%0A
     protecte
@@ -3341,32 +3341,214 @@
 em);%0A        %7D%0A%0A
+        @Override%0A        protected T%5B%5D toArray(IntFunction%3CT%5B%5D%3E allocator) %7B%0A            T%5B%5D res = allocator.apply(1);%0A            res%5B0%5D = elem;%0A            return res;%0A        %7D%0A%0A
         @Overrid
@@ -4838,32 +4838,32 @@
 ;%0A            %7D%0A
-
             retu
@@ -4871,32 +4871,180 @@
 n u;%0A        %7D%0A%0A
+        @Override%0A        protected T%5B%5D toArray(IntFunction%3CT%5B%5D%3E allocator) %7B%0A            return elems.toArray(allocator.apply(size()));%0A        %7D%0A%0A
         @Overrid

Add ListHelper.toArray method.