from typing import Optional
from typing import List
from typing import Tuple
import re


def get_generics(buffer: str) -> Optional[List[Tuple[str, str, str]]]:
    """Parses entity generics out of an input string

    The input is expected to be a string representing vhdl file content. If an
    entity is defined within this content, a generic block is parsed if one is
    found. If nothing is found that could be parsed, the function returns
    None. If the generic parameters could be parsed, they are returned with
    their individual properties.

    A generic parameter consists of the following properties:

    - name\n
    - type\n
    - default value (optional)\n

    Args:
        buffer (str): input string

    Returns:
        List[Tuple[str, str, str]]: generic names, types and default values
    """
    # (                 begin of capture group------------------ENTITY GENERICS
    #     generic       "generic"
    #     \s*           zero or more whitespace characters
    #     \(            opening parenthesis
    #     .*?           any character zero or more times. lazy evaluation
    #     \)            closing parenthesis
    #     \s*           zero or more whitespace characters
    #     ;             semicolon
    # )                 end of capture group
    # \s*               zero or more whitespace characters
    # port              "port"
    # \s*               zero or more whitespace characters
    # \(                opening parenthesis
    m = re.search(
        r"(generic\s*\(.*?\)\s*;)\s*port\s*\(", buffer, flags=re.IGNORECASE,
    )
    if m is None:
        return None
    generic_str = m.group(1)
    # generic variable names
    # (                 begin of capture group----------------------------NAMES
    #     [a-z]         lowercase letter (identifiers must begin with a letter)
    #     [a-z_0-9]     lowercase letter/underscore/digit or comma
    #     *             zero or more times
    # )                 end of capture group
    # \s*               zero or more whitespace characters
    # :                 double colon
    # [^=]              any character that is not an equal sign
    _generic_names = re.findall(
        r"([a-z][a-z_0-9,]*)\s*:[^=]", generic_str, flags=re.IGNORECASE
    )
    # capture generic "type to end" of generic description
    # used to identify the type and default value, but the default value is
    # optional
    # :                 double colon
    # \s*               zero or more whitespace characters
    # (                 begin of capture group-----------TYPES & DEFAULT VALUES
    #     .*?           any character zero or more times. lazy evaluation
    # )                 end of capture group
    # \s*               zero or more whitespace characters
    # (?:               begin of non-capture group
    #     ;             semicolon
    #     |             OR
    #     \)            closing parenthesis
    #     \s*           zero or more whitespace characters
    #     ;             semicolon
    # )                 end of non-capture group
    __generic_type_to_end = re.findall(
        r":\s*(.*?)\s*(?:;|\)\s*;)", generic_str, flags=re.IGNORECASE
    )
    # generic variable types (e.g. integer)
    _generic_types = [
        gtte.split(":=")[0].strip() for gtte in __generic_type_to_end
    ]
    # generic default values (e.g. 42)
    # only if one is specified with ":=", None otherwise
    _generic_def_vals = []
    for gt in __generic_type_to_end:
        def_val = gt.split(":=")[1].strip() if ":=" in gt else None
        _generic_def_vals.append(def_val)

    # account for multiple generic names in the same line, separated by comma
    count = [_gn.count(",") + 1 for _gn in _generic_names]
    # correct generic names list. every generic variable is an entry in a list
    generic_names = [gn for _gn in _generic_names for gn in _gn.split(",")]
    # correct generic types and default values. expand lists depending on count
    generic_types, generic_def_vals = [], []
    for c, _gt, _gd in zip(count, _generic_types, _generic_def_vals):
        generic_types.extend([_gt] * c)
        generic_def_vals.extend([_gd] * c)

    # generic names, types and default values as a list of tuples
    generics = [
        (gn, gt, gd)
        for gn, gt, gd in zip(generic_names, generic_types, generic_def_vals)
    ]
    return generics