/*
 * Copyright (c) 2005 - 2007 Aduna and Deutsches Forschungszentrum fuer Kuenstliche Intelligenz DFKI GmbH.
 * All rights reserved.
 * 
 * Licensed under the Open Software License version 3.0.
 */
package org.semanticdesktop.aperture;

import java.util.LinkedList;
import java.util.List;

import junit.framework.TestCase;

import org.ontoware.aifbcommons.collection.ClosableIterator;
import org.ontoware.rdf2go.RDF2Go;
import org.ontoware.rdf2go.exception.ModelException;
import org.ontoware.rdf2go.exception.ModelRuntimeException;
import org.ontoware.rdf2go.model.Model;
import org.ontoware.rdf2go.model.QueryResultTable;
import org.ontoware.rdf2go.model.QueryRow;
import org.ontoware.rdf2go.model.Statement;
import org.ontoware.rdf2go.model.node.DatatypeLiteral;
import org.ontoware.rdf2go.model.node.Literal;
import org.ontoware.rdf2go.model.node.Node;
import org.ontoware.rdf2go.model.node.Resource;
import org.ontoware.rdf2go.model.node.URI;
import org.ontoware.rdf2go.model.node.Variable;
import org.ontoware.rdf2go.model.node.impl.URIImpl;
import org.ontoware.rdf2go.vocabulary.RDF;
import org.semanticdesktop.aperture.rdf.RDFContainer;
import org.semanticdesktop.aperture.rdf.impl.RDFContainerImpl;
import org.semanticdesktop.aperture.vocabulary.GEO;
import org.semanticdesktop.aperture.vocabulary.NCAL;
import org.semanticdesktop.aperture.vocabulary.NCO;
import org.semanticdesktop.aperture.vocabulary.NEXIF;
import org.semanticdesktop.aperture.vocabulary.NFO;
import org.semanticdesktop.aperture.vocabulary.NID3;
import org.semanticdesktop.aperture.vocabulary.NIE;
import org.semanticdesktop.aperture.vocabulary.NMO;
import org.semanticdesktop.aperture.vocabulary.TAGGING;
import org.semanticdesktop.nepomuk.nrl.validator.ModelTester;
import org.semanticdesktop.nepomuk.nrl.validator.StandaloneValidator;
import org.semanticdesktop.nepomuk.nrl.validator.ValidationMessage;
import org.semanticdesktop.nepomuk.nrl.validator.ValidationReport;
import org.semanticdesktop.nepomuk.nrl.validator.exception.StandaloneValidatorException;
import org.semanticdesktop.nepomuk.nrl.validator.impl.StandaloneValidatorImpl;
import org.semanticdesktop.nepomuk.nrl.validator.testers.NRLClosedWorldModelTester;

/**
 * A common superclass for all unit tests of aperture.
 */
public class ApertureTestBase extends TestCase {

	protected static final String DOCS_PATH = "org/semanticdesktop/aperture/docs/";
    
    protected static StandaloneValidator validator;

    //////////////////////////////////////////////////////////////////////////////////////////////////////
    /////////////////////////////// CREATING MODELS AND RDFCONTAINERS ////////////////////////////////////
    //////////////////////////////////////////////////////////////////////////////////////////////////////
    
    /**
     * Creates an in-memory model
     * @return an in-memory model
     */
	protected Model createModel() {
		try {
            Model model = RDF2Go.getModelFactory().createModel(); 
            model.open();
			return model;
		}
		catch (ModelRuntimeException me) {
			return null;
		}
	}
	
	/**
	 * Creates an RDFContainer describing the given URI
	 * @param uri
	 * @return
	 */
	protected RDFContainer createRDFContainer(String uri) {
        return createRDFContainer(new URIImpl(uri,false));
    }
    
	/**
	 * Creates an RDFContainer describing the given URI
	 * @param uri
	 * @return
	 */
    protected RDFContainer createRDFContainer(URI uri) {
        Model newModel = createModel();
        return new RDFContainerImpl(newModel,uri);
    }
    
