"""

    AUTHOR:

        Khushal P Soonderji

    DATE:

        Saturday, 24th Aug., 2024

    OBJECTIVE:

        To provide an overview of any function or class in a string.
        The generated overview can then either be shown on the terminal, or transmitted over some other medium for
        collaborative work.

    REFERENCES:

        N/A

    DOWNLOADS:

        N/A

"""


#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                                   IMPORT                                                      ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


# System-level activities:
import io
import inspect


#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                           MACROS / ONE-TIME INIT                                              ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


# --- Nothing Yet


#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                                VARIABLES                                                      ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


# --- Nothing Yet


#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                                  FUNCTIONS                                                    ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


def get_help_for_class(cls, skip_methods = None):

    """
    Returns the help documentation to use this class.
    :param cls: The class whose help string is desired.
    :param skip_methods: A list of methods to NOT include in the help text.
    :return: This help documentation.
    """

    separator = "\n\n" + ("=" * 120) + "\n\n"
    if skip_methods is None: skip_methods = []
    elif not isinstance(skip_methods, list): skip_methods = [skip_methods]

    # Get class name and docstring:
    class_name = cls.__name__
    docstring = inspect.getdoc(cls) or ""
    help_string = "HELP FOR:\n\n"
    help_string += class_name + "\n\n"
    help_string += "This document has upto 120 chars per line.\n"
    help_string += "Best viewed with monospaced font :)"
    help_string += docstring + separator

    # Get all methods and their docstrings.
    # Then note the documentation of the methods while ignoring the blacklisted ones:
    members = inspect.getmembers(cls, predicate = inspect.isfunction)
    func_help = []
    for name, method in members:

        # Ignore if asked, or extract the details:
        if name in skip_methods or name.startswith(f"_{class_name}__"): continue
        else: func_help.append(get_help_for_function(method))

    # Put all the things together:
    func_help = separator.join(func_help)
    help_string += func_help

    # Done here:
    return help_string


# ---------------------------------------------------------------------------------------------------------------------


def get_help_for_function(func):

    """
    Get the help string for one function.
    It could be a standalone function, or a method of a class.
    :param func: The function (or method) whose help string is needed.
    :return: The help string of the function.
    """

    # Get the name and documentation:
    func_name = func.__name__
    async_indicator = "  (async)" if inspect.iscoroutinefunction(func) else ""
    func_doc = inspect.getdoc(func) or ""

    # Create the decorated header:
    func_decorator = "-" * (len(func_name) + 2)
    func_head = "." + func_decorator + f".\n| {func_name} |{async_indicator}\n`" + func_decorator + "`\n\n"

    # Add the 'args' and 'kwargs':
    func_args = []
    for name, param in inspect.signature(func).parameters.items():
        default = param.default
        if isinstance(default, str): default = f"\"{default}\""
        if default == inspect.Parameter.empty: func_args.append(f"{name}")
        else: func_args.append(f"{name}: {type(default).__name__} = {default}")
    if len(func_args) > 0: func_args = f"{func_name} (\n\t" + "\n\t".join(func_args) + "\n):\n\n"
    else: func_args = f"{func_name} ():\n\n"

    # Get the params and return value part from the doc:
    params_start = func_doc.find(":param")
    return_start = func_doc.find(":return")
    func_params = "\n" + func_doc[params_start:return_start] if params_start >= 0 else ""
    func_return = "\n" + func_doc[return_start:] if return_start >= 0 else ""

    # Isolate the documentation part:
    if params_start >= 0: func_doc = func_doc[:params_start]
    elif return_start >= 0: func_doc = func_doc[:return_start]

    # Done here:
    return func_head + func_args + func_doc + func_params + func_return


# ---------------------------------------------------------------------------------------------------------------------


def get_help(entity, skip_methods = None):

    """
    Get the help documentation for anything from its docstring.
    :param entity: The entity you want to get help for.
    :param skip_methods: A list of methods to ignore if inspecting a class. Not valid for standalone functions.
    :return: The help string.
    """

    if inspect.isclass(entity): return get_help_for_class(entity, skip_methods = skip_methods)
    else: return get_help_for_function(entity)


#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                               MAIN PROGRAM                                                    ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


if __name__ == "__main__":

    pass
