import logging
import time
from pathlib import Path
from threading import Thread
from typing import Callable

from matplotlib import animation, pyplot as plt


def animate(
    fig, frames: list[int] | None, **kwargs
) -> Callable[..., animation.FuncAnimation]:
    """Decorator-style wrapper for FuncAnimation."""
    # Why isn't this how the function is exposed by default?
    return lambda func: animation.FuncAnimation(fig, func, frames, **kwargs)

# writer = animation.writers["ffmpeg"](fps=15, metadata={"artist": "Me"})

def bindfig(fig):
    """
    Create a function which is compatible with the signature of `pyplot.figure`,
    but alters the already-existing figure `fig` instead.
    """
    def ret(**kwargs):
        for i, j in kwargs.items():
            if i == "figsize":
                if j is not None:
                    fig.set_figwidth(j[0])
                    fig.set_figheight(j[1])
                continue
            fig.__dict__["set_" + i](j)
        return fig
    return ret

class SympyAnimationWrapper:
    """
    Context manager for binding a figure for animation.
    """
    def __init__(self, filename: Path | str):
        self._filename = filename if isinstance(filename, Path) else Path(filename)
        self._current_figure = plt.gcf()

        self._figure_temp = plt.figure

    def __enter__(self):
        plt.figure = bindfig(self._current_figure)
        return self.animate

    def __exit__(self, *_):
        plt.figure = self._figure_temp

    def animate(self, *args, **kwargs):
        def wrapper(func):
            ret = animate(self._current_figure, *args, **kwargs)(func)
            current_save = ret.save
            def save(*args, **kwargs):
                if self._filename.exists():
                    logging.error("Ignoring saving animation '%s': file already exists", self._filename)
                    return
                thread = Thread(target=lambda: time.sleep(1) or plt.close())
                thread.run()
                current_save(self._filename, *args, **kwargs)
            ret.save = save

            return ret

        return wrapper
