#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (C) 2020-2021  The SymbiFlow Authors.
#
# Use of this source code is governed by a ISC-style
# license that can be found in the LICENSE file or at
# https://opensource.org/licenses/ISC
#
# SPDX-License-Identifier: ISC


"""
rr_graph docs: http://docs.verilogtorouting.org/en/latest/vpr/file_formats/
etree docs: http://lxml.de/api/lxml.etree-module.html

general philosophy
Data structures should generally be conceptual objects rather than direct XML manipulation
Generally one class per XML node type
However, RRGraph is special cased, operating on XML directly and holding

class Pin(MostlyReadOnly):
    xml: can import but doesn't keep track of a node
class PinClass(MostlyReadOnly):
    xml: can import but doesn't keep track of a node
class BlockType(MostlyReadOnly):
    xml: can import but doesn't keep track of a node
class Block(MostlyReadOnly):
    for <grid_loc>
class BlockGrid:
    Was: BlockGraph
    xml: nothing, handled by intneral Block objects though
class RoutingGraph:
    holds pins + edges
    xml: updated as pins are added
        inconsistent with the rest of the project
    However, outside generally only add pins through objects
    so they don't see the XML directly
    except print does iterate directly over the XML
class Graph:
    Top level class holding everything together has

enums
class BlockTypeEdge(enum.Enum):
    lightweight enum type
class PinClassDirection(enum.Enum):
    lightweight enum type

XXX: parse comments? Maybe can do a pass removing them
"""

import enum
import io
import re

from collections import namedtuple, OrderedDict
from types import MappingProxyType

import lxml.etree as ET

from . import Position
from . import Size
from . import Offset
from . import node_pos, single_element
from .channel import Channels, Track

from .utils.asserts import assert_eq
from .utils.asserts import assert_is
from .utils.asserts import assert_type
from .utils.asserts import assert_type_or_none

from .utils.collections_extra import MostlyReadOnly

_DEFAULT_MARKER = []


def dict_next_id(d):
    current_ids = [-1] + list(d.keys())
    return max(current_ids) + 1


def parse_net(
        s,
        _r=re.compile(
            "^(.*\\.)?([^.\\[]*[^0-9\\[.]+[^.\\[]*)?(\\[([0-9]+|[0-9]+:[0-9]+)]|[0-9]+|)$"
        )
):
    """
    Parses a Verilog/Verilog-To-Routing net/port definition.

    The general form of this net/port definition is;

       block_name.portname[startpin:endpin]

    Almost all parts of the definition are optional. See the examples below.

    Returns:
        - tuple (block_name, port_name, list of pin numbers)

    Fully specified
    >>> parse_net('a.b[0]')
    ('a', 'b', [0])
    >>> parse_net('c.d[1]')
    ('c', 'd', [1])
    >>> parse_net('c.d[40]')
    ('c', 'd', [40])
    >>> parse_net('VPR_PAD.outpad[0]')
    ('VPR_PAD', 'outpad', [0])

    Fully specified with more complex block names
    >>> parse_net('a.b.c[0]')
    ('a.b', 'c', [0])
    >>> parse_net('c-d.e[11]')
    ('c-d', 'e', [11])

    Fully specified with block names that include square brackets
    >>> parse_net('a.b[2].c[0]')
    ('a.b[2]', 'c', [0])
    >>> parse_net('c-d[3].e[11]')
    ('c-d[3]', 'e', [11])

    Fully specified range of pins
    >>> parse_net('a.b[11:8]')
    ('a', 'b', [8, 9, 10, 11])
    >>> parse_net('c.d[8:11]')
    ('c', 'd', [8, 9, 10, 11])

    Net with no pin index.
    >>> parse_net('VPR_PAD.outpad')
    ('VPR_PAD', 'outpad', None)

    Net with no block
    >>> parse_net('outpad[10]')
    (None, 'outpad', [10])
    >>> parse_net('outpad[10:12]')
    (None, 'outpad', [10, 11, 12])
    >>> parse_net('outpad[12:10]')
    (None, 'outpad', [10, 11, 12])

    No block or pin index
    >>> parse_net('outpad')
    (None, 'outpad', None)
    >>> parse_net('outpad0')
    (None, 'outpad0', None)
    >>> parse_net('0outpad')
    (None, '0outpad', None)

    >>> parse_net('0')
    (None, None, [0])
    """

    g = _r.match(s)
    if not g:
        raise TypeError("Pin {!r} not parsed.".format(s))
    block_name, port_name, pin_full, pin_idx = g.groups()

    if not block_name:
        block_name = None
    else:
        assert block_name[-1] == ".", block_name
        block_name = block_name[:-1]

    if not port_name:
        port_name = None
    else:
        assert "." not in port_name, port_name

    if not pin_full:
        start, end = None, None
        pins = None
    elif pin_idx:
        assert_eq(pin_full[0], '[')
        assert_eq(pin_full[-1], ']')
        assert_eq(len(pin_full), len(pin_idx) + 2)

        if ":" in pin_idx:
            assert_eq(pin_idx.count(':'), 1)
            start, end = (int(b) for b in pin_idx.split(":", 1))
        else:
            start = int(pin_idx)
            end = start
    else:
        start, end = int(pin_full), int(pin_full)

    if start is not None and end is not None:
        if start > end:
            end, start = start, end
        end += 1

        pins = list(range(start, end))

    return block_name, port_name, pins


class Pin(MostlyReadOnly):
    """
    A Pin turns into on IPIN/OPIN node for each block.

    Attributes
    ----------

    port_name : string

    port_index : int

    block_type_name : string
    block_type_subblk : int
        When a block has `capacity > 1` there are multiple "subblocks"
        contained.

    block_type_index : int
        Starts at 0 within a block and increments for each pin.
        Equal to the ptc property in the rr_graph XML.

    ptc : int
        Alias for block_type_index

    direction : PinClassDirection
        If the pin is an clock, input or output.

    side : RoutingNodeSide
        Side of the block the pin is found on.
    """

    __slots__ = [
        "_pin_class",
        "_port_name",
        "_port_index",
        "_block_type_name",
        "_block_type_subblk",
        "_block_type_index",
        "_side",
    ]

    # Index within a specific BlockType
    # BlockType has multiple pin classes
    # PinClass has multiple pins, usually 1
    @property
    def ptc(self):
        return self.block_type_index

    @property
    def direction(self):
        return self.pin_class.direction

    @property
    def block_type_fullname(self):
        bt_name = None
        if self.block_type_name is not None:
            bt_name = self.block_type_name
        elif self.pin_class is not None:
            bt_name = self.pin_class.block_type_name
        if bt_name and self.block_type_subblk is not None:
            return "{}[{}]".format(bt_name, self.block_type_subblk)
        return bt_name

    @property
    def name(self):
        """<portname>[<port_index>]"""
        assert_type(
            self._port_name, str,
            "Pin doesn't have port_name {}".format(repr(self))
        )
        assert_type(
            self._port_index, int,
            "Pin doesn't have port_index {}".format(repr(self))
        )
        return "{}[{}]".format(self.port_key, self._port_index)

    @property
    def port_key(self):
        if self.block_type_subblk is not None:
            return "[{}]{}".format(self.block_type_subblk, self.port_name)
        else:
            return self.port_name

    @property
    def xmlname(self):
        """Give name as originally in the XML. <block_type_name>.<name>"""
        return "{}.{}".format(self.block_type_name, self.name)

    def __init__(
            self,
            pin_class=None,
            port_name=None,
            port_index=None,
            block_type_name=None,
            block_type_subblk=None,
            block_type_index=None,
            side=None
    ):

        assert_type_or_none(pin_class, PinClass)

        assert_type_or_none(port_name, str)
        assert_type_or_none(port_index, int)

        assert_type_or_none(block_type_name, str)
        assert_type_or_none(block_type_subblk, int)
        assert_type_or_none(block_type_index, int)

        assert_type_or_none(side, RoutingNodeSide)

        self._pin_class = pin_class
        self._port_name = port_name
        self._port_index = port_index
        self._block_type_name = block_type_name
        self._block_type_subblk = block_type_subblk
        self._block_type_index = block_type_index
        self._side = side

        if pin_class is not None:
            pin_class._add_pin(self)

    def __str__(self):
        return "{}({})->{}[{}]".format(
            self.block_type_fullname, self.block_type_index, self.port_name,
            self.port_index
        )

    @classmethod
    def from_text(cls, pin_class, text, block_type_index=None):
        """Create a Pin object from a textual pin string.

        Parameters
        ----------
        pin_class : PinClass

        text : str
            Textual pin definition

        block_type_index : int or None, optional

        Examples
        ----------

        >>> pin = Pin.from_text(None, '0')
        >>> pin
        Pin(pin_class=None, port_name=None, port_index=None, block_type_name=None, block_type_subblk=None, block_type_index=0, side=None)
        >>> str(pin)
        'None(0)->None[None]'

        >>> pin = Pin.from_text(None, '10')
        >>> pin
        Pin(pin_class=None, port_name=None, port_index=None, block_type_name=None, block_type_subblk=None, block_type_index=10, side=None)
        >>> str(pin)
        'None(10)->None[None]'

        >>> pin = Pin.from_text(None, 'bt.outpad[2]')
        >>> pin
        Pin(pin_class=None, port_name='outpad', port_index=2, block_type_name='bt', block_type_subblk=None, block_type_index=None, side=None)
        >>> str(pin)
        'bt(None)->outpad[2]'

        >>> pin = Pin.from_text(None, 'bt[3].outpad[2]')
        >>> pin
        Pin(pin_class=None, port_name='outpad', port_index=2, block_type_name='bt', block_type_subblk=3, block_type_index=None, side=None)
        >>> str(pin)
        'bt[3](None)->outpad[2]'

        """  # noqa: E501
        assert_type(text, str)
        block_type_name, port_name, pins = parse_net(text.strip())
        assert pins is not None, text.strip()

        if block_type_name and '[' in block_type_name:
            _, block_type_name, (block_type_subblk, ) = parse_net(
                block_type_name.strip()
            )
        else:
            block_type_subblk = None

        assert_eq(len(pins), 1)
        if block_type_index is None and port_name is None:
            block_type_index = pins[0]
            port_index = None
        else:
            port_index = pins[0]

        return cls(
            pin_class=pin_class,
            port_name=port_name,
            port_index=port_index,
            block_type_name=block_type_name,
            block_type_subblk=block_type_subblk,
            block_type_index=block_type_index,
        )

    @classmethod
    def from_xml(cls, pin_class, pin_node):
        """Create a Pin object from an XML rr_graph node.

        Parameters
        ----------
        pin_class : PinClass

        pin_node : ET._Element
            An `<pin>` XML node from an rr_graph.

        Examples
        ----------
        >>> pc = PinClass(BlockType(name="bt"), direction=PinClassDirection.INPUT)
        >>> xml_string = '<pin ptc="1">bt.outpad[2]</pin>'
        >>> pin = Pin.from_xml(pc, ET.fromstring(xml_string))
        >>> pin
        Pin(pin_class=PinClass(), port_name='outpad', port_index=2, block_type_name='bt', block_type_subblk=None, block_type_index=1, side=None)
        >>> str(pin)
        'bt(1)->outpad[2]'
        >>> pin.ptc
        1
        """  # noqa: E501
        assert pin_node.tag == "pin"
        block_type_index = int(pin_node.attrib["ptc"])

        return cls.from_text(
            pin_class,
            pin_node.text.strip(),
            block_type_index=block_type_index
        )


class PinClassDirection(enum.Enum):
    INPUT = "input"
    OUTPUT = "output"
    CLOCK = "clock"
    UNKNOWN = "unknown"

    def __repr__(self):
        return repr(self.value)

    def __str__(self):
        return str(enum.Enum.__str__(self)).replace("PinClassDirection", "PCD")


