package com.gx.obe.bind.recursion;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;

public class Recursions<T> {
	
	private Function<T, Stream<T>> toStreamFun;
	
	public static <T> Recursions<T> array(Function<T, T[]> toArrayFun) {
		return new Recursions<>(toArrayFun.andThen(t -> Optional.ofNullable(t).map(Stream::of).orElse(null)));
	}
	
	public static <T> Recursions<T> list(Function<T, List<T>> toListFun) {
		return new Recursions<>(toListFun.andThen(t -> Optional.ofNullable(t).map(List::stream).orElse(null)));
	}
	
	public static <T> Recursions<T> stream(Function<T, Stream<T>> toStreamFun) {
		return new Recursions<>(toStreamFun);
	}
	
	public Recursions(Function<T, Stream<T>> toStreamFun) {
		this.toStreamFun = toStreamFun;
	}
	
	public RStream<T> toStream(T obj) {
		return toStream(Stream.of(obj));
	}
	
	public RStream<T> toStream(T[] array) {
		return toStream(Stream.of(array));
	}
	
	public RStream<T> toStream(List<T> stream) {
		return toStream(stream.stream());
	}
	
	public RStream<T> toStream(Stream<T> stream) {
		return new RStream<T>() {
			
			public void forEach(Consumer<T> action) {
				new ForEachRecursion(action).exe(stream);
			}
			
			public void forEach(TreeForEach<T> treeForEach) {
				new TreeForEachRecursion(treeForEach).exe(stream);
			}
			
			public void forEach(BiConsumer<List<Integer>, T> indexConsumer) {
				new IndexForEachRecursion(indexConsumer).exe(stream);
			}
			
			public Optional<T> findAny(Predicate<T> predicate) {
				Data data = new Data();
				new AnyMatchRecursion(t -> {
					boolean test = predicate.test(t);
					if (test) data.t = t;
					return test;
				}).exe(stream);
				return Optional.ofNullable(data.t);
			}
			
			public List<T> filter(Predicate<T> predicate) {
				List<T> list = new ArrayList<>();
				new ForEachRecursion(t -> {
					if (predicate.test(t)) list.add(t);
				}).exe(stream);
				return list;
			}
			
		};
	}
	
	private class AnyMatchRecursion {
		
		private Predicate<T> predicate;
		
		public AnyMatchRecursion(Predicate<T> predicate) {
			this.predicate = predicate;
		}
		
		private boolean exe(Stream<T> stream) {
			return stream.anyMatch(t -> {
				if (predicate.test(t)) return true;
				return Optional.ofNullable(t).map(toStreamFun).map(this::exe).orElse(false);
			});
		}
		
	}
	
	private class ForEachRecursion {
		
		private Consumer<T> consumer;
		
		public ForEachRecursion(Consumer<T> consumer) {
			this.consumer = consumer;
		}
		
		private void exe(Stream<T> stream) {
			stream.forEach(t -> {
				consumer.accept(t);
				Optional.ofNullable(t).map(toStreamFun).ifPresent(this::exe);
			});
		}
		
	}
	
	private class TreeForEachRecursion {
		
		private TreeForEach<T> treeForEach;
		
		public TreeForEachRecursion(TreeForEach<T> treeForEach) {
			this.treeForEach = treeForEach;
		}
		
		private void exe(Stream<T> stream) {
			recursion(stream.iterator(), true);
		}
		
		private void recursion(Iterator<T> iterator, boolean root) {
			iterator.forEachRemaining(t -> {
				Iterator<T> citerator = Optional.of(t).map(toStreamFun).map(Stream::iterator).orElse(null);
				boolean leaf = citerator == null || !citerator.hasNext();
				treeForEach.action(root, leaf, t);
				if (!leaf) recursion(citerator, false);
			});
		}
		
	}
	
	private class IndexForEachRecursion {
		
		private BiConsumer<List<Integer>, T> indexConsumer;
		
		public IndexForEachRecursion(BiConsumer<List<Integer>, T> indexConsumer) {
			this.indexConsumer = indexConsumer;
		}
		
		private void exe(Stream<T> stream) {
			recursion(stream, new ArrayList<>());
		}
		
		private void recursion(Stream<T> stream, List<Integer> pIndex) {
			AtomicInteger i = new AtomicInteger(1);
			stream.forEach(t -> {
				List<Integer> index = new ArrayList<>(pIndex);
				index.add(i.getAndIncrement());
				indexConsumer.accept(index, t);
				Optional.ofNullable(t).map(toStreamFun).ifPresent(s -> recursion(s, index));
			});
		}
		
	}
	
	private class Data {
		private T t;
	}
	
}