"""
This class is created for OpenFPGA related netlist transformations
"""
import logging
import xml.etree.ElementTree as ET
import spydrnet as sdn
from spydrnet_physical.util import OpenFPGA_Config_Generator, OpenFPGA_Tile_Generator
from spydrnet_physical.util.shell import launch_shell
logger = logging.getLogger("spydrnet_logs")
class config_chain_simple(OpenFPGA_Config_Generator):
    """
    This shows how the configuration chain can be built
    """
    def __init__(self, grid, netlist, library, top_module):
        super().__init__(grid, netlist, library, top_module)
        self.chains = []
        self.order = [
            "grid_io_right",
            "grid_io_top",
            "grid_io_left",
            "grid_io_bottom",
            "cbx_1__4_",
            "cbx_1__1_",
            "cbx_1__0_",
            "cby_0__1_",
            "cby_1__1_",
            "cby_4__1_",
            "sb_0__0_",
            "sb_0__1_",
            "sb_0__4_",
            "sb_1__0_",
            "sb_1__1_",
            "sb_1__4_",
            "sb_4__0_",
            "sb_4__1_",
            "sb_4__4_",
            "grid_clb",
        ]
    def create_chain(self):
        chain = []
        self.chains.append(chain)
        return chain
    def write_fabric_key(self, filename):
        # Create XML File
        seq = 0
        top = ET.Element("fabric_key")
        for indx, chain in enumerate(self.chains):
            region = ET.SubElement(top, "region", {"id": str(indx)})
            for indx, inst in enumerate(chain):
                ET.SubElement(
                    region, "key", {"id": str(seq + indx), "alias": inst.name}
                )
            seq = seq + indx
        ET.ElementTree(top).write(filename)
    def add_configuration_scheme(self):
        """Creates configuration chain"""
        logger.info("Running configuration")
        chain = self.create_chain()
        self.add_top_row(chain)
        self.add_middle_rows(chain)
        self.add_last_row(chain)
        for index, inst in enumerate(chain):
            chain[index] = next(self._top_module.get_instances(inst))
        self._connect_instances(self._top_module, chain)
        self._connect_top_module(chain)
    def _connect_top_module(self, chain):
        head_cable = next(self._top_module.get_cables(self.head), None)
        first_head = next(chain[0].get_port_pins(self.head))
        head_cable.wires[0].connect_pin(first_head)
        tail_cable = next(self._top_module.get_cables(self.tail), None)
        last_tail = next(chain[-1].get_port_pins(self.tail))
        tail_cable.wires[0].connect_pin(last_tail)
    def add_top_row(self, chain):
        x = self.fpga_size[0]
        y = self.fpga_size[1]
        chain.append(f"grid_io*{x+1}__{y}*")
        for x in range(self.fpga_size[0], 0, -1):
            chain.append(f"sb*{x}__{y}*")
            chain.append(f"cby*{x}__{y}*")
            chain.append(f"grid_clb*{x}__{y}*")
            chain.append(f"cbx*{x}__{y}*")
            chain.append(f"grid_io*{x}__{y+1}*")
        chain.append(f"sb*0__{y}*")
        chain.append(f"cby*0__{y}*")
        chain.append(f"grid_io*_0__{y}*")
    def add_middle_rows(self, chain):
        for y in range(self.fpga_size[1] - 1, 1, -1):
            if (y + 1) % 2:
                for x in range(self.fpga_size[0], 0, -1):
                    if x == self.fpga_size[0]:
                        chain.append(f"sb*_{x}__{y}*")
                        chain.append(f"grid_io*_{x+1}__{y}*")
                        chain.append(f"cby*_{x}__{y}*")
                    chain.append(f"grid_clb*{x}__{y}*")
                    chain.append(f"cbx*{x}__{y}*")
                    chain.append(f"sb*{x-1}__{y}*")
                    chain.append(f"cby*{x-1}__{y}*")
                chain.append(f"grid_io*_0__{y}*")
            else:
                for x in range(1, self.fpga_size[0] + 1):
                    if x == 1:
                        chain.append(f"sb*_0__{y}*")
                        chain.append(f"grid_io*_0__{y}*")
                        chain.append(f"cby*_0__{y}*")
                    chain.append(f"grid_clb*{x}__{y}*")
                    chain.append(f"cbx*{x}__{y}*")
                    chain.append(f"sb*{x}__{y}*")
                    chain.append(f"cby*{x}__{y}*")
                chain.append(f"grid_io*_{x+1}__{y}*")
    def add_last_row(self, chain):
        y = 1
        chain.append(f"sb*_0__1*")
        chain.append(f"cby*_0__1*")
        chain.append(f"grid_io*_0__1*")
        chain.append(f"sb*_0__0*")
        for x in range(1, self.fpga_size[0] + 1):
            chain.append(f"grid_io_*{x}__{y-1}*")
            chain.append(f"cbx*{x}__{y-1}*")
            chain.append(f"grid_clb*{x}__{y}*")
            chain.append(f"cbx*{x}__{y}*")
            chain.append(f"sb*{x}__{y}*")
            chain.append(f"cby*{x}__{y}*")
            chain.append(f"sb*{x}__{y-1}*")
        chain.append(f"grid_io_*{x+1}__{y}*")
