/*
 * Copyright The WildFly Authors
 * SPDX-License-Identifier: Apache-2.0
 */

package org.wildfly.clustering.marshalling.protostream;

import java.io.DataInput;
import java.lang.reflect.Array;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.PrimitiveIterator;
import java.util.stream.DoubleStream;
import java.util.stream.IntStream;
import java.util.stream.LongStream;

/**
 * {@link DataInput} implementation used to write the unexposed serializable fields of an object.
 * @author Paul Ferraro
 */
public class SimpleDataInput implements DataInput {

	private final Iterator<String> strings;
	private final Iterator<ByteBuffer> buffers;
	private final Iterator<Character> characters;
	private final Iterator<Boolean> booleans;
	private final Iterator<Byte> bytes;
	private final Iterator<Short> shorts;
	private final PrimitiveIterator.OfInt integers;
	private final PrimitiveIterator.OfLong longs;
	private final Iterator<Float> floats;
	private final PrimitiveIterator.OfDouble doubles;

	SimpleDataInput(Builder builder) {
		this.strings = builder.strings.iterator();
		this.buffers = builder.buffers.iterator();
		this.characters = builder.characters.iterator();
		this.booleans = builder.booleans.iterator();
		this.bytes = builder.bytes.iterator();
		this.shorts = builder.shorts.iterator();
		this.integers = builder.integers.iterator();
		this.longs = builder.longs.iterator();
		this.floats = builder.floats.iterator();
		this.doubles = builder.doubles.iterator();
	}

	ByteBuffer nextBuffer() {
		return this.buffers.next();
	}

	@Override
	public String readUTF() {
		return this.strings.next();
	}

	@Override
	public int readInt() {
		return this.integers.nextInt();
	}

	@Override
	public long readLong() {
		return this.longs.nextLong();
	}

	@Override
	public double readDouble() {
		return this.doubles.nextDouble();
	}

	@Override
	public void readFully(byte[] bytes) {
		this.nextBuffer().get(bytes);
	}

	@Override
	public void readFully(byte[] bytes, int offset, int length) {
		this.nextBuffer().get(bytes, offset, length);
	}

	@Override
	public int skipBytes(int n) {
		throw new UnsupportedOperationException();
	}

	@Override
	public boolean readBoolean() {
		return this.booleans.next();
	}

	@Override
	public byte readByte() {
		return this.bytes.next();
	}

	@Override
	public int readUnsignedByte() {
		return Byte.toUnsignedInt(this.bytes.next());
	}

	@Override
	public short readShort() {
		return this.shorts.next();
	}

	@Override
	public int readUnsignedShort() {
		return Short.toUnsignedInt(this.shorts.next());
	}

	@Override
	public char readChar() {
		return this.characters.next();
	}

	@Override
	public float readFloat() {
		return this.floats.next();
	}

	@Override
	public String readLine() {
		return this.strings.next();
	}

	public static class Builder {
		Iterable<String> strings = Collections.emptyList();
		Iterable<ByteBuffer> buffers = Collections.emptyList();
		Iterable<Character> characters = Collections.emptyList();
		Iterable<Boolean> booleans = Collections.emptyList();
		Iterable<Byte> bytes = Collections.emptyList();
		Iterable<Short> shorts = Collections.emptyList();
		IntStream integers = IntStream.empty();
		LongStream longs = LongStream.empty();
		Iterable<Float> floats = Collections.emptyList();
		DoubleStream doubles = DoubleStream.empty();

		public Builder with(String... values) {
			this.strings = Arrays.asList(values);
			return this;
		}

		public Builder with(ByteBuffer... buffers) {
			this.buffers = Arrays.asList(buffers);
			return this;
		}

		public Builder with(char... values) {
			this.characters = new ArrayIterable<>(values);
			return this;
		}

		public Builder with(boolean... values) {
			this.booleans = new ArrayIterable<>(values);
			return this;
		}

		public Builder with(byte... values) {
			this.bytes = new ArrayIterable<>(values);
			return this;
		}

		public Builder with(short... values) {
			this.shorts = new ArrayIterable<>(values);
			return this;
		}

		public Builder with(int... values) {
			this.integers = IntStream.of(values);
			return this;
		}

		public Builder with(long... values) {
			this.longs = LongStream.of(values);
			return this;
		}

		public Builder with(float... values) {
			this.floats = new ArrayIterable<>(values);
			return this;
		}

		public Builder with(double... values) {
			this.doubles = DoubleStream.of(values);
			return this;
		}

		public DataInput build() {
			return new SimpleDataInput(this);
		}
	}

	private static class ArrayIterable<T> implements Iterable<T> {
		private final Object array;

		ArrayIterable(Object array) {
			this.array = array;
		}

		@Override
		public Iterator<T> iterator() {
			Object array = this.array;
			return new Iterator<>() {
				private int index = 0;

				@Override
				public boolean hasNext() {
					return Array.getLength(array) > this.index;
				}

				@SuppressWarnings("unchecked")
				@Override
				public T next() {
					return (T) Array.get(array, this.index++);
				}
			};
		}
	}
}