class PinClass(MostlyReadOnly):
    """

    All pins inside a pin class are equivalent.

    ie same net. Would a LUT with swappable inputs count?
    For <pin_class> nodes

    A PinClass turns into one SOURCE (when direction==OUTPUT) or SINK (when
    direction in (INPUT, CLOCK)) per each block.

    Attributes
    ----------
    block_type : BlockType

    direction : PinClassDirection

    pins : tuple of Pin
        Pin inside this PinClass object. Useful for doing `for p in pc.pins`.

    port_name : str
        Name of the port this PinClass represents. In the form of;
            port_name[pin_idx]
            port_name[pin_idx:pin_idx]

    block_type_name : str
    """

    __slots__ = ["_block_type", "_direction", "_pins"]

    @property
    def port_name(self):
        """
        >>> bg = BlockGrid()
        >>> bt = BlockType(g=bg, id=0, name="B")
        >>> c1 = PinClass(block_type=bt, direction=PinClassDirection.OUTPUT)
        >>> c2 = PinClass(block_type=bt, direction=PinClassDirection.OUTPUT)
        >>> c3 = PinClass(block_type=bt, direction=PinClassDirection.OUTPUT)
        >>> p0 = Pin(pin_class=c1, port_name="P1", port_index=0)
        >>> p1 = Pin(pin_class=c2, port_name="P1", port_index=1)
        >>> p2 = Pin(pin_class=c2, port_name="P1", port_index=2)
        >>> p3 = Pin(pin_class=c2, port_name="P1", port_index=3)
        >>> p4 = Pin(pin_class=c3, port_name="P2", port_index=0)
        >>> c1.port_name
        'P1[0]'
        >>> c2.port_name
        'P1[3:1]'
        >>> c3.port_name
        'P2[0]'
        """
        port_indexes = [p.port_index for p in self.pins]
        pin_start = min(port_indexes)
        pin_end = max(port_indexes)
        assert_eq(port_indexes, list(range(pin_start, pin_end + 1)))
        if pin_start == pin_end:
            return "{}[{}]".format(self.pins[0].port_name, pin_end)
        return "{}[{}:{}]".format(self.pins[0].port_name, pin_end, pin_start)

    @property
    def block_type_name(self):
        if self.block_type is None:
            return None
        return self.block_type.name

    def __init__(self, block_type=None, direction=None, pins=None):
        assert_type_or_none(block_type, BlockType)
        assert_type_or_none(direction, PinClassDirection)

        self._block_type = block_type
        self._direction = direction
        # Although pins within a pin class have no defined order,
        # preserve input XML ordering for consistency
        self._pins = []

        if block_type is not None:
            block_type._add_pin_class(self)

        if pins is not None:
            for p in pins:
                self._add_pin(p)

    @classmethod
    def from_xml(cls, block_type, pin_class_node):
        """
        block_type: block this belongs to
        pin_class_node: XML object to parse

        >>> bt = BlockType(name="bt")
        >>> xml_string1 = '''
        ... <pin_class type="INPUT">
        ...   <pin ptc="2">bt.outpad[3]</pin>
        ...   <pin ptc="3">bt.outpad[4]</pin>
        ... </pin_class>
        ... '''
        >>> pc = PinClass.from_xml(bt, ET.fromstring(xml_string1))
        >>> pc # doctest: +ELLIPSIS
        PinClass(block_type=BlockType(), direction='input', pins=[...])
        >>> len(pc.pins)
        2
        >>> pc.pins[0]
        Pin(pin_class=PinClass(), port_name='outpad', port_index=3, block_type_name='bt', block_type_subblk=None, block_type_index=2, side=None)
        >>> pc.pins[1]
        Pin(pin_class=PinClass(), port_name='outpad', port_index=4, block_type_name='bt', block_type_subblk=None, block_type_index=3, side=None)
        >>> for p in pc.pins:
        ...   print("{}[{}]".format(p.port_name, p.port_index))
        outpad[3]
        outpad[4]
        >>> pc.port_name
        'outpad[4:3]'

        >>> bt = BlockType(name="a")
        >>> xml_string2 = '''
        ... <pin_class type="INPUT">
        ...   <pin ptc="0">a.b[1]</pin>
        ... </pin_class>
        ... '''
        >>> pc = PinClass.from_xml(bt, ET.fromstring(xml_string2))
        >>> pc # doctest: +ELLIPSIS
        PinClass(block_type=BlockType(), direction='input', pins=[...])
        >>> len(pc.pins)
        1
        >>> pc.pins[0]
        Pin(pin_class=PinClass(), port_name='b', port_index=1, block_type_name='a', block_type_subblk=None, block_type_index=0, side=None)

        >>> bt = BlockType(name="a")
        >>> xml_string3 = '''
        ... <pin_class type="OUTPUT">
        ...   <pin ptc="2">a.b[5]</pin>
        ...   <pin ptc="3">a.b[6]</pin>
        ...   <pin ptc="4">a.b[7]</pin>
        ... </pin_class>
        ... '''
        >>> pc = PinClass.from_xml(bt, ET.fromstring(xml_string3))
        >>> pc # doctest: +ELLIPSIS
        PinClass(block_type=BlockType(), direction='output', pins=[...])
        >>> len(pc.pins)
        3
        >>> pc.pins[0]
        Pin(pin_class=PinClass(), port_name='b', port_index=5, block_type_name='a', block_type_subblk=None, block_type_index=2, side=None)
        >>> pc.pins[1]
        Pin(pin_class=PinClass(), port_name='b', port_index=6, block_type_name='a', block_type_subblk=None, block_type_index=3, side=None)
        >>> pc.pins[2]
        Pin(pin_class=PinClass(), port_name='b', port_index=7, block_type_name='a', block_type_subblk=None, block_type_index=4, side=None)
        """  # noqa: E501
        assert_eq(pin_class_node.tag, "pin_class")
        assert "type" in pin_class_node.attrib
        class_direction = getattr(
            PinClassDirection, pin_class_node.attrib["type"]
        )
        assert_type(class_direction, PinClassDirection)

        pc_obj = cls(block_type, class_direction)

        pin_nodes = list(pin_class_node.iterfind("./pin"))
        # Old format with pins described in text field
        if len(pin_nodes) == 0:
            for n in pin_class_node.text.split():
                pc_obj._add_pin(Pin.from_text(pc_obj, n))
        # New format using XML nodes
        else:
            for pin_node in pin_nodes:
                pc_obj._add_pin(Pin.from_xml(pc_obj, pin_node))

        return pc_obj

    def __str__(self):
        return "{}.PinClass({}, [{}])".format(
            self.block_type_name,
            self.direction,
            ", ".join(str(i) for i in sorted(self.pins)),
        )

    def _add_pin(self, pin):
        assert_type(pin, Pin)

        # PinClass.from_xml() and Pin.from_xml() both call add_pin
        if pin in self._pins:
            return

        # Verify the pin has matching properties.
        # --------------------------------------------
        if pin.pin_class is not None:
            assert_eq(pin.pin_class, self)

        # if pin.port_name is not None:
        #     assert_eq(pin.port_name, self.port_name)

        # if pin.port_index is not None:
        #     assert_not_in(pin.port_index, self.ports_index)

        if pin.block_type_name is not None:
            assert_eq(pin._block_type_name, self.block_type_name)

        # Fill out any unset properties
        # --------------------------------------------
        if pin.pin_class is None:
            pin._pin_class = self

        self._pins.append(pin)

        # if pin.port_name is None:
        #     pin._port_name = self.port_name

        # if pin.port_index is None:
        #     pin._port_index = dict_next_id(self.block_type._ports[self.port_name])

        if pin.block_type_name is None:
            pin._block_type_name = self.block_type_name

        if self.block_type is not None:
            self.block_type._add_pin(pin)


class BlockType(MostlyReadOnly):
    """
    For <block_type> nodes

    Attributes
    ----------
    graph : Graph

    id : int

    name : str
        Name of the block type.

    size : Size
        Size of the block type, default is `Size(1, 1)`.

    pin_classes : tuple of PinClass
        Pin classes that this block contains.

    pins_index : mapping[int] -> Pin
        ptc value to pin mapping.

    ports_index : mapping[str] -> mapping[int] -> Pin

    pins : tuple of Pin
        List of pins on this BlockType.

    ports : tuple of str
        List of ports on this BlockType.
    """

    @property
    def pins(self):
        return tuple(self._pins_index.values())

    @property
    def ports(self):
        return tuple(self._ports_index.keys())

    @property
    def positions(self):
        for x in range(0, self.size.width):
            for y in range(0, self.size.height):
                yield Offset(x, y)

    __slots__ = [
        "_graph",
        "_id",
        "_name",
        "_size",
        "_pin_classes",
        "_pins_index",
        "_ports_index",
    ]

    def __init__(
            self, g=None, id=-1, name="", size=Size(1, 1), pin_classes=None
    ):
        assert_type_or_none(g, BlockGrid)
        assert_type_or_none(id, int)
        assert_type_or_none(name, str)
        assert_type_or_none(size, Size)

        self._graph = g
        self._id = id
        self._name = name
        self._size = size

        self._pin_classes = []
        self._pins_index = {}
        self._ports_index = {}
        if pin_classes is not None:
            for pc in pin_classes:
                self._add_pin_class(pc)

        if g is not None:
            g.add_block_type(self)

    def to_string(self, extra=False):
        if not extra:
            return "BlockType({name})".format(name=self.name)
        else:
            return "in 0x{graph_id:x} (pin_classes=[{pin_class_num} classes] pins_index=[{pins_index_num} pins])".format(  # noqa: E501
                graph_id=id(self._graph),
                pin_class_num=len(self.pin_classes),
                pins_index_num=len(self.pins_index)
            )

    @classmethod
    def from_xml(cls, g, block_type_node):
        """

        >>> xml_string = '''
        ... <block_type id="1" name="VPR_PAD" width="2" height="3">
        ...   <pin_class type="OUTPUT">
        ...     <pin ptc="0">VPR_PAD.outpad[0]</pin>
        ...   </pin_class>
        ...   <pin_class type="OUTPUT">
        ...     <pin ptc="1">VPR_PAD.outpad[1]</pin>
        ...   </pin_class>
        ...   <pin_class type="INPUT">
        ...     <pin ptc="2">VPR_PAD.inpad[0]</pin>
        ...   </pin_class>
        ... </block_type>
        ... '''
        >>> bt = BlockType.from_xml(None, ET.fromstring(xml_string))
        >>> bt # doctest: +ELLIPSIS
        BlockType(graph=None, id=1, name='VPR_PAD', size=Size(w=2, h=3), pin_classes=[...], pins_index={...})
        >>> len(bt.pin_classes)
        3
        >>> bt.pin_classes[0].direction
        'output'
        >>> bt.pin_classes[0] # doctest: +ELLIPSIS
        PinClass(block_type=BlockType(), direction='output', pins=[...])
        >>> bt.pin_classes[0].pins[0]
        Pin(pin_class=PinClass(), port_name='outpad', port_index=0, block_type_name='VPR_PAD', block_type_subblk=None, block_type_index=0, side=None)
        >>> bt.pin_classes[1].direction
        'output'
        >>> bt.pin_classes[1].pins[0]
        Pin(pin_class=PinClass(), port_name='outpad', port_index=1, block_type_name='VPR_PAD', block_type_subblk=None, block_type_index=1, side=None)
        >>> bt.pin_classes[2].direction
        'input'
        >>> bt.pin_classes[2].pins[0]
        Pin(pin_class=PinClass(), port_name='inpad', port_index=0, block_type_name='VPR_PAD', block_type_subblk=None, block_type_index=2, side=None)

        Multiple pins in a single pinclass.
        >>> xml_string = '''
        ... <block_type id="1" name="VPR_PAD" width="2" height="3">
        ...   <pin_class type="OUTPUT">
        ...     <pin ptc="0">VPR_PAD.outpad[0]</pin>
        ...     <pin ptc="1">VPR_PAD.outpad[1]</pin>
        ...   </pin_class>
        ...   <pin_class type="INPUT">
        ...     <pin ptc="2">VPR_PAD.inpad[0]</pin>
        ...   </pin_class>
        ... </block_type>
        ... '''
        >>> bt = BlockType.from_xml(None, ET.fromstring(xml_string))
        >>> bt # doctest: +ELLIPSIS
        BlockType(graph=None, id=1, name='VPR_PAD', size=Size(w=2, h=3), pin_classes=[...], pins_index={...}, ports_index={...})
        >>> bt.pin_classes[0] # doctest: +ELLIPSIS
        PinClass(block_type=BlockType(), direction='output', pins=[...])
        >>> len(bt.pins_index)
        3
        >>> len(bt.pin_classes)
        2
        >>> len(bt.pin_classes[0].pins)
        2
        >>> len(bt.pin_classes[1].pins)
        1
        >>> bt.pin_classes[0].pins[0]
        Pin(pin_class=PinClass(), port_name='outpad', port_index=0, block_type_name='VPR_PAD', block_type_subblk=None, block_type_index=0, side=None)
        >>> bt.pin_classes[0].pins[1]
        Pin(pin_class=PinClass(), port_name='outpad', port_index=1, block_type_name='VPR_PAD', block_type_subblk=None, block_type_index=1, side=None)
        >>> bt.pin_classes[1].pins[0]
        Pin(pin_class=PinClass(), port_name='inpad', port_index=0, block_type_name='VPR_PAD', block_type_subblk=None, block_type_index=2, side=None)
        >>>

        Multiple subblocks within a block_type
        >>> xml_string = '''
        ... <block_type id="1" name="VPR_PAD" width="2" height="3">
        ...   <pin_class type="OUTPUT">
        ...     <pin ptc="0">VPR_PAD[0].outpad[0]</pin>
        ...   </pin_class>
        ...   <pin_class type="INPUT">
        ...     <pin ptc="1">VPR_PAD[0].inpad[0]</pin>
        ...   </pin_class>
        ...   <pin_class type="OUTPUT">
        ...     <pin ptc="2">VPR_PAD[1].outpad[0]</pin>
        ...   </pin_class>
        ...   <pin_class type="INPUT">
        ...     <pin ptc="3">VPR_PAD[1].inpad[0]</pin>
        ...   </pin_class>
        ... </block_type>
        ... '''
        >>> bt = BlockType.from_xml(None, ET.fromstring(xml_string))
        >>> bt # doctest: +ELLIPSIS
        BlockType(graph=None, id=1, name='VPR_PAD', size=Size(w=2, h=3), pin_classes=[...], pins_index={...}, ports_index={...})
        >>> bt.pin_classes[0] # doctest: +ELLIPSIS
        PinClass(block_type=BlockType(), direction='output', pins=[...])
        >>> len(bt.pins_index)
        4
        >>> len(bt.pin_classes)
        4
        >>> # All the pin classes should only have one pin
        >>> bt.pin_classes[0].pins
        (Pin(pin_class=PinClass(), port_name='outpad', port_index=0, block_type_name='VPR_PAD', block_type_subblk=0, block_type_index=0, side=None),)
        >>> bt.pin_classes[1].pins
        (Pin(pin_class=PinClass(), port_name='inpad', port_index=0, block_type_name='VPR_PAD', block_type_subblk=0, block_type_index=1, side=None),)
        >>> bt.pin_classes[2].pins
        (Pin(pin_class=PinClass(), port_name='outpad', port_index=0, block_type_name='VPR_PAD', block_type_subblk=1, block_type_index=2, side=None),)
        >>> bt.pin_classes[3].pins
        (Pin(pin_class=PinClass(), port_name='inpad', port_index=0, block_type_name='VPR_PAD', block_type_subblk=1, block_type_index=3, side=None),)
        """  # noqa: E501
        assert block_type_node.tag == "block_type", block_type_node
        block_type_id = int(block_type_node.attrib['id'])
        block_type_name = block_type_node.attrib['name'].strip()
        block_type_width = int(block_type_node.attrib['width'])
        block_type_height = int(block_type_node.attrib['height'])

        bt = cls(
            g, block_type_id, block_type_name,
            Size(block_type_width, block_type_height)
        )
        for pin_class_node in block_type_node.iterfind("./pin_class"):
            bt._add_pin_class(PinClass.from_xml(bt, pin_class_node))
        return bt

    def _could_add_pin(self, pin):
        if pin.block_type_index is not None:
            if pin.block_type_index in self._pins_index:
                assert_is(pin, self._pins_index[pin.block_type_index])

    def _add_pin(self, pin):
        """

        >>> pc = PinClass(direction=PinClassDirection.INPUT)
        >>> len(pc.pins)
        0
        >>> pc._add_pin(Pin())
        >>> len(pc.pins)
        1
        >>> bt = BlockType()
        >>> len(bt.pins_index)
        0
        >>> bt._add_pin_class(pc)
        >>> len(bt.pins_index)
        1

        """
        assert_type(pin, Pin)
        self._could_add_pin(pin)

        # Verify the pin has matching properties.
        # --------------------------------------------
        assert pin.pin_class is not None

        if pin.port_key is not None and pin.port_index is not None:
            if pin.port_key in self._ports_index:
                if pin.port_index in self._ports_index[pin.port_key]:
                    assert_eq(
                        pin, self._ports_index[pin.port_key][pin.port_index]
                    )

        if pin.block_type_name is not None:
            assert_eq(pin._block_type_name, self.name)

        if pin.block_type_index is not None:
            if pin.block_type_index in self._pins_index:
                assert_eq(pin, self._pins_index[pin.block_type_index])

        # Fill out any unset properties
        # --------------------------------------------
        if pin.block_type_name is None:
            pin._block_type_name = self.name

        if pin.block_type_index is None:
            pin._block_type_index = dict_next_id(self._pins_index)
        self._pins_index[pin.block_type_index] = pin

        if pin.port_key is not None:
            if pin.port_key not in self._ports_index:
                self._ports_index[pin.port_key] = {}

            if pin.port_index is None:
                pin._port_index = dict_next_id(self._ports_index[pin.port_key])
            self._ports_index[pin.port_key][pin.port_index] = pin

    def _add_pin_class(self, pin_class):
        assert_type(pin_class, PinClass)
        for p in pin_class.pins:
            self._could_add_pin(p)

        if pin_class.block_type is None:
            pin_class.block_type = self
        assert self is pin_class.block_type

        for p in pin_class.pins:
            self._add_pin(p)

        if pin_class not in self._pin_classes:
            self._pin_classes.append(pin_class)