class config_chain_01(OpenFPGA_Config_Generator):
    """
    This example demonstrate how configuration chain can be restructured after
    the tile tranformation. This method is better suited while creating
    a configuration after the physical tranformation. However mapping the
    sequence back to the original sequence could require complex scripting.
    # TODO : Need better explanation
    """
    def __init__(self, grid, netlist, library, top_module):
        super().__init__(grid, netlist, library, top_module)
        self.order = [
            "grid_io_right",
            "grid_io_top",
            "grid_io_left",
            "grid_io_bottom",
            "cbx_1__4_",
            "cbx_1__1_",
            "cbx_1__0_",
            "cby_0__1_",
            "cby_1__1_",
            "cby_4__1_",
            "sb_0__0_",
            "sb_0__1_",
            "sb_0__4_",
            "sb_1__0_",
            "sb_1__1_",
            "sb_1__4_",
            "sb_4__0_",
            "sb_4__1_",
            "sb_4__4_",
            "grid_clb",
        ]
    def write_fabric_key(self):
        pass
    def add_configuration_scheme(self):
        """Creates configuration chain"""
        logger.info("Running configuration")
        self._create_intra_tiles()
        self._create_inter_tiles()
    def _create_inter_tiles(self):
        inst_list = []
        for y in range(self.fpga_size[1], 0, -1):
            for x in sorted(range(self.fpga_size[0], 0, -1), reverse=(y + 1) % 2):
                print(f"*{x}_{y}*")
                inst = next(self._top_module.get_instances(f"*{x}__{y}*"))
                inst_list.append(inst)
        self._connect_instances(self._top_module, inst_list)
        head_cable = next(self._top_module.get_cables(self.head))
        first_head = next(inst_list[0].get_port_pins(self.head))
        head_cable.wires[0].connect_pin(first_head)
        tail_cable = next(self._top_module.get_cables(self.tail))
        last_tail = next(inst_list[-1].get_port_pins(self.tail))
        tail_cable.wires[0].connect_pin(last_tail)
    def _create_intra_tiles(self):
        for each in [
            "top_right_tile",
            "top_tile",
            "top_left_tile",
            "left_tile",
            "tile",
            "right_tile",
            "bottom_right_tile",
            "bottom_left_tile",
        ]:
            tile = next(self._library.get_definitions(each))
            # Remove ccff related cables and port from tile
            for cable in list(tile.get_cables("ccff*")):
                for pin in list(cable.wires[0].pins):
                    pin.wire.disconnect_pin(pin)
                tile.remove_cable(cable)
            for port in list(tile.get_ports("ccff*")):
                tile.remove_port(port)
            # Create chain
            inst_list = sorted(
                [inst for inst in tile.get_instances()],
                key=(lambda x: self.order.index(x.reference.name)),
            )
            self._connect_instances(tile, inst_list)
            # Create ccff_head port
            tile.create_port(self._head, direction=sdn.IN, pins=1)
            ccff_head_wire = tile.create_cable(self._head, wires=1).wires[0]
            ccff_head_wire.connect_pin(next(inst_list[0].get_port_pins(self.head)))
            # Create ccff_tail port
            tile.create_port(self._tail, direction=sdn.OUT, pins=1)
            ccff_tail_wire = tile.create_cable(self._tail, wires=1).wires[0]
            ccff_tail_wire.connect_pin(next(inst_list[-1].get_port_pins(self.tail)))