    protected void closeIterator(ClosableIterator<? extends Object> iterator) {
        if (iterator != null) {
            iterator.close();
        }
    }
    
    //////////////////////////////////////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////// VALIDATION //////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////////////////////////////////////
    
	/**
	 * Validates the content of the given container and prints the report to the standard output.
	 * @param container
	 */
    public void validate(RDFContainer container) {
        validate(container,true);
    }
    
    /**
     * Validates the content and lets the user decide whether to print the report to the standard output.
     * @param container the container
     * @param print true if the report is to be printed, false otherwise
     */
    public void validate(RDFContainer container, boolean print) {
        validate(container.getModel(), print, null, (ModelTester[])null);
    }

    /**
     * Validates the given model and prints the report to the standard output.
     * @param model
     */
    public void validate(Model model) {
        validate(model,true, null,(ModelTester[])null);
    }
    
    /**
     * Validates the given model and lets the use decide whether to print the report to the standard output.
     * @param model
     * @param print
     */
    public void validate(Model model, boolean print) {
        validate(model,print,null,(ModelTester[])null);
    }
    
    /**
     * Validates the given model and performs additional test with the given additional model testers.
     * 
     * @param model the model to validate
     * @param print true if the report is to be printed on the standard output, false otherwise
     * @param dataSourceUri the uri of the datasource, the validator will temporarily insert an dataSourceUri
     *            rdf:type nie:DataSource triple to satiate the validator. This case is quite common and we
     *            would not like it to be treated as an error.
     * @param additionalTesters testers that will be used in addition to the default NRLClosedWorldModelTester
     */
    public void validate(Model model, boolean print, URI dataSourceUri, ModelTester ... additionalTesters) {
        boolean removeFlag = false;
        Statement statement = null;
        if (dataSourceUri != null) {
            statement = model.createStatement(dataSourceUri, RDF.type, NIE.DataSource);
            if (model.contains(statement)) {
                removeFlag = false;
            } else {
                model.addStatement(statement);
                removeFlag = true;
            }            
        }
        
        if (additionalTesters != null && additionalTesters.length > 0 && additionalTesters[0] != null) {
            ModelTester [] testers = new ModelTester[additionalTesters.length + 1];
            testers[0] = new NRLClosedWorldModelTester();
            for (int i = 0; i < additionalTesters.length; i++) {
                testers[i+1] = additionalTesters[i];
            }
            validateWithTesters(model,print,testers);
        } else {
            validateWithTesters(model,print,new NRLClosedWorldModelTester());
        }
        
        if (removeFlag) {
            model.removeStatement(statement);
        }
    }

    private void validateWithTesters(Model model, boolean print, ModelTester... testers) {
        try {
            if (validator == null) {
                initializeValidator();
            }
            validator.setModelTesters(testers);
            ValidationReport report = validator.validate(model);
            if ( !report.isValid() || (report.getMessages().size() > 0 && print)) {
                printValidationReport(report);
            }
            if (!report.isValid()) {
                fail();
            } 
            
        } catch (StandaloneValidatorException sve) {
            sve.printStackTrace();
            if (sve.getCause() != null) {
                sve.getCause().printStackTrace();
            }
            fail();
        } catch (Exception e) {
            e.printStackTrace();
            fail();
        }
    }
    
