/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.apache.jena.testing_framework.tuples;

import java.io.*;
import java.util.*;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TupleSet implements Iterator<List<TupleItem>> {
	BufferedReader in;
	public String line = null;
	public int lineNumber = 0;

	static final char COMMENTCHAR = '#';
	List<TupleItem> current = null;
	boolean finished = false;

	protected static Logger logger = LoggerFactory.getLogger(TupleSet.class);

	/** Creates new TupleSet */
	public TupleSet(Reader r) {
		if (!(r instanceof BufferedReader))
			in = new BufferedReader(r);
		else
			in = (BufferedReader) r;
	}

	@Override
	public boolean hasNext() {
		if (finished)
			return false;

		if (current == null)
			current = tuple();
		return current != null;
	}

	@Override
	public List<TupleItem> next() {
		if (hasNext()) {
			List<TupleItem> x = current;
			current = null;
			return x;
		} else
			return null;
	}

	@Override
	public void remove() {
		throw new java.lang.UnsupportedOperationException("TupleSet.remove");
	}

	private List<TupleItem> tuple() {

		try {
			lineNumber++;
			line = in.readLine();
		} catch (IOException e) {
		}

		if (line == null) {
			finished = true;
			return null;
		}

		// System.out.println("Line: "+line) ;
		List<TupleItem> tuple = new ArrayList<>();
		int i = 0;
		int j = 0;
		boolean errorFound = false;

		tupleLoop: for (;;) {
			// Move to beginning of next item.
			i = skipwhitespace(line, j);

			if (i < 0)
				break;

			int iStart = -2; // Points to the beginning of the item as found
			int jStart = -2; // Points to the item without quotes
			int iFinish = -2; // Points after the end of the item as found
			int jFinish = -2; // Points after the end of the item without quotes
			int dtStart = -2; // Points to start of datatype (after < quote)
			int dtFinish = -2; // Points to end of datatype
			int type = TupleItem.UNKNOWN;

			switch (line.charAt(i)) {
			case COMMENTCHAR:
				break tupleLoop;
			case '<':
				type = TupleItem.URI;
				iStart = i;
				jStart = i + 1;
				int newPosn = parseURI(i, line);
				if (newPosn < 0) {
					errorFound = true;
					break tupleLoop;
				}
				j = newPosn;

				iFinish = j + 1;
				jFinish = j;
				break;
			case '"':
				type = TupleItem.STRING;
				iStart = i;
				jStart = i + 1;
				boolean inEscape = false;
				for (j = i + 1; j < line.length(); j++) {
					char ch = line.charAt(j);
					if (inEscape) {
						// ToDo: escape
						inEscape = false;
						continue;
					}
					// Not an escape
					if (ch == '"')
						break;

					if (ch == '\\')
						inEscape = true;
					if (ch == '\n' || ch == '\r') {
						errorFound = true;
						break tupleLoop;

					}
				}

				// Malformed
				if (j == line.length()) {
					errorFound = true;
					break tupleLoop;
				}

				iFinish = j + 1;
				jFinish = j;
				// RDF literals may be followed by their type.

				if (j < line.length() - 3 && line.charAt(j + 1) == '^'
						&& line.charAt(j + 2) == '^'
						&& line.charAt(j + 3) == '<') {
					dtFinish = parseURI(j + 3, line);
					dtStart = j + 4;
					if (dtFinish < 0) {
						errorFound = true;
						break tupleLoop;
					}
					j = dtFinish + 1;
					// String dt = line.substring(dtStart, dtFinish) ;
					// System.out.println("I see a datatype:"+dt) ;
				}

				break;
			case '_':
				type = TupleItem.ANON;
				iStart = i;
				for (j = i + 1; j < line.length(); j++) {
					char ch = line.charAt(j);
					if (ch == ' ' || ch == '\t' || ch == '.')
						break;
					if (!Character.isLetterOrDigit(ch) && !(ch == '_')
							&& !(ch == ':')) {
						errorFound = true;
						break tupleLoop;
					}
				}
				iFinish = j;
				jStart = iStart;
				jFinish = iFinish;
				break;
			case '.':
			case '\n':
			case '\r':
				return tuple;
			default:
				type = TupleItem.UNQUOTED;
				iStart = i;
				jStart = i;
				for (j = i + 1; j < line.length(); j++) {
					char ch = line.charAt(j);
					if (ch == ' ' || ch == '\t' || ch == '.')
						break;

					// if ( ! Character.isLetterOrDigit(line.charAt(i)) )
					// {
					// errorFound = true ;
					// break tupleLoop;
					// }
				}
				// Malformed
				if (j == line.length() + 1) {
					errorFound = true;
					break tupleLoop;
				}
				iFinish = j;
				jFinish = j;
				break;
			}
			String item = line.substring(jStart, jFinish);
			String literal = line.substring(iStart, iFinish);
			String dt = null;
			if (dtStart > 0)
				dt = line.substring(dtStart, dtFinish);

			tuple.add(new TupleItem(item, literal, type, dt));
			j++;
			// End of item.
		}
		// End of this line.
		if (errorFound) {
			logger.error("Error in TupleSet.tuple: " + line);

			String s = "";
			int k = 0;
			for (; k < i; k++)
				s = s + " ";
			s = s + "^";
			for (; k < j - 1; k++)
				s = s + " ";
			s = s + "^";
			logger.error(s);
			return null;
		}

		if (tuple.size() == 0) {
			// Nothing found : loop by tail recursion
			return tuple();
		}
		return tuple;
	}

	private int skipwhitespace(String s, int i) {
		for (; i < s.length(); i++) {
			char ch = s.charAt(i);
			// Horizonal whitespace
			if (ch != ' ' && ch != '\t')
				return i;
		}
		return -1;
	}

	private int parseURI(int i, String line) {
		int j;
		for (j = i + 1; j < line.length(); j++) {
			char ch = line.charAt(j);
			if (ch == '>')
				break;
			if (ch == '\n' || ch == '\r')
				return -1;
		}
		// Malformed
		if (j == line.length())
			return -2;
		return j;
	}
}
