
import re
import pyblish.api
from reveries import plugins


def len_flattened(components):
    """Return the length of the list as if it was flattened.

    Maya will return consecutive components as a single entry
    when requesting with `maya.cmds.ls` without the `flatten`
    flag. Though enabling `flatten` on a large list (e.g. millions)
    will result in a slow result. This command will return the amount
    of entries in a non-flattened list by parsing the result with
    regex.

    Args:
        components (list): The non-flattened components.

    Returns:
        int: The amount of entries.

    """
    assert isinstance(components, (list, tuple))
    n = 0

    pattern = re.compile(r"\[(\d+):(\d+)\]")
    for c in components:
        match = pattern.search(c)
        if match:
            start, end = match.groups()
            n += int(end) - int(start) + 1
        else:
            n += 1
    return n


class ValidateMeshVerticesHaveEdges(pyblish.api.InstancePlugin):
    """Validate meshes have only vertices that are connected to edges.

    Maya can have invalid geometry with vertices that have no edges or
    faces connected to them.

    In Maya 2016 EXT 2 and later there's a command to fix this:
        `maya.cmds.polyClean(mesh, cleanVertices=True)`

    In older versions of Maya it works to select the invalid vertices
    and merge the components.

    To find these invalid vertices select all vertices of the mesh
    that are visible in the viewport (drag to select), afterwards
    invert your selection (Ctrl + Shift + I). The remaining selection
    contains the invalid vertices.

    """

    order = pyblish.api.ValidatorOrder + 0.3
    hosts = ["maya"]
    families = [
        "reveries.model",
        # "reveries.rig",
    ]
    label = "Mesh Vertices Have Edges"
    actions = [
        pyblish.api.Category("Select"),
        plugins.MayaSelectInvalidInstanceAction,
        pyblish.api.Category("Fix It"),
        plugins.RepairInstanceAction,
    ]

    optional = True

    @classmethod
    def get_invalid(cls, instance):
        from maya import cmds

        invalid = []

        meshes = cmds.ls(instance, type="mesh", long=True, noIntermediate=True)
        for mesh in meshes:
            num_vertices = cmds.polyEvaluate(mesh, vertex=True)

            # Vertices from all edges
            edges = "%s.e[*]" % mesh

            if not cmds.ls(edges):
                # Possible no vertex at all
                continue

            vertices = cmds.polyListComponentConversion(edges, toVertex=True)
            num_vertices_from_edges = len_flattened(vertices)

            if num_vertices != num_vertices_from_edges:
                invalid.append(mesh)

        return invalid

    def process(self, instance):

        invalid = self.get_invalid(instance)
        if invalid:
            raise RuntimeError("Meshes found in instance with vertices that "
                               "have no edges: %s" % invalid)

    @classmethod
    def fix_invalid(cls, instance):
        from maya import cmds

        # This fix only works in Maya 2016 EXT2 and newer
        if float(cmds.about(version=True)) <= 2016.0:
            raise RuntimeError("Repair not supported in Maya version below "
                               "2016 EXT 2")

        invalid = cls.get_invalid(instance)
        for node in invalid:
            cmds.polyClean(node, cleanVertices=True)
