from typing import Optional, List, Dict, Any, Type

from rdflib import Namespace, Graph, BNode, RDF, Literal

from altimeter.core.graph.link.base import Link
from altimeter.core.graph.link.links import link_from_dict
from altimeter.core.graph.node_cache import NodeCache


class Resource:
    """A Resource defines a single scanned resource which is directly translatable to a graph
    node.  It contains an id, type name and list of Links.

    Args:
         resource_id: id of this resource
         type_name: type name of this resource
         links: List of Links for this resource
    """

    def __init__(self, resource_id: str, type_name: str, links: Optional[List[Link]] = None):
        self.resource_id = resource_id
        self.type_name = type_name
        self.links = [] if links is None else links

    def to_dict(self) -> Dict[str, Any]:
        """Generate a dict representation of this Resource.

        Returns:
            dict representation of this Resource
        """
        scan_json: Dict[str, Any] = {"type": self.type_name}
        if self.links:
            scan_json["links"] = [link.to_dict() for link in self.links]
        return scan_json

    @classmethod
    def from_dict(
        cls: Type["Resource"], resource_id: str, resource_data: Dict[str, Any]
    ) -> "Resource":
        """Create an instances of this class from a resource_id and resource_data dict
        as generated by to_dict.

        Args:
            resource_id: resource id
            resource_data: dict of data for this resource

        Returns:
            Resource object
        """
        type_name = resource_data["type"]
        links: List[Link] = []
        for link in resource_data.get("links", []):
            links.append(link_from_dict(link))
        return cls(resource_id=resource_id, type_name=type_name, links=links)

    def to_rdf(self, namespace: Namespace, graph: Graph, node_cache: NodeCache) -> None:
        """Graph this Resource as a BNode on a Graph.

        Args:
            namespace: RDF namespace to use for predicates and objects when graphing
                       this resource's links
            graph: RDF graph
            node_cache: NodeCache to use for any cached BNode lookups
        """
        node = node_cache.setdefault(self.resource_id, BNode())
        graph.add((node, RDF.type, getattr(namespace, self.type_name)))
        graph.add((node, getattr(namespace, "id"), Literal(self.resource_id)))
        for link in self.links:
            link.to_rdf(subj=node, namespace=namespace, graph=graph, node_cache=node_cache)
