import hashlib
import os
from pathlib import Path
from .message import bright, info, error, warning
from .processor import Processor
from .config import Config
from .robot import Robot, Part
from .geometry import Mesh
import numpy as np
import pickle


class ProcessorConvexDecomposition(Processor):
    """
    Convex decomposition processor. Runs CoACD algorithm on collision meshes to use a convex approximation.
    """

    is_safe: bool = False

    def __init__(self, config: Config):
        super().__init__(config)

        # Enable convex decomposition
        self.convex_decomposition: bool = config.get("convex_decomposition", False)
        self.rainbow_colors: bool = config.get("rainbow_colors", False)

        self.check_coacd()

    def get_cache_path(self) -> Path:
        """
        Return the path to the user cache.
        """
        path = Path.home() / ".cache" / "onshape-to-robot-convex-decomposition"
        path.mkdir(parents=True, exist_ok=True)
        return path

    def check_coacd(self):
        if self.convex_decomposition:
            print(bright("* Checking CoACD presence..."))
            try:
                import coacd
                import trimesh
            except ImportError:
                print(bright("Can't import CoACD, disabling convex decomposition."))
                print(info("TIP: consider installing CoACD:"))
                print(info("pip install coacd trimesh"))
                self.convex_decomposition = False

    def process(self, robot: Robot):
        if self.convex_decomposition:
            os.makedirs(self.config.asset_path("convex_decomposition"), exist_ok=True)
            getcwd = os.getcwd()
            os.chdir(self.config.output_directory)

            for link in robot.links:
                for part in link.parts:
                    self.convex_decompose(part)

            os.chdir(getcwd)

    def convex_decompose(self, part: Part):
        import coacd
        import trimesh

        collision_meshes = [mesh for mesh in part.meshes if mesh.collision]
        if len(collision_meshes) > 0:
            if len(collision_meshes) > 1:
                print(
                    warning(
                        f"* Skipping convex decomposition for part {part.name} as it already has multiple collision meshes."
                    )
                )

            collision_mesh = collision_meshes[0]

            # Retrieving file SHA1
            sha1 = hashlib.sha1(open(collision_mesh.filename, "rb").read()).hexdigest()
            cache_filename = f"{self.get_cache_path()}/{sha1}.pkl"

            if os.path.exists(cache_filename):
                print(
                    info(
                        f"* Loading cached CoACD decomposition cache for part {part.name}"
                    )
                )
                with open(cache_filename, "rb") as f:
                    meshes = pickle.load(f)
            else:
                mesh = trimesh.load(collision_mesh.filename, force="mesh")
                mesh = coacd.Mesh(mesh.vertices, mesh.faces)
                meshes = coacd.run_coacd(mesh, max_convex_hull=16)
                with open(cache_filename, "wb") as f:
                    pickle.dump(meshes, f)

            part.collision_meshes = []
            filename = self.config.asset_path(
                f"convex_decomposition/{part.name}_%05d.stl"
            )
            for k, mesh in enumerate(meshes):
                mesh = trimesh.Trimesh(vertices=mesh[0], faces=mesh[1])
                mesh.export(filename % k)
                color = (
                    np.concatenate([np.random.rand(3), [1.0]]) if self.rainbow_colors else collision_mesh.color
                )
                part.meshes.append(
                    Mesh(
                        filename % k,
                        color,
                        visual=False,
                        collision=True,
                    )
                )
                part.collision_meshes.append(filename % k)

            collision_mesh.collision = False
            part.prune_unused_geometry()

            print(
                info(f"* Decomposed part {part.name} into {len(meshes)} convex shapes.")
            )
