/*******************************************************************************
 * Copyright (c) 2015 Eclipse RDF4J contributors, Aduna, and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Distribution License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *******************************************************************************/
package org.eclipse.rdf4j.query.dawg;

import static org.eclipse.rdf4j.query.dawg.DAWGTestResultSetSchema.BINDING;
import static org.eclipse.rdf4j.query.dawg.DAWGTestResultSetSchema.RESULTSET;
import static org.eclipse.rdf4j.query.dawg.DAWGTestResultSetSchema.RESULTVARIABLE;
import static org.eclipse.rdf4j.query.dawg.DAWGTestResultSetSchema.SOLUTION;
import static org.eclipse.rdf4j.query.dawg.DAWGTestResultSetSchema.VALUE;
import static org.eclipse.rdf4j.query.dawg.DAWGTestResultSetSchema.VARIABLE;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.rdf4j.model.Literal;
import org.eclipse.rdf4j.model.Model;
import org.eclipse.rdf4j.model.Resource;
import org.eclipse.rdf4j.model.Statement;
import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.model.impl.TreeModelFactory;
import org.eclipse.rdf4j.model.util.Models;
import org.eclipse.rdf4j.model.vocabulary.RDF;
import org.eclipse.rdf4j.query.Binding;
import org.eclipse.rdf4j.query.TupleQueryResultHandler;
import org.eclipse.rdf4j.query.TupleQueryResultHandlerException;
import org.eclipse.rdf4j.query.impl.MapBindingSet;
import org.eclipse.rdf4j.query.impl.SimpleBinding;
import org.eclipse.rdf4j.rio.RDFHandlerException;
import org.eclipse.rdf4j.rio.helpers.AbstractRDFHandler;

/**
 * @author Arjohn Kampman
 */
public class DAWGTestResultSetParser extends AbstractRDFHandler {

	/*-----------*
	 * Constants *
	 *-----------*/

	/**
	 * RDFHandler to report the generated statements to.
	 */
	private final TupleQueryResultHandler tqrHandler;

	/*-----------*
	 * Variables *
	 *-----------*/

	private Model graph = new TreeModelFactory().createEmptyModel();

	/*--------------*
	 * Constructors *
	 *--------------*/

	public DAWGTestResultSetParser(TupleQueryResultHandler tqrHandler) {
		this.tqrHandler = tqrHandler;
	}

	/*---------*
	 * Methods *
	 *---------*/

	@Override
	public void startRDF() throws RDFHandlerException {
		graph.clear();
	}

	@Override
	public void handleStatement(Statement st) throws RDFHandlerException {
		graph.add(st);
	}

	@Override
	public void endRDF() throws RDFHandlerException {
		try {
			Resource resultSetNode = Models.subject(graph.filter(null, RDF.TYPE, RESULTSET))
					.orElseThrow(() -> new RDFHandlerException("no instance of type ResultSet"));

			List<String> bindingNames = getBindingNames(resultSetNode);
			tqrHandler.startQueryResult(bindingNames);

			for (Value solutionNode : Models.getProperties(graph, resultSetNode, SOLUTION)) {
				if (solutionNode instanceof Resource) {
					reportSolution((Resource) solutionNode, bindingNames);
				} else {
					throw new RDFHandlerException("Value for " + SOLUTION + " is not a resource: " + solutionNode);
				}
			}

			tqrHandler.endQueryResult();
		} catch (TupleQueryResultHandlerException e) {
			throw new RDFHandlerException(e.getMessage(), e);
		}
	}

	private List<String> getBindingNames(Resource resultSetNode) throws RDFHandlerException {
		List<String> bindingNames = new ArrayList<>(16);

		for (Value varName : Models.getProperties(graph, resultSetNode, RESULTVARIABLE)) {
			if (varName instanceof Literal) {
				bindingNames.add(((Literal) varName).getLabel());
			} else {
				throw new RDFHandlerException("Value for " + RESULTVARIABLE + " is not a literal: " + varName);
			}
		}

		return bindingNames;
	}

	private void reportSolution(Resource solutionNode, List<String> bindingNames) throws RDFHandlerException {
		MapBindingSet bindingSet = new MapBindingSet(bindingNames.size());

		for (Value bindingNode : Models.getProperties(graph, solutionNode, BINDING)) {
			if (bindingNode instanceof Resource) {
				Binding binding = getBinding((Resource) bindingNode);
				bindingSet.addBinding(binding);
			} else {
				throw new RDFHandlerException("Value for " + BINDING + " is not a resource: " + bindingNode);
			}
		}

		try {
			tqrHandler.handleSolution(bindingSet);
		} catch (TupleQueryResultHandlerException e) {
			throw new RDFHandlerException(e.getMessage(), e);
		}
	}

	private Binding getBinding(Resource bindingNode) {
		Literal name = Models.getPropertyLiteral(graph, bindingNode, VARIABLE)
				.orElseThrow(() -> new RDFHandlerException("missing variable name for binding " + bindingNode));
		Value value = Models.getProperty(graph, bindingNode, VALUE)
				.orElseThrow(() -> new RDFHandlerException("missing variable value for binding " + bindingNode));
		return new SimpleBinding(name.getLabel(), value);
	}
}
