For reasons that are a bit beyond me, `--host` and `PLATFORM_TRIPLET`
seem to be independent, in particular we've had an empty
`PLATFORM_TRIPLET`. This is unfortunate because `PLATFORM_TRIPLET`
is used to generate the SOABI config variable which in turn is used
to decide whether a .so file is a good match for loading. We'd like
for linux Pythons not to try to import emscripten .so files (it
raises `ImportError: some_file.so: invalid ELF header`). Similarly,
we'd like to avoid attempting to load linux .so files in wasm. These
platform tags are our friends.

Anyways, this PR sets `PLATFORM_TRIPLET` and ensures that .so files
built by pywasmcross are tagged with our SOABI tag.

I moved the .so file renaming from pywasmcross to buildpkg just
before running the post script. That is a better place to put it in
case the package wants to look at the .so file after linking it. It
might be surprised that we moved it.

I also improved the error message if we try to `loadWebAssemblyModule`
something that is actually say a Linux .so file and updated get_dynlibs
to filter out .so files that have an incompatible abi tag.
Set PLATFORM_TRIPLET, include platform in so names, only load compatible so files. For reasons that are a bit beyond me, `--host` and `PLATFORM_TRIPLET`
seem to be independent, in particular we've had an empty
`PLATFORM_TRIPLET`. This is unfortunate because `PLATFORM_TRIPLET`
is used to generate the SOABI config variable which in turn is used
to decide whether a .so file is a good match for loading. We'd like
for linux Pythons not to try to import emscripten .so files (it
raises `ImportError: some_file.so: invalid ELF header`). Similarly,
we'd like to avoid attempting to load linux .so files in wasm. These
platform tags are our friends.

Anyways, this PR sets `PLATFORM_TRIPLET` and ensures that .so files
built by pywasmcross are tagged with our SOABI tag.

I moved the .so file renaming from pywasmcross to buildpkg just
before running the post script. That is a better place to put it in
case the package wants to look at the .so file after linking it. It
might be surprised that we moved it.

I also improved the error message if we try to `loadWebAssemblyModule`
something that is actually say a Linux .so file and updated get_dynlibs
to filter out .so files that have an incompatible abi tag.
"""
A matplotlib backend that renders to an HTML5 canvas in the same thread.

The Agg backend is used for the actual rendering underneath, and renders the
buffer to the HTML5 canvas. This happens with only a single copy of the data
into the Canvas -- passing the data from Python to JavaScript requires no
copies.

See matplotlib.backend_bases for documentation for most of the methods, since
this primarily is just overriding methods in the base class.
"""

# TODO: Figure resizing support

import base64
import io

from matplotlib import interactive
from matplotlib.backend_bases import FigureManagerBase, _Backend
from matplotlib.backends import backend_agg
from matplotlib.backends.browser_backend import FigureCanvasWasm, NavigationToolbar2Wasm

from js import ImageData, document

interactive(True)


class FigureCanvasAggWasm(backend_agg.FigureCanvasAgg, FigureCanvasWasm):
    def __init__(self, *args, **kwargs):
        backend_agg.FigureCanvasAgg.__init__(self, *args, **kwargs)
        FigureCanvasWasm.__init__(self, *args, **kwargs)

    def draw(self):
        from pyodide import create_proxy

        # Render the figure using Agg
        self._idle_scheduled = True
        orig_dpi = self.figure.dpi
        if self._ratio != 1:
            self.figure.dpi *= self._ratio
        pixels_proxy = None
        pixels_buf = None
        try:
            super().draw()
            # Copy the image buffer to the canvas
            width, height = self.get_width_height()
            canvas = self.get_element("canvas")
            if canvas is None:
                return
            pixels = self.buffer_rgba().tobytes()
            pixels_proxy = create_proxy(pixels)
            pixels_buf = pixels_proxy.getBuffer("u8clamped")
            image_data = ImageData.new(pixels_buf.data, width, height)
            ctx = canvas.getContext("2d")
            ctx.putImageData(image_data, 0, 0)
        finally:
            self.figure.dpi = orig_dpi
            self._idle_scheduled = False
            if pixels_proxy:
                pixels_proxy.destroy()
            if pixels_buf:
                pixels_buf.release()


