/*
 * Copyright (C) 2013 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.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkPositionIndexes;

import java.io.IOException;
import java.io.Reader;
import java.nio.CharBuffer;

/**
 * A {@link Reader} that reads the characters in a {@link CharSequence}. Like
 * {@code StringReader}, but works with any {@link CharSequence}.
 *
 * @author Colin Decker
 */
// TODO(user): make this public? as a type, or a method in CharStreams?
final class CharSequenceReader extends Reader {

	private CharSequence seq;
	private int pos;
	private int mark;

	/**
	 * Creates a new reader wrapping the given character sequence.
	 */
	public CharSequenceReader(CharSequence seq) {
		this.seq = checkNotNull(seq);
	}

	private void checkOpen() throws IOException {
		if (seq == null) {
			throw new IOException("reader closed");
		}
	}

	private boolean hasRemaining() {
		return remaining() > 0;
	}

	private int remaining() {
		return seq.length() - pos;
	}

	@Override
	public synchronized int read(CharBuffer target) throws IOException {
		checkNotNull(target);
		checkOpen();
		if (!hasRemaining()) {
			return -1;
		}
		int charsToRead = Math.min(target.remaining(), remaining());
		for (int i = 0; i < charsToRead; i++) {
			target.put(seq.charAt(pos++));
		}
		return charsToRead;
	}

	@Override
	public synchronized int read() throws IOException {
		checkOpen();
		return hasRemaining() ? seq.charAt(pos++) : -1;
	}

	@Override
	public synchronized int read(char[] cbuf, int off, int len) throws IOException {
		checkPositionIndexes(off, off + len, cbuf.length);
		checkOpen();
		if (!hasRemaining()) {
			return -1;
		}
		int charsToRead = Math.min(len, remaining());
		for (int i = 0; i < charsToRead; i++) {
			cbuf[off + i] = seq.charAt(pos++);
		}
		return charsToRead;
	}

	@Override
	public synchronized long skip(long n) throws IOException {
		checkArgument(n >= 0, "n (%s) may not be negative", n);
		checkOpen();
		int charsToSkip = (int) Math.min(remaining(), n); // safe because remaining is an int
		pos += charsToSkip;
		return charsToSkip;
	}

	@Override
	public synchronized boolean ready() throws IOException {
		checkOpen();
		return true;
	}

	@Override
	public boolean markSupported() {
		return true;
	}

	@Override
	public synchronized void mark(int readAheadLimit) throws IOException {
		checkArgument(readAheadLimit >= 0, "readAheadLimit (%s) may not be negative", readAheadLimit);
		checkOpen();
		mark = pos;
	}

	@Override
	public synchronized void reset() throws IOException {
		checkOpen();
		pos = mark;
	}

	@Override
	public synchronized void close() throws IOException {
		seq = null;
	}
}
