/*
 * #%L
 * Wikidata Toolkit RDF
 * %%
 * Copyright (C) 2014 Wikidata Toolkit Developers
 * %%
 * 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.
 * #L%
 */

package org.wikidata.wdtk.rdf;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.eclipse.rdf4j.model.Model;
import org.eclipse.rdf4j.model.Resource;
import org.eclipse.rdf4j.model.ValueFactory;
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
import org.eclipse.rdf4j.model.util.Models;
import org.eclipse.rdf4j.rio.RDFFormat;
import org.eclipse.rdf4j.rio.RDFHandlerException;
import org.eclipse.rdf4j.rio.RDFParseException;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.wikidata.wdtk.datamodel.helpers.Datamodel;
import org.wikidata.wdtk.datamodel.helpers.StatementBuilder;
import org.wikidata.wdtk.datamodel.implementation.DataObjectFactoryImpl;
import org.wikidata.wdtk.datamodel.implementation.SitesImpl;
import org.wikidata.wdtk.datamodel.interfaces.DataObjectFactory;
import org.wikidata.wdtk.datamodel.interfaces.DatatypeIdValue;
import org.wikidata.wdtk.datamodel.interfaces.GlobeCoordinatesValue;
import org.wikidata.wdtk.datamodel.interfaces.ItemDocument;
import org.wikidata.wdtk.datamodel.interfaces.ItemIdValue;
import org.wikidata.wdtk.datamodel.interfaces.PropertyDocument;
import org.wikidata.wdtk.datamodel.interfaces.PropertyIdValue;
import org.wikidata.wdtk.datamodel.interfaces.SiteLink;
import org.wikidata.wdtk.datamodel.interfaces.Statement;
import org.wikidata.wdtk.datamodel.interfaces.StatementGroup;
import org.wikidata.wdtk.datamodel.interfaces.StatementRank;
import org.wikidata.wdtk.datamodel.interfaces.ValueSnak;

public class RdfConverterTest {

	ByteArrayOutputStream out;

	RdfWriter rdfWriter;
	RdfConverter rdfConverter;

	SitesImpl sites;

	ValueFactory rdfFactory = SimpleValueFactory.getInstance();
	Resource resource = rdfFactory.createIRI("http://test.org/");

	final TestObjectFactory objectFactory = new TestObjectFactory();
	final DataObjectFactory dataObjectFactory = new DataObjectFactoryImpl();

	@Before
	public void setUp() {
		this.out = new ByteArrayOutputStream();
		this.rdfWriter = new RdfWriter(RDFFormat.TURTLE, out);
		this.sites = new SitesImpl();
		this.rdfConverter = new RdfConverter(this.rdfWriter, this.sites,
				new MockPropertyRegister());
		this.rdfWriter.start();
	}

	@Test
	public void testWriteItemDocument() throws RDFHandlerException,
			IOException, RDFParseException {
		ItemDocument document = this.objectFactory.createItemDocument();
		this.rdfConverter.writeItemDocument(document);
		this.rdfWriter.finish();
		Model model = RdfTestHelpers.parseRdf(out.toString());
		assertTrue(Models.isomorphic(
				model,
				RdfTestHelpers.parseRdf(RdfTestHelpers.getResourceFromFile("ItemDocument.rdf"))
		));
	}

	@Test
	public void testWriteItemDocumentWithNullPropertyTypes() throws RDFHandlerException,
			IOException, RDFParseException {
		this.rdfConverter = new RdfConverter(this.rdfWriter, this.sites,
				new MockPropertyRegister.WithNullPropertyTypes());

		ItemDocument document = this.objectFactory.createItemDocument();
		this.rdfConverter.writeItemDocument(document);
		this.rdfWriter.finish();
		Model model = RdfTestHelpers.parseRdf(out.toString());
		assertTrue(Models.isomorphic(
				model,
				RdfTestHelpers.parseRdf(RdfTestHelpers.getResourceFromFile("ItemDocumentUnknownPropertyTypes.rdf"))
		));
	}


    @Test
	public void testWritePropertyDocument() throws RDFHandlerException,
			RDFParseException, IOException {
		PropertyDocument document = this.objectFactory
				.createEmptyPropertyDocument();
		this.rdfConverter.writePropertyDocument(document);
		this.rdfWriter.finish();
		Model model = RdfTestHelpers.parseRdf(this.out.toString());
		assertEquals(model, RdfTestHelpers.parseRdf(RdfTestHelpers
				.getResourceFromFile("EmptyPropertyDocument.rdf")));
	}

