#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#  Subclassing FHIR's reference to add resolving capabilities

import logging
from . import reference

logger = logging.getLogger(__name__)


class FHIRReference(reference.Reference):
    """ Subclassing FHIR's `Reference` resource to add resolving capabilities.
    """
    
    def resolved(self, klass):
        """ Resolves the reference and caches the result, returning instance(s)
        of the referenced classes.
        
        :param klass: The expected class of the resource
        :returns: An instance (or list thereof) of the resolved reference if
            dereferencing was successful, `None` otherwise
        """
        owning_resource = self.owningResource()
        if owning_resource is None:
            raise Exception("Cannot resolve reference without having an owner (which must be a `DomainResource`)")
        if klass is None:
            raise Exception("Cannot resolve reference without knowing the class")
        
        refid = self.processedReferenceIdentifier()
        if not refid:
            logger.warning("No `reference` set, cannot resolve")
            return None
        
        # already resolved and cached?
        resolved = owning_resource.resolvedReference(refid)
        if resolved is not None:
            if isinstance(resolved, klass):
                return resolved
            logger.warning("Referenced resource {} is not a {} but a {}".format(refid, klass, resolved.__class__))
            return None
        
        # not yet resolved, see if it's a contained resource
        if owning_resource.contained is not None:
            for contained in owning_resource.contained:
                if contained.id == refid:
                    owning_resource.didResolveReference(refid, contained)
                    if isinstance(contained, klass):
                        return contained
                    logger.warning("Contained resource {} is not a {} but a {}".format(refid, klass, contained.__class__))
                    return None
        
        # are we in a bundle?
        ref_is_relative = '://' not in self.reference and 'urn:' != self.reference[:4]
        bundle = self.owningBundle()
        while bundle is not None:
            if bundle.entry is not None:
                fullUrl = self.reference
                if ref_is_relative:
                    base = bundle.origin_server.base_uri if bundle.origin_server else ''
                    fullUrl = base + self.reference
                
                for entry in bundle.entry:
                    if entry.fullUrl == fullUrl:
                        found = entry.resource
                        if isinstance(found, klass):
                            return found
                        logger.warning("Bundled resource {} is not a {} but a {}".format(refid, klass, found.__class__))
                        return None
            bundle = bundle.owningBundle()
        
        # relative references, use the same server
        server = None
        if ref_is_relative:
            server = owning_resource.origin_server if owning_resource else None
        
        # TODO: instantiate server for absolute resource
        if server is None:
            logger.warning("Not implemented: resolving absolute reference to resource {}"
                .format(self.reference))
            return None
        
        # fetch remote resource; unable to verify klass since we use klass.read_from()
        relative = klass.read_from(self.reference, server)
        owning_resource.didResolveReference(refid, relative)
        return relative
    
    def processedReferenceIdentifier(self):
        """ Normalizes the reference-id.
        """
        if self.reference and '#' == self.reference[0]:
            return self.reference[1:]
        return self.reference

