# -*- 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
^^^^^^^^^^^^^^^^
"""
from __future__ import annotations
import itertools
from abc import abstractmethod
from enum import Enum, Flag
from types import MappingProxyType as MapProxy
from typing import Any, Hashable, Iterable, NamedTuple, Sequence, TypeVar
from ..lang import Argument, Operator, Predicate, Quantifier
from ..logics import LogicType
from ..tools import EMPTY_QSET, EMPTY_SET, abcs, qsetf, ItemMapEnum
from ..tools.timing import Counter, StopWatch
_KT = TypeVar('_KT')
_VT = TypeVar('_VT')
__all__ = (
    'AccessNode',
    'adds',
    'anode',
    'Branch',
    'ClosingRule',
    'ClosureNode',
    'Designation',
    'EllipsisNode',
    'filters',
    'FlagNode',
    'helpers',
    'Modal',
    'Node',
    'QuitFlagNode',
    'Rule',
    'sdnode',
    'SentenceDesignationNode',
    'SentenceDesignationWorldNode',
    'SentenceNode',
    'SentenceWorldNode',
    'sdnode',
    'sdwnode',
    'snode',
    'swnode',
    'Tableau',
    'System',
    'TabWriter',
    'Target',
    'WorldNode',
    'WorldPair')
NOARG = object()
class WorldPair(NamedTuple):
    "An `access` int tuple (world1, world2)."
    world1: int
    "world 1"
    world2: int
    "world 2"
    @property
    def w1(self) -> int:
        """Alias for :attr:`world1`."""
        return self.world1
    @property
    def w2(self) -> int:
        """Alias for :attr:`world2`."""
        return self.world2
    @classmethod
    def fornode(cls, node):
        """Create instance from a node."""
        return cls._make(map(node.__getitem__, cls._fields))
    def tonode(self):
        """Create node from this instance."""
        return AccessNode({
            Node.Key.world1: self.world1,
            Node.Key.world2: self.world2})
    def reversed(self):
        """Create reversed instance."""
        return self._make(reversed(self))
class NodeMeta(abcs.AbcMeta):
    """Node meta class."""
    class Key(str, Enum):
        "Node keys."
        sentence = 'sentence'
        designation = designated = 'designated'
        world = 'world'
        world1 = w1 = 'world1'
        world2 = w2 = 'world2'
        is_flag = 'is_flag'
        flag = 'flag'
        closure = 'closure'
        quit = 'quit'
        info = 'info'
        ellipsis = 'ellipsis'
        def __str__(self):
            return self.value
    WORLD_KEYS = (Key.world, Key.world1, Key.world2)
    class PropMap(ItemMapEnum):
        Defaults = dict(
            designated = None,
            world = None)
        Closure = dict(
            closure = True,
            flag = 'closure',
            is_flag = True)
        QuitFlag = dict(
            quit = True,
            flag = 'quit',
            is_flag = True)
        Ellipsis = dict(
            ellipsis = True)
    @classmethod
    def __prepare__(cls, clsname, bases, **kw):
        return dict(__slots__ = EMPTY_SET)
class HelperMeta(abcs.AbcMeta):
    @classmethod
    def __prepare__(cls, clsname, bases, **kw):
        return dict(__slots__=EMPTY_SET)
class BranchMeta(abcs.AbcMeta):
    """Branch meta class."""
    class Events(str, Enum):
        'Branch events.'
        AFTER_CLOSE = 'AFTER_CLOSE'
        AFTER_ADD   = 'AFTER_ADD'
        AFTER_TICK  = 'AFTER_TICK'
class TableauMeta(abcs.AbcMeta):
    """Tableau meta class."""
    class Events(str, Enum):
        'Tableau events.'
        AFTER_BRANCH_ADD    = 'AFTER_BRANCH_ADD'
        AFTER_BRANCH_CLOSE  = 'AFTER_BRANCH_CLOSE'
        AFTER_NODE_ADD      = 'AFTER_NODE_ADD'
        AFTER_NODE_TICK     = 'AFTER_NODE_TICK'
        AFTER_TRUNK_BUILD   = 'AFTER_TRUNK_BUILD'
        BEFORE_TRUNK_BUILD  = 'BEFORE_TRUNK_BUILD'
        AFTER_RULE_APPLY    = 'AFTER_RULE_APPLY'
        AFTER_FINISH        = 'AFTER_FINISH'
    class Timers(NamedTuple):
        'Tableau timers data class.'
        build  : StopWatch
        trunk  : StopWatch
        tree   : StopWatch
        models : StopWatch
        @classmethod
        def create(cls):
            it = itertools.repeat(False, len(cls._fields))
            return cls._make(map(StopWatch, it))
    class Flag(Flag):
        'Tableau state bit flags.'
        TICKED = 1 << 0
        CLOSED = 1 << 1
        PREMATURE = 1 << 2
        FINISHED = 1 << 3
        TIMED_OUT = 1 << 4
        TRUNK_BUILT = 1 << 5
        TIMING_INACCURATE = 1 << 6
        HAS_STEP_LIMIT = 1 << 7
        HAS_TIME_LIMIT = 1 << 8
        STARTED = 1 << 9
    class StatKey(str, Enum):
        'Tableau ``stat()`` keys.'
        FLAGS       = 'FLAGS'
        STEP_ADDED  = 'STEP_ADDED'
        STEP_TICKED = 'STEP_TICKED'
        STEP_CLOSED = 'STEP_CLOSED'
        INDEX       = 'INDEX'
        PARENT      = 'PARENT'
        NODES       = 'NODES'
    class StepEntry(NamedTuple):
        rule: Rule
        "The rule instance."
        target: Target
        "The target produced by the rule."
        duration: Counter
        "The duration counter."
        def __repr__(self):
            return f'<StepEntry:{id(self)}:{self.rule.name}:{self.target.type}>'
class GetLogicMetaMixinMetaType(type):
    @property
    def Meta(self) -> type[LogicType.Meta]|None:
        return LogicType.Meta.for_module(self.__module__)
class SystemMeta(abcs.AbcMeta, GetLogicMetaMixinMetaType):
    @property
    def modal(self) -> bool|None:
        return self.Meta and self.Meta.modal
[docs]
class System(metaclass=SystemMeta):
    'Tableaux system base class.'
[docs]
    @classmethod
    @abstractmethod
    def build_trunk(cls, b: Branch, arg: Argument, /) -> None:
        """Build the trunk for an argument on the tableau.
        
        Args:
            b (Branch): The branch to construct.
            arg (Argument): The argument.
        """
        raise NotImplementedError 
[docs]
    @classmethod
    def branching_complexity(cls, node: Node, rules: RulesRoot, /) -> int:
        """Compute how many new branches would be added if a rule were to be
        applied to the node.
        Args:
            node (Node): The node instance.
            rules (RulesRoot): The rules on the tableau.
        Returns:
            int: The number of new branches.
        """
        return 0 
[docs]
    @classmethod
    def branching_complexity_hashable(cls, node: Node, /) -> Hashable:
        """Return a hashable object corresponding to a node's branching
        complexity, in order to save redundant computation time. By default
        it just returns the node itself.
        Args:
            node (Node): The node instance.
        Returns:
            A hashable object.
        """
        return node 
 
class RuleMeta(abcs.AbcMeta, GetLogicMetaMixinMetaType):
    """Rule meta class."""
    name: str
    class State(Flag):
        'Rule state bit flags.'
        INIT = 1 << 0
        LOCKED = 1 << 1
    class Events(str, Enum):
        'Rule events.'
        BEFORE_APPLY = 'BEFORE_APPLY'
        AFTER_APPLY = 'AFTER_APPLY'
    class Legend(str, Enum):
        closure = 'closure'
        negated = 'negated'
        operator = 'operator'
        quantifier = 'quantifier'
        predicate = 'predicate'
        designation = 'designation'
        marklegend = 'marklegend'
    @property
    def modal(self) -> bool|None:
        return self.Meta and self.Meta.modal
    def __prepare__(clsname, bases, **kw):
        return dict(__slots__=EMPTY_SET, name=clsname)
    def __new__(cls, clsname, bases, ns, intermediate=False, **kw):
        self: type[Rule] = super().__new__(cls, clsname, bases, ns, **kw)
        abcs.merge_attr(self, 'defaults', mcls=cls,
            default={}, transform=MapProxy)
        abcs.merge_attr(self, 'timer_names', mcls=cls,
            default=EMPTY_QSET, transform=qsetf)
        if self.autoattrs:
            for name, value in self.induce_attrs().items():
                setattr(self, name, value)
        configs: dict[type[Rule.Helper], Any] = {}
        for parent in abcs.mroiter(self, mcls=cls):
            value = parent.Helpers
            if isinstance(value, type):
                value = value,
            if isinstance(value, Sequence):
                configs = dict.fromkeys(value) | configs
            else:
                configs.update(value)
        self.Helpers = MapProxy(configs)
        for helpercls, config in configs.items():
            configs[helpercls] = helpercls.configure_rule(self, config)
        isconcrete = not intermediate and not abcs.isabstract(self)
        if isconcrete:
            self.legend = tuple(self.build_legend())
            if 'branching' not in self.__dict__:
                self.branching = self.induce_branching()
        return self
    def build_legend(self):
        """Build rule class legend."""
        Leg = self.Legend
        getters = {
            Leg.operator: Operator,
            Leg.quantifier: Quantifier,
            Leg.predicate: Predicate}
        for attr in Leg:
            value = getattr(self, attr, None)
            if attr is Leg.negated:
                if value:
                    yield attr, Operator.Negation
            elif attr is Leg.designation:
                if value is not None:
                    yield attr, value
            elif attr is Leg.marklegend:
                if value is not None:
                    yield from value
            elif attr is Leg.closure:
                if value:
                    yield attr, True
            elif attr in getters:
                if value:
                    yield attr, getters[attr](value)
    def induce_attrs(self):
        return RuleNameAttrInducer(self).build() or {}
    def induce_branching(self):
        tab = Tableau()
        rule: Rule = self(tab)
        branch = tab.branch().extend(rule.example_nodes())
        target = rule.target(branch)
        if target:
            rule.apply(target)
            return len(rule.tableau) - 1
        return NotImplemented
    class AbstractHelper(metaclass=HelperMeta):
        'Rule helper interface.'
        shareable = False
        requires = EMPTY_SET
        rule: Rule
        config: Any
        @property
        def tableau(self) -> Tableau:
            return self.rule.tableau
        @abstractmethod
        def __init__(self, rule: Rule, /):
            self.rule = rule
            self.config = rule.Helpers.get(type(self))
            self.listen_on()
        def listen_on(self):
            pass
        @classmethod
        def configure_rule(cls, rulecls: type[Rule], config: Any) -> Any:
            """Hook for initializing & verifiying a ``Rule`` class.
            
            Args:
                rulecls: The rule class using the helper class.
                config: Config from the rule class, if any.
            """
            return config
    class Helper(AbstractHelper):
        "Rule helper basic base class."
        __slots__ = ('rule', 'config')
        def __init__(self, rule: Rule, /):
            Rule.AbstractHelper.__init__(self, rule)
    class HelperDict(AbstractHelper, dict[_KT, _VT]):
        "Rule helper base class that also subclasses dict."
        __slots__ = ('rule', 'config')
        def __init__(self, rule: Rule, /):
            Rule.AbstractHelper.__init__(self, rule)
class RuleNameAttrInducer:
    names = (
        'operator',
        'quantifier',
        'predicate',
        'negated',
        'designation')
    doubleneg = 'DoubleNegation'
    designates = ('Undesignated', 'Designated')
    negatedstr = 'Negated'
    obj: Any
    name: str
    todo: str
    attrs: dict[str, Any]
    notfounds: list[str]
    conflicts: dict[str, tuple[str, str, str]]
    def __init__(self, obj: Any):
        self.obj = obj
        self.name = self.obj.__name__
        self.todo = self.name
        self.attrs = {}
        self.notfounds = []
        self.conflicts = {}
    @property
    def is_doubleneg(self):
        return self.name.startswith(self.doubleneg)
    def build(self):
        for name in self.names:
            name = Rule.Legend(name).value
            if not getattr(self, f'do_{name}')(name):
                self.notfounds.append(name)
        self.find_conflicts()
        if self.conflicts:
            raise TypeError(
                f'Direct __dict__ conflicts inducing autoattrs for '
                f'{self.obj} : {self.conflicts}')
        for name in self.notfounds:
            if hasattr(self.obj, name):
                self.attrs[name] = None
        if not self.todo:
            return self.attrs
    def do_operator(self, name: str):
        if self.is_doubleneg:
            return self.found(name, self.doubleneg, Operator.Negation)
        return self.common_enum(name, Operator)
    def do_quantifier(self, name: str):
        return self.common_enum(name, Quantifier)
    def do_predicate(self, name: str):
        return self.common_enum(name, Predicate.System)
    def do_negated(self, name: str):
        if self.is_doubleneg:
            return self.found(name, '', True)
        if self.todo.startswith(self.negatedstr):
            return self.found(name, self.negatedstr, True)
    def do_designation(self, name: str):
        for i, indicator in enumerate(self.designates):
            if self.todo.startswith(indicator):
                return self.found(name, indicator, bool(i))
    def found(self, name: str, indicator: str, value: Any):
        self.attrs[name] = value
        self.todo = self.todo.removeprefix(indicator)
        return True
    def find_conflicts(self):
        ns = self.obj.__dict__
        for name in filter(ns.__contains__, self.attrs):
            value = self.attrs[name]
            old = getattr(self.obj, name, value)
            if old != value:
                self.conflicts[name] = (old, value)
    def common_enum(self, name: str, it: Iterable[Operator|Quantifier|Predicate]):
        for item in it:
            if self.todo.startswith(item.name):
                return self.found(name, item.name, item)
def adds(*groups, **kw):
    """Target dict builder for `AdzHelper`.
    
    Args:
        *groups: node groups.
        **kw: dict keywords.
    Returns:
        A dict built from ``dict(adds = groups, **kw)``.
    """
    return dict(adds=groups, **kw)
def snode(s):
    'Make a sentence node.'
    return SentenceNode({Node.Key.sentence: s})
def swnode(s, w):
    'Make a sentence/world node. Excludes world if None.'
    if w is None:
        return snode(s)
    return SentenceWorldNode({
        Node.Key.sentence: s,
        Node.Key.world: w})
def sdnode(s, d):
    'Make a sentence/designated node.'
    if d is None:
        return snode(s)
    return SentenceDesignationNode({
        Node.Key.sentence: s,
        Node.Key.designation: d})
def sdwnode(s, d, w):
    'Make a sentence/designated/world node. Excludes world if None.'
    if w is None:
        return sdnode(s, d)
    if d is None:
        return swnode(s, w)
    return SentenceDesignationWorldNode({
        Node.Key.sentence: s,
        Node.Key.designation: d,
        Node.Key.world: w})
def anode(w1, w2):
    'Make an Access node.'
    return AccessNode({
        Node.Key.world1: w1,
        Node.Key.world2: w2})
def sdwgroup(*nodes):
    return *itertools.starmap(sdwnode, nodes),
from .common import AccessNode as AccessNode
from .common import Branch as Branch
from .common import ClosureNode as ClosureNode
from .common import Designation as Designation
from .common import DesignationNode as DesignationNode
from .common import EllipsisNode as EllipsisNode
from .common import FlagNode as FlagNode
from .common import Modal as Modal
from .common import Node as Node
from .common import QuitFlagNode as QuitFlagNode
from .common import SentenceDesignationNode as SentenceDesignationNode
from .common import SentenceDesignationWorldNode as SentenceDesignationWorldNode
from .common import SentenceNode as SentenceNode
from .common import SentenceWorldNode as SentenceWorldNode
from .common import Target as Target
from .common import WorldNode as WorldNode
from .tableaux import Rule as Rule
from .tableaux import RuleGroup as RuleGroup
from .tableaux import RuleGroups as RuleGroups
from .tableaux import RulesRoot as RulesRoot
from .tableaux import Tableau as Tableau
pass
from .rules import ClosingRule as ClosingRule
pass
from . import filters as filters
from . import helpers as helpers
from .writers import TabWriter as TabWriter