    protected void initializeValidator() throws Exception {
        validator = new StandaloneValidatorImpl();        
        Model tempModel  = RDF2Go.getModelFactory().createModel();
        tempModel.open();
        
        NIE.getNIEOntology(tempModel);
        validator.addOntology(tempModel, getOntUriFromNs(NIE.NS_NIE));
        tempModel.removeAll();
        
        NCO.getNCOOntology(tempModel);
        validator.addOntology(tempModel, getOntUriFromNs(NCO.NS_NCO));
        tempModel.removeAll();
        
        NFO.getNFOOntology(tempModel);
        validator.addOntology(tempModel, getOntUriFromNs(NFO.NS_NFO));
        tempModel.removeAll();
        
        NMO.getNMOOntology(tempModel);
        validator.addOntology(tempModel, getOntUriFromNs(NMO.NS_NMO));
        tempModel.removeAll();
        
        NCAL.getNCALOntology(tempModel);
        validator.addOntology(tempModel, getOntUriFromNs(NCAL.NS_NCAL));
        tempModel.removeAll();
        
        NEXIF.getNEXIFOntology(tempModel);
        validator.addOntology(tempModel, getOntUriFromNs(NEXIF.NS_NEXIF));
        tempModel.removeAll();
        
        NID3.getNID3Ontology(tempModel);
        validator.addOntology(tempModel, getOntUriFromNs(NID3.NS_NID3));
        tempModel.removeAll();
        
        TAGGING.getTAGGINGOntology(tempModel);
        validator.addOntology(tempModel, getOntUriFromNs(TAGGING.NS_TAGGING));
        tempModel.removeAll();
        
        GEO.getGEOOntology(tempModel);
        validator.addOntology(tempModel, getOntUriFromNs(TAGGING.NS_TAGGING));
        tempModel.removeAll();
        
        tempModel.close();
    }
    
    private String getOntUriFromNs(URI uri) {
        return uri.toString().substring(0,uri.toString().length() - 1);
    }

    private void printValidationReport(ValidationReport report) {
        System.out.println("Validation report");
        List<ValidationMessage> messages = report.getMessages();
        int i = 1;
        for (ValidationMessage msg : messages) {
            System.out.print  ("" + i + ": ");
            System.out.println(msg.getMessageType().toString() + " ");
            System.out.println("   " + msg.getMessageTitle() + " ");
            System.out.println("   " + msg.getMessage() + " ");
            for (Statement stmt : msg.getStatements()) {
                try {
                    System.out.println("   {" + stmt.getSubject().toSPARQL() + ",");
                    System.out.println("    " + stmt.getPredicate().toSPARQL() + ",");
                    System.out.println("    " + stmt.getObject().toSPARQL() + "}");
                } catch (Exception x) {
                    // YES, blank nodes do not support toSparql...
                    System.out.println("   {" + stmt + "}");
                }
            }
            i++;
        }
    }

    //////////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////// ASSERTIONS ////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////////////////////////////////////
    
    protected Resource findSingleObjectResource(Model model, Resource subject, URI predicate) throws ModelException {
        List<Resource> list = findObjectResourceList(model, subject, predicate);
        assertEquals(1,list.size());
        return list.get(0);
    }

    protected List<Resource> findObjectResourceList(Model model, Resource subject, URI predicate) throws ModelException {
        LinkedList<Resource> result = new LinkedList<Resource>();
        ClosableIterator<? extends Statement> iterator = null;
        try {
            iterator = model.findStatements(subject, predicate, Variable.ANY);
            while (iterator.hasNext()) {
                Statement statement = iterator.next();
                Node value = statement.getObject();
                assertTrue(value instanceof Resource);
                result.add(value.asResource());
            }
            return result;
        } finally {
            closeIterator(iterator);
        }
    }

    /**
     * Asserts that the given container contains the given property, and that one of the values of that
     * property is a literal, whose label contains the given substring.
     * 
     * @param property the property to look for
     * @param substring the substring to look for
     * @param container the container to look in
     * @throws ModelException if something goes wrong
     */
    public void checkStatement(URI property, String substring, RDFContainer container) 
            throws ModelException {
        // setup some info
        Model model = container.getModel();
        boolean encounteredSubstring = false;

        // loop over all statements that have the specified property uri as predicate
        ClosableIterator<? extends Statement> statements = model.findStatements(
            container.getDescribedUri(), property, Variable.ANY);
        try {
            while (statements.hasNext()) {
                // check the property type
                Statement statement = statements.next();
                assertTrue(statement.getPredicate().equals(property));

                // see if it has a Literal containing the specified substring
                Node object = statement.getObject();
                if (object instanceof Literal) {
                    String value = ((Literal) object).getValue();
                    if (value.indexOf(substring) >= 0) {
                        encounteredSubstring = true;
                        break;
                    }
                }
            }
        }
        finally {
            statements.close();
        }

        // see if any of the found properties contains the specified substring
        if (!encounteredSubstring)
            fail("Expected substring '"+substring+
                "' in property "+property+" not found");
        assertTrue(encounteredSubstring);
    }