class Block(MostlyReadOnly):
    """For <grid_loc> nodes"""

    __slots__ = ["_graph", "_block_type", "_position", "_offset"]

    @property
    def x(self):
        return self.position.x

    @property
    def y(self):
        return self.position.y

    @property
    def pins(self):
        return self.block_type.pins

    @property
    def positions(self):
        for offset in self.block_type.positions:
            yield self.position + offset

    def __init__(
            self,
            g=None,
            block_type_id=None,
            block_type=None,
            position=None,
            offset=Offset(0, 0)
    ):
        assert_type_or_none(g, BlockGrid)
        assert_type_or_none(block_type_id, int)
        assert_type_or_none(block_type, BlockType)
        assert_type_or_none(position, Position)
        assert_type_or_none(offset, Offset)

        if block_type_id is not None:
            if g is not None:
                assert block_type is None
                assert g.block_types is not None
                block_type = g.block_types[block_type_id]
            else:
                raise TypeError("Must provide g with numeric block_type")

        self._graph = g
        self._block_type = block_type
        self._position = position
        self._offset = offset

    @classmethod
    def from_xml(cls, g, grid_loc_node):
        """
        >>> g = BlockGrid()
        >>> g.add_block_type(BlockType(id=0, name="bt"))
        >>> xml_string = '''
        ... <grid_loc x="0" y="0" block_type_id="0" width_offset="0" height_offset="0"/>
        ... '''
        >>> bl1 = Block.from_xml(g, ET.fromstring(xml_string))
        >>> bl1 # doctest: +ELLIPSIS
        Block(graph=BG(0x...), block_type=BlockType(), position=P(x=0, y=0), offset=Offset(w=0, h=0))
        >>>
        >>> xml_string = '''
        ... <grid_loc x="2" y="5" block_type_id="0" width_offset="1" height_offset="2"/>
        ... '''
        >>> bl2 = Block.from_xml(g, ET.fromstring(xml_string))
        >>> bl2 # doctest: +ELLIPSIS
        Block(graph=BG(0x...), block_type=BlockType(), position=P(x=2, y=5), offset=Offset(w=1, h=2))
        """  # noqa: E501
        assert grid_loc_node.tag == "grid_loc"

        block_type_id = int(grid_loc_node.attrib["block_type_id"])
        pos = Position(
            int(grid_loc_node.attrib["x"]), int(grid_loc_node.attrib["y"])
        )
        offset = Offset(
            int(grid_loc_node.attrib["width_offset"]),
            int(grid_loc_node.attrib["height_offset"])
        )
        return Block(
            g=g, block_type_id=block_type_id, position=pos, offset=offset
        )

    def ptc2pin(self, ptc):
        """Return Pin for the given ptc (Pin.block_type_index)"""
        return self.block_type.pins_index[ptc]

    def __str__(self):
        return '%s@%s' % (self.block_type.name, self.position)


class BlockGrid:
    """
    For <grid>
    Stores blocks (tiles)
    Stores grid + type
    Does not have routing
    """

    def __init__(self):
        # block Pos to BlockType
        self.block_grid = {}
        # block id to BlockType
        self.block_types = LookupMap(BlockType)

    def __repr__(self):
        return "BG(0x{:x})".format(id(self))

    def _next_block_type_id(self):
        return len(self.block_types)

    def add_block_type(self, block_type):
        assert_type_or_none(block_type, BlockType)

        if block_type.id is None:
            block_type.id = self.block_types._next_id()

        self.block_types.add(block_type)

    def add_block(self, block):
        assert_type_or_none(block, Block)
        assert block.offset == (0, 0), block
        for pos in block.positions:
            assert (
                pos not in self.block_grid or self.block_grid[pos] is None
                or self.block_grid[pos] is block
            )
            self.block_grid[pos] = block

    @property
    def size(self):
        x_max = max(p.x for p in self.block_grid)
        y_max = max(p.y for p in self.block_grid)
        return Size(x_max + 1, y_max + 1)

    def blocks(self, positions):
        """Get the block objects for the given positions.

        Parameters
        ----------
        positions: sequence of Position

        Returns
        -------
        list of Block
        """
        return [self.block_grid[pos] for pos in positions]

    def block_types_for(self, col=None, row=None):
        ss = []
        for pos in sorted(self.block_grid):
            if col is not None:
                if pos.x != col:
                    continue
            if row is not None:
                if pos.y != row:
                    continue

            ss.append(self.block_grid[pos].block_type)
        return ss

    def blocks_for(self, col=None, row=None):
        ss = []
        for pos in sorted(self.block_grid):
            if col is not None:
                if pos.x != col:
                    continue
            if row is not None:
                if pos.y != row:
                    continue

            block = self.block_grid[pos]
            if block.position != pos:
                continue
            ss.append(block)
        return ss

    def __getitem__(self, pos):
        return self.block_grid[pos]

    def __iter__(self):
        for pos in sorted(self.block_grid):
            block = self.block_grid[pos]
            if block.position != pos:
                continue
            yield self.block_grid[pos]


SegmentTiming = namedtuple("SegmentTiming", ("R_per_meter", "C_per_meter"))


class Segment(MostlyReadOnly):
    """
    A segment.

    Attributes
    ----------
    id : int
    name : str

    timing : SegmentTiming
    """
    __slots__ = [
        "_id",
        "_name",
        "_timing",
    ]

    def __init__(self, id, name, timing=None):
        assert_type(id, int)
        assert_type(name, str)
        assert_type_or_none(timing, SegmentTiming)
        self._id = id
        self._name = name
        self._timing = timing

    @classmethod
    def from_xml(cls, segment_xml):
        """Create Segment object from an ET.Element XML node.

        Parameters
        ----------
        switch_xml : ET._Element

        Returns
        -------
        Segment

        Examples
        --------
        >>> xml_string = '''
        ... <segment id="0" name="span">
        ...     <timing R_per_meter="101" C_per_meter="2.25000005e-14"/>
        ... </segment>
        ... '''
        >>> Segment.from_xml(ET.fromstring(xml_string))
        Segment(id=0, name='span', timing=SegmentTiming(R_per_meter=101.0, C_per_meter=2.25000005e-14))
        """  # noqa: E501
        assert_type(segment_xml, ET._Element)
        seg_id = int(segment_xml.get('id'))
        name = segment_xml.get('name')

        timing = None
        timings = list(segment_xml.iterfind('timing'))
        if len(timings) == 1:
            timing = timings[0]
            timing_r = float(timing.get('R_per_meter', 0))
            timing_c = float(timing.get('C_per_meter', 0))
            timing = SegmentTiming(R_per_meter=timing_r, C_per_meter=timing_c)
        else:
            assert len(timings) == 0
        return cls(seg_id, name, timing)

    def to_xml(self, segments_xml):
        timing_xml = ET.SubElement(
            segments_xml, 'segment', {
                'id': str(self.id),
                'name': self.name
            }
        )
        if self.timing:
            ET.SubElement(
                timing_xml, "timing",
                {k: str(v)
                 for k, v in self.timing.items()}
            )


SwitchTiming = namedtuple("SwitchTiming", ("R", "Cin", "Cout", "Tdel"))
SwitchSizing = namedtuple("SwitchSizing", ("mux_trans_size", "buf_size"))


class SwitchType(enum.Enum):
    MUX = "mux"
    TRISTATE = "tristate"
    PASS_GATE = "pass_gate"
    SHORT = "short"
    BUFFER = "buffer"


class Switch(MostlyReadOnly):
    """A Switch.

    Attributes
    ----------
    id : int
    name : str
    type : SwitchType

    timing : SwitchTiming
    sizing : SwitchSizing
    """
    __slots__ = [
        "_id",
        "_name",
        "_type",
        "_timing",
        "_sizing",
    ]

    def __init__(self, id, type, name, timing=None, sizing=None):
        assert_type(id, int)
        assert_type(type, SwitchType)
        assert_type(name, str)
        assert_type_or_none(timing, SwitchTiming)
        assert_type_or_none(sizing, SwitchSizing)
        self._id = id
        self._name = name
        self._type = type
        self._timing = timing
        self._sizing = sizing

    def to_xml(self, parent_node):
        sw_node = ET.Element(
            "switch",
            attrib=OrderedDict(
                (
                    ("id", str(self._id)),
                    ("name", self._name),
                    ("type", self._type.value),
                )
            )
        )
        ET.SubElement(
            sw_node,
            "timing",
            attrib=OrderedDict(
                (
                    ("R", str(self._timing.R)),
                    ("Cin", str(self._timing.Cin)),
                    ("Cout", str(self._timing.Cout)),
                    ("Cinternal", "0"),
                    ("Tdel", str(self._timing.Tdel)),
                )
            )
        )
        ET.SubElement(
            sw_node,
            "sizing",
            attrib=OrderedDict(
                (
                    ("mux_trans_size", str(self._sizing.mux_trans_size)),
                    ("buf_size", str(self._sizing.buf_size)),
                )
            ),
        )
        if parent_node is not None:
            parent_node.append(sw_node)
        return sw_node

    @classmethod
    def from_xml(cls, switch_xml):
        """Create Switch object from an ET._Element XML node.

        Parameters
        ----------
        switch_xml : ET._Element

        Returns
        -------
        Switch

        Examples
        --------

        >>> xml_string = '''
        ... <switch id="0" type="mux" name="buffer">
        ...  <timing R="551" Cin="7.70000012e-16" Cout="4.00000001e-15" Tdel="5.80000006e-11"/>
        ...  <sizing mux_trans_size="2.63073993" buf_size="27.6459007"/>
        ... </switch>
        ... '''
        >>> sw = Switch.from_xml(ET.fromstring(xml_string))
        >>> sw
        Switch(id=0, name='buffer', type=<SwitchType.MUX: 'mux'>, timing=SwitchTiming(R=551.0, Cin=7.70000012e-16, Cout=4.00000001e-15, Tdel=5.80000006e-11), sizing=SwitchSizing(mux_trans_size=2.63073993, buf_size=27.6459007))
        >>> print(ET.tostring(sw.to_xml(None), pretty_print=True).decode('utf-8').strip())
        <switch id="0" name="buffer" type="mux">
          <timing R="551.0" Cin="7.70000012e-16" Cout="4.00000001e-15" Cinternal="0" Tdel="5.80000006e-11"/>
          <sizing mux_trans_size="2.63073993" buf_size="27.6459007"/>
        </switch>
        """  # noqa: E501
        assert_type(switch_xml, ET._Element)
        sw_id = int(switch_xml.attrib.get('id'))
        sw_type = SwitchType(switch_xml.attrib.get('type'))
        name = switch_xml.attrib.get('name')

        timing = None
        timings = list(switch_xml.iterfind('timing'))
        if len(timings) == 1:
            timing = timings[0]
            timing_r = float(timing.get('R', 0))
            timing_cin = float(timing.get('Cin', 0))
            timing_cout = float(timing.get('Cout', 0))
            timing_tdel = float(timing.get('Tdel', 0))
            timing = SwitchTiming(
                timing_r, timing_cin, timing_cout, timing_tdel
            )
        else:
            assert len(timings) == 0

        sizing = None
        sizings = list(switch_xml.iterfind('sizing'))
        if len(sizings) == 1:
            sizing = sizings[0]
            sizing_mux_trans_size = float(sizing.get('mux_trans_size', 0))
            sizing_buf_size = float(sizing.get('buf_size', 0))
            sizing = SwitchSizing(sizing_mux_trans_size, sizing_buf_size)
        else:
            assert len(sizings) == 0

        return cls(
            id=sw_id, type=sw_type, name=name, timing=timing, sizing=sizing
        )


class LookupMap:
    """Store a way to lookup by ID or name."""

    def __init__(self, obj_type):
        self.obj_type = obj_type
        self._names = {}
        self._ids = {}

    def clear(self):
        self._names.clear()
        self._ids.clear()

    def add(self, obj):
        assert_type(obj, self.obj_type)
        assert_type(obj.id, int)
        assert_type(obj.name, str)

        assert obj.id not in self._ids, obj.id
        assert obj.name not in self._names, obj.name

        self._names[obj.name] = obj
        self._ids[obj.id] = obj

    def __getitem__(self, key):
        if isinstance(key, int):
            return self._ids[key]
        elif isinstance(key, str):
            return self._names[key]

    def __iter__(self):
        return self._names.itervalues()

    @property
    def names(self):
        return self._names.iterkeys()

    @property
    def ids(self):
        return self._ids.iterkeys()

    def next_id(self):
        return max(self._ids.keys()) + 1


