#!/usr/bin/env python
# coding: utf8
# from gluon import *  #temporary, until we're ready to integrate
import requests
from lxml import etree

#  This module is used to load owl format ontologies.
#  There should probably be a class for returned ontology information,
#  but a dict might suffice


RDF_PREFIX = "{http://www.w3.org/1999/02/22-rdf-syntax-ns#}"
RDFS_PREFIX = "{http://www.w3.org/2000/01/rdf-schema#}"
OWL_PREFIX = "{http://www.w3.org/2002/07/owl#}"
OBO_PURL_PREFIX = "{http://purl.obolibrary.org/obo/}"

OWL_SUFFIX = ".owl"

RDF_RESOURCE = RDF_PREFIX + 'resource'
RDF_ABOUT = RDF_PREFIX + 'about'

RDFS_LABEL = RDFS_PREFIX + 'label'

OWL_CLASS = OWL_PREFIX + 'Class'
OWL_EQUIVALENTCLASS = OWL_PREFIX + 'equivalentClass'
OWL_RESTRICTION = OWL_PREFIX + 'Restriction'
OWL_SUBCLASS_OF = RDFS_PREFIX + 'subClassOf'
OWL_OBJECT_PROPERTY = OWL_PREFIX + 'ObjectProperty'
IAO_ANNOTATION = OBO_PURL_PREFIX + 'IAO_0000115'
BFO_PART_OF = OBO_PURL_PREFIX + 'BFO_0000050'
BFO_HAS_PARTICIPANT = OBO_PURL_PREFIX + 'BFO_0000057'
RO_PART_OF = OBO_PURL_PREFIX + 'RO_0000050'
RO_HAS_PARTICIPANT = OBO_PURL_PREFIX + 'RO_0000057'


class ClassTarget(object):
    """
    receives notifications from the lxml 'etree' target (SAX-like) parser
    it currently ignores pretty much everything except OWL classes, and
    captures 'rdf:about', 'rdfs:label', IAO class comments (IAO:0000115),
    and tries not to stumble with owl restrictions and equivalent classes.
    The parser is pretty dependent on the owl rendering generated from the
    OWLAPI, which covers most or all of the OBO foundry ontologies, as well
    as the stuff generated by the owlbuilder tool.
    """
    def __init__(self):
        self.text = []
        self.classes = []
        self.object_properties = []
        self.containerclass = None
        self.containerproperty = None

    def start(self, tag, attrib):
        self.is_class = False
        self.is_object_property = False
        self.is_label = False
        self.has_parent = False
        self.is_equivalent_class = False
        self.is_restriction = False
        self.is_comment = False
        if tag == OWL_CLASS:
            self.is_class = True
            if attrib:
                if RDF_ABOUT in attrib:
                    self.containerclass = {'about': attrib[RDF_ABOUT]}
        if tag == OWL_OBJECT_PROPERTY:
            self.is_object_property = True
            if attrib:
                if RDF_ABOUT in attrib:
                    self.containerproperty = {'about': attrib[RDF_ABOUT]}
        elif tag == OWL_EQUIVALENTCLASS:
            self.is_equivalent_class = True
        elif tag == OWL_RESTRICTION:
            self.is_restriction = True
        elif self.containerclass:
            if tag == RDFS_LABEL:
                self.is_label = True
            elif tag == OWL_SUBCLASS_OF:
                if RDF_RESOURCE in attrib:
                    self.containerclass['parent'] = attrib[RDF_RESOURCE]
                    self.has_parent = True
            elif tag == IAO_ANNOTATION:
                self.is_comment = True
        elif self.containerproperty:
            if tag == RDFS_LABEL:
                self.is_label = True

    def end(self, tag):
        if tag == OWL_CLASS:
            if not self.is_equivalent_class:
                self.classes.append(self.containerclass)
                self.containerclass = None
                self.is_class = False
        elif tag == OWL_OBJECT_PROPERTY:
            self.object_properties.append(self.containerproperty)
            self.containerproperty = None
            self.is_object_property = False
        elif tag == OWL_EQUIVALENTCLASS:
            self.is_equivalent_class = False
        elif tag == OWL_RESTRICTION:
            self.is_restriction = False
        self.is_label = False
        self.has_parent = False
        self.is_comment = False
        pass

    def data(self, data):
        if self.containerclass or self.containerproperty:
            if self.is_label:
                self.mergedata('label', data)
            elif self.is_comment:
                self.mergedata('comment', data)

    def mergedata(self, tag, data):
        if self.containerclass:
            if tag in self.containerclass:
                self.containerclass[tag] = self.containerclass[tag] + data
            else:
                self.containerclass[tag] = data
        elif self.containerproperty:
            if tag in self.containerproperty:
                self.containerproperty[tag] = self.containerproperty[tag] + data
            else:
                self.containerproperty[tag] = data

    def close(self):
        return (self.classes, self.object_properties)

ARACHNID_NODE = u'http://purl.obolibrary.org/obo/NCBITaxon_6854'


