from __future__ import annotations

import json
import textwrap
from collections.abc import Iterable


class FontEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, Font):
            return {
                "__gradio_font__": True,
                "name": obj.name,
                "class": "google"
                if isinstance(obj, GoogleFont)
                else "local"
                if isinstance(obj, LocalFont)
                else "font",
                "weights": obj.weights
                if isinstance(obj, (GoogleFont, LocalFont))
                else None,
            }
        # Let the base class default method raise the TypeError
        return json.JSONEncoder.default(self, obj)


def as_font(dct):
    if "__gradio_font__" in dct:
        name = dct["name"]
        if dct["class"] == "google":
            return (
                GoogleFont(name, weights=dct["weights"])
                if "weights" in dct
                else GoogleFont(name)
            )
        if dct["class"] == "local":
            return (
                LocalFont(name, weights=dct["weights"])
                if "weights" in dct
                else LocalFont(name)
            )
        return Font(name)
    return dct


class Font:
    def __init__(self, name: str):
        self.name = name

    def __str__(self) -> str:
        return (
            self.name
            if self.name in ["sans-serif", "serif", "monospace", "cursive", "fantasy"]
            else f"'{self.name}'"
        )

    def stylesheet(self) -> dict:
        return {"url": None, "css": None}

    def __eq__(self, other: Font) -> bool:
        return self.name == other.name and self.stylesheet() == other.stylesheet()

    def __repr__(self) -> str:
        klass = type(self)
        class_repr = klass.__module__ + "." + klass.__qualname__
        attrs = ", ".join([k + "=" + repr(v) for k, v in self.__dict__.items()])
        return f"<{class_repr} ({attrs})>"


class GoogleFont(Font):
    def __init__(self, name: str, weights: Iterable[int] = (400, 600)):
        self.name = name
        self.weights = weights

    def stylesheet(self) -> dict:
        url = f"https://fonts.googleapis.com/css2?family={self.name.replace(' ', '+')}:wght@{';'.join(str(weight) for weight in self.weights)}&display=swap"
        return {"url": url, "css": None}


class LocalFont(Font):
    def __init__(self, name: str, weights: Iterable[int] = (400, 700)):
        super().__init__(name)
        self.weights = weights

    def stylesheet(self) -> dict:
        css_template = textwrap.dedent("""
            @font-face {{
                font-family: '{name}';
                src: url('static/fonts/{file_name}/{file_name}-{file_weight}.woff2') format('woff2');
                font-weight: {css_weight};
                font-style: normal;
            }}
            """)
        css_rules = []
        for weight in self.weights:
            file_weight = (
                "Regular" if weight == 400 else "Bold" if weight == 700 else str(weight)
            )
            css_rules.append(
                css_template.format(
                    name=self.name,
                    file_name=self.name.replace(" ", ""),
                    file_weight=file_weight,
                    css_weight=weight,
                )
            )
        return {"url": None, "css": "\n".join(css_rules)}
