from DateTime import DateTime
from logging import getLogger
from networkx import DiGraph
from plone import api
from plone.memoize.view import memoize
from plone.protect.interfaces import IDisableCSRFProtection
from ploneintranet.workspace.interfaces import IBaseWorkspaceFolder
from Products.Five import BrowserView
from zope.annotation import IAnnotations
from zope.interface import alsoProvides

logger = getLogger(__name__)


class BaseGraphView(BrowserView):
    pas_name = ""

    @property
    @memoize
    def pas_view(self):
        return api.content.get_view("pas_view", api.portal.get(), self.request)

    @property
    @memoize
    def context_path(self):
        return "/".join(self.context.getPhysicalPath())

    @property
    @memoize
    def uid(self):
        return self.join(self.context.UID())

    @property
    @memoize
    def name(self):
        return self.join(self.context.getId())

    @property
    @memoize
    def path(self):
        return self.join(self.context_path)

    def join(self, *args):
        args = [self.pas_name] + list(args)
        return ":".join(args)

    @memoize
    def get_node_info(self):
        info = {(self.uid, self.name, self.path)}
        return info

    @memoize
    def get_edges(self):
        return set()


class UserprofileGraphView(BaseGraphView):

    pas_name = "userprofile"


class WorkgroupGraphView(BaseGraphView):

    pas_name = "workgroup"

    @property
    @memoize
    def name(self):
        return self.join(self.context.getGroupId())

    def get_edges(self):
        pas_view = api.content.get_view("pas_view", api.portal.get(), self.request)
        edges = set()
        uid = self.uid
        member_uids = list(map(pas_view.principalname2uid, self.context.members))
        for member_uid in member_uids:
            if member_uid:
                edges.add((uid, member_uid))
        return edges


class InitPASGraphView(BrowserView):

    _btrees = ("name2uid", "uid2path", "path2name")

    def get_template(self, principal):
        if principal.portal_type == "ploneintranet.userprofile.userprofile":
            return "Profile:{}"
        if principal.portal_type == "ploneintranet.userprofile.workgroup":
            return "Group:{}"

    def get_node_info_for(self, obj):
        view = api.content.get_view("pas-graph", obj, self.request)
        return view.get_node_info()

    def get_edges_for(self, obj):
        view = api.content.get_view("pas-graph", obj, self.request)
        return view.get_edges()

    def get_interesting_objs(self):
        portal = api.portal.get()
        objs = []
        objs.extend(portal.profiles.listFolderContents())
        objs.extend(portal.groups.listFolderContents())
        try:
            objs.extend(portal.workspaces.listFolderContents())
        except AttributeError:
            # This object is installed by ploneintranet.workspace
            pass
        try:
            objs.extend(
                [
                    obj
                    for obj in portal.templates.listFolderContents()
                    if IBaseWorkspaceFolder.providedBy(obj)
                ]
            )
        except AttributeError:
            # This object is installed by ploneintranet.workspace
            pass
        return objs

    def clear_graph(self):
        annotations = IAnnotations(api.portal.get())
        for key in ("pas_uid2path", "pas_path2name", "pas_name2uid", "pas_edges"):
            annotations.pop(key, None)

    def init_graph(self):
        response_lines = ["Start: %r" % DateTime()]
        name2uid = {}
        uid2path = {}
        path2name = {}
        edges = set()

        objs = self.get_interesting_objs()
        for obj in objs:
            for uid, name, path in self.get_node_info_for(obj):
                uid2path[uid] = path
                name2uid[name] = uid
                path2name[path] = name

        pas_view = api.content.get_view("pas_view", api.portal.get(), self.request)

        for dict_name in self._btrees:
            stored_dict = getattr(pas_view, dict_name)
            local_dict = locals()[dict_name]
            # remove obsolete keys
            obsolete = set(stored_dict).difference(local_dict)
            if obsolete:
                response_lines.append(
                    f"Purging {sorted(obsolete)!r} from {dict_name!r}"
                )
                logger.info(response_lines[-1])
                list(map(stored_dict.pop, set(stored_dict).difference(local_dict)))
            # add new values
            new = {key for key in local_dict if key not in stored_dict}
            if new:
                response_lines.append(f"Adding {sorted(new)!r} to {dict_name!r}")
                logger.info(response_lines[-1])
                for key in new:
                    stored_dict[key] = local_dict[key]

        for obj in objs:
            edges.update(self.get_edges_for(obj))

        stored_edges = pas_view.edges
        # remove obsolete edges
        obsolete = set(stored_edges).difference(edges)
        if obsolete:
            response_lines.append("Purging %r from edges" % sorted(obsolete))
            logger.info(response_lines[-1])
            list(map(stored_edges.remove, obsolete))
        # add new ones
        new = set(edges).difference(stored_edges)
        if new:
            response_lines.append("Adding %r to edges" % sorted(new))
            logger.info(response_lines[-1])
            list(map(stored_edges.add, new))

        graph = DiGraph()
        graph.add_nodes_from(uid2path)
        graph.add_edges_from(edges)
        logger.info("Edges: %r", graph.number_of_edges())
        logger.info("Nodes: %r", graph.number_of_nodes())

        alsoProvides(self.request, IDisableCSRFProtection)
        response_lines.append("Stop: %r" % DateTime())
        return "\n".join(response_lines)

    def __call__(self):
        if self.request.get("clear"):
            return self.clear_graph()
        else:
            return self.init_graph()