class RoutingGraphPrinter:
    @classmethod
    def node(cls, xml_node, block_grid=None):
        """Get a globally unique name for an `node` in the rr_nodes.

        Without a block graph, the name won't include the block type.
        >>> RoutingGraphPrinter.node(ET.fromstring('''
        ... <node id="0" type="SINK" capacity="1">
        ...   <loc xlow="0" ylow="3" xhigh="0" yhigh="3" ptc="0"/>
        ...   <timing R="0" C="0"/>
        ... </node>
        ... '''))
        '0 X000Y003[00].SINK-<'
        >>> RoutingGraphPrinter.node(ET.fromstring('''
        ... <node id="1" type="SOURCE" capacity="1">
        ...   <loc xlow="1" ylow="2" xhigh="1" yhigh="2" ptc="1"/>
        ...   <timing R="0" C="0"/>
        ... </node>
        ... '''))
        '1 X001Y002[01].SRC-->'
        >>> RoutingGraphPrinter.node(ET.fromstring('''
        ... <node id="2" type="IPIN" capacity="1">
        ...   <loc xlow="2" ylow="1" xhigh="2" yhigh="1" side="TOP" ptc="0"/>
        ...   <timing R="0" C="0"/>
        ... </node>
        ... '''))
        '2 X002Y001[00].T-PIN<'
        >>> RoutingGraphPrinter.node(ET.fromstring('''
        ... <node id="6" type="OPIN" capacity="1">
        ...   <loc xlow="3" ylow="0" xhigh="3" yhigh="0" side="RIGHT" ptc="1"/>
        ...   <timing R="0" C="0"/>
        ... </node>
        ... '''))
        '6 X003Y000[01].R-PIN>'

        With a block graph, the name will include the block type.
        >>> bg = simple_test_block_grid()
        >>> RoutingGraphPrinter.node(ET.fromstring('''
        ... <node id="0" type="SINK" capacity="1">
        ...   <loc xlow="0" ylow="3" xhigh="0" yhigh="3" ptc="0"/>
        ...   <timing R="0" C="0"/>
        ... </node>
        ... '''), bg)
        '0 X000Y003_INBLOCK[00].C[3:0]-SINK-<'
        >>> RoutingGraphPrinter.node(ET.fromstring('''
        ... <node id="1" type="SOURCE" capacity="1">
        ...   <loc xlow="1" ylow="2" xhigh="1" yhigh="2" ptc="1"/>
        ...   <timing R="0" C="0"/>
        ... </node>
        ... '''), bg)
        '1 X001Y002_DUALBLK[01].B[0]-SRC-->'
        >>> RoutingGraphPrinter.node(ET.fromstring('''
        ... <node id="2" type="IPIN" capacity="1">
        ...   <loc xlow="2" ylow="1" xhigh="2" yhigh="1" side="TOP" ptc="0"/>
        ...   <timing R="0" C="0"/>
        ... </node>
        ... '''), bg)
        '2 X002Y001_DUALBLK[00].A[0]-T-PIN<'
        >>> RoutingGraphPrinter.node(ET.fromstring('''
        ... <node id="6" type="OPIN" capacity="1">
        ...   <loc xlow="3" ylow="0" xhigh="3" yhigh="0" side="RIGHT" ptc="1"/>
        ...   <timing R="0" C="0"/>
        ... </node>
        ... '''), bg)
        '6 X003Y000_OUTBLOK[01].D[1]-R-PIN>'

        Edges don't require a block graph, as they have the full information on
        the node.
        >>> RoutingGraphPrinter.node(ET.fromstring('''
        ... <node capacity="1" direction="INC_DIR" id="372" type="CHANX">
        ...   <loc ptc="4" xhigh="3" xlow="3" yhigh="0" ylow="0"/>
        ...   <timing C="2.72700004e-14" R="101"/>
        ...   <segment segment_id="1"/>
        ... </node>
        ... '''))
        '372 X003Y000--04->X003Y000'
        >>> RoutingGraphPrinter.node(ET.fromstring('''
        ... <node capacity="1" direction="DEC_DIR" id="373" type="CHANY">
        ...   <loc ptc="5" xhigh="3" xlow="3" yhigh="0" ylow="0"/>
        ...   <timing C="2.72700004e-14" R="101"/>
        ...   <segment segment_id="1"/>
        ... </node>
        ... '''))
        '373 X003Y000<|05||X003Y000'
        >>> RoutingGraphPrinter.node(ET.fromstring('''
        ... <node capacity="1" direction="BI_DIR" id="374" type="CHANX">
        ...   <loc ptc="5" xhigh="3" xlow="3" yhigh="0" ylow="0"/>
        ...   <timing C="2.72700004e-14" R="101"/>
        ...   <segment segment_id="1"/>
        ... </node>
        ... '''))
        '374 X003Y000<-05->X003Y000'

        'X002Y003_BT[00].T-PIN<'
        'X002Y003_BT[00].L-PIN<'
        'X002Y003_BT[00].R-PIN<'
        'X002Y003_BT[00].B-PIN<'
        'X002Y003_BT[00].SINK<'
        'X002Y003_BT[00].SRC>'
        'X002Y003_BT[00].T-PIN>'
        'X002Y003_BT[00].L-PIN>'
        'X002Y003_BT[00].R-PIN>'
        'X002Y003_BT[00].B-PIN>'

        'X003Y000--05->X003Y000'
        'X003Y000<-05--X003Y000'

        'X003Y000||05|>X003Y000'
        'X003Y000<|05||X003Y000'
        """
        assert_type(xml_node, ET._Element)

        loc_node = list(xml_node.iterfind("./loc"))[0]
        low = Position(
            int(loc_node.attrib["xlow"]), int(loc_node.attrib["ylow"])
        )
        high = Position(
            int(loc_node.attrib["xhigh"]), int(loc_node.attrib["yhigh"])
        )
        ptc = int(loc_node.attrib["ptc"])
        edge = loc_node.attrib.get("side", " ")[0]

        if block_grid is not None:
            block = block_grid[low]
        else:
            block = None

        node_id = RoutingGraph._get_xml_id(xml_node)
        type_str = None
        node_type = RoutingNodeType.from_xml(xml_node)
        if False:
            pass
        elif node_type in (RoutingNodeType.CHANX, RoutingNodeType.CHANY):
            direction = xml_node.attrib.get("direction")
            direction_fmt = {
                'INC_DIR': '{f}{f}{ptc:02d}{f}>',
                'DEC_DIR': '<{f}{ptc:02d}{f}{f}',
                'BI_DIR': '<{f}{ptc:02d}{f}>',
            }.get(direction, None)
            assert direction_fmt, "Bad direction %s" % direction
            return "{} X{:03d}Y{:03d}{}X{:03d}Y{:03d}".format(
                node_id, low.x, low.y,
                direction_fmt.format(
                    f={
                        RoutingNodeType.CHANX: '-',
                        RoutingNodeType.CHANY: '|'
                    }[node_type],
                    ptc=ptc
                ), high.x, high.y
            )
        elif node_type is RoutingNodeType.SINK:
            type_str = "SINK-<"
            # FIXME: Check high == block.position + block.block_type.size
        elif node_type is RoutingNodeType.SOURCE:
            type_str = "SRC-->"
            # FIXME: Check high == block.position + block.block_type.size
        elif node_type is RoutingNodeType.IPIN:
            assert edge in "TLRB", edge
            type_str = "{}-PIN<".format(edge)
        elif node_type is RoutingNodeType.OPIN:
            assert edge in "TLRB", edge
            type_str = "{}-PIN>".format(edge)
        else:
            assert False, "Unknown node_type {}".format(node_type)
        assert type_str

        if block_grid is not None:
            block = block_grid[low]
            block_name = "_" + block.block_type.name
            x = block.position.x
            y = block.position.y

            if node_type in (RoutingNodeType.IPIN, RoutingNodeType.OPIN):
                try:
                    ptc_str = "[{:02d}].{}-".format(
                        ptc, block.block_type.pins_index[ptc].name
                    )
                except TypeError:
                    ptc_str = "[{:02d} :-(].".format(ptc)
            elif node_type in (RoutingNodeType.SINK, RoutingNodeType.SOURCE):
                try:
                    pc = block.block_type.pin_classes[ptc]
                    ptc_str = "[{:02d}].{}-".format(ptc, pc.port_name)
                except TypeError:
                    ptc_str = "[{:02d} :-(].".format(ptc)
            else:
                ptc_str = "[{:02d}].".format(ptc)

        else:
            block_name = ""
            x = low.x
            y = low.y
            ptc_str = "[{:02d}].".format(ptc)

        return "{id} X{x:03d}Y{y:03d}{t}{i}{s}".format(
            id=node_id, t=block_name, x=x, y=y, i=ptc_str, s=type_str
        )

    @classmethod
    def edge(cls, routing, xml_node, block_grid=None, flip=False):
        """Get a globally unique name for an `edge` in the rr_edges.

        An edge goes between two `node` objects.

        >>> bg = simple_test_block_grid()
        >>> xml_string1 = '''
        ... <rr_graph>
        ...  <rr_nodes>
        ...   <node id="0" type="SOURCE" capacity="1">
        ...     <loc xlow="0" ylow="3" xhigh="0" yhigh="3" ptc="0"/>
        ...     <timing R="0" C="0"/>
        ...   </node>
        ...   <node capacity="1" direction="INC_DIR" id="1" type="CHANY">
        ...     <loc ptc="5" xhigh="3" xlow="0" yhigh="0" ylow="3"/>
        ...     <timing C="2.72700004e-14" R="101"/>
        ...     <segment segment_id="1"/>
        ...   </node>
        ...  </rr_nodes>
        ... <rr_edges />
        ... <switches />
        ... </rr_graph>
        ... '''
        >>> rg = RoutingGraph(xml_graph=ET.fromstring(xml_string1))
        >>> RoutingGraphPrinter.edge(rg, ET.fromstring('''
        ... <edge sink_node="1" src_node="0" switch_id="1"/>
        ... '''))
        '0 X000Y003[00].SRC--> ->>- 1 X000Y003||05|>X003Y000'
        >>> RoutingGraphPrinter.edge(rg, ET.fromstring('''
        ... <edge sink_node="1" src_node="0" switch_id="1"/>
        ... '''), bg)
        '0 X000Y003_INBLOCK[00].C[3:0]-SRC--> ->>- 1 X000Y003||05|>X003Y000'
        """
        src_node, snk_node = routing.nodes_for_edge(xml_node)
        if flip:
            s = "{} -<<- {}"
        else:
            s = "{} ->>- {}"
        return s.format(
            cls.node(src_node, block_grid=block_grid),
            cls.node(snk_node, block_grid=block_grid)
        )


class MappingLocalNames(dict):
    """
    Class for keeping track of the local name for a given node.
    """

    def __init__(self, *args, type=ET._Element, **kw):
        self.type = type
        self.localnames = {}
        dict.__init__(self, *args, **kw)

    def add(self, pos, name, value):
        assert_type(value, self.type)
        assert_type(pos, Position)
        assert_type(name, str)

        self[(pos, name)] = value

    def clear(self):
        self.localnames.clear()
        dict.clear(self)

    def __setitem__(self, key, value):
        """
        map[(Position, name)] = type
        """
        assert_type(value, self.type)

        pos, name = key
        assert_type(pos, Position)
        assert_type(name, str)

        if pos not in self.localnames:
            self.localnames[pos] = set()
        if key in self:
            assert_eq(self[key], value, msg="%s already exist!" % (key, ))
        self.localnames[pos].add(name)

        dict.__setitem__(self, key, value)

    def __getitem__(self, key):
        """
        map[pos] -> list of node_id
        map[(position, name)] = node_id
        """
        if isinstance(key, Position):
            return self.localnames[key]
        else:
            assert_type(key, tuple)
            assert_eq(len(key), 2)
            assert_type(key[0], Position)
            assert_type(key[1], str)
            return dict.__getitem__(self, key)


class MappingGlobalNames(dict):
    """
    Class for keeping track of the global names for a given node.
    """

    def add(self, name, xml_node):
        self[name] = xml_node

    def __setitem__(self, name, xml_node):
        """
        map[name] = ET._Element
        """
        assert_type(name, str)
        assert_type(xml_node, ET._Element)
        assert name not in self, "{} in {}".format(name, self)
        dict.__setitem__(self, name, xml_node)


_RoutingNodeTiming = namedtuple("RoutingNodeTiming", ("R", "C"))


class RoutingNodeTiming(_RoutingNodeTiming):
    def to_xml(self):
        return ET.Element('timing', {'R': str(self.R), 'C': str(self.C)})


class RoutingNodeType(enum.Enum):
    IPIN = 'IPIN'
    OPIN = 'OPIN'
    SINK = 'SINK'
    SOURCE = 'SOURCE'
    CHANX = 'CHANX'
    CHANY = 'CHANY'

    @classmethod
    def from_xml(cls, xml_node):
        assert xml_node.tag == "node", xml_node
        return RoutingNodeType(xml_node.attrib["type"])

    @property
    def track(self):
        """Is this RoutingNodeType a track?"""
        return self in (RoutingNodeType.CHANX, RoutingNodeType.CHANY)

    @property
    def output(self):
        """Is this RoutingNodeType an output?"""
        return self in (RoutingNodeType.OPIN, RoutingNodeType.SOURCE)

    @property
    def input(self):
        """Is this RoutingNodeType an input?"""
        return self in (RoutingNodeType.IPIN, RoutingNodeType.SINK)

    @property
    def pin(self):
        """Is this RoutingNodeType an pin?"""
        return self in (RoutingNodeType.OPIN, RoutingNodeType.IPIN)

    @property
    def pin_class(self):
        """Is this RoutingNodeType an pin_class?"""
        return self in (RoutingNodeType.SINK, RoutingNodeType.SOURCE)

    @property
    def can_sink(self):
        """Can be a destination of an edge."""
        # -> XXX
        return self in (
            RoutingNodeType.IPIN, RoutingNodeType.CHANX, RoutingNodeType.CHANY
        )

    @property
    def can_source(self):
        """Can be a source of an edge."""
        # XXX ->
        return self in (
            RoutingNodeType.OPIN, RoutingNodeType.CHANX, RoutingNodeType.CHANY
        )


