#
# Copyright (c) 2016 - 2024 -- Lars Heuer
# All rights reserved.
#
# License: BSD License
#
"""\
QR Code and Micro QR Code implementation.

"QR Code" and "Micro QR Code" are registered trademarks of DENSO WAVE INCORPORATED.
"""

import io
import sys

from . import encoder, utils, writers
from .encoder import DataOverflowError

__version__ = "1.6.6"

__all__ = (
    "DataOverflowError",
    "QRCode",
    "QRCodeSequence",
    "make",
    "make_micro",
    "make_qr",
    "make_sequence",
)


def make(
    content,
    error=None,
    version=None,
    mode=None,
    mask=None,
    encoding=None,
    eci=False,
    micro=None,
    boost_error=True,
):
    return QRCode(
        encoder.encode(
            content,
            error,
            version,
            mode,
            mask,
            encoding,
            eci,
            micro,
            boost_error=boost_error,
        )
    )


class QRCode:
    __slots__ = ("_error", "_matrix_size", "_mode", "_version", "mask", "matrix")

    def __init__(self, code):
        matrix = code.matrix
        self.matrix = matrix
        self.mask = code.mask
        self._matrix_size = len(matrix[0]), len(matrix)
        self._version = code.version
        self._error = code.error
        self._mode = code.segments[0].mode if len(code.segments) == 1 else None

    @property
    def version(self):
        return encoder.get_version_name(self._version)

    @property
    def error(self):
        if self._error is None:
            return None
        return encoder.get_error_name(self._error)

    @property
    def mode(self):
        if self._mode is not None:
            return encoder.get_mode_name(self._mode)
        return None

    @property
    def designator(self):
        version = str(self.version)
        return "-".join((version, self.error) if self.error else (version,))

    @property
    def default_border_size(self):
        return utils.get_default_border_size(self._matrix_size)

    @property
    def is_micro(self):
        return self._version < 1

    def __eq__(self, other):
        return self.__class__ == other.__class__ and self.matrix == other.matrix

    __hash__ = None

    def symbol_size(self, scale=1, border=None):
        return utils.get_symbol_size(self._matrix_size, scale=scale, border=border)

    def matrix_iter(self, scale=1, border=None, verbose=False):
        iterfn = utils.matrix_iter_verbose if verbose else utils.matrix_iter
        return iterfn(self.matrix, self._matrix_size, scale, border)

    def show(
        self, delete_after=20, scale=10, border=None, dark="#000", light="#fff"
    ):  # pragma: no cover
        import os
        import tempfile
        import threading
        import time
        import webbrowser
        from urllib.parse import urljoin
        from urllib.request import pathname2url

        def delete_file(name):
            time.sleep(delete_after)
            try:
                os.unlink(name)
            except OSError:
                pass

        f = tempfile.NamedTemporaryFile("wb", suffix=".png", delete=False)
        try:
            self.save(f, scale=scale, dark=dark, light=light, border=border)
        except:
            f.close()
            os.unlink(f.name)
            raise
        f.close()
        webbrowser.open_new_tab(urljoin("file:", pathname2url(f.name)))
        if delete_after is not None:
            t = threading.Thread(target=delete_file, args=(f.name,))
            t.start()

    def svg_data_uri(self, xmldecl=False, encode_minimal=False, omit_charset=False, nl=False, **kw):
        return writers.as_svg_data_uri(
            self.matrix,
            self._matrix_size,
            xmldecl=xmldecl,
            nl=nl,
            encode_minimal=encode_minimal,
            omit_charset=omit_charset,
            **kw,
        )

    def svg_inline(self, **kw):
        buff = io.BytesIO()
        self.save(buff, kind="svg", xmldecl=False, svgns=False, nl=False, **kw)
        return buff.getvalue().decode(kw.get("encoding", "utf-8"))

    def png_data_uri(self, **kw):
        return writers.as_png_data_uri(self.matrix, self._matrix_size, **kw)

    def terminal(self, out=None, border=None, compact=False):
        if compact:
            writers.write_terminal_compact(
                self.matrix, self._matrix_size, out or sys.stdout, border
            )
        elif out is None and sys.platform == "win32":  # pragma: no cover
            try:
                writers.write_terminal_win(self.matrix, self._matrix_size, border)
            except OSError:
                writers.write_terminal(self.matrix, self._matrix_size, sys.stdout, border)
        else:
            writers.write_terminal(self.matrix, self._matrix_size, out or sys.stdout, border)

    def save(self, out, kind=None, **kw):
        writers.save(self.matrix, self._matrix_size, out, kind, **kw)

    def __getattr__(self, name):
        if name.startswith("to_"):
            try:
                import importlib_metadata as metadata
            except ImportError:
                from importlib import metadata
            from functools import partial

            for ep in metadata.entry_points(group="segno.plugin.converter", name=name[3:]):
                plugin = ep.load()
                return partial(plugin, self)
        raise AttributeError(f"{self.__class__} object has no attribute {name}")


class QRCodeSequence(tuple):
    __slots__ = ()

    def __new__(cls, qrcodes):
        return super().__new__(cls, qrcodes)

    def terminal(self, out=None, border=None, compact=False):
        for qrcode in self:
            qrcode.terminal(out=out, border=border, compact=compact)

    def save(self, out, kind=None, **kw):
        filename = lambda o, n: o  # noqa: E731
        m = len(self)
        if m > 1 and isinstance(out, str):
            dot_idx = out.rfind(".")
            if dot_idx > -1:
                out = out[:dot_idx] + "-{0:02d}-{1:02d}" + out[dot_idx:]
                filename = lambda o, n: o.format(m, n)  # noqa: E731
        for n, qrcode in enumerate(self, start=1):
            qrcode.save(filename(out, n), kind=kind, **kw)

    def __getattr__(self, item):
        if len(self) == 1:
            return getattr(self[0], item)
        raise AttributeError(f"{self.__class__} object has no attribute '{item}'")
