from datetime import datetime
import os 
import os.path
import re

from rdflib import Graph, URIRef, Literal, BNode, OWL
from rdflib.namespace import NamespaceManager


import docx.text
import docx.text.paragraph
import docx.enum.style
import docx.enum.text
import docx.styles
import docx.styles.style
import docx.enum
from docx import Document
from docx.shared import Inches , Pt, Cm

dir_local = 'ts/debug/'
dir_win = '/home/rawsthorne/Seafile/Travail/dechets/saref4envi/ts/debug/'

SAREF4ABCD = "SAREF4ENVI"
LANG="en"

g = Graph()
nm = NamespaceManager(g)
nm.bind("saref", "https://saref.etsi.org/core/")
nm.bind("s4auto", "https://saref.etsi.org/saref4auto/")
nm.bind("s4syst", "https://saref.etsi.org/saref4syst/")
nm.bind("s4envi", "https://saref.etsi.org/saref4envi/")

def delete_paragraph(paragraph):
    p = paragraph._element
    p.getparent().remove(p)
    p._p = p._element = None

document = Document("scripts/stub.docx")


code_title = document.styles.add_style("Consolas_title", docx.enum.style.WD_STYLE_TYPE.CHARACTER) #type: docx.styles.style.CharacterStyle
code_title.font.name = "Consolas"

code = document.styles.add_style("Consolas", docx.enum.style.WD_STYLE_TYPE.CHARACTER) #type: docx.styles.style.CharacterStyle
code.font.name = "Consolas"
code.font.size = Pt(9)

visited = set()

n= [4]

### add functions

def add_runs(s, p:docx.text.paragraph.Paragraph=None, style=code, header = None):
    _p = p or document.add_paragraph()
    if header:
        _p.add_run(header).font.bold = True
    if isinstance(s, list):
        s = ", ".join(s)
    if not isinstance(s, str):
        s = str(s)
    s = re.sub(r"((xsd|owl|geo|time|foaf|vcard|org|saref|s4[a-z]{4}):[a-zA-Z0-9_-]+)",r"`\1`", s)
    s = re.sub(r"``",r"`", s)
    for s1, _, s2 in re.findall(r"([^`]*)(`([^`]+)`)?", s):
        if s1:
            _p.add_run(s1)
        if s2:
            _p.add_run(s2, style=style)


def add_figures(title, figures):
    for figure in figures:
        if os.path.isfile(f"documentation/diagrams/{figure}"):
            p = document.add_paragraph()
            p.paragraph_format.alignment = docx.enum.text.WD_PARAGRAPH_ALIGNMENT.CENTER
            p.add_run().add_picture(f'documentation/diagrams/{figure}', width=Cm(17))
            # add_runs(title, document.add_paragraph(style="TF"))

def add_heading(title):
    n[-1]+=1
    p = document.add_heading(f'{".".join([str(x) for x in n])}\t', len(n))
    add_runs(title, p, code_title)

def start_subsection_if_needed(query=None):
    has_subsections = not query or g.query(query).askAnswer
    if has_subsections:
        n.append(-1)           
        add_heading("Definition")
    return has_subsections

def add_note_for_query(header, query):
    ts = [nm.qname(t) if isinstance(t, URIRef) else str(t) for t, in g.query(query)]
    if ts:
        add_runs(ts, header=header)
        
def add_note_for_query_2(header, query):
    ts = []
    for s,o in g.query(query):
        a = nm.qname(s)
        a += " to "
        if isinstance(o, URIRef):
            a += nm.qname(o)
        else:
            a += str(o)
        ts.append(a)
    if ts:
        add_runs(",".join(ts), header=header)
        
def add_comment(subject):
    for comment, in g.query(f"""SELECT ?comment WHERE {{ {subject.n3()} rdfs:comment ?comment FILTER( lang(?comment)="{LANG}") }}"""):
        add_runs(str(comment))

def add_note_punning(subject, fks, fois):
    if g.query(f"""ASK{{ {subject.n3()} a owl:Class . }}""").askAnswer:
        p = document.add_paragraph()
        p.add_run(nm.qname(subject), style=code).font.bold = True
        p.add_run(" belongs to the eponym class ").font.bold = True
        p.add_run(nm.qname(subject), style=code).font.bold = True
        p.add_run(". This class groups ").font.bold = True 
        p.add_run(nm.qname(subject), style=code).font.bold = True
        p.add_run(f", narrower {fks}, and {fois} of this kind.").font.bold = True
        
