package ua.nure.jfn.task2;

public class StreamImpl<T> implements Stream<T> {
	private Container<?> source;
	private Array<Operation> operations;

	private interface Operation {
		Container<?> apply(Container<?> input);
	}

	private static class FilterOperation<T> implements Operation {
		private Function<? super T, Boolean> predicate;

		public FilterOperation(Function<? super T, Boolean> predicate) {
			this.predicate = predicate;
		}

		@SuppressWarnings("unchecked")
		@Override
		public Container<?> apply(Container<?> input) {
			Array<T> newContainer = new ArrayImpl<T>();

			for (Object element : input)
				if (predicate.apply((T) element))
					newContainer.add((T) element);

			return newContainer;
		}
	}

	private static class MapOperation<T, R> implements Operation {
		private Function<? super T, ? extends R> function;

		public MapOperation(Function<? super T, ? extends R> function) {
			this.function = function;
		}

		@SuppressWarnings("unchecked")
		@Override
		public Container<?> apply(Container<?> input) {
			Array<R> newContainer = new ArrayImpl<R>();

			for (Object element : input)
				newContainer.add(function.apply((T) element));

			return newContainer;
		}
	}

	public StreamImpl(Container<? extends T> container) {
		this.source = container;
		this.operations = new ArrayImpl<>();
	}

	private StreamImpl(Container<?> source, Array<Operation> operations) {
		this.source = source;
		this.operations = operations;
	}

	@Override
	public Stream<T> filter(Function<? super T, Boolean> predicate) {
		Array<Operation> newOperations = new ArrayImpl<>(operations);
		newOperations.add(new FilterOperation<>(predicate));
		return new StreamImpl<T>(source, newOperations);
	}

	@Override
	public <R> Stream<R> map(Function<? super T, ? extends R> function) {
		Array<Operation> newOperations = new ArrayImpl<>(operations);
		newOperations.add(new MapOperation<>(function));
		return new StreamImpl<R>(source, newOperations);
	}

	@Override
	public int count() {
		int count = 0;

		for (Operation operation : operations) {
			source = operation.apply(source);
			count++;
		}

		return count;
	}

	@SuppressWarnings("unchecked")
	@Override
	public void forEach(Action<? super T> action) {
		for (Operation operation : operations)
			source = operation.apply(source);

		for (Object element : source)
			action.perform((T) element);
	}
}