class NavigationToolbar2AggWasm(NavigationToolbar2Wasm):
    def download(self, format, mimetype):
        # Creates a temporary `a` element with a URL containing the image
        # content, and then virtually clicks it. Kind of magical, but it
        # works...
        element = document.createElement("a")
        data = io.BytesIO()
        try:
            self.canvas.figure.savefig(data, format=format)
        except Exception:
            raise
        element.setAttribute(
            "href",
            "data:{};base64,{}".format(
                mimetype, base64.b64encode(data.getvalue()).decode("ascii")
            ),
        )
        element.setAttribute("download", f"plot.{format}")
        element.style.display = "none"
        document.body.appendChild(element)
        element.click()
        document.body.removeChild(element)


class FigureManagerAggWasm(FigureManagerBase):
    def __init__(self, canvas, num):
        FigureManagerBase.__init__(self, canvas, num)
        self.set_window_title("Figure %d" % num)
        self.toolbar = NavigationToolbar2AggWasm(canvas)

    def show(self):
        self.canvas.show()

    def resize(self, w, h):
        pass

    def set_window_title(self, title):
        self.canvas.set_window_title(title)


@_Backend.export
class _BackendWasmCoreAgg(_Backend):
    FigureCanvas = FigureCanvasAggWasm
    FigureManager = FigureManagerAggWasm

    @staticmethod
    def show():
        from matplotlib import pyplot as plt

        plt.gcf().canvas.show()

"""
A matplotlib backend that renders to an HTML5 canvas in the same thread.

The Agg backend is used for the actual rendering underneath, and renders the
buffer to the HTML5 canvas. This happens with only a single copy of the data
into the Canvas -- passing the data from Python to JavaScript requires no
copies.

See matplotlib.backend_bases for documentation for most of the methods, since
this primarily is just overriding methods in the base class.
"""

# TODO: Figure resizing support

import base64
import io

from matplotlib import interactive
from matplotlib.backend_bases import FigureManagerBase, _Backend
from matplotlib.backends import backend_agg
from matplotlib.backends.browser_backend import FigureCanvasWasm, NavigationToolbar2Wasm

from js import ImageData, document

interactive(True)


class FigureCanvasAggWasm(backend_agg.FigureCanvasAgg, FigureCanvasWasm):
    def __init__(self, *args, **kwargs):
        backend_agg.FigureCanvasAgg.__init__(self, *args, **kwargs)
        FigureCanvasWasm.__init__(self, *args, **kwargs)

    def draw(self):
        from pyodide import create_proxy

        # Render the figure using Agg
        self._idle_scheduled = True
        orig_dpi = self.figure.dpi
        if self._ratio != 1:
            self.figure.dpi *= self._ratio
        pixels_proxy = None
        pixels_buf = None
        try:
            super().draw()
            # Copy the image buffer to the canvas
            width, height = self.get_width_height()
            canvas = self.get_element("canvas")
            if canvas is None:
                return
            pixels = self.buffer_rgba().tobytes()
            pixels_proxy = create_proxy(pixels)
            pixels_buf = pixels_proxy.getBuffer("u8clamped")
            image_data = ImageData.new(pixels_buf.data, width, height)
            ctx = canvas.getContext("2d")
            ctx.putImageData(image_data, 0, 0)
        finally:
            self.figure.dpi = orig_dpi
            self._idle_scheduled = False
            if pixels_proxy:
                pixels_proxy.destroy()
            if pixels_buf:
                pixels_buf.release()


class NavigationToolbar2AggWasm(NavigationToolbar2Wasm):
    def download(self, format, mimetype):
        # Creates a temporary `a` element with a URL containing the image
        # content, and then virtually clicks it. Kind of magical, but it
        # works...
        element = document.createElement("a")
        data = io.BytesIO()
        try:
            self.canvas.figure.savefig(data, format=format)
        except Exception:
            raise
        element.setAttribute(
            "href",
            "data:{};base64,{}".format(
                mimetype, base64.b64encode(data.getvalue()).decode("ascii")
            ),
        )
        element.setAttribute("download", f"plot.{format}")
        element.style.display = "none"
        document.body.appendChild(element)
        element.click()
        document.body.removeChild(element)


class FigureManagerAggWasm(FigureManagerBase):
    def __init__(self, canvas, num):
        FigureManagerBase.__init__(self, canvas, num)
        self.set_window_title("Figure %d" % num)
        self.toolbar = NavigationToolbar2AggWasm(canvas)

    def show(self):
        self.canvas.show()

    def resize(self, w, h):
        pass

    def set_window_title(self, title):
        self.canvas.set_window_title(title)


@_Backend.export
class _BackendWasmCoreAgg(_Backend):
    FigureCanvas = FigureCanvasAggWasm
    FigureManager = FigureManagerAggWasm

    @staticmethod
    def show():
        from matplotlib import pyplot as plt

        plt.gcf().canvas.show()