### print functions

def print_for_query(query, func, title=None):
    queryResults = list(g.query(query))
    if not queryResults:
        return
    # for queryResult in queryResults:
        # print("queryResult: ",queryResult)
    queryResults = list(filter(lambda x: x[0] not in visited, queryResults))
    if title and len(queryResults)>1:
        add_heading(title)
        n.append(0)
    for other, in queryResults:
        func(other)
    if title and len(queryResults)>1:
        n.pop()    

def print_organisation(subject):
    if subject in visited:
        return
    visited.add(subject)
    add_heading("Class " + nm.qname(subject))
    add_comment(subject)
   

def print_system(subject):
    if subject in visited:
        return
    visited.add(subject)
    add_heading("Class " + nm.qname(subject))
    add_comment(subject)

def print_connection(subject):
    if subject in visited:
        return
    visited.add(subject)
    add_heading("Class " + nm.qname(subject))
    add_comment(subject)
    
def print_connection_point(subject):
    if subject in visited:
        return
    visited.add(subject)
    add_heading("Class " + nm.qname(subject))
    add_comment(subject)

def print_class(subject):
    if subject in visited:
        return
    visited.add(subject)
    add_heading("Class " + nm.qname(subject))
    
    has_subsections = start_subsection_if_needed(f"""ASK {{ ?inst a {subject.n3()} ; rdfs:comment ?comment . }}""")
    add_comment(subject)
    print_for_query(f"""SELECT ?inst WHERE {{ ?inst a {subject.n3()} ; 
                    rdfs:comment ?comment . 
                    FILTER NOT EXISTS {{ ?inst skos:broader ?i }} }} ORDER BY ?inst""", print_inst)
    if has_subsections:
        n.pop()

def print_op_or_dp(subject):
    if subject in visited:
        return
    visited.add(subject)
    add_heading("Property " + nm.qname(subject))
    add_note_for_query("Is a: ", f"""SELECT ?type WHERE {{ {subject.n3()} rdf:type ?type }} ORDER BY ?type """)
    add_note_for_query("Is sub-property of: ", f"""SELECT ?super WHERE {{ {subject.n3()} rdfs:subPropertyOf ?super }} ORDER BY ?super """)
    
    # for domain with only one class:
    add_note_for_query("Domain: ", f"""SELECT ?domain WHERE {{ {subject.n3()} rdfs:domain ?domain . FILTER isIri(?domain)}} ORDER BY ?domain """)

    # for domain a unionOf multiple classes:
    add_note_for_query("Domain is union of: ", f"""SELECT ?domain WHERE {{ {subject.n3()} rdfs:domain ?unionOf . ?unionOf rdf:type owl:Class ; owl:unionOf/(rdf:rest*/rdf:first)* ?domain . FILTER isIri(?domain)}} ORDER BY ?domain """)

    # for range with only one class:
    # add_note_for_query("Range: ", f"""SELECT ?range WHERE {{ {subject.n3() } rdfs:range ?range }} ORDER BY ?range """)
    
    # for range a single class or a unionOf multiple classes:
    # Query to get the range or unionOf the range
    range_query = f"""SELECT ?range WHERE {{ {subject.n3()} rdfs:range ?range }} ORDER BY ?range """    
    range_results = g.query(range_query)
    ranges = []
    for result in range_results:
        range_node = result['range']     
        if isinstance(range_node, BNode):  # Check if it's a blank node indicating a unionOf
            # Extract classes specifically from the unionOf of this blank node
            union_query = f"""SELECT ?class WHERE {{ {subject.n3()} rdfs:range {range_node.n3()} . {range_node.n3()} rdf:type owl:Class ; owl:unionOf/(rdf:rest*/rdf:first)* ?class . FILTER isIri(?class) }} """
            union_classes = g.query(union_query)
            ranges.extend(nm.qname(cls['class']) for cls in union_classes if cls['class'])
        else:
            ranges.append(nm.qname(range_node))
    # Format the ranges and add the note
    range_output = ", ".join(ranges)
    if range_output and len(ranges)==1:
        add_runs([range_output], header="Range: ")
    elif range_output and len(ranges)>1:
        add_runs([range_output], header="Range is union of: ")
    
    # if the property is used to link kinds
    add_note_for_query_2("Links: ", f"""SELECT ?s ?o WHERE {{ ?s {subject.n3()} ?o }} ORDER BY ?s """)

    add_comment(subject)
        