	@Test
	public void testWriteStatementRankTriple() throws RDFHandlerException,
			RDFParseException, IOException {
		StatementRank rank = StatementRank.DEPRECATED;
		Resource subject = this.rdfFactory
				.createIRI("http://www.wikidata.org/Q10Snone");
		this.rdfConverter.writeStatementRankTriple(subject, rank, false);
		this.rdfWriter.finish();
		Model model = RdfTestHelpers.parseRdf(this.out.toString());
		assertEquals(RdfTestHelpers.parseRdf(RdfTestHelpers
				.getResourceFromFile("StatementRankTriple.rdf")), model);
	}

	@Test
	public void testWriteStatementRankTripleBest() throws RDFHandlerException,
			RDFParseException, IOException {
		StatementRank rank = StatementRank.NORMAL;
		Resource subject = this.rdfFactory
				.createIRI("http://www.wikidata.org/Q10Snone");
		this.rdfConverter.writeStatementRankTriple(subject, rank, true);
		this.rdfWriter.finish();
		Model model = RdfTestHelpers.parseRdf(this.out.toString());
		assertEquals(RdfTestHelpers.parseRdf(RdfTestHelpers
				.getResourceFromFile("StatementRankTripleBest.rdf")), model);
	}

	@Test
	public void testStatementSimpleValue() throws RDFHandlerException,
			RDFParseException, IOException {
		Statement statement = objectFactory.createStatement("Q100", "P227").withStatementId("Q100-id111");
		this.rdfConverter.writeFullStatement(statement, false);
		this.rdfWriter.finish();
		Model model = RdfTestHelpers.parseRdf(this.out.toString());
		assertEquals(model, RdfTestHelpers.parseRdf(RdfTestHelpers
				.getResourceFromFile("Statement.rdf")));
	}

	@Test
	public void testStatementComplexValue() throws RDFHandlerException,
			RDFParseException, IOException {
		GlobeCoordinatesValue value = Datamodel.makeGlobeCoordinatesValue(51,
				13, GlobeCoordinatesValue.PREC_DEGREE,
				GlobeCoordinatesValue.GLOBE_EARTH);
		Statement statement = StatementBuilder
				.forSubjectAndProperty(ItemIdValue.NULL, PropertyIdValue.NULL)
				.withId("Q0$test")
				.withValue(value).build();
		this.rdfConverter.writeFullStatement(statement, false);
		this.rdfWriter.finish();
		Model model = RdfTestHelpers.parseRdf(this.out.toString());
		assertEquals(model, RdfTestHelpers.parseRdf(RdfTestHelpers
				.getResourceFromFile("StatementCplx.rdf")));
	}

	@Test
	public void testStatementNoValue() throws RDFHandlerException,
			RDFParseException, IOException {
		PropertyIdValue pid = dataObjectFactory.getPropertyIdValue("P31", "http://www.wikidata.org/");
		Statement statement = StatementBuilder
				.forSubjectAndProperty(ItemIdValue.NULL, pid)
				.withId("Q0$test")
				.withNoValue().build();
		this.rdfConverter.writeFullStatement(statement, false);
		this.rdfWriter.finish();
		Model model = RdfTestHelpers.parseRdf(this.out.toString());
		assertEquals(model, RdfTestHelpers.parseRdf(RdfTestHelpers
				.getResourceFromFile("StatementNoValue.rdf")));
	}

	@Test
	public void testWriteBasicDeclarations() throws RDFHandlerException,
			RDFParseException, IOException {
		this.rdfConverter.writeBasicDeclarations();
		this.rdfWriter.finish();
		Model model = RdfTestHelpers.parseRdf(this.out.toString());
		assertEquals(RdfTestHelpers.parseRdf(RdfTestHelpers
				.getResourceFromFile("BasicDeclarations.rdf")), model);
	}

	@Test
	public void testWriteNamespaceDeclarations() throws RDFHandlerException,
			RDFParseException, IOException {
		this.rdfConverter.writeNamespaceDeclarations();
		this.rdfWriter.finish();
		Model model = RdfTestHelpers.parseRdf(this.out.toString());
		assertEquals(RdfTestHelpers.parseRdf(RdfTestHelpers
				.getResourceFromFile("Namespaces.rdf")), model);
	}

