# -*- coding: utf-8 -*-
# pytableaux, a multi-logic proof generator.
# Copyright (C) 2014-2023 Doug Owings.
# 
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
# 
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""
pytableaux.proof.writers
========================
"""
from __future__ import annotations
from abc import abstractmethod
from typing import TYPE_CHECKING, Callable, MutableMapping, Self, TypeVar
from ...errors import Emsg, check
from ...lang import LexWriter, Notation, StringTable
from ...tools import EMPTY_MAP, MapCover, abcs
from ..tableaux import Tableau
if TYPE_CHECKING:
    from typing import overload
NOARG = object()
_T = TypeVar('_T')
_TWT = TypeVar('_TWT', bound='TabWriter')
__all__ = (
    'registries',
    'registry',
    'TabWriter',
    'TabWriterRegistry')
class TabWriterMeta(abcs.AbcMeta):
    def __call__(cls: type[_T]|Self, *args, **kw) -> _T:
        if cls is not TabWriter:
            return super().__call__(*args, **kw)
        try:
            reg = registries['default']
        except KeyError:
            reg = registry
        if args:
            fmt, *args = args
        elif 'format' in kw:
            fmt = kw.pop('format')
        else:
            return reg.default(*args, **kw)
        try:
            reg[fmt]
        except KeyError:
            for reg in registries.values():
                if fmt in reg:
                    break
            else:
                raise
        return reg[fmt](*args, **kw)
[docs]
class TabWriter(metaclass=TabWriterMeta):
    """Tableau writer base class.
    Constructing a ``TabWriter``.
    Examples::
    >>> # make an instance of the default writer class, with the default notation.
    >>> writer = TabWriter()
    >>> # make an HtmlTabWriter, with the default notation.
    >>> writer = TabWriter('html')
    >>> # make an HtmlTabWriter, with standard notation.
    >>> writer = TabWriter('html', 'standard')
    """
    engine: str = 'unknown'
    "The writer engine (jinja, doctree, etc.)"
    format: str
    "The format registry identifier."
    file_extension: str
    "The file extension for output files."
    defaults = EMPTY_MAP
    "Default options."
    lw: LexWriter
    "The writer's `LexWriter` instance."
    opts: dict
    "The writer's options."
    __slots__ = ('lw', 'opts')
    def __init__(self, notation: Notation = None, strings: StringTable|None = None, *, lw: LexWriter = None, **opts):
        """__init__(self, format: str|None = None, notation: Notation = None, strings: StringTable|None = None, *, lw: LexWriter = None, **opts)
        Create a TabWriter
        Args:
            format (str): The format, e.g. 'html', 'latex'
            notation (str|Notation): The notation
            strings (StringTable): A specific string table to use
        
        Keyword Args:
            lw (LexWriter): A specific LexWriter to use
            **opts: Options to pass to the TabWriter
        """
        if lw is None:
            if notation is None:
                notation = LexWriter.DEFAULT_NOTATION
            else:
                notation = Notation(notation)
            lw = LexWriter(notation=notation, format=self.format, strings=strings, **opts)
        elif lw.format != self.format or strings is not None and strings.format != self.format:
            raise Emsg.ValueConflict(lw.format, self.format)
        self.lw = lw
        self.opts = dict(self.defaults) | opts
    def attachments(self, /):
        return EMPTY_MAP
    @abstractmethod
    def __call__(self, tab: Tableau, **kw) -> str:
        raise NotImplementedError
    def __init_subclass__(cls) -> None:
        super().__init_subclass__()
        try:
            cls.file_extension
        except AttributeError:
            try:
                cls.file_extension = cls.format
            except AttributeError:
                pass 
[docs]
class TabWriterRegistry(MapCover[str, type[TabWriter]], MutableMapping[str, type[TabWriter]]):
    "A tableau writer class registry."
    __slots__ = ('default', 'name', 'register', '__delitem__')
    name: str|None
    default: type[TabWriter]|None
    def __init__(self, *, name: str|None = None):
        super().__init__(mapping := {})
        self.name = name
        self.default = None
        def register(cls=NOARG, /, *, key=None, force=False, default=None):
            if cls is NOARG:
                return lambda cls: register(cls, key=key, force=force, default=default)
            if abcs.isabstract(cls := check.subcls(cls, TabWriter)):
                raise TypeError(f'Cannot register abstract class: {cls}')
            if key is None:
                key = cls.format
            if not force and key in self:
                raise KeyError(f"Format/key {key} already registered")
            mapping[check.inst(key, str)] = cls
            if default or default is None and not self.default:
                self.default = cls
            return cls
        self.register = register
        self.__delitem__ = mapping.__delitem__
    def __setitem__(self, key, value):
        self.register(value, key=key, force=True)
    if TYPE_CHECKING:
        @overload
        def register(self, cls:type[_TWT], /, *, key:str=..., force:bool=..., default:bool|None=...) -> type[_TWT]:
            """Register a ``TabWriter`` class. Returns the argument, so it can be
            used as a decorator.
            Args:
                cls: The writer class.
            Keyword Args:
                key: An alternate key to store, default is the writer's format.
                force: Replace format/key if exists, default False.
                default: Set as default writer.
            Returns:
                The writer class.
            """
        @overload
        def register(self, /, *, key:str=..., force:bool=..., default:bool|None=...) -> Callable[[type[_TWT]], type[_TWT]]:
            """Decorator factory for registering with options.
            Keyword Args:
                key: An alternate key to store, default is the writer's format.
                force: Replace format/key if exists.
                default: Set as default writer.
            
            Returns:
                Class decorator.
            """ 
registry = TabWriterRegistry(name='default')
"The default tableau writer class registry."
registries = {registry.name: registry}
try:
    from . import jinja as jinja
except ImportError:
    import traceback
    import warnings
    traceback.print_exc()
    warnings.warn('Failed to import jinja')
else:
    registries[jinja.registry.name] = jinja.registry
    registry.update(jinja.registry)
from . import doctree
registries[doctree.registry.name] = doctree.registry
registry.update(doctree.registry)
registry.default = doctree.registry.default