def print_inst(subject):
    if subject in visited:
        return
    visited.add(subject)

    add_heading(nm.qname(subject))

    # has_subsections = start_subsection_if_needed(f"""ASK {{ ?inst skos:broader {subject.n3()} ;
    #                                 rdfs:comment ?comment . }}""")
    has_subsections = False

    add_note_for_query("Is instance of: ", f"""SELECT ?t WHERE {{
            {subject.n3()} rdf:type ?t .
            FILTER ( ?t != owl:NamedIndividual )
        }} ORDER BY ?t """)
    add_comment(subject)
    # if has_subsections:
    #     n.pop()
    print_for_query(f"""SELECT ?inst WHERE {{ ?inst skos:broader {subject.n3()} ; 
                    rdfs:comment ?comment . }} ORDER BY ?inst""", print_inst)

def print_property_category(subject):
    if subject in visited:
        return
    visited.add(subject)
    add_heading("Property class " + nm.qname(subject))
    has_subsections = start_subsection_if_needed(f"""ASK {{ ?inst a {subject.n3()} . }}""")
    add_comment(subject)
    print_for_query(f"""SELECT ?inst WHERE {{ ?inst a  {subject.n3()} . }} ORDER BY ?inst""", print_inst)
    if has_subsections:
        n.pop()
    
def print_property(subject):
    if subject in visited:
        return
    visited.add(subject)

    # this section
    add_heading("Property " + nm.qname(subject))
    has_subsections = start_subsection_if_needed(f"""ASK{{ {{ [] saref:isValueOfProperty {subject.n3()} }} UNION {{ {subject.n3()} s4auto:isComposedOf []}} }}""")
        
    add_note_for_query("In category: ", f"""SELECT ?t WHERE {{
            {subject.n3()} rdf:type ?t .
            FILTER ( ?t != saref:Property )                    
            FILTER ( ?t != owl:NamedIndividual )
        }} ORDER BY ?t """)
    add_note_for_query("Broader property: ", f"""SELECT ?broader WHERE {{
            {subject.n3()} skos:broader ?broader .                    
        }} ORDER BY ?broader """)
    add_note_for_query("Is property of: ", f"""SELECT DISTINCT ?fk WHERE {{
            {{ {subject.n3()} saref:isPropertyOf ?fk }} UNION {{ ?fk saref:hasProperty {subject.n3()} }}                    
        }} ORDER BY ?fk """)
    add_comment(subject)

    print_for_query(f"""SELECT ?pv WHERE {{
        ?pv a saref:PropertyValue ;
            saref:isValueOfProperty {subject.n3()}
        OPTIONAL {{ ?pv saref:hasValue ?value }} 
    }} ORDER BY ?value ?pv""", print_property_value)

    print_for_query(f"""SELECT ?property WHERE {{
        {subject.n3()} s4auto:isComposedOf ?property .                 
        ?property a saref:Property ;
            rdfs:comment ?comment .
    }} ORDER BY ?property""", print_property)
    
    if has_subsections:
        n.pop()
    
    # next sections
    
    print_for_query(f"""SELECT ?property WHERE {{
        ?property a saref:Property ;
            rdfs:comment ?comment ;
            skos:broader {subject.n3()}                    
    }} ORDER BY ?property""", print_property)

def print_property_value(subject):
    if subject in visited:
        return
    visited.add(subject)
    add_heading("Property value " + nm.qname(subject))
    add_note_for_query("Is value of property: ", f"""SELECT ?property WHERE {{
            {subject.n3()} saref:isValueOfProperty ?property
        }} ORDER BY ?property""")
    add_note_for_query("Value: ", f"""SELECT ?value WHERE {{
            {subject.n3()} saref:hasValue ?value
        }} ORDER BY ?value""")    
    add_note_for_query("Value: ", f"""SELECT ?unit WHERE {{
            {subject.n3()} saref:isMeasuredIn ?unit
        }} ORDER BY ?unit""")
    add_comment(subject)

