#!/usr/bin/env python3
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
r"""Helper functions for parsing a systemverilog module header.
"""

from io import StringIO
from pathlib import Path


class Param():
    name = ""
    datatype = ""
    style = ""
    value = ""

    def __init__(self, name_="", datatype_="", style_="", value_=""):
        self.name = name_
        self.datatype = datatype_
        self.style = style_
        self.value = value_


class Port():
    name = ""
    datatype = ""
    direction = ""

    def __init__(self, name_="", datatype_="", direction_=""):
        self.name = name_
        self.datatype = datatype_
        self.direction = direction_


class Dut():
    name = ""
    pkgs = []
    params = []
    ports = []
    deps = []
    is_cip = False

    def __init__(self, name_="", pkgs_=[], ports_=[], \
        params_=[], deps_=[], is_cip_=False):
        self.name = name_
        self.pkgs = pkgs_
        self.ports = ports_
        self.params = params_
        self.deps = deps_
        self.is_cip = is_cip_

    # filters out localparams
    def get_param_style(self, style):
      params = []
      for p in self.params:
        params += [p] if p.style == style else []
      return params

# strip // comments
def strip_comments(buf):
    outbuf = ""
    for line in buf.split('\n'):
        for k in range(len(line) - 1):
            if line[k:k + 2] == "//":
                break
            else:
                outbuf += line[k]
        else:
            if line:
                outbuf += line[-1]
        outbuf += " "

    return outbuf


PARENTH_STYLES = {'(': ')', '[': ']', '{': '}'}


# parse parenthesis and optionally handle the body using the handler function
# if no handler is specified, it just calls itself recursively
def parse_parenthesis(hdl_raw, dut, style='(', handler=None):
    if style not in PARENTH_STYLES:
        print("Unknown parenthesis style %s, aborting." % style)
    else:
        par_opened = False
        while hdl_raw:
            c = hdl_raw.pop(0)
            if c == style:
                par_opened = True
                if handler:
                    handler(hdl_raw, dut)
                else:
                    parse_parenthesis(hdl_raw, dut, style)
            if c == PARENTH_STYLES[style]:
                if not par_opened:
                    hdl_raw.insert(0, c)
                break
    return


# parse individual port declarations
# may not be fully correct with unpacked dimensions,
# but works for the common case
def parse_port(buf, dut):
    words = buf.split()
    if words:
        if words[0] not in ["input", "inout", "output"]:
            print("Warning, expected input, inout or output keyword")
        else:
            if len(words) > 2:
                dut.ports += [Port(words[-1], "".join(words[1:-1]), words[0])]
            elif len(words) == 2:
                dut.ports += [Port(words[-1], "", words[0])]
            else:
                print("Warning, port declaration incomplete")
    else:
        print("Warning, port declaration empty")
    return


# parse individual parameter declaration
def parse_param(buf, dut):
    words = buf.split('=')
    value = '='.join(words[1:])
    words = words[0].split()

    if words:
        if words[0] not in ["parameter", "localparam"]:
            print("Warning, expected parameter or localparam keyword")
        else:
            if len(words) > 2:
                dut.params += [
                    Param(words[-1], " ".join(words[1:-1]), words[0], value)
                ]
            elif len(words) == 2:
                dut.params += [Param(words[-1], "", words[0], value)]
            else:
                print("Warning, parameter declaration incomplete")
    else:
        print("Warning, port declaration empty")
    return


# extract individual declarations
def parse_declaration(hdl_raw, dut, handler):
    buf = ""
    par_opened = 0
    while hdl_raw:
        c = hdl_raw.pop(0)
        # end of this port
        if c == ',':
            handler(buf, dut)
            buf = ""
        elif c == '(':
            par_opened = par_opened + 1
            buf += c
        elif c == ')':
            if par_opened:
                # part of the declaration
                par_opened = par_opened - 1
                buf += c
            else:
                # end of the declaration list
                handler(buf, dut)
                hdl_raw.insert(0, ')')
                break
        else:
            buf += c
    return


def parse_ports(hdl_raw, dut):
    parse_declaration(hdl_raw, dut, parse_port)


def parse_params(hdl_raw, dut):
    parse_declaration(hdl_raw, dut, parse_param)


def parse_module(words, dut):
    # check for imports first
    while words:
        w = words.pop(0)
        if w == "import":
            if words:
                # get package names to import
                pkg = words.pop(0).split(";")
                dut.pkgs += [pkg[0]]
            else:
                print("Unexpected end")
        # stop package scan and move on to body
        elif '#' in w or '(' in w:
            words.insert(0, w)
            break

    hdl_raw = list(' '.join(words))
    while hdl_raw:
        c = hdl_raw.pop(0)
        if c == '#':
            parse_parenthesis(hdl_raw, dut, '(', parse_params)
        elif c == '(':
            hdl_raw.insert(0, '(')
            parse_parenthesis(hdl_raw, dut, '(', parse_ports)
        elif c == ';':
            break
    return


# simplistic module declaration parser.
# this works in most cases, but there are exceptions.
def parse_file(file):
    dut = Dut()
    hdl_raw = ""
    with open(file, 'r') as fp:
        hdl_raw = strip_comments(fp.read())
    # extract first module declaration in file and extract port list
    # also look for imported packages (either in the module declaration
    # or before it)
    words = hdl_raw.split()
    while words:
        w = words.pop(0)
        if w == "import":
            if words:
                # get package names to import
                pkg = words.pop(0).split(";")
                dut.pkgs += [pkg[0]]
            else:
                print("Unexpected end")
        elif w == "module":
            if words:
                # get module name
                dut.name = words[0]
                # parse the module params and port list and exit
                parse_module(words, dut)
            break
    return dut
