/*
 * Copyright (C) 2012 The Guava Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package com.google.common.io;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkPositionIndexes;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;

import com.google.common.annotations.GwtCompatible;
import com.google.common.annotations.GwtIncompatible;

/**
 * Provides simple GWT-compatible substitutes for {@code InputStream},
 * {@code OutputStream}, {@code Reader}, and {@code Writer} so that
 * {@code BaseEncoding} can use streaming implementations while remaining
 * GWT-compatible.
 *
 * @author Louis Wasserman
 */
@GwtCompatible(emulated = true)
final class GwtWorkarounds {
	private GwtWorkarounds() {
	}

	/**
	 * A GWT-compatible substitute for a {@code Reader}.
	 */
	interface CharInput {
		int read() throws IOException;

		void close() throws IOException;
	}

	/**
	 * Views a {@code Reader} as a {@code CharInput}.
	 */
	@GwtIncompatible("Reader")
	static CharInput asCharInput(final Reader reader) {
		checkNotNull(reader);
		return new CharInput() {
			@Override
			public int read() throws IOException {
				return reader.read();
			}

			@Override
			public void close() throws IOException {
				reader.close();
			}
		};
	}

	/**
	 * Views a {@code CharSequence} as a {@code CharInput}.
	 */
	static CharInput asCharInput(final CharSequence chars) {
		checkNotNull(chars);
		return new CharInput() {
			int index = 0;

			@Override
			public int read() {
				if (index < chars.length()) {
					return chars.charAt(index++);
				} else {
					return -1;
				}
			}

			@Override
			public void close() {
				index = chars.length();
			}
		};
	}

	/**
	 * A GWT-compatible substitute for an {@code InputStream}.
	 */
	interface ByteInput {
		int read() throws IOException;

		void close() throws IOException;
	}

	/**
	 * Views a {@code ByteInput} as an {@code InputStream}.
	 */
	@GwtIncompatible("InputStream")
	static InputStream asInputStream(final ByteInput input) {
		checkNotNull(input);
		return new InputStream() {
			@Override
			public int read() throws IOException {
				return input.read();
			}

			@Override
			public int read(byte[] b, int off, int len) throws IOException {
				checkNotNull(b);
				checkPositionIndexes(off, off + len, b.length);
				if (len == 0) {
					return 0;
				}
				int firstByte = read();
				if (firstByte == -1) {
					return -1;
				}
				b[off] = (byte) firstByte;
				for (int dst = 1; dst < len; dst++) {
					int readByte = read();
					if (readByte == -1) {
						return dst;
					}
					b[off + dst] = (byte) readByte;
				}
				return len;
			}

			@Override
			public void close() throws IOException {
				input.close();
			}
		};
	}

	/**
	 * A GWT-compatible substitute for an {@code OutputStream}.
	 */
	interface ByteOutput {
		void write(byte b) throws IOException;

		void flush() throws IOException;

		void close() throws IOException;
	}

	/**
	 * Views a {@code ByteOutput} as an {@code OutputStream}.
	 */
	@GwtIncompatible("OutputStream")
	static OutputStream asOutputStream(final ByteOutput output) {
		checkNotNull(output);
		return new OutputStream() {
			@Override
			public void write(int b) throws IOException {
				output.write((byte) b);
			}

			@Override
			public void flush() throws IOException {
				output.flush();
			}

			@Override
			public void close() throws IOException {
				output.close();
			}
		};
	}

	/**
	 * A GWT-compatible substitute for a {@code Writer}.
	 */
	interface CharOutput {
		void write(char c) throws IOException;

		void flush() throws IOException;

		void close() throws IOException;
	}

	/**
	 * Views a {@code Writer} as a {@code CharOutput}.
	 */
	@GwtIncompatible("Writer")
	static CharOutput asCharOutput(final Writer writer) {
		checkNotNull(writer);
		return new CharOutput() {
			@Override
			public void write(char c) throws IOException {
				writer.append(c);
			}

			@Override
			public void flush() throws IOException {
				writer.flush();
			}

			@Override
			public void close() throws IOException {
				writer.close();
			}
		};
	}

	/**
	 * Returns a {@code CharOutput} whose {@code toString()} method can be used to
	 * get the combined output.
	 */
	static CharOutput stringBuilderOutput(int initialSize) {
		final StringBuilder builder = new StringBuilder(initialSize);
		return new CharOutput() {

			@Override
			public void write(char c) {
				builder.append(c);
			}

			@Override
			public void flush() {
			}

			@Override
			public void close() {
			}

			@Override
			public String toString() {
				return builder.toString();
			}
		};
	}
}