[docs]
class Tile01(OpenFPGA_Tile_Generator):
[docs]
    def create_tiles(self):
        """
        Creates tiles
        """
        # ##############  Main Tiles  ##############
        self._main_tile()
        # ##############  Side Tiles  ##############
        self._left_tile()
        self._right_tile()
        self._top_tile()
        self._bottom_tile()
        # ############## Corner Tiles ##############
        self._top_left_tile()
        self._top_right_tile()
        self._bottom_left_tile()
        self._bottom_right_tile() 
[docs]
    def merge_and_update(self, instance_list, tile_name):
        """
        Merges given list of instances and updates width and height parameter
        """
        self._top_module.merge_multiple_instance(
            instance_list, new_definition_name=tile_name
        )
        tile = next(self._library.get_definitions(tile_name))
        tile.OptPins()
        width, height = self._get_width_height(instance_list)
        self._update_placement(instance_list)
        tile.properties["WIDTH"], tile.properties["HEIGHT"] = width, height 
    def _get_width_height(self, instance_list):
        x_min, y_min = float("inf"), float("inf")
        x_max, y_max = 0, 0
        for instance in instance_list[0][0]:
            properties = instance.properties
            ref_properties = instance.reference.properties
            LOC_X = properties.get("LOC_X", 0)
            LOC_Y = properties.get("LOC_Y", 0)
            x_min = min(x_min, LOC_X)
            y_min = min(y_min, LOC_Y)
            x_max = max(x_max, LOC_X + ref_properties.get("WIDTH", 50))
            y_max = max(y_max, LOC_Y + ref_properties.get("HEIGHT", 50))
        return ((x_max - x_min), (y_max - y_min))
    def _update_placement(self, instance_list):
        x_loc, y_loc = float("inf"), float("inf")
        # get instance with llx and lly value
        for indx, instance in enumerate(instance_list[0][0]):
            x = instance.properties.get("LOC_X", 0)
            y = instance.properties.get("LOC_Y", 0)
            if x < x_loc:
                index_x, x_loc = indx, x
            if y < y_loc:
                index_y, y_loc = indx, y
        for instances, new_name in instance_list:
            new_inst = next(self._top_module.get_instances(new_name))
            new_inst.properties["LOC_X"] = instances[index_x].properties.get("LOC_X", 0)
            new_inst.properties["LOC_Y"] = instances[index_y].properties.get("LOC_Y", 0)
            logger.debug(
                f"{new_name} assigned %d %d"
                % (new_inst.properties["LOC_X"], new_inst.properties["LOC_Y"])
            )
[docs]
    def _main_tile(self):
        """Create main Tiles
        .. rst-class:: ascii
        ::
                             +-----+
                             |     |
               +-------+ +----     ---+
               |  CBY  | |     SB     |
               +-------+ +----     ---+
           +---------------+ |     |
           |               | +-----+
           |               | +-----+
           |               | |     |
           |      CLB      | | CBX |
           |               | +-----+
           |               |
           +---------------+
        """
        instance_list = []
        for x in range(2, self.fpga_size[0]):
            for y in range(2, self.fpga_size[1]):
                clb = next(self._top_module.get_instances(f"grid_clb_{x}__{y}_"))
                cbx = next(self._top_module.get_instances(f"cbx_{x}__{y}_"))
                cby = next(self._top_module.get_instances(f"cby_{x}__{y}_"))
                sb = next(self._top_module.get_instances(f"sb_{x}__{y}_"))
                instance_list.append(((clb, cbx, cby, sb), f"tile_{x}__{y}_"))
        self.merge_and_update(instance_list, "tile") 