	@Test
	public void testWriteSiteLinks() throws RDFHandlerException, IOException,
			RDFParseException {
		this.sites.setSiteInformation("enwiki", "wikipedia", "en", "mediawiki",
				"http://en.wikipedia.org/w/$1",
				"http://en.wikipedia.org/wiki/$1");
		this.sites.setSiteInformation("dewiki", "wikipedia", "de", "mediawiki",
				"http://de.wikipedia.org/w/$1",
				"http://de.wikipedia.org/wiki/$1");
		Map<String, SiteLink> siteLinks = objectFactory.createSiteLinks();
		this.rdfConverter.writeSiteLinks(this.resource, siteLinks);
		this.rdfWriter.finish();
		Model model = RdfTestHelpers.parseRdf(out.toString());
		assertEquals(model, RdfTestHelpers.parseRdf(RdfTestHelpers
				.getResourceFromFile("SiteLinks.rdf")));

	}

	private ItemDocument createTestItemDocument() {
		ItemIdValue itemValue = dataObjectFactory.getItemIdValue("Q100",
				"http://www.wikidata.org/");
		ItemIdValue value1 = dataObjectFactory.getItemIdValue("Q10",
				"http://www.wikidata.org/");
		ItemIdValue value2 = dataObjectFactory.getItemIdValue("Q11",
				"http://www.wikidata.org/");
		PropertyIdValue propertyIdValueP31 = dataObjectFactory
				.getPropertyIdValue("P31", "http://www.wikidata.org/");
		PropertyIdValue propertyIdValueP279 = dataObjectFactory
				.getPropertyIdValue("P279", "http://www.wikidata.org/");
		// Statement InstaceOf - P31
		ValueSnak mainSnak1 = dataObjectFactory.getValueSnak(
				propertyIdValueP31, value1);
		Statement statement1 = dataObjectFactory.getStatement(itemValue, mainSnak1,
				Collections.emptyList(), Collections.emptyList(),
				StatementRank.NORMAL, "10103");
		List<Statement> statementList1 = new ArrayList<>();
		statementList1.add(statement1);
		StatementGroup statementGroup1 = this.dataObjectFactory
				.getStatementGroup(statementList1);
		// Statement SubclassOf - P279
		ValueSnak mainSnak2 = dataObjectFactory.getValueSnak(
				propertyIdValueP279, value2);
		Statement statement2 = dataObjectFactory.getStatement(itemValue, mainSnak2,
				Collections.emptyList(), Collections.emptyList(),
				StatementRank.NORMAL, "10104");
		List<Statement> statementList2 = new ArrayList<>();
		statementList2.add(statement2);
		StatementGroup statementGroup2 = this.dataObjectFactory
				.getStatementGroup(statementList2);

		List<StatementGroup> statementGroups = new ArrayList<>();
		statementGroups.add(statementGroup1);
		statementGroups.add(statementGroup2);
		return dataObjectFactory.getItemDocument(itemValue,
				Collections.emptyList(),
				Collections.emptyList(),
				Collections.emptyList(),
				statementGroups, Collections.emptyMap(), 0);
	}

	@Test
	public void testWriteSimpleStatements() throws RDFHandlerException,
			RDFParseException, IOException {
		ItemDocument document = createTestItemDocument();
		this.rdfConverter.setTasks(RdfSerializer.TASK_SIMPLE_STATEMENTS);
		this.rdfConverter.writeStatements(document);
		this.rdfWriter.finish();
		Model model = RdfTestHelpers.parseRdf(this.out.toString());
		assertEquals(
				RdfTestHelpers
						.parseRdf("\n<http://www.wikidata.org/Q100> <http://www.wikidata.org/prop/direct/P31> <http://www.wikidata.org/Q10> ;\n"
								+ "<http://www.wikidata.org/prop/direct/P279> <http://www.wikidata.org/Q11> .\n"),
				model);
	}

	@Test
	public void testWriteInterPropertyLinks() throws RDFHandlerException,
			RDFParseException, IOException {
		PropertyDocument document = this.dataObjectFactory.getPropertyDocument(
				this.dataObjectFactory.getPropertyIdValue("P17",
						"http://www.wikidata.org/"),
						Collections.emptyList(), Collections.emptyList(),
						Collections.emptyList(), Collections.emptyList(),
						this.dataObjectFactory.getDatatypeIdValue(DatatypeIdValue.DT_ITEM), 0);
		this.rdfConverter.writeInterPropertyLinks(document);
		this.rdfWriter.finish();
		Model model = RdfTestHelpers.parseRdf(out.toString());

		assertEquals(RdfTestHelpers.parseRdf(RdfTestHelpers
				.getResourceFromFile("InterPropertyLinks.rdf")), model);
	}

	@After
	public void clear() throws RDFHandlerException, IOException {
		this.out.close();
	}

}