    /**
     * Asserts that the given container contains the given property AND that there is exactly
     * one value for this property AND that the value of this property is a Resource with an
     * rdf:type of nco:Contact, which has a nco:fullname property whose value is equal to the
     * given string.
     * @param property
     * @param fullname
     * @param container
     */
    public void checkSimpleContact(URI property, String fullname, RDFContainer container) {
        Node node = container.getNode(property);
        assertTrue(node instanceof Resource);
        Resource resource = (Resource)node;
        Model model = container.getModel();
        assertTrue(model.contains(resource, RDF.type, NCO.Contact));
        assertTrue(model.contains(resource, NCO.fullname, fullname));
    }
    
    /**
     * Asserts that the given container contains the given property AND that one of the values of this
     * property is a Resource with an rdf:type of nco:Contact, which has a nco:fullname property whose value
     * is equal to the given string. There may be more values of the given property in this container.
     * 
     * @param property
     * @param fullname
     * @param container
     */
    public void checkMultipleSimpleContacts(URI property, String fullname, RDFContainer container) {
        Model model = container.getModel();
        QueryResultTable table = model.sparqlSelect(
                "PREFIX nco: <" + NCO.NS_NCO + "> " +
                "SELECT ?contact " +
                "WHERE" +
                "  {" + container.getDescribedUri().toSPARQL() + " " + property.toSPARQL() + " ?contact ." +
                "    ?contact nco:fullname ?name . " +
                "    FILTER (regex(?name,\"" + fullname + "\"))" +
                "  }");
        ClosableIterator<QueryRow> iterator = null;
        try {
            iterator = table.iterator();
            assertTrue(iterator.hasNext());;
        } finally {
            iterator.close();
        }
    }

    /**
     * Asserts that the given container contains the given property, and that one of the values
     * of that property (there may be more) is an URI equal to the given value.
     * @param property
     * @param value
     * @param container
     * @throws ModelException
     */
    public void checkStatement(URI property, URI value, RDFContainer container) 
            throws ModelException {
        URI subject = container.getDescribedUri(); 
        checkStatement(subject, property, value, container);
    }

    /**
     * Asserts that the given container contains the given property, and that one of the values
     * of that property (there may be more) is a Node equal to the given node.
     * @param subject
     * @param property
     * @param value
     * @param container
     * @throws ModelException
     */
    public void checkStatement(URI subject, URI property, Node value, RDFContainer container) 
            throws ModelException {
        checkStatement(subject, property, value, container.getModel());
    }

    /**
     * Asserts that the given model contains a statement with the given subject, property and value.
     * @param subject
     * @param property
     * @param value
     * @param model
     * @throws ModelException
     */
    public void checkStatement(URI subject, URI property, Node value, Model model) throws ModelException {
        boolean encounteredValue = false;

        // loop over all statements that have the specified property uri as predicate
        ClosableIterator<? extends Statement> statements = model.findStatements(subject,property,Variable.ANY);
        try {
            while (statements.hasNext()) {
                // check the property type
                Statement statement = (Statement) statements.next();
                assertTrue(statement.getPredicate().equals(property));

                // see if it has a Literal containing the specified substring
                Node object = statement.getObject();
                if (object.equals(value)) {
                    encounteredValue = true;
                    break;
                }
            }
        }
        finally {
            statements.close();
        }

        // see if any of the found properties contains the specified substring
        assertTrue(encounteredValue);
    }
    
    protected void assertSingleValueProperty(Model model, Resource subject, URI predicate, String objectLabel)
            throws ModelException {
        assertSingleValueProperty(model, subject, predicate, model.createPlainLiteral(objectLabel));
    }

