text: # Copyright lowRISC contributors.
  # Licensed under the Apache License, Version 2.0, see LICENSE for details.
  # SPDX-License-Identifier: Apache-2.0
  import logging as log
  from collections import OrderedDict
  from functools import partial
  
  from reggen.validate import check_bool, check_int, val_types
  
  from .item import Node, NodeType
  from .lib import simplify_addr
  from .xbar import Xbar
  
  # val_types = {
  #     'd': ["int", "integer (binary 0b, octal 0o, decimal, hex 0x)"],
  #     'x': ["xint", "x for undefined otherwise int"],
  #     'b': [
  #         "bitrange", "bit number as decimal integer, \
  #                     or bit-range as decimal integers msb:lsb"
  #     ],
  #     'l': ["list", "comma separated list enclosed in `[]`"],
  #     'ln': ["name list", 'comma separated list enclosed in `[]` of '\
  #            'one or more groups that have just name and dscr keys.'\
  #            ' e.g. `{ name: "name", desc: "description"}`'],
  #     'lnw': ["name list+", 'name list that optionally contains a width'],
  #     'lp': ["parameter list", 'parameter list having default value optionally'],
  #     'g': ["group", "comma separated group of key:value enclosed in `{}`"],
  #     'lg': ["list of group", "comma separated group of key:value enclosed in `{}`"\
  #            " the second entry of the list is the sub group format"],
  #     's': ["string", "string, typically short"],
  #     't': ["text", "string, may be multi-line enclosed in `'''` "\
  #           "may use `**bold**`, `*italic*` or `!!Reg` markup"],
  #     'T': ["tuple", "tuple enclosed in ()"],
  #     'pi': ["python int", "Native Python type int (generated)"],
  #     'pb': ["python Bool", "Native Python type Bool (generated)"],
  #     'pl': ["python list", "Native Python type list (generated)"],
  #     'pe': ["python enum", "Native Python type enum (generated)"]
  # }
  addr = {
      'name': 'Address configuration',
      'description':
      '''Device Node address configuration. It contains the base address and the size in bytes.
  ''',
      'required': {
          'base_addr':
          ['d', 'Base address of the device. It is required for the device'],
          'size_byte':
          ['d', 'Memory space of the device. It is required for the device'],
      },
      'optional': {},
      'added': {}
  }
  
  node = {
      'name': 'Node configuration',
      'description': '''
  Crossbar node description. It can be host, device, or internal nodes.
  ''',
      'required': {
          'name': ['s', 'Module instance name'],
          'stub': ['pb', 'Real node or stub.  Stubs only occupy address ranges'],
          'type': [
              's',
              'Module type: {"host", "device", "async", "socket_1n", "socket_m1"}'
          ],
      },
      'optional': {
          'clock': ['s', 'main clock of the port'],
          'reset': ['s', 'main reset of the port'],
          'pipeline': ['pb', 'If true, pipeline is added in front of the port'],
          'req_fifo_pass': ['pb',
                            'If true, pipeline fifo has passthrough behavior on req'],
          'rsp_fifo_pass': ['pb',
                            'If true, pipeline fifo has passthrough behavior on rsp'],
          'inst_type': ['s', 'Instance type'],
          'xbar': ['pb', 'If true, the node is connected to another Xbar'],
          'addr_range': ['lg', addr]
      },
      'added': {}
  }
  
  root = {
      'name': 'Top configuration',
      'description': '''
  Crossbar configuration format.
  ''',
      'required': {
          'name': ['s', 'Name of the crossbar'],
          'clock': [
              's', 'Main clock. Internal components use this clock.'
              ' If not specified, it is assumed to be in main clock domain'
          ],
          'reset': ['s', 'Main reset'],
          'connections': [
              'g',
              "List of edge. Key is host, entry in value list is device"
          ],
          'clock_connections': ['g', 'list of clocks'],
          'nodes': ['lg', node]
      },
      'optional': {
          'type': ['s', 'Indicate Hjson type. "xbar" always if exist'],
          'clock_group': ['s', "Remnant from auto-generation scripts. Ignore."],
          'clock_srcs': ['g', "Remnant from auto-generation scripts. Ignore."],
          'domain': ['s', 'Power domain for the crossbar']
      },
      'added': {
          'reset_connections': [
              'g', "Generated by topgen. Key is the reset signal inside IP"
              " and value is the top reset signal"
          ],
      }
  }
  
  
  def check_keys(obj, control, prefix=""):
      """ Check the keys recursively.
  
      The control parameter is a control group to check obj data structure.
      """
      error = 0
  
      # required
      for k, v in control["required"].items():
          if k not in obj:
              error += 1
              log.error(prefix + " missing required key " + k)
  
      # Check every fields' correctness
      for k, v in obj.items():
          checker = ['', '']
          prefix_name = prefix + " " + k
  
          if k in control["required"]:
              checker = control["required"][k]
          elif k in control["optional"]:
              checker = control["optional"][k]
          elif k in control["added"]:
              log.warning(prefix + " contains generated key " + k)
              checker = control["added"][k]
          else:
              log.warning(prefix + " contains extra key " + k)
              continue
  
          # Type and value check
          if checker[0] is not None and checker[0] not in val_types:
              log.error(prefix +
                        " field {} is undefined type. Check val_types {}".format(
                            k, checker[0]))
          if checker[0] is None:
              pass
          elif checker[0] == 'lg':
              # List of subgroup
              error += sum(
                  map(
                      partial(check_keys, control=checker[1],
                              prefix=prefix_name), obj[k]))
          elif checker[0] == 'g':
              # if second entry isn't string type, call recursively
              if isinstance(checker[1], str):
                  log.info(
                      "Skipping {} as no further control group is given".format(
                          prefix_name))
                  continue
  
              error += check_keys(obj=obj[k],
                                  control=checker[1],
                                  prefix=prefix_name)
  
          elif checker[0] == 'd':
              int_v, err = check_int(obj[k], prefix_name)
              if err:
                  error += 1
  
          elif checker[0] == 's' or checker[0] == 't':
              # don't care the string
              pass
  
          elif checker[0] == 'pb':
              b_v, err = check_bool(obj[k], prefix_name)
              if err:
                  error += 1
  
          elif checker[0] == 'l':
              if not isinstance(obj[k], list):
                  error += 1
          else:
              log.error(prefix_name +
                        " is not supported in this configuration format")
  
      return error
  
  
  def get_nodetype(t):  # t: str -> NodeType
      if t == "host":
          return NodeType.HOST
      elif t == "device":
          return NodeType.DEVICE
      elif t == "async_fifo":
          return NodeType.ASYNC_FIFO
      elif t == "socket_1n":
          return NodeType.SOCKET_1N
      elif t == "socket_m1":
          return NodeType.SOCKET_M1
  
      log.error("Cannot process type {}".format(t))
      raise
  
  
  def checkNameExist(name, xbar):  # name: str -> xbar: Xbar -> bool
      return name.lower() in [x.name for x in xbar.nodes]
  
  
  def isOverlap(range1, range2):  # Tuple[int,int] -> Tuple[int,int] -> bool
      return not (range2[1] < range1[0] or range2[0] > range1[1])
  
  
  # Tuple[int,int] -> List[Tuple[]] -> bool
  def checkAddressOverlap(addr, ranges):
      result = [x for x in ranges if isOverlap(x, addr)]
      return len(result) != 0
  
  
  # this returns 1 if the size mask overlapps with the address base
  def checkBaseSizeOverlap(addr_base, size):
      return ((size - 1) & addr_base)
  
  
  def validate(obj: OrderedDict) -> Xbar:  # OrderedDict -> Xbar
      xbar = Xbar()
      xbar.name = obj["name"].lower()
      xbar.clock = obj["clock"].lower()
      xbar.reset = obj["reset"].lower()
      addr_ranges = []
  
      obj, err = validate_hjson(obj)  # validate Hjson format first
      if err > 0:
          log.error("Hjson structure error")
          return
  
      # collection of all clocks and resets of this xbar
      xbar.clocks = [clock for clock in obj["clock_connections"].keys()]
      xbar.resets = [reset for reset in obj["reset_connections"].keys()]
  
      # Nodes
      for nodeobj in obj["nodes"]:
  
          if checkNameExist(nodeobj["name"], xbar):
              log.error("Duplicated name: %s" % (nodeobj["name"]))
              raise SystemExit("Duplicated name in the configuration")
  
          clock = nodeobj["clock"].lower() if "clock" in nodeobj.keys(
          ) else xbar.clock
  
          reset = nodeobj["reset"].lower() if "reset" in nodeobj.keys(
          ) else xbar.reset
  
          if clock not in xbar.clocks:
              log.error(
                  "Clock %s for module %s does not exist in xbar_%s, check xbar hjson"
                  % (clock, nodeobj['name'], obj['name']))
              raise SystemExit("Clock does not exist")
  
          if reset not in xbar.resets:
              log.error(
                  "Reset %s for module %s does not exist in xbar_%s, check xbar hjson"
                  % (reset, nodeobj['name'], obj['name']))
              raise SystemExit("Reset does not exist")
  
          node = Node(name=nodeobj["name"].lower(),
                      node_type=get_nodetype(nodeobj["type"].lower()),
                      clock=clock,
                      reset=reset)
  
          if node.node_type == NodeType.DEVICE:
              node.xbar = nodeobj["xbar"]
              node.addr_range = []
  
              # Compact the address ranges
              if node.xbar:
                  nodeobj["addr_range"] = simplify_addr(nodeobj, obj)
  
              for addr in nodeobj["addr_range"]:
                  address_from = int(addr["base_addr"], 0)
                  size = int(addr["size_byte"], 0)
                  address_to = address_from + size - 1
  
                  addr_entry = (address_from, address_to)
  
                  if checkBaseSizeOverlap(address_from, size):
                      log.error(
                          "Size mask and base address are overlapping. "
                          " Check the config. Addr(0x%x - 0x%x)"
                          % (addr_entry[0], addr_entry[1]))
                      raise SystemExit("Base/size overlapping error occurred")
  
                  if checkAddressOverlap(addr_entry, addr_ranges):
                      log.error(
                          "Address is overlapping. Check the config. Addr(0x%x - 0x%x)"
                          % (addr_entry[0], addr_entry[1]))
                      raise SystemExit("Address overlapping error occurred")
  
                  addr_ranges.append(addr_entry)
                  node.addr_range.append(addr_entry)
  
          if node.node_type in [NodeType.DEVICE, NodeType.HOST
                                ] and "pipeline" in nodeobj:
              node.pipeline = True if nodeobj["pipeline"] else False
          else:
              node.pipeline = False
          if node.node_type in [NodeType.DEVICE, NodeType.HOST]:
              node.req_fifo_pass = nodeobj["req_fifo_pass"] \
                  if "req_fifo_pass" in nodeobj else False
  
              node.rsp_fifo_pass = nodeobj["rsp_fifo_pass"] \
                  if "rsp_fifo_pass" in nodeobj else False
  
          else:
              node.req_fifo_pass = False
              node.rsp_fifo_pass = False
          xbar.nodes.append(node)
  
      # Edge
      for host in obj["connections"].keys():
          # host: [device]
          for device in obj["connections"][host]:
              xbar.connect_nodes(host.lower(), device.lower())
  
      return xbar
  
  
  def validate_hjson(obj):
      if "type" not in obj:
          obj["type"] = "xbar"
      if "name" not in obj:
          log.error("Component has no name. Aborting.")
          return None, 1
  
      component = obj["name"]
      error = check_keys(obj, root, component)
  
      if error > 0:
          log.error("{} has top level error. Aborting".format(component))
          return None, error
      return obj, 0
  