def _metadata(parent_node):
    elements = list(parent_node.iterfind("metadata"))
    if len(elements) == 1:
        return elements[0]
    return None


def _set_metadata(parent_node, key, value, offset=None):
    metadata = _metadata(parent_node)
    if metadata is None:
        metadata = ET.SubElement(parent_node, "metadata")

    attribs = [("name", key)]

    metanode = None
    for node in metadata.iterfind("./meta"):
        matches = True
        for n, v in attribs:
            if node.attrib.get(n, None) != v:
                matches = False
                break
        if matches:
            metanode = node
            break
    else:
        metanode = ET.SubElement(metadata, "meta", attrib=dict(attribs))

    for n, v in attribs:
        metanode.attrib[n] = v
    metanode.text = str(value)


_get_metadata_sentry = []


def _get_metadata(parent_node, key, default=_get_metadata_sentry):
    metanode = None

    metadata = _metadata(parent_node)
    if metadata is not None:
        for node in metadata.iterfind("./meta"):
            if node.attrib["name"] == key:
                metanode = node
                break

    if metanode is None:
        if default is not _get_metadata_sentry:
            return default
        else:
            raise ValueError(
                "No metadata {} on\n{}".format(
                    key,
                    ET.tostring(parent_node,
                                pretty_print=True).decode().strip()
                )
            )

    return metanode.text


class RoutingNodeSide(enum.Enum):
    LEFT = 'LEFT'
    RIGHT = 'RIGHT'
    TOP = 'TOP'
    BOTTOM = 'BOTTOM'


class RoutingNodeDir(enum.Enum):
    INC_DIR = 'INC_DIR'
    DEC_DIR = 'DEC_DIR'
    BI_DIR = 'BI_DIR'


class RoutingNode(ET.ElementBase):
    TAG = "node"

    def set_metadata(self, key, value, offset=None):
        _set_metadata(self, key, value, offset=offset)

    def get_metadata(self, key, default=_get_metadata_sentry):
        return _get_metadata(self, key, default)


class RoutingEdge(ET.ElementBase):
    TAG = "edge"

    def set_metadata(self, key, value, offset=None):
        _set_metadata(self, key, value, offset=offset)

    def get_metadata(self, key, default=_get_metadata_sentry):
        return _get_metadata(self, key, default)


class RoutingGraph:
    """
    The RoutingGraph object keeps track of the actual "graph" found in
    rr_graph.xml files.

    The Graph is represented by two XML node types, they are; `<rr_nodes>` and
    `<rr_edges>` objects which are connected by the ID objects.


    """

    @staticmethod
    def _get_xml_id(xml_node):
        node_id = xml_node.get('id', None)
        if node_id is not None:
            node_id = int(node_id)
        return node_id

    @staticmethod
    def set_metadata(node, key, value, offset=None):
        """
        Examples
        --------
        >>> # Works with edges
        >>> r = simple_test_routing()
        >>> sw = Switch(id=0, type=SwitchType.MUX, name="sw")
        >>> r.create_edge_with_ids(0, 1, sw)
        >>> e1 = r.get_edge_by_id(4)
        >>> # Call directly on the edge
        >>> e1.get_metadata("test", default=":-(")
        ':-('
        >>> e1.set_metadata("test", "123")
        >>> print(ET.tostring(e1, pretty_print=True).decode().strip())
        <edge src_node="0" sink_node="1" switch_id="0">
          <metadata>
            <meta name="test">123</meta>
          </metadata>
        </edge>
        >>> e1.get_metadata("test", default=":-(")
        '123'
        >>> # Or via the routing object
        >>> r.set_metadata(e1, "test", "234")
        >>> r.get_metadata(e1, "test")
        '234'
        >>> # Exception if no default provided
        >>> r.get_metadata(e1, "not_found")
        Traceback (most recent call last):
            ...
        ValueError: No metadata not_found on
        <edge src_node="0" sink_node="1" switch_id="0">
          <metadata>
            <meta name="test">234</meta>
          </metadata>
        </edge>
        >>> r.set_metadata(e1, "test", 1)
        >>> # Works with nodes
        >>> n1 = r.get_node_by_id(0)
        >>> # Call directly on the node
        >>> n1.get_metadata("test", default=":-(")
        ':-('
        >>> n1.set_metadata("test", "123")
        >>> print(ET.tostring(n1, pretty_print=True).decode().strip())
        <node id="0" type="SOURCE" capacity="1">
          <loc xlow="0" ylow="0" xhigh="0" yhigh="0" ptc="0"/>
          <timing R="0" C="0"/>
          <metadata>
            <meta name="test">123</meta>
          </metadata>
        </node>
        >>> n1.get_metadata("test", default=":-(")
        '123'
        >>> # Or via the routing object
        >>> r.set_metadata(n1, "test", "234")
        >>> r.get_metadata(n1, "test")
        '234'

        """
        _set_metadata(node, key, value, offset)

    @staticmethod
    def get_metadata(node, key, default=_get_metadata_sentry):
        return _get_metadata(node, key, default)

    def __init__(self, xml_graph=None, verbose=True, clear_fabric=False):
        """
        >>> g = simple_test_graph()
        """
        self.verbose = verbose

        # Lookup XML node for given an ID
        self.id2element = {RoutingNode: {}, RoutingEdge: {}}
        # Names for each node at a given position
        self.localnames = MappingLocalNames(type=ET._Element)
        # Global names for each node
        self.globalnames = MappingGlobalNames()

        self._cache_nodes2edges = {}

        if xml_graph is None:
            xml_graph = ET.Element("rr_graph")
            ET.SubElement(xml_graph, "rr_nodes")
            ET.SubElement(xml_graph, "rr_edges")

        self._xml_graph = xml_graph

        if clear_fabric:
            self.clear()
        else:
            for node in self._xml_parent(RoutingNode):
                self._add_xml_element(node, existing=True)
            for edge in self._xml_parent(RoutingEdge):
                self._add_xml_element(edge, existing=True, add_id_attrib=False)
            self._build_cache_node2edge()

    def clear(self):
        """Delete the existing rr_nodes and rr_edges."""
        self._xml_parent(RoutingNode).clear()
        self._xml_parent(RoutingEdge).clear()

        self.id2element[RoutingNode].clear()
        self.id2element[RoutingEdge].clear()

        self.localnames.clear()
        self.globalnames.clear()

        self._cache_nodes2edges.clear()

    def _xml_type(self, xml_node):
        """Get the type of an ET._Element object.

        Parameters
        ----------
        xml_node: RoutingNode or RoutingEdge

        Returns
        -------
        RoutingNode.__class__ or RoutingEdge.__class___
        """
        if xml_node.tag == "node":
            return RoutingNode
        elif xml_node.tag == "edge":
            return RoutingEdge
        else:
            assert False, xml_node.tag

    def _xml_parent(self, xml_type):
        """Get the ET._Element parent for a give type."""
        if xml_type is RoutingNode:
            return single_element(self._xml_graph, 'rr_nodes')
        elif xml_type is RoutingEdge:
            return single_element(self._xml_graph, 'rr_edges')

    def _ids_map(self, xml_type):
        """Get the ID mapping for a give type."""
        return self.id2element[xml_type]

    def _next_id(self, xml_type):
        """Get the next ID available for a give type."""
        return len(self._ids_map(xml_type))

    def _add_xml_element(self, xml_node, existing=False, add_id_attrib=True):
        """Add an existing ET._Element object to the map.

        Parameters
        ----------
        xml_node: RoutingNode or RoutingEdge
        """
        xml_type = self._xml_type(xml_node)

        # If the XML node doesn't have an ID, allocate it one.
        node_id = self._get_xml_id(xml_node)
        if node_id is None:
            new_node_id = self._next_id(xml_type)
            if add_id_attrib:
                xml_node.attrib['id'] = str(new_node_id)
        else:
            new_node_id = node_id

        # Make sure we don't duplicate IDs
        ids2element = self._ids_map(xml_type)
        if new_node_id in ids2element:
            assert xml_node is ids2element[
                new_node_id], "Error at {}: {} ({}) is not {} ({})".format(
                    new_node_id, xml_node, ET.tostring(xml_node),
                    ids2element[new_node_id],
                    ET.tostring(ids2element[new_node_id])
                )
        else:
            ids2element[new_node_id] = xml_node

        # FIXME: Make sure the node is included on the XML parent.
        # if node_id is None:
        #    pass
        if not existing:
            parent = self._xml_parent(xml_type)
            parent.append(xml_node)
            self._add_cache_node2edge(xml_node, new_node_id)

    def _add_cache_node2edge(self, xml_node, node_id):
        xml_type = self._xml_type(xml_node)
        if xml_type == RoutingNode:
            assert node_id not in self._cache_nodes2edges
            self._cache_nodes2edges[node_id] = set()
        elif xml_type == RoutingEdge:
            src_id, snk_id = self.node_ids_for_edge(xml_node)
            self._cache_nodes2edges[src_id].add(node_id)
            self._cache_nodes2edges[snk_id].add(node_id)
        else:
            assert False, "Unknown xml_node {}".format(xml_node)

    def _build_cache_node2edge(self):
        assert len(self._cache_nodes2edges) == 0
        for node_id, node in self._ids_map(RoutingNode).items():
            self._add_cache_node2edge(node, node_id)

        for edge_id, edge in self._ids_map(RoutingEdge).items():
            self._add_cache_node2edge(edge, edge_id)

    def get_by_name(self, name, pos=None, default=_DEFAULT_MARKER):
        """Get the RoutingNode using name (and pos).

        Parameters
        ----------
        name: str
        pos: Position
        default: Optional value to return if not found.

        Returns
        -------
        RoutingNode (ET._Element)

        Examples
        --------
        FIXME: Add example.

        """
        assert_type(name, str)
        assert_type_or_none(pos, Position)

        r = _DEFAULT_MARKER
        if pos is not None:
            r = self.localnames.get((pos, name), _DEFAULT_MARKER)
        if r is _DEFAULT_MARKER:
            r = self.globalnames.get(name, _DEFAULT_MARKER)
        if r is not _DEFAULT_MARKER:
            return r
        if default is not _DEFAULT_MARKER:
            return default
        else:
            raise KeyError(
                "No node named {} globally or locally at {}".format(name, pos)
            )

    def get_node_by_id(self, node_id):
        """Get the RoutingNode from a given ID.

        Parameters
        ----------
        node_id: int

        Returns
        -------
        RoutingNode (ET._Element)

        Examples
        --------
        >>> r = simple_test_routing()
        >>> RoutingGraphPrinter.node(r.get_node_by_id(0))
        '0 X000Y000[00].SRC-->'
        >>> RoutingGraphPrinter.node(r.get_node_by_id(1))
        '1 X000Y000[00].R-PIN>'
        >>> RoutingGraphPrinter.node(r.get_node_by_id(2))
        '2 X000Y000<-00->X000Y010'
        >>> RoutingGraphPrinter.node(r.get_node_by_id(3))
        '3 X000Y010[00].L-PIN<'
        >>> RoutingGraphPrinter.node(r.get_node_by_id(4))
        '4 X000Y010[00].SINK-<'
        >>> RoutingGraphPrinter.node(r.get_node_by_id(5))
        Traceback (most recent call last):
            ...
        KeyError: 5

        """
        return self._ids_map(RoutingNode)[node_id]

    def get_edge_by_id(self, node_id):
        """Get the RoutingEdge from a given ID.

        Parameters
        ----------
        node_id: int

        Returns
        -------
        RoutingEdge (ET._Element)

        Examples
        --------
        >>> r = simple_test_routing()
        >>> RoutingGraphPrinter.edge(r, r.get_edge_by_id(0))
        '0 X000Y000[00].SRC--> ->>- 1 X000Y000[00].R-PIN>'
        >>> RoutingGraphPrinter.edge(r, r.get_edge_by_id(1))
        '1 X000Y000[00].R-PIN> ->>- 2 X000Y000<-00->X000Y010'
        >>> RoutingGraphPrinter.edge(r, r.get_edge_by_id(2))
        '2 X000Y000<-00->X000Y010 ->>- 3 X000Y010[00].L-PIN<'
        >>> RoutingGraphPrinter.edge(r, r.get_edge_by_id(3))
        '3 X000Y010[00].L-PIN< ->>- 4 X000Y010[00].SINK-<'
        >>> r.get_edge_by_id(4)
        Traceback (most recent call last):
            ...
        KeyError: 4
        """
        return self._ids_map(RoutingEdge)[node_id]

    @staticmethod
    def node_ids_for_edge(xml_node):
        """Return the node ids associated with given edge.

        Parameters
        ----------
        xml_node: RoutingEdge

        Returns
        -------
        (int, int)
            Source RoutingNode ID, Sink RoutingNode ID

        Example
        -------
        >>> e = ET.fromstring('<edge src_node="0" sink_node="1" switch_id="1"/>')
        >>> RoutingGraph.node_ids_for_edge(e)
        (0, 1)
        """
        assert xml_node.tag == 'edge'
        src_node_id = int(xml_node.attrib.get("src_node", 0))
        snk_node_id = int(xml_node.attrib.get("sink_node", 0))
        return src_node_id, snk_node_id

    def nodes_for_edge(self, xml_node):
        """Return all nodes associated with given edge.

        Parameters
        ----------
        xml_node: RoutingEdge

        Returns
        -------
        (RoutingNode, RoutingNode)
            Source RoutingNode, Sink RoutingNode - XML nodes (ET._Element)
            associated with the given edge.

        Example
        --------
        >>> r = simple_test_routing()
        >>> e1 = r.get_edge_by_id(0)
        >>> RoutingGraphPrinter.edge(r, e1)
        '0 X000Y000[00].SRC--> ->>- 1 X000Y000[00].R-PIN>'
        >>> [RoutingGraphPrinter.node(n) for n in r.nodes_for_edge(e1)]
        ['0 X000Y000[00].SRC-->', '1 X000Y000[00].R-PIN>']
        >>> e2 = r.get_edge_by_id(1)
        >>> RoutingGraphPrinter.edge(r, e2)
        '1 X000Y000[00].R-PIN> ->>- 2 X000Y000<-00->X000Y010'
        >>> [RoutingGraphPrinter.node(n) for n in r.nodes_for_edge(e2)]
        ['1 X000Y000[00].R-PIN>', '2 X000Y000<-00->X000Y010']
        """
        src_node_id, snk_node_id = self.node_ids_for_edge(xml_node)

        ids2element = self._ids_map(RoutingNode)
        assert snk_node_id in ids2element, "{:r} in {}".format(
            snk_node_id, ids2element.keys()
        )
        assert src_node_id in ids2element, "{:r} in {}".format(
            src_node_id, ids2element.keys()
        )
        return ids2element[src_node_id], ids2element[snk_node_id]

    def edges_for_allnodes(self):
        """Return a mapping between edges and associated nodes.

        Returns
        -------
        dict[int] -> list of RoutingEdge
            Mapping from RoutingNode ID to XML edges (ET._Element) associated
            with the given node.

        Example
        -------

        """
        return MappingProxyType(self._cache_nodes2edges)

    def edges_for_node(self, xml_node):
        """Return all edges associated with given node.

        Parameters
        ----------
        xml_node: RoutingNode

        Returns
        -------
        list of RoutingEdge
            XML edges (ET._Element) associated with the given node.

        Examples
        --------
        >>> r = simple_test_routing()
        >>> [RoutingGraphPrinter.edge(r, e) for e in r.edges_for_node(r.get_node_by_id(1))]
        ['0 X000Y000[00].SRC--> ->>- 1 X000Y000[00].R-PIN>', '1 X000Y000[00].R-PIN> ->>- 2 X000Y000<-00->X000Y010']
        >>> [RoutingGraphPrinter.edge(r, e) for e in r.edges_for_node(r.get_node_by_id(2))]
        ['1 X000Y000[00].R-PIN> ->>- 2 X000Y000<-00->X000Y010', '2 X000Y000<-00->X000Y010 ->>- 3 X000Y010[00].L-PIN<']
        """  # noqa: E501
        return [
            self.get_edge_by_id(i)
            for i in self.edges_for_allnodes()[self._get_xml_id(xml_node)]
        ]

    ######################################################################
    # Constructor methods
    ######################################################################

    def create_node(
            self,
            low,
            high,
            ptc,
            ntype,
            direction=None,
            segment_id=None,
            side=None,
            timing=None,
            capacity=1,
            metadata={}
    ):
        """Create an node.

        Parameters
        ----------
        low : Position
        high : Position
        ptc : int
        ntype : RoutingNodeType

        direction : RoutingNodeDir, optional
        segment_id : int, optional
        side : RoutingNodeSide, optional
        timing : RoutingNodeTiming, optional
        metadata : {str: Any}, optional

        Returns
        -------
        RoutingNode
        """
        if isinstance(ntype, str):
            ntype = RoutingNodeType[ntype]

        # <node>
        attrib = OrderedDict(
            (
                ('id', str(self._next_id(RoutingNode))),
                ('type', ntype.value),
                ('capacity', str(capacity)),
            )
        )
        if ntype.track:
            assert direction is not None
            attrib['direction'] = direction.value
        elif not ntype.pin_class:
            assert low == high, (low, high)

        node = RoutingNode(attrib=attrib)

        # <loc> needed for all nodes
        attrib = {
            'xlow': str(low.x),
            'ylow': str(low.y),
            'xhigh': str(high.x),
            'yhigh': str(high.y),
            'ptc': str(ptc),
        }
        if ntype.pin:
            assert_type(side, RoutingNodeSide)
            attrib['side'] = side.value
        else:
            assert side is None
        ET.SubElement(node, 'loc', attrib)

        # <timing> needed for all nodes
        if timing is None:
            if ntype.track:
                # Seems to confuse VPR when 0
                # XXX: consider requiring the user to give instead of defaulting
                timing = RoutingNodeTiming(R=1e-9, C=1e-9)
            else:
                timing = RoutingNodeTiming(R=0, C=0)
        assert len(timing) == 2
        assert_type(timing, RoutingNodeTiming)
        assert_type(timing.R, (float, int))
        assert_type(timing.C, (float, int))
        node.append(timing.to_xml())

        # <segment> needed for CHANX/CHANY nodes
        if ntype.track:
            assert_type(segment_id, int)
            ET.SubElement(node, 'segment', {'segment_id': str(segment_id)})

        for offset, values in metadata.items():
            for k, v in values.items():
                node.set_metadata(k, v, offset=offset)

        self._add_xml_element(node)

        return node

    def create_edge_with_ids(
            self, src_node_id, sink_node_id, switch, metadata={}, bidir=None
    ):
        """Create an RoutingEdge between given IDs for two RoutingNodes.

        Parameters
        ----------
        src_node_id : int
        sink_node_id : int
        switch : Switch
        metadata : {str: Any}, optional

        Returns
        -------
        RoutingEdge (ET._Element)

        Examples
        --------
        >>> r = simple_test_routing()
        >>> sw = Switch(id=0, type=SwitchType.MUX, name="sw")
        >>> r.create_edge_with_ids(0, 1, sw)
        >>> e1 = r.get_edge_by_id(4)
        >>> RoutingGraphPrinter.edge(r, e1)
        '0 X000Y000[00].SRC--> ->>- 1 X000Y000[00].R-PIN>'

        The code protects against invalid edge creation;
        >>> r.create_edge_with_ids(0, 2, sw)
        Traceback (most recent call last):
          ...
        TypeError: RoutingNodeType.SOURCE -> RoutingNodeType.CHANX not valid, Only SOURCE -> OPIN is valid
        0 X000Y000[00].SRC--> b'<node id="0" type="SOURCE" capacity="1"><loc xlow="0" ylow="0" xhigh="0" yhigh="0" ptc="0"/><timing R="0" C="0"/></node>'
          ->
        2 X000Y000<-00->X000Y010 b'<node id="2" type="CHANX" capacity="1" direction="BI_DIR"><loc xlow="0" ylow="0" xhigh="0" yhigh="10" ptc="0"/><timing R="1e-09" C="1e-09"/><segment segment_id="0"/></node>'
        >>> r.create_edge_with_ids(1, 4, sw)
        Traceback (most recent call last):
          ...
        TypeError: RoutingNodeType.OPIN -> RoutingNodeType.SINK not valid, Only OPIN -> IPIN, CHANX, CHANY (IE A sink) is valid
        1 X000Y000[00].R-PIN> b'<node id="1" type="OPIN" capacity="1"><loc xlow="0" ylow="0" xhigh="0" yhigh="0" ptc="0" side="RIGHT"/><timing R="0" C="0"/></node>'
          ->
        4 X000Y010[00].SINK-< b'<node id="4" type="SINK" capacity="1"><loc xlow="0" ylow="10" xhigh="0" yhigh="10" ptc="0"/><timing R="0" C="0"/></node>'
        """  # noqa: E501

        id2node = self.id2element[RoutingNode]
        assert src_node_id in id2node, src_node_id
        src_node_type = RoutingNodeType.from_xml(id2node[src_node_id])
        assert sink_node_id in id2node, sink_node_id
        sink_node_type = RoutingNodeType.from_xml(id2node[sink_node_id])

        valid, msg = self._is_valid(src_node_type, sink_node_type)
        if not valid:
            src_node = id2node[src_node_id]
            sink_node = id2node[sink_node_id]
            raise TypeError(
                "{} -> {} not valid, {}\n{} {}\n  ->\n{} {}".format(
                    src_node_type,
                    sink_node_type,
                    msg,
                    RoutingGraphPrinter.node(src_node),
                    ET.tostring(src_node),
                    RoutingGraphPrinter.node(sink_node),
                    ET.tostring(sink_node),
                )
            )

        sw_bidir = switch.type in (SwitchType.SHORT, SwitchType.PASS_GATE)
        if bidir is None:
            bidir = sw_bidir
        elif sw_bidir:
            assert bidir, "Switch type {} must be bidir {} ({})".format(
                switch, (sw_bidir, bidir), (sink_node_id, src_node_id)
            )

        self._create_edge_with_ids(src_node_id, sink_node_id, switch, metadata)

        valid, msg = self._is_valid(sink_node_type, src_node_type)
        if valid and bidir:
            self._create_edge_with_ids(
                sink_node_id, src_node_id, switch, metadata
            )

    @staticmethod
    def _is_valid(src_node_type, sink_node_type):
        valid = False
        if False:
            pass
        elif src_node_type == RoutingNodeType.IPIN:
            msg = "Only IPIN -> SINK valid"
            valid = (sink_node_type == RoutingNodeType.SINK)
        elif src_node_type == RoutingNodeType.OPIN:
            msg = "Only OPIN -> IPIN, CHANX, CHANY (IE A sink) is valid"
            valid = sink_node_type.can_sink
        elif src_node_type == RoutingNodeType.SINK:
            msg = "SINK can't be a source."
            valid = False
        elif src_node_type == RoutingNodeType.SOURCE:
            msg = "Only SOURCE -> OPIN is valid"
            valid = (sink_node_type == RoutingNodeType.OPIN)
        elif src_node_type == RoutingNodeType.CHANX:
            msg = "Only CHANX -> IPIN, CHANX, CHANY (IE A sink) is valid"
            valid = sink_node_type.can_sink
        elif src_node_type == RoutingNodeType.CHANY:
            msg = "Only CHANY -> IPIN, CHANX, CHANY (IE A sink) is valid"
            valid = sink_node_type.can_sink
        else:
            assert False
        return valid, msg

    def _create_edge_with_ids(
            self, src_node_id, sink_node_id, switch, metadata={}
    ):
        # <edge src_node="34" sink_node="44" switch_id="1"/>
        assert_type(src_node_id, int)
        assert_type(sink_node_id, int)
        assert_type(switch, Switch)

        edge = RoutingEdge(
            attrib=OrderedDict(
                (
                    ('src_node',
                     str(src_node_id)), ('sink_node', str(sink_node_id)),
                    ('switch_id', str(switch.id))
                )
            )
        )

        for offset, values in metadata.items():
            for k, v in values.items():
                edge.set_metadata(k, v, offset=offset)

        self._add_xml_element(edge, add_id_attrib=False)

    def create_edge_with_nodes(
            self, src_node, sink_node, switch, metadata={}, bidir=None
    ):
        """Create an RoutingEdge between given two RoutingNodes.

        Parameters
        ----------
        src_node : RoutingNode
        sink_node : RoutingNode
        switch : Switch

        Returns
        -------
        RoutingEdge
        """
        # <edge src_node="34" sink_node="44" switch_id="1"/>
        assert_type(src_node, ET._Element)
        assert_eq(src_node.tag, "node")
        assert_type(sink_node, ET._Element)
        assert_eq(sink_node.tag, "node")

        self.create_edge_with_ids(
            self._get_xml_id(src_node),
            self._get_xml_id(sink_node),
            switch,
            metadata=metadata,
            bidir=bidir
        )