    /**
     * Asserts that a given triple in the model exists AND that it is the only one with the given subject and
     * predicate.
     * 
     * @param model
     * @param subject
     * @param predicate
     * @param object
     */
    protected void assertSingleValueProperty(Model model, Resource subject, URI predicate, Node object)
            throws ModelException {
        ClosableIterator<? extends Statement> iterator = null;
        try {
            iterator = model.findStatements(subject, predicate, Variable.ANY);
            assertTrue(iterator.hasNext());
            Statement statement = iterator.next();
            assertFalse(iterator.hasNext());
            assertEquals(statement.getObject().toString(), object.toString());
            iterator.close();
        }
        finally {
            closeIterator(iterator);
        }
    }

    /**
     * Asserts that a given triple in the model exists AND that it is the only one with the given subject and
     * predicate AND that the object is a literal with a given XSD datatype.
     * 
     * @param model
     * @param subject
     * @param predicate
     * @param objectLabel
     * @param xsdDatatype
     */
    protected void assertSingleValueProperty(Model model, Resource subject, URI predicate,
            String objectLabel, URI xsdDatatype) throws ModelException {
        ClosableIterator<? extends Statement> iterator = null;
        try {
            iterator = model.findStatements(subject, predicate, Variable.ANY);
            assertTrue(iterator.hasNext()); // statement exists
            Statement statement = iterator.next();
            assertFalse(iterator.hasNext()); // it is the only one
            iterator.close();
            Node object = statement.getObject();
            assertTrue(object instanceof DatatypeLiteral); // the object is a literal
            DatatypeLiteral literal = (DatatypeLiteral) object;
            assertEquals(literal.getValue(), objectLabel); // it's label is as given
            assertEquals(literal.getDatatype(), xsdDatatype); // and datatype as well
        }
        finally {
            closeIterator(iterator);
        }
    }

    protected void assertSingleValueURIProperty(Model model, Resource parentNode, URI predicate, String label)
            throws ModelException {
        Resource attachedUri = findSingleObjectResource(model, parentNode, predicate);
        assertTrue(attachedUri instanceof URI);
        assertEquals(attachedUri.toString(), label);
    }

    /**
     * Asserts that the given triple exists in the given model. It doesn't need to be the only one with the
     * given subject and predicate.
     * 
     * @param model
     * @param subject
     * @param predicate
     * @param value
     */
    protected void assertMultiValueProperty(Model model, Resource subject, URI predicate, String valueLabel)
            throws ModelException {
        assertMultiValueProperty(model, subject, predicate, model.createPlainLiteral(valueLabel));
    }

    /**
     * Asserts that the given triple exists in the given model. It doesn't need to be the only one with the
     * given subject and predicate.
     * 
     * @param model
     * @param subject
     * @param predicate
     * @param value
     */
    protected void assertMultiValueProperty(Model model, Resource subject, URI predicate, Node value)
            throws ModelException {
        ClosableIterator<? extends Statement> iterator = null;
        try {
            iterator = model.findStatements(subject, predicate, value);
            assertTrue(iterator.hasNext());
            iterator.close();
        }
        finally {
            closeIterator(iterator);
        }
    }

    protected void assertSparqlQuery(Model model, String query) {
        ClosableIterator<QueryRow> queryIterator = null;
        QueryResultTable table = model.sparqlSelect(query);
        try {
            queryIterator = table.iterator();
            assertTrue(queryIterator.hasNext());
            queryIterator.close();
        }
        finally {
            closeIterator(queryIterator);
        }
    }
    
    protected void assertNoResultSparqlQuery(Model model, String query) {
        ClosableIterator<QueryRow> queryIterator = null;
        QueryResultTable table = model.sparqlSelect(query);
        try {
            queryIterator = table.iterator();
            assertFalse(queryIterator.hasNext());
            queryIterator.close();
        }
        finally {
            closeIterator(queryIterator);
        }
    }
}