[docs]
    def _left_tile(self):
        """Create Left Tiles
        .. rst-class:: ascii
        ::
           +-----+                  +-----+
           |     |                  |     |
           |     +--+ +-------+ +---+     +--+
           | SB     | |  CBY  | |     SB     |
           |     +--+ +-------+ +---+     +--+
           |     | +--------------+ |     |
           +-----+ |              | +-----+
           +-----+ |              | +-----+
           |     | |              | |     |
           | CBX | |     CLB      | | CBX |
           +-----+ |              | +-----+
                   |              |
                   +--------------+
        """
        instance_list = []
        for i in range(2, self.fpga_size[0]):
            clb = next(self._top_module.get_instances(f"grid_clb_1__{i}_"))
            cby0 = next(self._top_module.get_instances(f"cby_0__{i}_"))
            cby1 = next(self._top_module.get_instances(f"cby_1__{i}_"))
            cbx1 = next(self._top_module.get_instances(f"cbx_1__{i}_"))
            sb0 = next(self._top_module.get_instances(f"sb_0__{i}_"))
            sb1 = next(self._top_module.get_instances(f"sb_1__{i}_"))
            # grid_io = next(self._top_module.get_instances(
            #     f"grid_io_left_0__{i}_"))
            instance_list.append(((clb, cby0, cby1, cbx1, sb0, sb1), f"tile_1__{i}_"))
        self.merge_and_update(instance_list, "left_tile") 
[docs]
    def _right_tile(self):
        """Create Right Tiles
        .. rst-class:: ascii
        ::
                             +-----+
                             |     |
                +-------+ +--+     |
                |  CBY  | |    SB  |
                +-------+ +--+     |
            +--------------+ |     |
            |              | +-----+
            |              | +-----+
            |              | |     |
            |     CLB      | | CBX |
            |              | +-----+
            |              |
            +--------------+
        """
        instance_list = []
        for i in range(2, self.fpga_size[0]):
            clb = next(
                self._top_module.get_instances(f"grid_clb_{self.fpga_size[0]}__{i}_")
            )
            cbx1 = next(
                self._top_module.get_instances(f"cbx_{self.fpga_size[0]}__{i}_")
            )
            cby0 = next(
                self._top_module.get_instances(f"cby_{self.fpga_size[0]}__{i}_")
            )
            sb0 = next(self._top_module.get_instances(f"sb_{self.fpga_size[0]}__{i}_"))
            # grid_io = next(self._top_module.get_instances(
            #     f"grid_io_right_{self.fpga_size[0]+1}__{i}_"))
            instance_list.append(
                ((clb, cbx1, cby0, sb0), f"tile_{self.fpga_size[0]}__{i}_")
            )
        self.merge_and_update(instance_list, "right_tile") 
[docs]
    def _top_tile(self):
        """Create Top Tiles
        .. rst-class:: ascii
        ::
               +-------+ +------------+
               |  CBY  | |     SB     |
               +-------+ +---+     +--+
            +--------------+ |     |
            |              | +-----+
            |              | +-----+
            |              | |     |
            |     CLB      | | CBX |
            |              | +-----+
            |              |
            +--------------+
        """
        instance_list = []
        for i in range(2, self.fpga_size[1]):
            clb = next(
                self._top_module.get_instances(f"grid_clb_{i}__{self.fpga_size[1]}_")
            )
            cbx = next(self._top_module.get_instances(f"cbx_{i}__{self.fpga_size[1]}_"))
            cby = next(self._top_module.get_instances(f"cby_{i}__{self.fpga_size[1]}_"))
            sb = next(self._top_module.get_instances(f"sb_{i}__{self.fpga_size[1]}_"))
            # grid_io = next(self._top_module.get_instances(
            #     f"grid_io_top_{i}__{self.fpga_size[1]+1}_"))
            instance_list.append(
                ((clb, cbx, cby, sb), f"tile_{i}__{self.fpga_size[1]}_")
            )
        self.merge_and_update(instance_list, "top_tile") 