def print_feature_kind(subject):
    if subject in visited:
        return
    visited.add(subject)
    add_heading("Feature kind " + nm.qname(subject))
    
    has_subsections = start_subsection_if_needed(f"""ASK{{ 
                          {{ ?v skos:broader {subject.n3()} }} 
                          UNION 
                          {{ {subject.n3()} saref:hasProperty ?p  FILTER NOT EXISTS {{{subject.n3()} skos:broader [ saref:hasProperty ?p ] }} }}
                          }}""")
    
    add_note_for_query("Broader feature kind: ", f"""SELECT ?broader WHERE {{
            {subject.n3()} skos:broader ?broader .                    
        }} ORDER BY ?broader """)
    
    add_comment(subject)
    add_note_punning(subject, "feature kinds", "features of interest")
    
    print_for_query(f"""SELECT ?p WHERE {{
        ?p rdfs:domain {subject.n3()} ;
           rdfs:comment ?comment .
        }} ORDER BY ?p""", print_op_or_dp)
    
    print_for_query(f"""SELECT ?p WHERE {{
        {{ {subject.n3()} saref:hasProperty ?p }} UNION {{ ?p saref:isPropertyOf {subject.n3()}   }}
        ?p a saref:Property ;
        rdfs:label ?label ;
        rdfs:comment ?comment
        FILTER NOT EXISTS {{{subject.n3()} skos:broader [ saref:hasProperty ?p ] }}   
    }} ORDER BY ?subject""", print_property)
    
    print_for_query(f"""SELECT ?subject WHERE {{
        ?subject a saref:FeatureKind ;
        rdfs:label ?label ;
        rdfs:comment ?comment ;
        skos:broader {subject.n3()}                    
    }} ORDER BY ?subject""", print_feature_kind)

    if has_subsections:
        n.pop()

def print_feature_kind_category(subject):
    if subject in visited:
        return
    visited.add(subject)
    add_heading("Category of Feature Kind " + nm.qname(subject))
    
    has_subsections = start_subsection_if_needed(f"""ASK{{ 
                          {{ ?other a {subject.n3()} }}
                          UNION
                          {{ ?other rdfs:domain {subject.n3()} }}
                          }}""")
    
    add_note_for_query("Sub-class of: ", f"""SELECT ?super WHERE {{
            {subject.n3()} rdfs:subClassOf ?super .    
            FILTER( isuri(?super) )                
        }} ORDER BY ?super """)
    
    add_comment(subject)
    
    print_for_query(f"""SELECT ?other WHERE {{
        ?other rdfs:domain {subject.n3()} ;
          rdfs:label ?label }}   
     ORDER BY ?other""", print_op_or_dp)
    
    print_for_query(f"""SELECT ?other WHERE {{
        ?other a {subject.n3()} ;
          rdfs:label ?label }}   
     ORDER BY ?other""", print_inst)
    
    if has_subsections:
        n.pop()

    print_for_query(f"""SELECT ?other WHERE {{
        ?other rdfs:subClassOf {subject.n3()} ;
          rdfs:label ?label }}   
     ORDER BY ?other""", print_feature_kind_category)
    
def print_feature_category(subject):
    if subject in visited:
        return
    visited.add(subject)
    add_heading("Class " + nm.qname(subject))
    
    has_subsections = start_subsection_if_needed(f"""ASK{{ 
                          ?p rdfs:domain {subject.n3()} 
                          }}""")
    
    add_note_for_query("Sub-class of: ", f"""SELECT ?super WHERE {{
            {subject.n3()} rdfs:subClassOf ?super .    
            FILTER( isuri(?super) )                
        }} ORDER BY ?super """)
    
    add_comment(subject)

    # print("Subject: ",subject.n3())
    
    # for single class in domain
    print_for_query(f"""SELECT ?other WHERE {{
        ?other rdfs:domain {subject.n3()} ;
          rdfs:label ?label }}   
     ORDER BY ?other""", print_op_or_dp)
    
    # for domain a unionOf multiple classes
    print_for_query(f"""SELECT ?other WHERE {{ ?other rdfs:domain ?domain . ?domain rdf:type owl:Class ; owl:unionOf/(rdf:rest*/rdf:first)* {subject.n3()} . FILTER isIri({subject.n3()})}} ORDER BY ?other """, print_op_or_dp)
    
    if has_subsections:
        n.pop()

    print_for_query(f"""SELECT ?other WHERE {{
        ?other rdfs:subClassOf {subject.n3()} ;
          rdfs:label ?label }}   
     ORDER BY ?other""", print_feature_category)
    

