package it.unibz.inf.ontop.spec.mapping.parser.impl;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.inject.Inject;
import eu.optique.r2rml.api.binding.rdf4j.RDF4JR2RMLMappingManager;
import eu.optique.r2rml.api.model.*;
import eu.optique.r2rml.api.model.impl.InvalidR2RMLMappingException;
import it.unibz.inf.ontop.exception.MappingIOException;
import it.unibz.inf.ontop.injection.SpecificationFactory;
import it.unibz.inf.ontop.spec.mapping.pp.SQLPPMapping;
import it.unibz.inf.ontop.spec.mapping.pp.SQLPPTriplesMap;
import it.unibz.inf.ontop.utils.ImmutableCollectors;
import org.apache.commons.rdf.api.Graph;
import org.apache.commons.rdf.rdf4j.RDF4J;
import org.eclipse.rdf4j.model.Namespace;
import org.eclipse.rdf4j.model.impl.LinkedHashModel;
import org.eclipse.rdf4j.rio.*;
import it.unibz.inf.ontop.exception.InvalidMappingException;
import it.unibz.inf.ontop.injection.SQLPPMappingFactory;
import it.unibz.inf.ontop.spec.mapping.PrefixManager;
import it.unibz.inf.ontop.spec.mapping.parser.SQLMappingParser;
import org.eclipse.rdf4j.rio.helpers.StatementCollector;

import java.io.*;
import java.net.URL;
import java.util.*;

/**
 * High-level class that implements the MappingParser interface for R2RML.
 */
public class R2RMLMappingParser implements SQLMappingParser {

    private final SQLPPMappingFactory ppMappingFactory;
    private final SpecificationFactory specificationFactory;
    private final R2RMLToSQLPPTriplesMapConverter transformer;
    private final RDF4JR2RMLMappingManager manager;

    @Inject
    private R2RMLMappingParser(SQLPPMappingFactory ppMappingFactory, SpecificationFactory specificationFactory,
                               R2RMLToSQLPPTriplesMapConverter transformer) {
        this.ppMappingFactory = ppMappingFactory;
        this.specificationFactory = specificationFactory;
        this.transformer = transformer;
        this.manager = RDF4JR2RMLMappingManager.getInstance();
    }


    @Override
    public SQLPPMapping parse(File mappingFile) throws InvalidMappingException, MappingIOException {
        LinkedHashModel rdf4jGraph = new LinkedHashModel();
        RDFParser parser = Rio.createParser(RDFFormat.TURTLE);
        StatementCollector collector = new StatementCollector(rdf4jGraph);
        parser.setRDFHandler(collector);
        try (InputStream in = new FileInputStream(mappingFile)) {
            URL documentUrl = new URL("file://" + mappingFile);
            parser.parse(in, documentUrl.toString());
            return parse(new RDF4J().asGraph(rdf4jGraph), extractPrefixes(rdf4jGraph));
        }
        catch (IOException e) {
            throw new MappingIOException(e);
        }
        catch (RDFParseException | RDFHandlerException e) {
            throw new InvalidMappingException(e.getMessage());
        }
    }


    @Override
    public SQLPPMapping parse(Reader reader) throws InvalidMappingException, MappingIOException {
        LinkedHashModel rdf4jGraph = new LinkedHashModel();
        RDFParser parser = Rio.createParser(RDFFormat.TURTLE);
        StatementCollector collector = new StatementCollector(rdf4jGraph);
        parser.setRDFHandler(collector);
        try (Reader r = reader) {
            // TODO: get the base IRI in a proper way
            parser.parse(r, "http://example.org/baseIRI/");
            return parse(new RDF4J().asGraph(rdf4jGraph), extractPrefixes(rdf4jGraph));
        }
        catch (IOException e) {
            throw new MappingIOException(e);
        }
        catch (RDFParseException | RDFHandlerException e) {
            throw new InvalidMappingException(e.getMessage());
        }
    }

    private ImmutableMap<String, String> extractPrefixes(LinkedHashModel rdf4jGraph) {
        return rdf4jGraph.getNamespaces().stream()
                .collect(ImmutableCollectors.toMap(
                        namespace -> namespace.getPrefix() + ":",
                        Namespace::getName));
    }

    @Override
    public SQLPPMapping parse(Graph mappingGraph) throws InvalidMappingException {
        return parse(mappingGraph, ImmutableMap.of());
    }

    protected SQLPPMapping parse(Graph mappingGraph, ImmutableMap<String, String> prefixes) throws InvalidMappingException {
        try {
            Collection<TriplesMap> tripleMaps = manager.importMappings(mappingGraph);

            ImmutableList<SQLPPTriplesMap> sourceMappings = transformer.convert(tripleMaps);

            PrefixManager prefixManager = specificationFactory.createPrefixManager(prefixes);

            return ppMappingFactory.createSQLPreProcessedMapping(sourceMappings, prefixManager);
        }
        catch (InvalidR2RMLMappingException e) {
            throw new InvalidMappingException(e.getMessage());
        }
    }
}