[docs]
    def _bottom_tile(self):
        """Create Bottom Tiles
        .. rst-class:: ascii
        ::
                             +-----+
                             |     |
               +-------+ +---+     +--+
               |  CBY  | |     SB     |
               +-------+ +---+     +--+
            +--------------+ |     |
            |              | +-----+
            |              | +-----+
            |              | |     |
            |     CLB      | | CBX |
            |              | +-----+
            |              | +-----+
            +--------------+ |     |
               +-------+ +---+     +--+
               |  CBY  | |     SB     |
               +-------+ +------------+
        """
        instance_list = []
        for i in range(2, self.fpga_size[1]):
            clb = next(self._top_module.get_instances(f"grid_clb_{i}__1_"))
            cbx0 = next(self._top_module.get_instances(f"cbx_{i}__0_"))
            cbx1 = next(self._top_module.get_instances(f"cbx_{i}__1_"))
            cby1 = next(self._top_module.get_instances(f"cby_{i}__1_"))
            sb0 = next(self._top_module.get_instances(f"sb_{i}__0_"))
            sb1 = next(self._top_module.get_instances(f"sb_{i}__1_"))
            # grid_io = next(self._top_module.get_instances(
            #     f"grid_io_bottom_{i}__0_"))
            instance_list.append(((clb, cbx0, cbx1, cby1, sb0, sb1), f"tile_{i}__1_"))
        self.merge_and_update(instance_list, "bottom_tile") 
[docs]
    def _top_left_tile(self):
        """Create top left tile
        .. rst-class:: ascii
        ::
            +--------+ +-------+ +------------+
            |  SB    | |  CBX  | |     SB     |
            |     +--+ +-------+ +---+     +--+
            |     | +--------------+ |     |
            +-----+ |              | +-----+
            +-----+ |              | +-----+
            |     | |              | |     |
            | CBY | |     CLB      | | CBY |
            +-----+ |              | +-----+
                    |              |
                    +--------------+
        """
        instance_list = []
        clb = next(self._top_module.get_instances(f"grid_clb_1__{self.fpga_size[1]}_"))
        cbx0 = next(self._top_module.get_instances(f"cbx_1__{self.fpga_size[1]}_"))
        cby0 = next(self._top_module.get_instances(f"cby_0__{self.fpga_size[1]}_"))
        cby1 = next(self._top_module.get_instances(f"cby_1__{self.fpga_size[1]}_"))
        sb0 = next(self._top_module.get_instances(f"sb_0__{self.fpga_size[1]}_"))
        sb1 = next(self._top_module.get_instances(f"sb_1__{self.fpga_size[1]}_"))
        # grid_io_0 = next(self._top_module.get_instances(
        #     f"grid_io_top_1__{self.fpga_size[1]+1}_"))
        # grid_io_1 = next(self._top_module.get_instances(
        #     f"grid_io_left_0__{self.fpga_size[1]}_"))
        instance_list.append(
            ((clb, cbx0, cby0, cby1, sb0, sb1), f"tile_1__{self.fpga_size[1]}_")
        )
        self.merge_and_update(instance_list, "top_left_tile") 
[docs]
    def _top_right_tile(self):
        """Create top right tile
        .. rst-class:: ascii
        ::
           +------------+ +-------+ +---------+
           |     SB     | |  CBY  | |     SB  |
           +---+     +--+ +-------+ +---+     |
               |     | +--------------+ |     |
               +-----+ |              | +-----+
               +-----+ |              | +-----+
               |     | |              | |     |
               | CBX | |     CLB      | | CBX |
               +-----+ |              | +-----+
                       |              |
                       +--------------+
        """
        instance_list = []
        clb = next(
            self._top_module.get_instances(
                f"grid_clb_{self.fpga_size[0]}__{self.fpga_size[1]}_"
            )
        )
        cbx0 = next(
            self._top_module.get_instances(
                f"cbx_{self.fpga_size[0]}__{self.fpga_size[1]}_"
            )
        )
        cby0 = next(
            self._top_module.get_instances(
                f"cby_{self.fpga_size[0]}__{self.fpga_size[1]}_"
            )
        )
        sb0 = next(
            self._top_module.get_instances(
                f"sb_{self.fpga_size[0]}__{self.fpga_size[1]}_"
            )
        )
        # grid_io_0 = next(self._top_module.get_instances(
        #     f"grid_io_right_{self.fpga_size[0]+1}__{self.fpga_size[1]}_"))
        # grid_io_1 = next(self._top_module.get_instances(
        #     f"grid_io_top_{self.fpga_size[0]}__{self.fpga_size[1]+1}_"))
        instance_list.append(
            ((clb, cbx0, cby0, sb0), f"tile_{self.fpga_size[0]}__{self.fpga_size[1]}_")
        )
        self.merge_and_update(instance_list, "top_right_tile") 
