from ..util.sparqlutils import SPARQLUtils

from qgis.core import Qgis
from qgis.core import QgsMessageLog

MESSAGE_CATEGORY = 'GraphUtils'

class GraphUtils:

    testQueries = {
        "geosparql": "PREFIX geof:<http://www.opengis.net/def/function/geosparql/> SELECT ?a ?b ?c WHERE { BIND( \"POINT(1 1)\"^^<http://www.opengis.net/ont/geosparql#wktLiteral> AS ?a) BIND( \"POINT(1 1)\"^^<http://www.opengis.net/ont/geosparql#wktLiteral> AS ?b) FILTER(geof:sfIntersects(?a,?b))}",
        "sparql11": "SELECT ?a ?b ?c WHERE { ?a ?b ?c . BIND( <http://www.opengis.net/ont/geosparql#> AS ?b) } LIMIT 1",
        "available": "SELECT ?a ?b ?c WHERE { ?a ?b ?c .} LIMIT 1",
        "hasRDFSLabel": "PREFIX rdfs:<http://www.w3.org/2000/01/rdf-schema#> ASK { ?a rdfs:label ?c . }",
        "hasSKOSPrefLabel": "PREFIX skos:<http://www.w3.org/2004/02/skos/core#> ASK { ?a skos:prefLabel ?c . }",
        "hasDCTermsTitleLabel": "PREFIX dc:<http://purl.org/dc/terms/> ASK { ?a dc:title ?c . }",
        "hasRDFType": "PREFIX rdf:<http:/www.w3.org/1999/02/22-rdf-syntax-ns#> ASK { ?a <http:/www.w3.org/1999/02/22-rdf-syntax-ns#type> ?c . }",
        "hasWKT": "PREFIX geosparql:<http://www.opengis.net/ont/geosparql#> ASK { ?a geosparql:asWKT ?c .}",
        "hasGeometry": "PREFIX geosparql:<http://www.opengis.net/ont/geosparql#> ASK { ?a geosparql:hasGeometry ?c .}",
        "hasJusoGeometry": "PREFIX juso:<http://rdfs.co/juso/> ASK { ?a juso:geometry ?c .}",
        "hasGML": "PREFIX geosparql:<http://www.opengis.net/ont/geosparql#> ASK { ?a geosparql:asGML ?c .}",
        "hasKML": "PREFIX geosparql:<http://www.opengis.net/ont/geosparql#> ASK { ?a geosparql:asKML ?c .}",
        "hasGeoJSON": "PREFIX geosparql:<http://www.opengis.net/ont/geosparql#> ASK { ?a geosparql:asGeoJSON ?c .}",
        "hasWgs84LatLon": "PREFIX geo:<http://www.w3.org/2003/01/geo/wgs84_pos#> ASK { ?a geo:lat ?c . ?a geo:long ?d . }",
        "hasWgs84Geometry": "PREFIX geo:<http://www.w3.org/2003/01/geo/wgs84_pos#> ASK { ?a geo:geometry ?c . }",
        "hasSchemaOrgGeoLatLon": "PREFIX schema:<https://schema.org/> ASK { ?a schema:geo ?c . ?c schema:latitude ?d . ?c schema:longitude ?e . }",
        "hasSchemaOrgGeoPolygon": "PREFIX schema:<https://schema.org/> ASK { ?a schema:geo ?c .  ?c schema:polygon ?d . }",
        "namespaceQuery": "select distinct ?ns where {  ?s ?p ?o . bind( replace( str(?s), \"(#|/)[^#/]*$\", \"$1\" ) as ?ns )} limit 10"
    }

    def __init__(self,testURL):
        self.feasibleConfiguration=False
        self.configuration={}
        self.testURL=testURL
        self.message=""

    def addDefaultConfigurationParameters(self,triplestorename,triplestoreurl,credentialUserName=None,credentialPassword=None,authmethod=None):
        self.configuration = {}
        self.configuration["name"] = triplestorename
        self.configuration["resource"]={}
        if isinstance(triplestoreurl,str):
            self.configuration["type"] = "sparqlendpoint"
            self.configuration["resource"]["type"]="endpoint"
            self.configuration["resource"]["url"] = triplestoreurl
        else:
            self.configuration["type"] = "file"
            self.configuration["resource"]["type"]="file"
            self.configuration["resource"]["instance"] = triplestoreurl
        self.configuration["geoconceptlimit"] = 500
        self.configuration["crs"] = 4326
        if credentialUserName!=None and credentialUserName!="" and credentialPassword!=None and credentialPassword!=None:
            self.configuration["auth"]={}
            self.configuration["auth"]["userCredential"] = credentialUserName
            self.configuration["auth"]["userPassword"] = credentialPassword
            self.configuration["auth"]["method"]=authmethod
        self.configuration["typeproperty"] = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"
        self.configuration["labelproperty"] = ["http://www.w3.org/2000/01/rdf-schema#label"]
        self.configuration["subclassproperty"] = "http://www.w3.org/2000/01/rdf-schema#subClassOf"
        self.configuration["whattoenrichquery"] = "SELECT DISTINCT (COUNT(distinct ?con) AS ?countcon) (COUNT(?rel) AS ?countrel) ?rel ?valtype\n WHERE { ?con %%typeproperty%% %%concept%% .\n ?con ?rel ?val .\n BIND( datatype(?val) AS ?valtype )\n } GROUP BY ?rel ?valtype\n ORDER BY DESC(?countrel)"
        self.configuration["staticconcepts"] = []
        self.configuration["mandatoryvariables"] = []
        self.configuration["active"] = True
        self.configuration["prefixes"] = {"owl": "http://www.w3.org/2002/07/owl#",
                                          "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
                                          "rdfs": "http://www.w3.org/2000/01/rdf-schema#",
                                          "geosparql": "http://www.opengis.net/ont/geosparql#",
                                          "geof": "http://www.opengis.net/def/function/geosparql/",
                                          "geor": "http://www.opengis.net/def/rule/geosparql/",
                                          "sf": "http://www.opengis.net/ont/sf#",
                                          "wgs84_pos": "http://www.w3.org/2003/01/geo/wgs84_pos#"}
        self.configuration["whattoenrichquery"] = "SELECT DISTINCT (COUNT(distinct ?con) AS ?countcon) (COUNT(?rel) AS ?countrel) ?rel ?valtype\n WHERE {\n ?con %%typeproperty%% %%concept%% .\n ?con ?rel ?val .\n BIND( datatype(?val) AS ?valtype )\n } GROUP BY ?rel ?valtype \n ORDER BY DESC(?countrel)"
        return self.configuration

    ## Creates a String representation of the capabilities of the triple store
    def createCapabilityMessage(self,capabilitylist):
        capabilitymessage="URL depicts a valid SPARQL Endpoint with the following capabilities: <ul>"
        for cap in capabilitylist:
            capabilitymessage+="<li>"+cap+"</li>"
        capabilitymessage+="</ul>Would you like to add this SPARQL endpoint?"
        return capabilitymessage

    def detectLiteralType(self,configuration,credentialUserName,credentialPassword, authmethod,capabilitylist):
        configuration["geotriplepattern"]=[]
        configuration["querytemplate"] = []
        gottype=False
        geomobjprop="?item ?rel ?item_geom . "
        if self.testTripleStoreConnection(configuration["resource"], self.testQueries["hasGeometry"],
                                          credentialUserName, credentialPassword, authmethod):
            configuration["geometryproperty"] = ["http://www.opengis.net/ont/geosparql#hasGeometry"]
            geomobjprop = "?item <http://www.opengis.net/ont/geosparql#hasGeometry> ?item_geom . "
            #configuration["geotriplepattern"].append(str(geomobjprop) + " ?item_geom ?georel ?geo . ")
        elif self.testTripleStoreConnection(configuration["resource"], self.testQueries["hasWgs84Geometry"],
                                            credentialUserName, credentialPassword, authmethod):
            configuration["geometryproperty"] = ["http://www.w3.org/2003/01/geo/wgs84_pos#geometry"]
            geomobjprop="?item <http://www.w3.org/2003/01/geo/wgs84_pos#geometry> ?item_geom . "
            #configuration["geotriplepattern"].append(str(geomobjprop) + " ?item_geom ?georel ?geo . ")
        if self.testTripleStoreConnection(configuration["resource"],self.testQueries["hasWKT"],credentialUserName,credentialPassword,authmethod):
            capabilitylist.append("WKT Literals")
            configuration["mandatoryvariables"] = ["item", "geo"]
            if "geometryproperty" not in configuration:
                configuration["geometryproperty"] = ["http://www.opengis.net/ont/geosparql#asWKT"]
            configuration["geotriplepattern"].append(str(geomobjprop) + " ?item_geom <http://www.opengis.net/ont/geosparql#asWKT> ?geo . ")
            configuration["geotriplepattern"].append(" ?item <http://www.opengis.net/ont/geosparql#asWKT> ?geo . ")
            gottype=True
        if self.testTripleStoreConnection(configuration["resource"], self.testQueries["hasGML"],
                                          credentialUserName, credentialPassword, authmethod):
            capabilitylist.append("GML Literals")
            configuration["mandatoryvariables"] = ["item", "geo"]
            configuration["geotriplepattern"].append(str(geomobjprop)+" ?item_geom <http://www.opengis.net/ont/geosparql#asGML> ?geo . ")
            configuration["geotriplepattern"].append(" ?item <http://www.opengis.net/ont/geosparql#asGML> ?geo . ")
            if "geometryproperty" not in configuration:
                configuration["geometryproperty"] = ["http://www.opengis.net/ont/geosparql#asGML"]
            gottype = True
        if self.testTripleStoreConnection(configuration["resource"], self.testQueries["hasKML"],
                                          credentialUserName, credentialPassword, authmethod):
            capabilitylist.append("KML Literals")
            configuration["mandatoryvariables"] = ["item", "geo"]
            configuration["geotriplepattern"].append(str(geomobjprop)+" ?item_geom <http://www.opengis.net/ont/geosparql#asKML> ?geo . ")
            configuration["geotriplepattern"].append(" ?item <http://www.opengis.net/ont/geosparql#asKML> ?geo . ")
            if "geometryproperty" not  in configuration:
                configuration["geometryproperty"] = ["http://www.opengis.net/ont/geosparql#asKML"]
            gottype = True
        if self.testTripleStoreConnection(configuration["resource"], self.testQueries["hasGeoJSON"],
                                          credentialUserName, credentialPassword, authmethod):
            capabilitylist.append("GeoJSON Literals")
            configuration["mandatoryvariables"] = ["item", "geo"]
            configuration["geotriplepattern"].append(str(geomobjprop)+" ?item_geom <http://www.opengis.net/ont/geosparql#asGeoJSON> ?geo . ")
            configuration["geotriplepattern"].append(" ?item <http://www.opengis.net/ont/geosparql#asGeoJSON> ?geo . ")
            if "geometryproperty" not  in configuration:
                configuration["geometryproperty"] = ["http://www.opengis.net/ont/geosparql#asGeoJSON"]
            gottype = True
        if self.testTripleStoreConnection(configuration["resource"], self.testQueries["hasWgs84LatLon"],
                                       credentialUserName, credentialPassword, authmethod):
            capabilitylist.append("WGS84 Lat/Lon")
            configuration["geometryproperty"] = ["http://www.w3.org/2003/01/geo/wgs84_pos#long",
                                                      "http://www.w3.org/2003/01/geo/wgs84_pos#lat"]
            configuration["mandatoryvariables"] = ["item", "lat","lon"]
            configuration["geotriplepattern"].append(" ?item <http://www.w3.org/2003/01/geo/wgs84_pos#lat> ?lat . ?item <http://www.w3.org/2003/01/geo/wgs84_pos#long> ?lon . ")
            gottype = True
        if self.testTripleStoreConnection(configuration["resource"], self.testQueries["hasSchemaOrgGeoLatLon"],
                                       credentialUserName, credentialPassword, authmethod):
            capabilitylist.append("Schema.org Lat/Lon")
            configuration["mandatoryvariables"] = ["item", "lat", "lon"]
            configuration["geometryproperty"] = ["https://schema.org/geo"]
            configuration["geotriplepattern"].append(" ?item <http://www.w3.org/2003/01/geo/wgs84_pos#lat> ?lat . ?item <http://www.w3.org/2003/01/geo/wgs84_pos#long> ?lon . ")
            gottype = True
        if self.testTripleStoreConnection(configuration["resource"], self.testQueries["hasSchemaOrgGeoPolygon"],
                                       credentialUserName, credentialPassword, authmethod):
            capabilitylist.append("Schema.org Geo Polygon")
            configuration["mandatoryvariables"] = ["item", "geo"]
            configuration["geometryproperty"] = ["https://schema.org/geo"]
            configuration["geotriplepattern"].append(" ?item_geom <https://schema.org/polygon> ?geo . ")
            gottype = True
        geoconceptquery="SELECT DISTINCT ?class WHERE {\n"
        for pat in configuration["geotriplepattern"]:
            geoconceptquery+="OPTIONAL { ?item %%typeproperty%% ?class . "+str(pat)+" }\n"
        geoconceptquery+="} ORDER BY ?class"
        configuration["geoconceptquery"] = geoconceptquery
        if "geotriplepattern" in self.configuration and len(self.configuration["geotriplepattern"])>0:
            self.configuration["querytemplate"].append(
                {"label": "10 Random Geometries",
                 "query": "SELECT ?item ?lat ?lon WHERE {\n ?item %%typeproperty%% <%%concept%%> .\n "+str(self.configuration["geotriplepattern"][0])+"\n } LIMIT 10"})
            self.configuration[
                "subclassquery"] = "SELECT DISTINCT ?subclass ?label WHERE { ?item %%typeproperty%% ?subclass . ?item ?rel ?item_geom . " + str(
                configuration["geotriplepattern"][
                    0]) + " ?item_geom <http://www.opengis.net/ont/geosparql#asWKT> ?wkt ."" OPTIONAL { ?subclass %%labelproperty%% ?label . } ?subclass rdfs:subClassOf %%concept%% . }"
        self.configuration["geocollectionquery"] = "SELECT DISTINCT ?colinstance ?label WHERE { ?colinstance %%typeproperty%% %%concept%% . OPTIONAL { ?colinstance %%labelproperty%% ?label . } }"
        return gottype

    ## Detects namespaces available in the given triple store in subject, predicate and object position
    def detectNamespaces(self, subpredobj,progress,triplestoreurl,credentialUserName=None,credentialPassword=None,authmethod=None):
        if subpredobj < 0 or subpredobj == None:
            query = "select distinct ?ns where { ?s ?p ?o . bind( replace( str(?s), \"(#|/)[^#/]*$\", \"$1\" ) as ?ns )} limit 10"
        elif subpredobj == 0:
            query = "select distinct ?ns where { ?s ?p ?o . bind( replace( str(?p), \"(#|/)[^#/]*$\", \"$1\" ) as ?ns )} limit 10"
            newtext = "\n".join(progress.labelText().split("\n")[0:-1])
            progress.setLabelText(newtext + "\nCurrent Task: Namespace detection (2/3)")
        else:
            query = "select distinct ?ns where { ?s ?p ?o . bind( replace( str(?o), \"(#|/)[^#/]*$\", \"$1\" ) as ?ns )} limit 10"
            newtext = "\n".join(progress.labelText().split("\n")[0:-1])
            progress.setLabelText(newtext + "\nCurrent Task: Namespace detection (3/3)")
        results=SPARQLUtils.executeQuery(triplestoreurl,query,{"auth":{"method":authmethod,"userCredential":credentialUserName,"userPassword":credentialPassword}})
        if results==False:
            return []
        reslist = []
        for nss in results["results"]["bindings"]:
            if "ns" in nss:
                reslist.append(nss["ns"]["value"])
        return reslist

    def detectTripleStoreType(self,configuration,credentialUserName,credentialPassword, authmethod,capabilitylist):
        if self.testTripleStoreConnection(configuration["resource"], self.testQueries["sparql11"], credentialUserName,
                                          credentialPassword, authmethod):
            configuration["resource"]["sparql11"] = True
            capabilitylist.append("SPARQL 1.1 Support")
        else:
            configuration["resource"]["sparql11"] = False
            capabilitylist.append("SPARQL 1.0 Support")
        if self.testTripleStoreConnection(configuration["resource"], self.testQueries["geosparql"], credentialUserName,
                                          credentialPassword, authmethod):
            configuration["resource"]["geosparql10"] = True
            configuration["featurecollectionclasses"] = ["http://www.opengis.net/ont/geosparql#FeatureCollection"]
            configuration["geometrycollectionclasses"] = ["http://www.opengis.net/ont/geosparql#GeometryCollection"]
            configuration["type"] = "geosparqlendpoint"
            configuration["bboxquery"] = {}
            configuration["bboxquery"]["type"] = "geosparql"
            configuration["bboxquery"][
                "query"] = "FILTER(<http://www.opengis.net/def/function/geosparql/sfIntersects>(?geo,\"POLYGON((%%x1%% %%y1%%, %%x1%% %%y2%%, %%x2%% %%y2%%, %%x2%% %%y1%%, %%x1%% %%y1%%))\"^^<http://www.opengis.net/ont/geosparql#wktLiteral>))"
            capabilitylist.append("GeoSPARQL 1.0 Support")
        else:
            configuration["resource"]["geosparql10"] = False
            capabilitylist.append("No GeoSPARQL Support")
        return capabilitylist

    def testTripleStoreConnection(self, triplestoreurl, query="SELECT ?a ?b ?c WHERE { ?a ?b ?c .} LIMIT 1",credentialUserName=None,credentialPassword=None,authmethod=None):
        #QgsMessageLog.logMessage("Execute query: "+str(query), MESSAGE_CATEGORY, Qgis.Info)
        results=SPARQLUtils.executeQuery(triplestoreurl,query,{"auth":{"method":authmethod,"userCredential":credentialUserName,"userPassword":credentialPassword}})
        #QgsMessageLog.logMessage("Query results: "+str(results), MESSAGE_CATEGORY, Qgis.Info)
        if results!=False:
            if self.testURL and not self.testConfiguration:
                self.message = "URL depicts a valid SPARQL Endpoint!"
            if "ASK" in query:
                #QgsMessageLog.logMessage("Result: "+str(results["boolean"]), MESSAGE_CATEGORY, Qgis.Info)
                return results["boolean"]
            self.feasibleConfiguration = True
            return True
        return results

    ## Detects default configurations of common geospatial triple stores.
    #  @param self The object pointer.
    def detectTripleStoreConfiguration(self,triplestorename,triplestoreurl,detectnamespaces,prefixstore,progress,credentialUserName=None,credentialPassword=None,authmethod=None):
        self.configuration=self.addDefaultConfigurationParameters(triplestorename,triplestoreurl,credentialUserName,credentialPassword,authmethod)
        capabilitylist=[]
        if self.testTripleStoreConnection(self.configuration["resource"],self.testQueries["available"],credentialUserName,credentialPassword,authmethod):
            capabilitylist=self.detectTripleStoreType(self.configuration,credentialUserName,credentialPassword,authmethod,capabilitylist)
            gottype=self.detectLiteralType(self.configuration,credentialUserName,credentialPassword,authmethod,capabilitylist)
            if not gottype:
                self.message = "SPARQL endpoint does not seem to include the following geometry relations:<ul><li>geo:asWKT</li><li>geo:asGeoJSON</li><li> geo:lat, geo:long</li></ul><br>A manual configuration is probably necessary to include this SPARQL endpoint if it contains geometries<br>Do you still want to add this SPARQL endpoint?"
            self.feasibleConfiguration = True
            res = set()
            if detectnamespaces:
                newtext = "\n".join(progress.labelText().split("\n")[0:-1])
                progress.setLabelText(newtext + "\nCurrent Task: Namespace detection (1/3)")
                res = set(self.detectNamespaces(-1,progress,self.configuration["resource"],credentialUserName,credentialPassword,authmethod) + self.detectNamespaces(0,progress,self.configuration["resource"],credentialUserName,credentialPassword,authmethod) + self.detectNamespaces(1,progress,self.configuration["resource"],credentialUserName,credentialPassword,authmethod))
            i = 0
            for ns in res:
                if ns != "http://" and ns.startswith("http://"):
                    if ns in prefixstore["reversed"]:
                        self.configuration["prefixes"][prefixstore["reversed"][ns]] = ns
                    else:
                        self.configuration["prefixes"]["ns" + str(i)] = ns
                        i = i + 1
            self.feasibleConfiguration = True
            QgsMessageLog.logMessage(str(self.configuration))
            if self.testTripleStoreConnection(self.configuration["resource"],self.testQueries["hasDCTermsTitleLabel"]):
                self.configuration["labelproperty"].append("http://purl.org/dc/terms/title")
            if self.testTripleStoreConnection(self.configuration["resource"],self.testQueries["hasSKOSPrefLabel"]):
                self.configuration["labelproperty"].append("http://www.w3.org/2004/02/skos/core#prefLabel")
            self.configuration["classfromlabelquery"] = "SELECT DISTINCT ?class ?label { ?class %%typeproperty%% <http://www.w3.org/2002/07/owl#Class> . \n"+SPARQLUtils.resolvePropertyToTriplePattern("%%labelproperty%%","?label","?class",self.configuration,"OPTIONAL","")+" FILTER(CONTAINS(?label,\"%%label%%\"))} LIMIT 100 "
            self.configuration[
                "propertyfromlabelquery"] = "SELECT DISTINCT ?class ?label { ?class %%typeproperty%% <http://www.w3.org/2002/07/owl#ObjectProperty> . \n"+SPARQLUtils.resolvePropertyToTriplePattern("%%labelproperty%%","?label","?class",self.configuration,"OPTIONAL","")+" FILTER(CONTAINS(?label,\"%%label%%\"))} LIMIT 100 "
            #QgsMessageLog.logMessage(str("SELECT DISTINCT ?acon ?rel WHERE { ?a a ?acon . ?a ?rel ?item. "+str(self.configuration["geotriplepattern"][0])+" }"))
            if "geotriplepattern" in self.configuration and len(self.configuration["geotriplepattern"]) > 0:
                results=SPARQLUtils.executeQuery(self.configuration["resource"],"SELECT DISTINCT ?acon ?rel WHERE { ?a <"+str(self.configuration["typeproperty"])+"> ?acon . ?a ?rel ?item. "+str(self.configuration["geotriplepattern"][0])+" }")
                if results!=False:
                    self.configuration["geoobjproperty"] = set()
                    self.configuration["geoclasses"] = {}
                    for result in results["results"]["bindings"]:
                        if "rel" in result \
                                and result["rel"]["value"] not in SPARQLUtils.georelationproperties\
                                and SPARQLUtils.namespaces["owl"] not in result["rel"]["value"]\
                                and SPARQLUtils.namespaces["rdfs"] not in result["rel"]["value"]\
                                and SPARQLUtils.namespaces["skos"] not in result["rel"]["value"]:
                            self.configuration["geoobjproperty"].add(result["rel"]["value"])
                            if "acon" in result:
                                if result["acon"]["value"] not in self.configuration["geoclasses"]:
                                    self.configuration["geoclasses"][result["acon"]["value"]]=set()
                                self.configuration["geoclasses"][result["acon"]["value"]].add(result["rel"]["value"])
                    for cls in self.configuration["geoclasses"]:
                        self.configuration["geoclasses"][cls]=list(self.configuration["geoclasses"][cls])
                    #QgsMessageLog.logMessage(str(self.configuration["geoobjproperty"]))
        else:
            self.message = "URL does not depict a valid SPARQL Endpoint!"
            self.feasibleConfiguration = False
            return False
        self.message=self.createCapabilityMessage(capabilitylist)
        return True