from dataclasses import dataclass
from io import BytesIO
from typing import List, Optional

from pyhanko.pdf_utils import generic

from ..writer import BasePdfFileWriter
from .api import FontEngine, FontEngineFactory, ShapeResult

pdf_name = generic.NameObject

__all__ = [
    'SimpleFontEngineFactory', 'SimpleFontEngine',
    'SimpleFontMeta', 'get_courier'
]


@dataclass(frozen=True)
class SimpleFontMeta:
    first_char: int
    last_char: int
    widths: List[int]
    descriptor: generic.DictionaryObject


COURIER_META = SimpleFontMeta(
    # only define one width, let everything default to MissingWidth
    first_char=32, last_char=32, widths=[600],
    descriptor=generic.DictionaryObject({
        pdf_name('/Type'): pdf_name('/FontDescriptor'),
        pdf_name('/FontName'): pdf_name('/Courier'),
        # set fixed pitch, serif and nonsymbolic
        pdf_name('/Flags'): generic.NumberObject(0b100011),
        pdf_name('/FontBBox'): generic.ArrayObject(
            map(generic.NumberObject, [-23, -250, 715, 805])
        ),
        pdf_name('/Ascent'): generic.NumberObject(629),
        pdf_name('/Descent'): generic.NumberObject(-157),
        pdf_name('/CapHeight'): generic.NumberObject(629),
        pdf_name('/ItalicAngle'): generic.NumberObject(0),
        pdf_name('/StemV'): generic.NumberObject(51),
        pdf_name('/MissingWidth'): generic.NumberObject(600),
        pdf_name('/AvgWidth'): generic.NumberObject(600),
        pdf_name('/MaxWidth'): generic.NumberObject(600),
    })

)


class SimpleFontEngine(FontEngine):
    """
    Simplistic font engine that effectively only works with PDF standard fonts,
    and does not care about font metrics. Best used with monospaced fonts such
    as Courier.
    """

    @property
    def uses_complex_positioning(self):
        return False

    def __init__(self, writer: BasePdfFileWriter, name: str, avg_width: float,
                 meta: Optional[SimpleFontMeta] = None):
        self.avg_width = avg_width
        self.name = name
        self.meta = meta
        super().__init__(writer, name, embedded_subset=False)

    def shape(self, txt) -> ShapeResult:
        ops = BytesIO()
        generic.TextStringObject(txt).write_to_stream(ops)
        ops.write(b" Tj")
        total_len = len(txt) * self.avg_width

        return ShapeResult(
            graphics_ops=ops.getvalue(), x_advance=total_len,
            y_advance=0
        )

    def as_resource(self):
        font_dict = generic.DictionaryObject({
            pdf_name('/Type'): pdf_name('/Font'),
            pdf_name('/BaseFont'): pdf_name('/' + self.name),
            pdf_name('/Subtype'): pdf_name('/Type1'),
            pdf_name('/Encoding'): pdf_name('/WinAnsiEncoding'),
        })
        # TODO maybe we could be a bit more clever to avoid duplication
        #  (cf. how GlyphAccumulator does it)
        meta = self.meta
        if meta is not None:
            font_dict['/FirstChar'] = generic.NumberObject(meta.first_char)
            font_dict['/LastChar'] = generic.NumberObject(meta.last_char)
            font_dict['/Widths'] = generic.ArrayObject(
                map(generic.NumberObject, meta.widths)
            )
            font_dict['/FontDescriptor'] = self.writer.add_object(
                generic.DictionaryObject(meta.descriptor)
            )

        return font_dict


class SimpleFontEngineFactory(FontEngineFactory):
    def __init__(self, name: str, avg_width: float,
                 meta: Optional[SimpleFontMeta] = None):
        self.avg_width = avg_width
        self.name = name
        self.meta = meta

    def create_font_engine(self, writer: BasePdfFileWriter, obj_stream=None):
        return SimpleFontEngine(
            writer, self.name, self.avg_width, self.meta
        )

    @staticmethod
    def default_factory():
        """
        :return:
            A :class:`.FontEngineFactory` instance representing the Courier
            standard font.
        """
        return SimpleFontEngineFactory('Courier', 0.6, COURIER_META)


def get_courier(pdf_writer: BasePdfFileWriter):
    """
    Quick-and-dirty way to obtain a Courier font resource.

    :param pdf_writer:
        A PDF writer.
    :return:
        A resource dictionary representing the standard Courier font
        (or one of its metric equivalents).
    """

    return SimpleFontEngineFactory.default_factory() \
        .create_font_engine(pdf_writer).as_resource()