def print_device_kind(subject):
    if subject in visited:
        return
    visited.add(subject)
    add_heading("Device kind " + nm.qname(subject))
    
    add_note_for_query("Broader device kind: ", f"""SELECT ?broader WHERE {{
            {subject.n3()} skos:broader ?broader .                    
        }} ORDER BY ?broader """)

    add_comment(subject)
    add_note_punning(subject, "device kinds", "devices")
    
    print_for_query(f"""SELECT ?subject WHERE {{
        ?subject a saref:DeviceKind ;
        rdfs:label ?label ;
        rdfs:comment ?comment ;
        skos:broader {subject.n3()}                    
    }} ORDER BY ?subject""", print_device_kind)

    print_for_query(f"""SELECT ?subject WHERE {{
        ?subject a owl:Class ;
        rdfs:subClassOf {subject.n3()} ;
        rdfs:label ?label ;
        rdfs:comment ?comment           
    }} ORDER BY ?subject""", print_device_class)
    
def print_sensor_kind(subject):
    if subject in visited:
        return
    visited.add(subject)
    add_heading("Sensor kind " + nm.qname(subject))
    
    add_note_for_query("Broader sensor kind: ", f"""SELECT ?broader WHERE {{
            {subject.n3()} skos:broader ?broader .                    
        }} ORDER BY ?broader """)

    add_comment(subject)
    add_note_punning(subject, "sensor kinds", "sensors")
    
    print_for_query(f"""SELECT ?subject WHERE {{
        ?subject a saref:DeviceKind ;
        rdfs:label ?label ;
        rdfs:comment ?comment ;
        skos:broader {subject.n3()}                    
    }} ORDER BY ?subject""", print_sensor_kind)

    print_for_query(f"""SELECT ?subject WHERE {{
        ?subject a owl:Class ;
        rdfs:subClassOf {subject.n3()} ;
        rdfs:label ?label ;
        rdfs:comment ?comment           
    }} ORDER BY ?subject""", print_sensor)
    

def print_device_class(subject):
    if subject in visited:
        return
    visited.add(subject)
    add_heading("Category of Device " + nm.qname(subject))
    has_subsections = start_subsection_if_needed(f"""ASK{{ 
                          {{ ?p rdfs:domain {subject.n3()} }} 
                          UNION 
                          {{ ?other rdfs:subClassOf {subject.n3()} }}
                          }}""")
    
    add_note_for_query("Sub-class of: ", f"""SELECT ?super WHERE {{
            {subject.n3()} rdfs:subClassOf ?super .    
            FILTER( isuri(?super) )                
        }} ORDER BY ?super """)
    
    add_comment(subject)
    
    print_for_query(f"""SELECT ?other WHERE {{
        ?other rdfs:domain {subject.n3()} ;
          rdfs:label ?label }}   
     ORDER BY ?other""", print_op_or_dp)
    
    print_for_query(f"""SELECT ?other WHERE {{
        ?other rdfs:subClassOf {subject.n3()} ;
          rdfs:label ?label }}   
     ORDER BY ?other""", print_device_class)
    
    if has_subsections:
        n.pop()

def print_task_category(subject):
    if subject in visited:
        return
    visited.add(subject)
    add_heading("Category of Task " + nm.qname(subject))
    
    has_subsections = start_subsection_if_needed(f"""ASK{{ 
                          {{ ?other a {subject.n3()} }}
                          UNION
                          {{ ?other rdfs:domain {subject.n3()}}}
                          }}""")
    
    add_note_for_query("Sub-class of: ", f"""SELECT ?super WHERE {{
            {subject.n3()} rdfs:subClassOf ?super .    
            FILTER( isuri(?super) )                
        }} ORDER BY ?super """)
    
    add_comment(subject)
    
    print_for_query(f"""SELECT ?other WHERE {{
        ?other rdfs:domain {subject.n3()} ;
          rdfs:label ?label }}   
     ORDER BY ?other""", print_op_or_dp)
    
    print_for_query(f"""SELECT ?other WHERE {{
        ?other a {subject.n3()} ;
          rdfs:label ?label }}   
     ORDER BY ?other""", print_inst)
    
    if has_subsections:
        n.pop()

    print_for_query(f"""SELECT ?other WHERE {{
        ?other rdfs:subClassOf {subject.n3()} ;
          rdfs:label ?label }}   
     ORDER BY ?other""", print_task_category)
    
    
