# -*- coding: utf-8 -*-

# Copyright 2013 Vincent Jacques
# vincent@vincent-jacques.net

# This file is part of AnotherPyGraphvizAgain. http://jacquev6.github.com/AnotherPyGraphvizAgain

# AnotherPyGraphvizAgain is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License
# as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

# AnotherPyGraphvizAgain is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.

# You should have received a copy of the GNU Lesser General Public License along with AnotherPyGraphvizAgain.  If not, see <http://www.gnu.org/licenses/>.

import subprocess


class Attributes:
    def __init__(self):
        self.__attr = dict()

    def set(self, name, value):
        if value is None:
            if name in self.__attr:
                del self.__attr[name]
        else:
            self.__attr[name] = value

    def dotString(self, begin, sep, end):
        if len(self.__attr) == 0:
            return ""
        else:
            return begin + sep.join(sorted(k + "=\"" + self.__attr[k].replace('"', '\\"') + "\"" for k in self.__attr)) + end


class Attributed:
    def __init__(self):
        self.attr = Attributes()

    def set(self, *args):
        self.attr.set(*args)
        return self


class Link(Attributed):
    def __init__(self, origin, destination):
        Attributed.__init__(self)
        self.__origin = origin
        self.__destination = destination

    def dotString(self):
        return self.__origin.id + "->" + self.__destination.id + self.attr.dotString("[", ",", "]") + ";"


class LinkFromTo:
    def __init__(self, origin, destination):
        self.origin = origin
        self.destination = destination
        self.link = Link(origin, destination)

    def set(self, *args):
        self.link.set(*args)
        return self


class Node(Attributed):
    def __init__(self, id):
        Attributed.__init__(self)
        self.id = id

    def dotString(self):
        return self.id + self.attr.dotString("[", ",", "]") + ";"

    def getOneNode(self):
        return self

    def linkTo(self, destination):
        return LinkFromTo(self, destination)


class BasicGraph(Attributed):
    def __init__(self):
        Attributed.__init__(self)
        self.nodeAttr = Attributes()
        self.edgeAttr = Attributes()
        self.__nodes = set()
        self.__links = set()

    def add(self, element):
        if isinstance(element, Link):
            self.__links.add(element)
        elif isinstance(element, LinkFromTo):
            self.add(element.origin)
            self.add(element.destination)
            self.add(element.link)
        elif isinstance(element, Node) or isinstance(element, Subgraph):
            self.__nodes.add(element)
        else:
            raise Exception("Unknown element type:" + element.__class__.__name__)
        return self

    def getOneNode(self):
        for n in sorted(self.__nodes, key=lambda n: n.id):
            return n.getOneNode()
        raise Exception("Empty")

    def contentDotString(self):
        return (
            "{"
            + self.attr.dotString("", ";", ";")
            + self.nodeAttr.dotString("node [", ",", "];")
            + self.edgeAttr.dotString("edge [", ",", "];")
            + "".join(sorted(n.dotString() for n in self.__nodes))
            + "".join(sorted(l.dotString() for l in self.__links))
            + "}"
        )

    def drawTo(self, fileName):  # pragma no cover (Really calls 'dot')
        p = subprocess.Popen(["dot", "-Tpng", "-o" + fileName], stdin=subprocess.PIPE)
        p.communicate(self.dotString().encode())


### @todo digraph/graph
class Graph(BasicGraph):
    def __init__(self, name):
        BasicGraph.__init__(self)
        self.__name = name

    def dotString(self):
        return "digraph \"" + self.__name + "\" " + self.contentDotString()


class Subgraph(BasicGraph):
    def __init__(self, id):
        BasicGraph.__init__(self)
        self.id = id

    def dotString(self):
        return "subgraph " + self.id + self.contentDotString() + ";"


def makeId(s):
    def shouldReplace(c):
        return c not in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

    def replaceIfNeeded(c):
        if shouldReplace(c):
            return replace(c)
        else:
            return c

    def replace(c):
        return "_" + format(ord(c), "02x")

    s = str(s)

    if shouldReplace(s[0]) or s[0] in "0123456789":
        s2 = replace(s[0])
    else:
        s2 = s[0]

    for c in s[1:]:
        s2 += replaceIfNeeded(c)

    return s2
