"""This plugin provides menus to list unused entities

This menu adds some submenus to /Navigate that will show the list of entities
that are declared in the whole application, a specific project or a specific
file, and that are not used anywhere. In some cases, the compiler can warn
about unused entities local to a subprogram (or even to a package body in
Ada), but cannot do so for entities declared in specs.

The global entities that are not used anywhere will be listed in the locations
window, so that you can click on them to jump to their declaration.

For the time being, subprograms that are primitive operations of a tagged
type (or method of a class in languages other than Ada) are not listed, since
they might actually be called through dynamic dispatching and should not be
removed.

It is recommended that you remove all .ali files and then recompile your
application before running this script. It needs up to date cross-reference
information generated by the compiler. It will also read all .ali files it
finds in your object directories, so if you still have old files lying around,
they might show references to entities even though these entities are in fact
no longer used (which means GPS will not correctly report all cases of unused
entities).

Executing this script blocks the whole GPS interface, so it is normal that
GPS becomes unresponsive. Depending of the size of your project, this can
take a while to execute.
Note that you can save the contents of the Locations window, after execution,
through the GPS.Locations.dump() method in the python console.
"""

#############################################################################
# No user customization below this line
#############################################################################

from GPS import Preference, Project, Console, Editor, File, Locations, EditorBuffer, MDI
from gs_utils import interactive

xmlada_projects = [
    "xmlada_sax",
    "xmlada_dom",
    "xmlada_schema",
    "xmlada_unicode",
    "xmlada_input",
    "xmlada_shared",
    "xmlada",
]
aws_projects = [
    "aws_config",
    "aws_libz",
    "aws_shared",
    "aws_ssl_support",
    "aws_components",
    "aws_xmlada",
    "aws",
]

Preference("Plugins/unused_entities/ignoreprj").create(
    "Ignored projects",
    "string",
    """Comma-separated list of projects for which we never want to look for
    unused entities. # This should in general include those projects from
    third-party libraries. You can still search for unusued entities if you
    select that project specifically.""",
    ",".join(xmlada_projects + aws_projects),
)


def EntityIterator(where):
    """Return all entities from WHERE"""
    if not where:
        ignore_projects = [
            s.strip().lower()
            for s in Preference("Plugins/unused_entities/ignoreprj").get().split(",")
        ]

        for p in Project.root().dependencies(recursive=True):
            if p.name().lower() not in ignore_projects:
                Console().write(
                    "Searching unused entities in project " + p.name() + "\n"
                )
                for s in p.sources():
                    for e in s.entities(local=True):
                        yield e
    elif isinstance(where, Project):
        for s in where.sources():
            for e in s.entities(local=True):
                yield e
    elif isinstance(where, File):
        for e in where.entities(local=True):
            yield e


def GlobalIterator(where):
    """Return all global entities from WHERE"""
    for e in EntityIterator(where):
        if e.attributes()["global"]:
            yield e


def is_unused(entity):
    refs = entity.references(include_implicit=True, synchronous=True, show_kind=True)
    for loc, kind in refs.items():
        if kind != "declaration" and kind != "body" and kind != "label":
            return False

    # If we have a primitive operation, do not report it for now, since it
    # might actually be called through dispatching. We do not know yet how
    # to test that

    if entity.primitive_of():
        return False

    return True


def UnusedIterator(where, globals_only):
    """Return all unused entities from WHERE, and only global entities if
    GLOBALS_ONLY is true"""
    if globals_only:
        iter = GlobalIterator
    else:
        iter = EntityIterator

    for e in iter(where):
        if is_unused(e):
            yield e


def show_unused_entities(where, globals_only):
    """List all unused global entities from WHERE in the locations window"""
    Editor.register_highlighting("Unused_Entities", "blue")
    Locations.remove_category("Unused entity")
    MDI.get("Messages").raise_window()

    for e in UnusedIterator(where, globals_only=globals_only):
        Locations.add(
            category="Unused entity",
            file=e.declaration().file(),
            line=e.declaration().line(),
            column=e.declaration().column(),
            message="unused entity " + e.name(),
            highlight="Unused_Entities",
            length=len(e.name()),
        )

    Console().write("Done searching for unused entities\n")


@interactive(
    name="show unused entities from file",
    menu="/Navigate/List unused entities/From file",
    after="Goto Body",
)
def show_unused_entities_in_file():
    """
    Show all entities from the current file that are unused anywhere in your
    application.
    """
    show_unused_entities(EditorBuffer.get().file(), True)


@interactive(
    name="show unused entities from project",
    menu="/Navigate/List unused entities/From project",
    after="Goto Body",
)
def show_unused_entities_in_project():
    """
    Show all entities from the current project that are unused anywhere in
    your application.
    """
    show_unused_entities(EditorBuffer.get().file().project(), True)


@interactive(
    name="show unused entities from all projects",
    menu="/Navigate/List unused entities/From all projects",
    after="Goto Body",
)
def show_unused_entities_in_projects():
    """
    Show all entities from any loaded project that are unused anywhere in
    your application.
    """
    show_unused_entities(None, True)