def pin_meta_always_right(*a, **kw):
    return (RoutingNodeSide.RIGHT, Offset(0, 0))


class Graph:
    """
    Top level representation, holds the XML root
    For <rr_graph> node
    """

    def __init__(
            self,
            rr_graph_file=None,
            verbose=False,
            clear_fabric=False,
            switch_name=None,
            pin_meta=pin_meta_always_right
    ):
        """

        Parameters
        ----------
        rr_graph_file : filename
        verbose : bool
        clear_fabric : bool
            Remove the rr_graph (IE All nodes and edges - and thus channels too).
        pin_meta : callable(Block, Pin) -> (RoutingNodeSide, Offset)

        Examples
        --------

        Look at the segments via name or ID number;
        >>> g = simple_test_graph()
        >>> g.segments[0]
        Segment(id=0, name='local', ...)
        >>> g.segments["local"]
        Segment(id=0, name='local', ...)

        Look at the switches via name or ID number;
        >>> g = simple_test_graph()
        >>> g.switches[0]
        Switch(id=0, name='mux', type=<SwitchType.MUX: 'mux'>, ...)
        >>> g.switches[1]
        Switch(id=1, name='__vpr_delayless_switch__', type=<SwitchType.MUX: 'mux'>, ...)
        >>> g.switches["mux"]
        Switch(id=0, name='mux', type=<SwitchType.MUX: 'mux'>, ...)
        >>> g.switches["__vpr_delayless_switch__"]
        Switch(id=1, name='__vpr_delayless_switch__', type=<SwitchType.MUX: 'mux'>, ...)

        Look at the block grid;
        >>> g = simple_test_graph()
        >>> g.block_grid.size
        Size(w=4, h=3)
        >>> g.block_grid[Position(0, 0)]
        Block(..., position=P(x=0, y=0), offset=Offset(w=0, h=0))
        >>> g.block_grid[Position(2, 1)]
        Block(..., position=P(x=2, y=1), offset=Offset(w=0, h=0))
        >>> g.block_grid[Position(4, 4)]
        Traceback (most recent call last):
            ...
        KeyError: P(x=4, y=4)
        >>> g.block_grid.block_types["IBUF"]
        BlockType(graph=..., id=1, name='IBUF', size=Size(w=1, h=1), ...)
        >>> g.block_grid.block_types[2]
        BlockType(graph=..., id=2, name='OBUF', size=Size(w=1, h=1), ...)
        >>> for block in g.block_grid.blocks_for(row=1):
        ...     print(block.position, block.block_type.name)
        P(x=0, y=1) IBUF
        P(x=1, y=1) TILE
        P(x=2, y=1) OBUF
        P(x=3, y=1) EMPTY
        >>> for bt in g.block_grid.block_types_for(row=1):
        ...     print(bt.name)
        IBUF
        TILE
        OBUF
        EMPTY

        """
        self.verbose = verbose

        self.segments = LookupMap(Segment)
        self.switches = LookupMap(Switch)

        # Read in existing file
        if rr_graph_file:
            self.block_grid = BlockGrid()
            self._xml_graph = ET.parse(
                rr_graph_file, ET.XMLParser(remove_blank_text=True)
            )
            self._import_block_types()
            self._import_block_grid()
            self._import_segments()
            self._import_switches()
        else:
            self._xml_graph = ET.Element("rr_graph")
            ET.SubElement(self._xml_graph, "rr_nodes")
            ET.SubElement(self._xml_graph, "rr_edges")

            self.switches.add(
                Switch(
                    id=self.switches.next_id(),
                    type="mux",
                    name="__vpr_delayless_switch__"
                )
            )

        self.routing = RoutingGraph(
            self._xml_graph, verbose=verbose, clear_fabric=clear_fabric
        )

        # Recreate the routing nodes for blocks if we cleared the routing
        if clear_fabric:
            switch = self.switches[switch_name]
            self.create_block_pins_fabric(switch=switch, pin_meta=pin_meta)
        else:
            self._index_pin_localnames()

        # Channels import requires rr_nodes
        self.channels = Channels(self.block_grid.size - Size(1, 1))
        if rr_graph_file:
            self._import_xml_channels()

    def _index_pin_localnames(self):
        for node in self.routing._xml_parent(RoutingNode):
            if node.tag == ET.Comment:
                continue

            ntype = node.get('type')
            loc = single_element(node, 'loc')
            pos_low, pos_high = node_pos(node)
            ptc = int(loc.get('ptc'))

            if ntype in ('IPIN', 'OPIN'):
                assert pos_low == pos_high, (pos_low, pos_high)
                pos = pos_low

                # Lookup Block/<grid_loc>
                # ptc is the associated pin ptc value of the block_type
                block = self.block_grid[pos]
                assert_type(block, Block)
                pin = block.ptc2pin(ptc)
                assert pin.name is not None, pin.name
                self.routing.localnames.add(pos, pin.name, node)

    def _import_block_types(self):
        # Create in the block_types information
        for block_type in self._xml_graph.iterfind("./block_types/block_type"):
            BlockType.from_xml(self.block_grid, block_type)

    def _import_block_grid(self):
        for block_xml in self._xml_graph.iterfind("./grid/grid_loc"):
            b = Block.from_xml(self.block_grid, block_xml)
            if b.offset == (0, 0):
                self.block_grid.add_block(b)
        size = self.block_grid.size
        assert size.x > 0
        assert size.y > 0

    def _import_segments(self):
        for segment_xml in self._xml_graph.iterfind("./segments/segment"):
            self.segments.add(Segment.from_xml(segment_xml))

    def _import_switches(self):
        for switch_xml in self._xml_graph.iterfind("./switches/switch"):
            self.switches.add(Switch.from_xml(switch_xml))

    def _import_xml_channels(self):
        self.channels.from_xml_nodes(self.routing._xml_parent(RoutingNode))

    def add_switch(self, sw):
        assert_type(sw, Switch)
        self.switches.add(sw)
        switches = single_element(self._xml_graph, "./switches")
        sw.to_xml(switches)

    def create_block_pins_fabric(
            self, switch=None, pin_meta=pin_meta_always_right
    ):
        if switch is None:
            switch = self.switches[0]
        self.create_nodes_from_blocks(self.block_grid, switch, pin_meta)

    def set_tooling(self, name, version, comment):
        root = self._xml_graph.getroot()
        root.set("tool_name", name)
        root.set("tool_version", version)
        root.set("tool_comment", comment)

    def create_node_from_pin(self, block, pin, side, offset):
        """Creates an IPIN/OPIN RoutingNode from `class Pin` object.

        Parameters
        ----------
        block : Block
        pin : Pin
        side : RoutingNodeSide
        offset : Offset

        Returns
        -------
        RoutingNode
        """
        assert_type(block, Block)
        assert_type(pin, Pin)
        assert_type(pin.pin_class, PinClass)
        assert_type(pin.pin_class.block_type, BlockType)

        pc = pin.pin_class
        # Connection within the same tile
        pos = block.position + offset

        pin_node = None
        if pc.direction in (PinClassDirection.INPUT, PinClassDirection.CLOCK):
            pin_node = self.routing.create_node(
                pos, pos, pin.ptc, 'IPIN', side=side
            )
        elif pin.pin_class.direction in (PinClassDirection.OUTPUT, ):
            pin_node = self.routing.create_node(
                pos, pos, pin.ptc, 'OPIN', side=side
            )
        else:
            assert False, "Unknown dir of {}.{}".format(pin, pin.pin_class)

        assert pin_node is not None, pin_node

        if self.verbose:
            print(
                "Adding pin {:55s} on tile ({:12s}, {:12s})@{:4d} {}".format(
                    str(pin), str(pos), str(pos), pin.ptc,
                    RoutingGraphPrinter.node(pin_node, self.block_grid)
                )
            )

        self.routing.localnames.add(pos, pin.name, pin_node)

        return pin_node

    def create_node_from_track(self, track, capacity=1):
        """
        Creates the CHANX/CHANY node for a Track object.

        Parameters
        ----------
        track : channels.Track

        Returns
        -------
        RoutingNode

        Examples
        --------
        """
        assert_type(track, Track)
        assert track.idx is not None

        track_node = self.routing.create_node(
            track.start,
            track.end,
            track.idx,
            track.type.value,
            direction=track.direction,
            segment_id=track.segment_id,
            capacity=capacity
        )

        if track.name is not None:
            self.routing.globalnames.add(track.name, track_node)

        return track_node

    def create_nodes_from_pin_class(self, block, pin_class, switch, pin_meta):
        """Creates a SOURCE or SINK RoutingNode from a `class PinClass` object.

        Parameters
        ----------
        block : Block
        pin_class : PinClass
        switch : Switch
        pin_meta : callable(Block, Pin) -> (RoutingNodeSide, Offset)
        """
        assert_type(block, Block)
        assert_type(block.block_type, BlockType)
        assert_type(pin_class, PinClass)
        assert_type(pin_class.block_type, BlockType)
        assert_eq(block.block_type, pin_class.block_type)
        assert_type(switch, Switch)

        pos_low = block.position
        pos_high = block.position + pin_class.block_type.size - Size(1, 1)

        # Assuming only one pin per class for now
        assert len(
            pin_class.pins
        ) == 1, 'Expect one pin per pin class, got %s' % (pin_class.pins, )

        pin = pin_class.pins[0]
        if pin_class.direction in (PinClassDirection.INPUT,
                                   PinClassDirection.CLOCK):
            # Sink node
            sink_node = self.routing.create_node(
                pos_low, pos_high, pin.ptc, 'SINK'
            )

            if self.verbose:
                print(
                    "Adding snk {:55s} on tile ({:12s}, {:12s}) {}".format(
                        str(pin_class), str(pos_low), str(pos_high),
                        RoutingGraphPrinter.node(sink_node, self.block_grid)
                    )
                )

            for p in pin_class.pins:
                pin_node = self.create_node_from_pin(
                    block, p, *pin_meta(block, p)
                )

                # Edge PIN->SINK
                self.routing.create_edge_with_nodes(
                    pin_node, sink_node, switch
                )

        elif pin_class.direction in (PinClassDirection.OUTPUT, ):
            # Source node
            src_node = self.routing.create_node(
                pos_low, pos_high, pin.ptc, 'SOURCE'
            )

            if self.verbose:
                print(
                    "Adding src {:55s} on tile ({:12s}, {:12s}) {}".format(
                        str(pin_class), str(pos_low), str(pos_high),
                        RoutingGraphPrinter.node(src_node, self.block_grid)
                    )
                )

            for p in pin_class.pins:
                pin_node = self.create_node_from_pin(
                    block, p, *pin_meta(block, p)
                )

                # Edge SOURCE->PIN
                self.routing.create_edge_with_nodes(src_node, pin_node, switch)

        else:
            assert False, "Unknown dir of {} for {}".format(
                pin_class.direction, str(pin_class)
            )

    def create_nodes_from_block(self, block, switch, pin_meta):
        """
        Creates the SOURCE/SINK nodes for each pin class
        Creates the IPIN/OPIN nodes for each pin inside a pin class.
        Creates the edges which connect these together.

        Parameters
        ----------
        block : Block
        switch : Switch
        pin_meta : callable(Block, Pin) -> (RoutingNodeSide, Offset)

        Examples
        --------
        """
        for pc in block.block_type.pin_classes:
            self.create_nodes_from_pin_class(block, pc, switch, pin_meta)

    def create_nodes_from_blocks(self, blocks, switch, pin_meta):
        """
        Parameters
        ----------
        block : Block
        switch : Switch
        pin_meta : callable(Block, Pin) -> (RoutingNodeSide, Offset)
        """
        for block in blocks:
            self.create_nodes_from_block(block, switch, pin_meta)

    def connect_pin_to_track(self, block, pin, track, switch):
        """
        Create an edge from given pin in block to given track with switching properties of switch
        """
        assert_type(block, Block)
        assert_type(pin, Pin)
        assert_type(track, Track)
        assert_type(switch, Switch)

        pin_node = self.routing.localnames[(block.position, pin.name)]
        track_node = self.routing.globalnames[track.name]
        if pin.direction == PinClassDirection.OUTPUT:
            self.routing.create_edge_with_nodes(pin_node, track_node, switch)
        else:
            self.routing.create_edge_with_nodes(track_node, pin_node, switch)

    def connect_track_to_track(self, src, dst, switch):
        assert_type(src, Track)
        assert_type(dst, Track)
        src_node = self.routing.globalnames[src.name]
        dst_node = self.routing.globalnames[dst.name]
        self.routing.create_edge_with_nodes(src_node, dst_node, switch)

    def create_xy_track(
            self,
            start,
            end,
            segment,
            idx=None,
            name=None,
            typeh=None,
            direction=None,
            capacity=1
    ):
        """Create track object and corresponding nodes"""
        if not isinstance(start, Position):
            start = Position(*start)
        assert_type(start, Position)
        if not isinstance(end, Position):
            end = Position(*end)
        assert_type(end, Position)

        track = self.channels.create_xy_track(
            start,
            end,
            segment.id,
            idx=idx,
            name=name,
            typeh=typeh,
            direction=direction
        )
        track_node = self.create_node_from_track(track, capacity)

        return track, track_node

    def pad_channels(self, segment):
        """Workaround for https://github.com/verilog-to-routing/vtr-verilog-to-routing/issues/339"""
        for track in self.channels.pad_channels(segment):
            self.create_node_from_track(track, capacity=0)

    def extract_pin_meta(self):
        """Export pin placement as pin_meta[(block, pin)] to import when rebuilding pin nodes"""
        sides = MappingLocalNames(type=RoutingNodeSide)
        offsets = MappingLocalNames(type=Offset)
        for block in self.block_grid:
            for pin_class in block.block_type.pin_classes:
                for pin in pin_class.pins:
                    for offset in block.block_type.positions:
                        pos = block.position + offset
                        try:
                            node = self.routing.localnames[(pos, pin.name)]
                        except KeyError:
                            continue

                        assert node is not None, "{}:{} not found at {}\n{}".format(
                            block, pin.name, list(block.positions),
                            self.routing.localnames
                        )
                        side = single_element(node, 'loc').get('side')
                        assert side is not None, ET.tostring(node)
                        sides[(block.position,
                               pin.name)] = RoutingNodeSide(side)
                        offsets[(block.position, pin.name)] = offset
        return sides, offsets

    def to_xml(self):
        """Return an ET object representing this rr_graph"""
        self.set_tooling("g.py", "dev", "Generated from black magic")

        # <rr_nodes>, <rr_edges>, and <switches> should be good as is
        # note <rr_nodes> includes channel tracks, but not width definitions

        # FIXME: regenerate <block_types>
        # FIXME: regenerate <grid>

        self.channels.to_xml(self._xml_graph)
        return self._xml_graph

    def connect_all(
            self, start, end, name, segment, metadata={}, spine=None,
            switch=None
    ):
        """Add a track which is present at all tiles within a range.

        Returns:
            List of ET.RoutingNode
        """
        assert_type(start, Position)
        assert_type(end, Position)
        assert_type(name, str)
        assert_type(segment, Segment)
        assert_type(metadata, dict)
        assert_type_or_none(spine, int)
        if spine is None:
            spine = start.y + (end.y - start.y) // 2

        assert start.x <= end.x, "x - {} < {}".format(start, end)
        assert start.y <= end.y, "y - {} < {}".format(start, end)

        if switch is None:
            switch = self.switches["short"]

        # Vertical wires
        v_tracks = []
        for x in range(start.x, end.x + 1):
            spos = Position(x, start.y)
            epos = Position(x, end.y)
            track, track_node = self.create_xy_track(
                spos,
                epos,
                segment=segment,
                typeh=Track.Type.Y,
                direction=Track.Direction.BI
            )
            v_tracks.append(track_node)

            for offset, values in metadata.items():
                for k, v in values.items():
                    track_node.set_metadata(k, v, offset=offset)

            for y in range(start.y, end.y + 1):
                pos = Position(x, y)
                self.routing.localnames.add(pos, name, track_node)

        # One horizontal wire
        spos = Position(start.x, spine)
        epos = Position(end.x, spine)
        track, track_node = self.create_xy_track(
            spos,
            epos,
            segment=segment,
            typeh=Track.Type.X,
            direction=Track.Direction.BI
        )

        for offset, values in metadata.items():
            for k, v in values.items():
                track_node.set_metadata(k, v, offset=offset)

        for x in range(start.x, end.x + 1):
            pos = Position(x, spine)
            self.routing.localnames.add(pos, name + "_h", track_node)

        assert_eq(len(v_tracks), len(range(start.x, end.x + 1)))
        # Connect the vertical wires to the horizontal one to make a single
        # global network
        for i, x in enumerate(range(start.x, end.x + 1)):
            pos = Position(x, spine)
            self.routing.create_edge_with_nodes(
                v_tracks[i], track_node, switch, bidir=True
            )

        return v_tracks + [track_node]


