/*
 * Copyright 2006-2007 the original author or 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 org.springframework.batch.item.file;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;

import org.springframework.core.io.Resource;

/**
 * A {@link BufferedReaderFactory} useful for reading simple binary (or text)
 * files with no line endings, such as those produced by mainframe copy books.
 * The reader splits a stream up across fixed line endings (rather than the
 * usual convention based on plain text). The line endings are discarded, just
 * as with the default plain text implementation.
 * 
 * @author Dave Syer
 * 
 * @since 2.1
 */
public class SimpleBinaryBufferedReaderFactory implements BufferedReaderFactory {

	/**
	 * The default line ending value.
	 */
	private static final String DEFAULT_LINE_ENDING = "\n";

	private String lineEnding = DEFAULT_LINE_ENDING;

	/**
	 * @param lineEnding
	 */
	public void setLineEnding(String lineEnding) {
		this.lineEnding = lineEnding;
	}

    @Override
	public BufferedReader create(Resource resource, String encoding) throws UnsupportedEncodingException, IOException {
		return new BinaryBufferedReader(new InputStreamReader(resource.getInputStream(), encoding), lineEnding);
	}

	/**
	 * BufferedReader extension that splits lines based on a line ending, rather
	 * than the usual plain text conventions.
	 * 
	 * @author Dave Syer
	 * 
	 */
	private final class BinaryBufferedReader extends BufferedReader {

		private final String ending;

		/**
		 * @param in
		 */
		private BinaryBufferedReader(Reader in, String ending) {
			super(in);
			this.ending = ending;
		}

		@Override
		public String readLine() throws IOException {

			StringBuilder buffer = null;

			synchronized (lock) {

				int next = read();
				if (next == -1) {
					return null;
				}

				buffer = new StringBuilder();
				StringBuilder candidateEnding = new StringBuilder();

				while (!isEndOfLine(buffer, candidateEnding, next)) {
					next = read();
				}
				buffer.append(candidateEnding);

			}

			if (buffer != null && buffer.length() > 0) {
				return buffer.toString();
			}
			return null;

		}

		/**
		 * Check for end of line and accumulate a buffer for next time.
		 * 
		 * @param buffer the current line excluding the candidate ending
		 * @param candidate a buffer containing accumulated state
		 * @param next the next character (or -1 for end of file)
		 * @return true if the values together signify the end of a file
		 */
		private boolean isEndOfLine(StringBuilder buffer, StringBuilder candidate, int next) {

			if (next == -1) {
				return true;
			}

			char c = (char) next;
			if (ending.charAt(0) == c || candidate.length() > 0) {
				candidate.append(c);
			}

			if (candidate.length() == 0) {
				buffer.append(c);
				return false;
			}

			boolean end = ending.equals(candidate.toString());
			if (end) {
				candidate.delete(0, candidate.length());
			}
			else if (candidate.length() >= ending.length()) {
				buffer.append(candidate);
				candidate.delete(0, candidate.length());
			}

			return end;

		}
	}

}