def print_function_category(subject):
    if subject in visited:
        return
    visited.add(subject)
    add_heading("Category of Function " + nm.qname(subject))
    
    has_subsections = start_subsection_if_needed(f"""ASK{{ 
                          {{ ?other a {subject.n3()} }}
                          UNION
                          {{ ?other rdfs:domain {subject.n3()}  }}
                          }}""")
    
    add_note_for_query("Sub-class of: ", f"""SELECT ?super WHERE {{
            {subject.n3()} rdfs:subClassOf ?super .    
            FILTER( isuri(?super) )                
        }} ORDER BY ?super """)
    
    add_comment(subject)
    
    print_for_query(f"""SELECT ?other WHERE {{
        ?other rdfs:domain {subject.n3()} ;
          rdfs:label ?label }}   
     ORDER BY ?other""", print_op_or_dp)
    
    print_for_query(f"""SELECT ?other WHERE {{
        ?other a {subject.n3()} ;
          rdfs:label ?label }}   
     ORDER BY ?other""", print_inst)
    
    if has_subsections:
        n.pop()

    print_for_query(f"""SELECT ?other WHERE {{
        ?other rdfs:subClassOf {subject.n3()} ;
          rdfs:label ?label }}   
     ORDER BY ?other""", print_function_category)
    
def print_sensor(subject):
    if subject in visited:
        return
    visited.add(subject)
    add_heading("Sensor category " + nm.qname(subject))
    add_comment(subject)

def print_procedure_execution(subject):
    if subject in visited:
        return
    visited.add(subject)
    add_heading("Procedure Execution " + nm.qname(subject))
    add_comment(subject)
    
    print_for_query(f"""SELECT ?class WHERE {{
        {subject.n3()} rdfs:subClassOf [ 
            owl:onProperty saref:hasResult ;
            owl:allValuesFrom ?class ] .
        ?class rdfs:comment ?comment }}""", print_class)

def print_observation(subject):
    if subject in visited:
        return
    visited.add(subject)

    add_heading(nm.qname(subject))
    has_subsections = start_subsection_if_needed()
    add_comment(subject)
    
    for prop, clazz in g.query(f"""SELECT DISTINCT ?prop ?clazz WHERE {{ {subject.n3()} rdfs:subClassOf [
            owl:onProperty ?prop ;
            (owl:someValuesFrom|owl:allValuesFrom) ?clazz]}} ORDER BY DESC(?prop)"""):
        print_op_or_dp(prop)
        print_class(clazz)

    if has_subsections:
        n.pop()

def print_has_identifier(subject):
    if subject in visited:
        return
    visited.add(subject)
    add_heading("Identifier " + nm.qname(subject))
    add_comment(subject)

def get_chunks(source):
    first_section_pos = re.search(r"^#+\*", source, flags=re.MULTILINE).start()
    prefixes = source[0:first_section_pos]

    chunks = list()
    for head, title, fragment in re.findall(r"\n(#+)\*([^\n]*?)\n(.*?)(?=\n#+\*|$)", source, re.DOTALL):
        figures = list([ fig.strip() for fig in re.findall(r"^#- *Figure:(.*)$", fragment, re.MULTILINE) ])
        examples = list([ fig.strip() for fig in re.findall(r"^#- *Example:(.*)$", fragment, re.MULTILINE) ])
        chunks.append({"level": len(head), 
                       "title": title.strip(), 
                       "graph": Graph(namespace_manager=nm).parse(data=prefixes+fragment), 
                       "figures": figures, 
                       "examples": examples})
        # print("."*len(head) + title, figures, fragment)
    return chunks
     
from pathlib import Path
source = Path(f'ontology/{SAREF4ABCD.lower()}.ttl').read_text()
chunks = get_chunks(source)