def simple_test_routing():
    """
    >>> r = simple_test_routing()
    """
    routing = RoutingGraph()
    routing.create_node(
        Position(0, 0), Position(0, 0), 0, ntype=RoutingNodeType.SOURCE
    )
    routing.create_node(
        Position(0, 0),
        Position(0, 0),
        0,
        ntype=RoutingNodeType.OPIN,
        side=RoutingNodeSide.RIGHT
    )
    routing.create_node(
        Position(0, 0),
        Position(0, 10),
        0,
        ntype=RoutingNodeType.CHANX,
        segment_id=0,
        direction=RoutingNodeDir.BI_DIR
    )
    routing.create_node(
        Position(0, 10),
        Position(0, 10),
        0,
        ntype=RoutingNodeType.IPIN,
        side=RoutingNodeSide.LEFT
    )
    routing.create_node(
        Position(0, 10), Position(0, 10), 0, ntype=RoutingNodeType.SINK
    )
    sw = Switch(id=0, name="sw", type=SwitchType.MUX)
    routing.create_edge_with_ids(0, 1, sw)  # SRC->OPIN
    routing.create_edge_with_ids(1, 2, sw)  # OPIN->CHANX
    routing.create_edge_with_ids(2, 3, sw)  # CHANX->IPIN
    routing.create_edge_with_ids(3, 4, sw)  # IPIN->SINK
    return routing