[docs]
    def _bottom_left_tile(self):
        """Create bottom left tile
        .. rst-class:: ascii
        ::
             +-----+                  +-----+
             |     |                  |     |
             |     +--+ +-------+ +---+     +--+
             | SB     | |  CBY  | |     SB  |  |
             |     +--+ +-------+ +---+     +--+
             |     | +--------------+ |     |
             +-----+ |              | +-----+
             +-----+ |              | +-----+
             |     | |              | |     |
             | CBX | |     CLB      | | CBX |
             +-----+ |              | +-----+
             +-----+ |              | +-----+
             |     | +--------------+ |     |
             |     +--+ +-------+ +---+     +--+
             | SB     | |  CBY  | |     SB     |
             +--------+ +-------+ +------------+
        """
        instance_list = []
        clb = next(self._top_module.get_instances("grid_clb_1__1_"))
        cbx0 = next(self._top_module.get_instances("cbx_1__0_"))
        cbx1 = next(self._top_module.get_instances("cbx_1__1_"))
        cby0 = next(self._top_module.get_instances("cby_0__1_"))
        cby1 = next(self._top_module.get_instances("cby_1__1_"))
        sb0 = next(self._top_module.get_instances("sb_0__0_"))
        sb1 = next(self._top_module.get_instances("sb_0__1_"))
        sb2 = next(self._top_module.get_instances("sb_1__0_"))
        sb3 = next(self._top_module.get_instances("sb_1__1_"))
        # grid_io_0 = next(self._top_module.get_instances("grid_io_left_0__1_"))
        # grid_io_1 = next(self._top_module.get_instances("grid_io_bottom_1__0_"))
        instance_list.append(
            ((clb, cbx0, cbx1, cby0, cby1, sb0, sb1, sb2, sb3), "tile_1__1_")
        )
        self.merge_and_update(instance_list, "bottom_left_tile") 
[docs]
    def _bottom_right_tile(self):
        """Create bottom right tile
        .. rst-class:: ascii
        ::
                             +-----+
                             |     |
               +-------+ +---+     |
               |  CBY  | |     SB  |
               +-------+ +---+     |
            +--------------+ |     |
            |              | +-----+
            |              | +-----+
            |              | |     |
            |     CLB      | | CBX |
            |              | +-----+
            |              | +-----+
            +--------------+ |     |
               +-------+ +---+     |
               |  CBY  | |     SB  |
               +-------+ +---------+
        """
        instance_list = []
        clb = next(self._top_module.get_instances(f"grid_clb_{self.fpga_size[0]}__1_"))
        cbx0 = next(self._top_module.get_instances(f"cbx_{self.fpga_size[0]}__0_"))
        cbx1 = next(self._top_module.get_instances(f"cbx_{self.fpga_size[0]}__1_"))
        cby0 = next(self._top_module.get_instances(f"cby_{self.fpga_size[0]}__1_"))
        sb0 = next(self._top_module.get_instances(f"sb_{self.fpga_size[0]}__0_"))
        sb1 = next(self._top_module.get_instances(f"sb_{self.fpga_size[0]}__1_"))
        # grid_io_0 = next(self._top_module.get_instances(
        #     f"grid_io_bottom_{self.fpga_size[0]}__0_"))
        # grid_io_1 = next(self._top_module.get_instances(
        #     f"grid_io_right_{self.fpga_size[0]+1}__1_"))
        instance_list.append(
            ((clb, cbx0, cbx1, cby0, sb0, sb1), f"tile_{self.fpga_size[0]}__1_")
        )
        self.merge_and_update(instance_list, "bottom_right_tile")