def update_ontology(ont, type_name, config, app_name):
    """
    builds term list from the specified ontology source
    ont - row from ontology_source table
    type_name - controls processing - if NCBI taxonomy, this will
    only include terms subsumed by a particular node (e.g., all arachnids)
    filter terms based on labels (e.g., remove samples w/o possible behavior)
    """
    source_url = ont.source_url
    ontology_cache = config.get('ontology', 'cache')
    taxonomy_root = config.get('ontology', 'taxonomy_root')
    terms = []
    object_properties = []
    if type_name == 'NCBI taxonomy':  # check symbolically
        (terms, object_properties) = load_ontology(source_url,
                                                   build_ontology_tree,
                                                   config,
                                                   app_name)
    elif type_name == 'OWL ontology':
        (terms, object_properties) = load_ontology(source_url, 
                                                   simple_builder, 
                                                   config, 
                                                   app_name)
    else:
        print 'unknown type name'
    return (terms, object_properties)


def load_ontology(ont_url, processor, config, app_name):
    """
    opens the url, parses the stream, then postprocesses
    (e.g., taxonomy filtering)
    ont_url - location of ontology text
    processor - function to pass over the list of terms returned by the parser
    config - holds configuration object (to find cache and root if needed
    app_name - used to build the specification directory for the cache
    """
    local_file = cache_ontology(ont_url, config, app_name)
    return load_locally(local_file, processor, config)


def cache_ontology(ont_url, config, app_name):
    """
    """
    cache = ("applications/%s/" % app_name) + config.get('ontology', 'cache')
    print "cache is %s" % cache
    local_file = open(copy_to_cache(ont_url, cache))
    return local_file


def load_locally(local_file, processor, config):
    parser = etree.XMLParser(target=ClassTarget())
    (classes, object_properties) = etree.parse(local_file, parser)
    taxonomy_root = config.get('ontology', 'taxonomy_root')
    print "root is %s" % taxonomy_root
    return (processor(classes, taxonomy_root), object_properties)


def copy_to_cache(url, cache):
    from urlparse import urlparse
    local_filename = cache + '/' + urlparse(url).path.split('/')[-1]
    print "local filename is %s" % local_filename
    r = requests.get(url, stream=True)
    with open(local_filename, 'wb') as f:
        for chunk in r.iter_content(chunk_size=1024):
            if chunk:  # filter out keep-alive new chunks
                f.write(chunk)
                f.flush()
    return local_filename


def pplist(terms):
    """ debugging parser output"""
    import pprint
    pp = pprint.PrettyPrinter(indent=2)
    for term in terms:
        pp.pprint(term)


def process_tree(ont_tree):
    """debugging postprocessor output"""
    for child in ont_tree.getroot():
        print child.tag
    return ont_tree

FILTERLABELELEMENTS = ["sp. BOLD:",
                       "JXZ-2013",
                       "PS-2009",
                       "WOCS-2009",
                       "LB-2013",
                       "MCZDNAD",
                       "DNA10"
                       ]


def build_ontology_tree(terms, root=None, label_filter=None):
    """
    postprocess tree returned from parsing taxonomic ontology
    terms - list of terms from parser
    root -  root of clade to save - everything else ignored
    Note: this currently returns dictionary of uri:term, rather than
    a simple list of terms.  Ought to fix to match the list of terms that
    comes in.
    """
    import pprint
    pp = pprint.PrettyPrinter(indent=2)
    roots = []
    tree_dict = dict()
    parent_dict = dict()
    for term in terms:
        if term_filter(term, label_filter):
            if 'about' in term:
                tree_dict[term['about']] = term
            if 'parent' in term:
                tp = term['parent']
                if tp in parent_dict and parent_dict[tp] is not None:
                    parent_dict[tp].append(term)
                else:
                    parent_dict[tp] = [term]
            else:
                roots.append(term)
    final_list = []
    children = [tree_dict[root]]
    while (children):
        child = children.pop(0)
        if 'label' in child:
            clabel = child['label']
            if not clabel.startswith('unclassified'):
                if 'about' in child:
                    final_list.append(tree_dict[child['about']])
                if child['about'] in parent_dict:
                    newchildren = parent_dict[child['about']]
                    children.extend(newchildren)
    return final_list


def simple_builder(terms, root=None):
    """
    >>> simple_builder(['a'])
    ['a']
    """
    return terms


def term_filter(term, filter):
    if term:
        if filter:
            return filter(term)
        else:
            return True
    else:
        return False


def simple_label_filter(term):
    """will reject labels suggesting sample identifiers from NCBI;
    returns boolean"""
    return True


def check_date(urlstr):
    """
    Opens a connection, tries to retrieve a last-modified in the headers

    >>> check_date('http://purl.obolibrary.org/obo/bfo.owl')
    'Fri, 16 May 2014 14:46:38 GMT'

    """
    r = requests.get(urlstr)
    for header in r.headers:
        if header == 'last-modified':
            timestr = r.headers[header]
            r.close()
            return timestr
    else:
        print "No timestamp found in %s" % str(r.headers)
        r.close()
        return ''

if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser(description='check for testing option')
    parser.add_argument("--test", help="activate testing", action="store_true")
    parser.add_argument("--verbose", help="verbose test output", action="store_true")
    args = parser.parse_args()
    if args.test:
        import doctest
        if args.verbose:
            doctest.testmod(verbose=True)
        else:
            doctest.testmod(verbose=False)