def simple_test_block_grid():
    """
    >>> bg = simple_test_block_grid()
    """
    bg = BlockGrid()

    # Create a block type with one input and one output pin
    bt = BlockType(g=bg, id=0, name="DUALBLK")
    pci = PinClass(block_type=bt, direction=PinClassDirection.INPUT)
    Pin(pin_class=pci, port_name="A", port_index=0)
    pco = PinClass(block_type=bt, direction=PinClassDirection.OUTPUT)
    Pin(pin_class=pco, port_name="B", port_index=0)

    # Create a block type with one input class with 4 pins
    bt = BlockType(g=bg, id=1, name="INBLOCK")
    pci = PinClass(block_type=bt, direction=PinClassDirection.INPUT)
    Pin(pin_class=pci, port_name="C", port_index=0)
    Pin(pin_class=pci, port_name="C", port_index=1)
    Pin(pin_class=pci, port_name="C", port_index=2)
    Pin(pin_class=pci, port_name="C", port_index=3)

    # Create a block type with out input class with 2 pins
    bt = BlockType(g=bg, id=2, name="OUTBLOK")
    pci = PinClass(block_type=bt, direction=PinClassDirection.OUTPUT)
    Pin(pin_class=pci, port_name="D", port_index=0)
    Pin(pin_class=pci, port_name="D", port_index=1)
    Pin(pin_class=pci, port_name="D", port_index=2)
    Pin(pin_class=pci, port_name="D", port_index=3)

    # Add some blocks
    bg.add_block(Block(g=bg, block_type_id=1, position=Position(0, 0)))
    bg.add_block(Block(g=bg, block_type_id=1, position=Position(0, 1)))
    bg.add_block(Block(g=bg, block_type_id=1, position=Position(0, 2)))
    bg.add_block(Block(g=bg, block_type_id=1, position=Position(0, 3)))

    bg.add_block(Block(g=bg, block_type_id=0, position=Position(1, 0)))
    bg.add_block(Block(g=bg, block_type_id=0, position=Position(1, 1)))
    bg.add_block(Block(g=bg, block_type_id=0, position=Position(1, 2)))
    bg.add_block(Block(g=bg, block_type_id=0, position=Position(1, 3)))

    bg.add_block(Block(g=bg, block_type_id=0, position=Position(2, 0)))
    bg.add_block(Block(g=bg, block_type_id=0, position=Position(2, 1)))
    bg.add_block(Block(g=bg, block_type_id=0, position=Position(2, 2)))
    bg.add_block(Block(g=bg, block_type_id=0, position=Position(2, 3)))

    bg.add_block(Block(g=bg, block_type_id=2, position=Position(3, 0)))
    bg.add_block(Block(g=bg, block_type_id=2, position=Position(3, 1)))
    bg.add_block(Block(g=bg, block_type_id=2, position=Position(3, 2)))
    bg.add_block(Block(g=bg, block_type_id=2, position=Position(3, 3)))

    return bg


def simple_test_graph(**kwargs):
    """
    Simple graph, containing one input block, one pass through block and one
    output block, with some routing between them.

    The rr_graph was generated by running the following in the tests directory;
    # make \\
        ARCH=testarch \\
        DEVICE_SUBARCH=wire-bidir-min \\
        DEVICE=1x1.min \\
        ROUTE_CHAN_WIDTH=1 \\
        clean wire.rr_graph.xml

    >>> g = simple_test_graph()
    """

    xml_str = """
<rr_graph tool_name="vpr" tool_version="ga5684b2e4" tool_comment="symbiflow-arch-defs/testarch/devices/wire-bidir-min/arch.merged.xml">
    <channels>
        <channel chan_width_max ="1" x_min="1" y_min="1" x_max="1" y_max="1"/>
        <x_list index ="0" info="1"/>
        <x_list index ="1" info="1"/>
        <y_list index ="0" info="1"/>
        <y_list index ="1" info="1"/>
        <y_list index ="2" info="1"/>
    </channels>
    <switches>
        <switch id="0" type="mux" name="mux">
            <timing R="551" Cin="7.70000012e-16" Cout="4.00000001e-15" Tdel="5.80000006e-11"/>
            <sizing mux_trans_size="2.63073993" buf_size="27.6459007"/>
        </switch>
        <switch id="1" type="mux" name="__vpr_delayless_switch__">
            <timing R="0" Cin="0" Cout="0" Tdel="0"/>
            <sizing mux_trans_size="0" buf_size="0"/>
        </switch>
    </switches>

    <segments>
        <segment id="0" name="local">
            <timing R_per_meter="101" C_per_meter="2.25000005e-14"/>
        </segment>
    </segments>

    <block_types>
        <block_type id="0" name="EMPTY" width="1" height="1">
        </block_type>
        <block_type id="1" name="IBUF" width="1" height="1">
            <pin_class type="OUTPUT">
                <pin ptc="0">IBUF.I[0]</pin>
            </pin_class>
        </block_type>
        <block_type id="2" name="OBUF" width="1" height="1">
            <pin_class type="INPUT">
                <pin ptc="0">OBUF.O[0]</pin>
            </pin_class>
        </block_type>
        <block_type id="3" name="TILE" width="1" height="1">
            <pin_class type="INPUT">
                <pin ptc="0">TILE.IN[0]</pin>
            </pin_class>
            <pin_class type="OUTPUT">
                <pin ptc="1">TILE.OUT[0]</pin>
            </pin_class>
        </block_type>
    </block_types>

    <grid>
        <grid_loc x="0" y="0" block_type_id="0" width_offset="0" height_offset="0"/>
        <grid_loc x="0" y="1" block_type_id="1" width_offset="0" height_offset="0"/>
        <grid_loc x="0" y="2" block_type_id="0" width_offset="0" height_offset="0"/>
        <grid_loc x="1" y="0" block_type_id="0" width_offset="0" height_offset="0"/>
        <grid_loc x="1" y="1" block_type_id="3" width_offset="0" height_offset="0"/>
        <grid_loc x="1" y="2" block_type_id="0" width_offset="0" height_offset="0"/>
        <grid_loc x="2" y="0" block_type_id="0" width_offset="0" height_offset="0"/>
        <grid_loc x="2" y="1" block_type_id="2" width_offset="0" height_offset="0"/>
        <grid_loc x="2" y="2" block_type_id="0" width_offset="0" height_offset="0"/>
        <grid_loc x="3" y="0" block_type_id="0" width_offset="0" height_offset="0"/>
        <grid_loc x="3" y="1" block_type_id="0" width_offset="0" height_offset="0"/>
        <grid_loc x="3" y="2" block_type_id="0" width_offset="0" height_offset="0"/>
    </grid>

    <rr_nodes>
        <node id="0" type="SOURCE" capacity="1">
            <loc xlow="0" ylow="1" xhigh="0" yhigh="1" ptc="0"/>
            <timing R="0" C="0"/>
        </node>
        <node id="1" type="OPIN" capacity="1">
            <loc xlow="0" ylow="1" xhigh="0" yhigh="1" side="RIGHT" ptc="0"/>
            <timing R="0" C="0"/>
        </node>
        <node id="2" type="SINK" capacity="1">
            <loc xlow="1" ylow="1" xhigh="1" yhigh="1" ptc="0"/>
            <timing R="0" C="0"/>
        </node>
        <node id="3" type="SOURCE" capacity="1">
            <loc xlow="1" ylow="1" xhigh="1" yhigh="1" ptc="1"/>
            <timing R="0" C="0"/>
        </node>
        <node id="4" type="IPIN" capacity="1">
            <loc xlow="1" ylow="1" xhigh="1" yhigh="1" side="RIGHT" ptc="0"/>
            <timing R="0" C="0"/>
        </node>
        <node id="5" type="OPIN" capacity="1">
            <loc xlow="1" ylow="1" xhigh="1" yhigh="1" side="RIGHT" ptc="1"/>
            <timing R="0" C="0"/>
        </node>
        <node id="6" type="SINK" capacity="1">
            <loc xlow="2" ylow="1" xhigh="2" yhigh="1" ptc="0"/>
            <timing R="0" C="0"/>
        </node>
        <node id="7" type="IPIN" capacity="1">
            <loc xlow="2" ylow="1" xhigh="2" yhigh="1" side="RIGHT" ptc="0"/>
            <timing R="0" C="0"/>
        </node>
        <node id="8" type="CHANX" direction="BI_DIR" capacity="1">
            <loc xlow="1" ylow="0" xhigh="1" yhigh="0" ptc="0"/>
            <timing R="101" C="3.60400017e-14"/>
            <segment segment_id="0"/>
        </node>
        <node id="9" type="CHANX" direction="BI_DIR" capacity="1">
            <loc xlow="2" ylow="0" xhigh="2" yhigh="0" ptc="0"/>
            <timing R="101" C="3.60400017e-14"/>
            <segment segment_id="0"/>
        </node>
        <node id="10" type="CHANX" direction="BI_DIR" capacity="1">
            <loc xlow="1" ylow="1" xhigh="1" yhigh="1" ptc="0"/>
            <timing R="101" C="3.60400017e-14"/>
            <segment segment_id="0"/>
        </node>
        <node id="11" type="CHANX" direction="BI_DIR" capacity="1">
            <loc xlow="2" ylow="1" xhigh="2" yhigh="1" ptc="0"/>
            <timing R="101" C="3.60400017e-14"/>
            <segment segment_id="0"/>
        </node>
        <node id="12" type="CHANY" direction="BI_DIR" capacity="1">
            <loc xlow="0" ylow="1" xhigh="0" yhigh="1" ptc="0"/>
            <timing R="101" C="3.60400017e-14"/>
            <segment segment_id="0"/>
        </node>
        <node id="13" type="CHANY" direction="BI_DIR" capacity="1">
            <loc xlow="1" ylow="1" xhigh="1" yhigh="1" ptc="0"/>
            <timing R="101" C="4.48100012e-14"/>
            <segment segment_id="0"/>
        </node>
        <node id="14" type="CHANY" direction="BI_DIR" capacity="1">
            <loc xlow="2" ylow="1" xhigh="2" yhigh="1" ptc="0"/>
            <timing R="101" C="3.28100008e-14"/>
            <segment segment_id="0"/>
        </node>
    </rr_nodes>

    <rr_edges>
        <edge src_node="0" sink_node="1" switch_id="1"/>
        <edge src_node="1" sink_node="12" switch_id="0"/>
        <edge src_node="3" sink_node="5" switch_id="1"/>
        <edge src_node="4" sink_node="2" switch_id="1"/>
        <edge src_node="5" sink_node="13" switch_id="0"/>
        <edge src_node="7" sink_node="6" switch_id="1"/>
        <edge src_node="8" sink_node="9" switch_id="0"/>
        <edge src_node="8" sink_node="13" switch_id="0"/>
        <edge src_node="8" sink_node="12" switch_id="0"/>
        <edge src_node="9" sink_node="8" switch_id="0"/>
        <edge src_node="9" sink_node="14" switch_id="0"/>
        <edge src_node="9" sink_node="13" switch_id="0"/>
        <edge src_node="10" sink_node="11" switch_id="0"/>
        <edge src_node="10" sink_node="13" switch_id="0"/>
        <edge src_node="10" sink_node="12" switch_id="0"/>
        <edge src_node="11" sink_node="10" switch_id="0"/>
        <edge src_node="11" sink_node="14" switch_id="0"/>
        <edge src_node="11" sink_node="13" switch_id="0"/>
        <edge src_node="12" sink_node="10" switch_id="0"/>
        <edge src_node="12" sink_node="8" switch_id="0"/>
        <edge src_node="13" sink_node="11" switch_id="0"/>
        <edge src_node="13" sink_node="9" switch_id="0"/>
        <edge src_node="13" sink_node="10" switch_id="0"/>
        <edge src_node="13" sink_node="8" switch_id="0"/>
        <edge src_node="13" sink_node="4" switch_id="0"/>
        <edge src_node="14" sink_node="11" switch_id="0"/>
        <edge src_node="14" sink_node="9" switch_id="0"/>
        <edge src_node="14" sink_node="7" switch_id="0"/>
    </rr_edges>
</rr_graph>
"""  # noqa: E501
    return Graph(io.StringIO(xml_str), **kwargs)


def test_create_block_pins_fabric():
    """
    >>> test_create_block_pins_fabric()
    """
    # 2 input pins, 2 output pins
    # - 2 * SINK,   2 * IPIN, 2 edges
    # - 2 * SOURCE, 2 * OPIN, 2 edges
    # Should have added 4 edges to connect edge to pin
    g1 = simple_test_graph()

    # Clear the fabric
    g1.routing.clear()
    assert_eq(len(g1.routing._xml_parent(RoutingNode)), 0)
    assert_eq(len(g1.routing._xml_parent(RoutingEdge)), 0)

    # Create the fabric for the block pins
    g1.create_block_pins_fabric()
    assert_eq(len(g1.routing._xml_parent(RoutingNode)), 8)
    assert_eq(len(g1.routing._xml_parent(RoutingEdge)), 4)

    # Check clearing on import
    g2 = simple_test_graph(clear_fabric=True)
    assert_eq(len(g2.routing._xml_parent(RoutingNode)), 8)
    assert_eq(len(g2.routing._xml_parent(RoutingEdge)), 4)


def node_ptc(node):
    locs = list(node.iterfind("loc"))
    assert len(locs) == 1, locs
    loc = locs[0]
    return int(loc.get('ptc'))


def main():
    import doctest

    print('Doctest begin')
    failure_count, test_count = doctest.testmod(optionflags=doctest.ELLIPSIS)
    assert test_count > 0
    assert failure_count == 0, "Doctests failed!"
    print('Doctest end')


if __name__ == "__main__":
    main()