all_graph = Graph(namespace_manager=nm).parse(data=source)

level = 1
for chunk in chunks:
    g = chunk["graph"]
    if chunk["level"]>level:
        n.append(0)
        level = chunk["level"]
    elif chunk["level"] < level:
        n.pop()
        level = chunk["level"]

    add_heading(chunk["title"])
    print(f"{'.'.join([str(x) for x in n])} {chunk['title']}")
    n.append(-1)

    add_heading("Overview")
    document.add_paragraph("TODO")

    add_figures(chunk["title"], chunk["figures"])

    add_figures(chunk["title"], chunk["examples"])
    

# sous-classes de feaeture of interest
# sous-classes de feature kind
# instances de feature kind si il en reste
# sous-classes de device
# sous-classes de task
# sous-classes de function
# properties



    print_for_query("""SELECT ?subject WHERE {
            ?subject rdfs:subClassOf <http://www.w3.org/ns/org#Organisation> ;
            rdfs:label ?label .
        } ORDER BY ?subject""", print_organisation, "Sub-classes of org:Organisation")
    
    print_for_query("""SELECT ?subject WHERE {
            ?subject rdfs:subClassOf s4syst:System ;
            rdfs:label ?label .
        } ORDER BY ?subject""", print_system, "Sub-classes of s4syst:System")
    
    print_for_query("""SELECT ?subject WHERE {
            ?subject rdfs:subClassOf s4syst:Connection ;
            rdfs:label ?label .
        } ORDER BY ?subject""", print_connection, "Sub-classes of s4syst:Connection")
    
    print_for_query("""SELECT ?subject WHERE {
            ?subject rdfs:subClassOf s4syst:ConnectionPoint ;
            rdfs:label ?label .
        } ORDER BY ?subject""", print_connection_point, "Sub-classes of s4syst:ConnectionPoint")
    

    print_for_query("""SELECT ?subject WHERE {
            ?subject rdfs:subClassOf saref:FeatureOfInterest ;
            rdfs:label ?label .
            FILTER NOT EXISTS { ?subject rdfs:subClassOf ?other . ?other rdfs:label ?l }
        } ORDER BY ?subject""", print_feature_category, "Sub-classes of saref:FeatureOfInterest")
    
    
    print_for_query("""SELECT ?subject WHERE {
            ?subject rdfs:subClassOf saref:FeatureKind ;
            rdfs:label ?label .
            FILTER NOT EXISTS { ?subject rdfs:subClassOf ?other . ?other rdfs:label ?l }
        } ORDER BY ?subject""", print_feature_kind_category, "Categories of saref:FeatureKind")
    

    print_for_query("""SELECT ?subject WHERE {
            ?subject a saref:FeatureKind ;
            rdfs:label ?label .
            FILTER NOT EXISTS { ?subject skos:broader ?p . ?p rdfs:label ?l }
        } ORDER BY ?subject""", print_feature_kind, "Instances of saref:FeatureKind")
    
    print_for_query("""SELECT ?subject WHERE {
            ?subject a saref:DeviceKind ;
            rdfs:label ?label .
            FILTER NOT EXISTS { ?subject skos:broader ?p . ?p rdfs:label ?l }
        } ORDER BY ?subject""", print_device_kind, "Instances of saref:DeviceKind")
    
    print_for_query("""SELECT ?subject WHERE {
            ?subject a saref:DeviceKind ;
            rdfs:label ?label .
            FILTER NOT EXISTS { ?subject skos:broader ?p . ?p rdfs:label ?l }
        } ORDER BY ?subject""", print_sensor_kind, "Instances of saref:DeviceKind")
    
    print_for_query("""SELECT ?subject WHERE {
            ?subject rdfs:subClassOf saref:Device ;
            rdfs:label ?label .
            FILTER NOT EXISTS { ?subject rdfs:subClassOf ?other . ?other rdfs:label ?l }
        } ORDER BY ?subject""", print_feature_category, "Sub-classes of saref:Device")
    
    print_for_query("""SELECT ?subject WHERE {
            ?subject rdfs:subClassOf saref:Task ;
            rdfs:label ?label .
            FILTER NOT EXISTS { ?subject rdfs:subClassOf ?other . ?other rdfs:label ?l }
        } ORDER BY ?subject""", print_task_category, "Sub-classes of saref:Task")
    
    print_for_query("""SELECT ?subject WHERE {
            ?subject rdfs:subClassOf saref:Function ;
            rdfs:label ?label .
            FILTER NOT EXISTS { ?subject rdfs:subClassOf ?other . ?other rdfs:label ?l }
        } ORDER BY ?subject""", print_function_category, "Sub-classes of saref:Function")

    print_for_query("""SELECT ?subject WHERE {
            ?subject rdfs:subClassOf saref:Property ;
            rdfs:label ?label .
        } ORDER BY ?subject""", print_property_category, "Categories of saref:Property")

    print_for_query("""SELECT ?subject WHERE {
            ?subject a saref:Property ;
            rdfs:label ?label .
            FILTER NOT EXISTS { ?p s4auto:isComposedOf ?subject ; rdfs:label ?l }
            FILTER NOT EXISTS { ?fk saref:hasProperty ?subject ; rdfs:label ?l }
            FILTER NOT EXISTS { ?subject skos:broader ?p . ?p rdfs:label ?l }
        } ORDER BY ?subject""", print_property)#, "Instances of saref:Property")
        
    print_for_query("""SELECT ?subject WHERE {
            ?subject rdfs:subPropertyOf saref:hasIdentifier ;
            rdfs:label ?label .
        } ORDER BY ?subject""", print_has_identifier)
    
    print_for_query("""SELECT ?subject WHERE {
            ?subject rdfs:subClassOf saref:ProcedureExecution ;
            rdfs:label ?label .
        } ORDER BY ?subject""", print_procedure_execution)#, "Categories of Procedure Executions")
    
    print_for_query("""SELECT ?subject WHERE {
            ?subject rdfs:subClassOf saref:Observation ;
            rdfs:label ?label .
        } ORDER BY ?subject""", print_observation)#, "Categories of Observations")
    
    print_for_query("""SELECT ?subject WHERE {
            ?subject rdfs:subClassOf saref:Sensor ;
            rdfs:label ?label .
        } ORDER BY ?subject""", print_sensor)#, "Categories of Sensors")
    
    print_for_query(f"""SELECT ?subject WHERE {{
        ?subject a owl:ObjectProperty  ;
          rdfs:label ?label }}   
     ORDER BY ?other""", print_op_or_dp)
    
    print_for_query(f"""SELECT ?subject WHERE {{
        ?subject a owl:DatatypeProperty  ;
          rdfs:label ?label }}   
     ORDER BY ?other""", print_op_or_dp)
    
    print_for_query(f"""SELECT ?subject WHERE {{
        ?subject a owl:Class  ;
          rdfs:label ?label }}   
     ORDER BY ?other""", print_class)
    
    n.pop()


    # document.add_heading(f'{n_sec}.{n_ssec}.{n_sssec}\tSubclasses of ', 3).add_run('saref:Property', style=code)
    
    # document.add_paragraph(f"""{SAREF4ABCD} defines sub-classes of """).add_run('saref:Property', style=code).add_run(".")

    # table = document.add_table(rows=1, cols=2, style="Normal Table")
    # hdr_cells = table.rows[0].cells
    # p = hdr_cells[0].paragraphs[0]
    # p.add_run("Property Class").bold = True
    # p.alignment = docx.enum.text.WD_ALIGN_PARAGRAPH.CENTER
    # p = hdr_cells[1].paragraphs[0]
    # p.add_run('Definition').bold = True
    # p.alignment = docx.enum.text.WD_ALIGN_PARAGRAPH.CENTER
    # for: 
    #     row_cells = table.add_row().cells
    #     p = row_cells[0].paragraphs[0]
    #     p.add_run(nm.qname(subject), style=code)
    #     insert_runs(row_cells[1].paragraphs[0], str(comment))



# document.add_page_break()

file = f'demo_{datetime.now().isoformat()}.docx'.replace(":","-")
document.save(f"{dir_local}{file}")
# os.system(f'cmd.exe /C start "{dir_win}{file}"')

for subject, in all_graph.query("""SELECT ?subject WHERE {
            ?subject rdfs:label ?label .
        } ORDER BY ?subject"""):
    if subject not in visited:
        print("ignored: " + str(subject))

