# -*- 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/>.
from __future__ import annotations
from ..proof import adds, sdnode
from ..tools import group
from . import fde as FDE
from . import k3 as K3
from . import LogicType
class Meta(K3.Meta):
    name = 'P3'
    title = 'Post 3-valued Logic'
    quantified = False
    description = (
        'Emil Post three-valued logic (T, F, and N) '
        'with mirror-image negation')
    category_order = 20
class Model(FDE.Model):
    class TruthFunction(K3.Model.TruthFunction):
        def Negation(self, value, /):
            return self.values_sequence[self.values_indexes[value] - 1]
        def Conjunction(self, a, b, /):
            return self.Negation(self.Disjunction(*map(self.Negation, (a, b))))
class System(FDE.System): pass
[docs]
class Rules(LogicType.Rules):
    closure = K3.Rules.closure
[docs]
    class DoubleNegationDesignated(System.OperatorNodeRule):
        """
        From an unticked, designated, double-negation node `n` on a branch `b`,
        add two undesignated nodes to `b`, one with the double-negatum, and one
        with the negatum. Then tick `n`.
        """
        def _get_sd_targets(self, s, d, /):
            yield adds(
                group(sdnode(~s.lhs, not d), sdnode(s.lhs, not d))) 
[docs]
    class DoubleNegationUndesignated(System.OperatorNodeRule):
        """
        From an unticked, undesignated, double-negation node `n` on a branch `b`,
        make two branches `b'` and `b''` from `b`. On `b'` add a designated
        node with the negatum, and on `b'` add a designated node with the
        double-negatum. Then tick `n`.
        """
        def _get_sd_targets(self, s, d, /):
            yield adds(
                group(sdnode(~s.lhs, not d)),
                group(sdnode( s.lhs, not d))) 
[docs]
    class ConjunctionDesignated(System.OperatorNodeRule):
        """
        From an unticked, designated conjunction node `n` on a branch `b`, add
        four undesignated nodes to `b`, one for each conjunct, and one for the
        negation of each conjunct. Then tick `n`.
        """
        def _get_sd_targets(self, s, d, /):
            yield adds(
                group(
                    sdnode(~s.lhs, not d),
                    sdnode( s.lhs, not d),
                    sdnode(~s.rhs, not d),
                    sdnode( s.rhs, not d))) 
[docs]
    class ConjunctionNegatedDesignated(System.OperatorNodeRule):
        """
        From an unticked, designated, negated conjunction node `n` on a branch
        `b`, make two branches `b'` and `b''` from `b`. On `b'` add a designated
        node with the first conjunct, and an undesignated node with the negation
        of the second conjunct. On `b''` add a designated node with the second
        conjunct, and an undesignated node with the negation of the frist conjunct.
        Then tick `n`.
        """
        def _get_sd_targets(self, s, d, /):
            yield adds(
                group(sdnode(s.lhs, d), sdnode(~s.rhs, not d)),
                group(sdnode(s.rhs, d), sdnode(~s.lhs, not d))) 
[docs]
    class ConjunctionUndesignated(System.OperatorNodeRule):
        """
        From an unticked, undesignated conjunction node `n` on a branch `b`, make
        four branches `b'`, `b''`, `b'''`, and `b''''` from `b`. On `b'`, add a
        designated node with the negation of the first conjunct. On `b''`, add
        a designated node ith the first conjunct. On `b'''`, add a designated
        node with the negation of the second conjunct. On `b''''`, add a designated
        node with the second conjunct. Then tick `n`.
        """
        def _get_sd_targets(self, s, d, /):
            yield adds(
                group(sdnode(~s.lhs, not d)),
                group(sdnode( s.lhs, not d)),
                group(sdnode( s.rhs, not d)),
                group(sdnode(~s.rhs, not d))) 
[docs]
    class ConjunctionNegatedUndesignated(System.OperatorNodeRule):
        """
        From an unticked, undesignated, negated conjunction node `n` on a branch
        `b`, make three branches `b'`, `b''`, and `b'''` from `b`. On `b'`, add
        four undesignated nodes, one for each conjunct, and one for the negation
        of each conjunct. On `b''`, add a designated node with the negation of
        the first conjunct. On `b'''`, add a designated node with the negation
        of the second conjunct. Then tick `n`.
        """
        def _get_sd_targets(self, s, d, /):
            lhs, rhs = s
            yield adds(
                group(
                    sdnode(~lhs, d),
                    sdnode( lhs, d),
                    sdnode(~rhs, d),
                    sdnode( rhs, d)),
                group(sdnode(~lhs, not d)),
                group(sdnode(~rhs, not d))) 
[docs]
    class MaterialConditionalDesignated(System.MaterialConditionalReducingRule): pass 
[docs]
    class MaterialConditionalNegatedDesignated(System.MaterialConditionalReducingRule): pass 
[docs]
    class MaterialConditionalUndesignated(System.MaterialConditionalReducingRule): pass 
[docs]
    class MaterialConditionalNegatedUndesignated(System.MaterialConditionalReducingRule): pass 
[docs]
    class MaterialBiconditionalDesignated(System.MaterialConditionalConjunctsReducingRule): pass 
[docs]
    class MaterialBiconditionalNegatedDesignated(System.MaterialConditionalConjunctsReducingRule): pass 
[docs]
    class MaterialBiconditionalUndesignated(System.MaterialConditionalConjunctsReducingRule): pass 
[docs]
    class MaterialBiconditionalNegatedUndesignated(System.MaterialConditionalConjunctsReducingRule): pass 
[docs]
    class ConditionalDesignated(System.MaterialConditionalReducingRule): pass 
[docs]
    class ConditionalNegatedDesignated(System.MaterialConditionalReducingRule): pass 
[docs]
    class ConditionalUndesignated(System.MaterialConditionalReducingRule): pass 
[docs]
    class ConditionalNegatedUndesignated(System.MaterialConditionalReducingRule): pass 
[docs]
    class BiconditionalDesignated(System.ConditionalConjunctsReducingRule): pass 
[docs]
    class BiconditionalNegatedDesignated(System.ConditionalConjunctsReducingRule): pass 
[docs]
    class BiconditionalUndesignated(System.ConditionalConjunctsReducingRule): pass 
[docs]
    class BiconditionalNegatedUndesignated(System.ConditionalConjunctsReducingRule): pass 
    # class ExistentialNegatedDesignated(System.QuantifierFatRule):
    #     """
    #     From an unticked, designated, negated existential node `n` on a branch
    #     `b`, for any constant `c` on `b`, let `r` be the result of substituting
    #     `c` for the variable bound by the sentence of `n`. If the negation of `r`
    #     does not appear on `b`, then add a designated node with the negation of
    #     `r` to `b`. If there are no constants yet on `b`, use a new constant.
    #     The node `n` is never ticked.
    #     """
    #     def _get_constant_nodes(self, node, c, branch, /):
    #         yield sdnode(c >> self.sentence(node), self.designation)
    # class ExistentialNegatedUndesignated(System.QuantifierFatRule):
    #     """
    #     From an unticked, undesignated, negated existential node `n` on a branch
    #     `b`, for a new constant `c` for `b`, let `r` be the result of substituting
    #     `c` for the variable bound by the sentence of `n`. If the negation of `r`
    #     does not appear on `b`, then add an undesignated node with the negation
    #     of `r` to `b`. If there are no constants yet on `b`, use a new constant.
    #     The node `n` is never ticked.
    #     """
    #     def _get_constant_nodes(self, node, c, branch, /):
    #         yield sdnode(~(c >> self.sentence(node)), not self.designation)
    # class UniversalDesignated(System.QuantifierFatRule):
    #     """
    #     From a designated universal node `n` on a branch `b`, if there are no
    #     constants on `b`, add two undesignated nodes to `b`, one with the
    #     quantified sentence, substituting a new constant for the variable, and
    #     the other with the negation of that sentence. If there are constants
    #     already on `b`, then use any of those constants instead of a new one,
    #     provided that the both the nodes to be added do not already appear on
    #     `b`. The node is never ticked.
    #     """
    #     def _get_constant_nodes(self, node, c, branch, /):
    #         r = c >> self.sentence(node)
    #         d = self.designation
    #         yield sdnode(r, not d)
    #         yield sdnode(~r, not d)
    # class UniversalNegatedDesignated(System.QuantifierSkinnyRule):
    #     """
    #     From an unticked, negated universal node `n` on a branch `b`, add a
    #     designated node to `b` with the quantified sentence, substituting a
    #     constant new to `b` for the variable. Then tick `n`.
    #     """
    #     def _get_node_targets(self, node, branch, /):
    #         s = self.sentence(node)
    #         yield adds(
    #             # Keep designation neutral for UniversalUndesignated
    #             group(sdnode(branch.new_constant() >> s, self.designation)))
    # class UniversalUndesignated(UniversalNegatedDesignated): pass
    # class UniversalNegatedUndesignated(System.QuantifierSkinnyRule):
    #     """
    #     From an unticked, undesignated, negated universal node `n` on a branch
    #     `b`, make two branches `b'` and `b''` from `b`. On `b'` add a designated
    #     node with the negation of the quantified sentence, substituing a constant
    #     new to `b` for the variable. On `b''` add a designated node with the
    #     negatum of `n`. Then tick `n`.
    #     """
    #     def _get_node_targets(self, node, branch, /):
    #         s = self.sentence(node)
    #         d = self.designation
    #         yield adds(
    #             group(sdnode(branch.new_constant() >> s, not d)),
    #             group(sdnode(s, not d)))
    groups = (
        group(
            # non-branching rules
            FDE.Rules.AssertionDesignated,
            FDE.Rules.AssertionUndesignated,
            FDE.Rules.AssertionNegatedDesignated,
            FDE.Rules.AssertionNegatedUndesignated,
            ConjunctionDesignated,
            FDE.Rules.DisjunctionUndesignated,
            FDE.Rules.DisjunctionNegatedDesignated,
            DoubleNegationDesignated,
            # reduction rules (thus, non-branching)
            MaterialConditionalDesignated,
            MaterialConditionalUndesignated,
            MaterialConditionalNegatedDesignated,
            MaterialConditionalNegatedUndesignated,
            ConditionalDesignated,
            ConditionalUndesignated,
            ConditionalNegatedDesignated,
            ConditionalNegatedUndesignated,
            MaterialBiconditionalDesignated,
            MaterialBiconditionalUndesignated,
            MaterialBiconditionalNegatedDesignated,
            MaterialBiconditionalNegatedUndesignated,
            BiconditionalDesignated,
            BiconditionalUndesignated,
            BiconditionalNegatedDesignated,
            BiconditionalNegatedUndesignated),
        group(
            # two-branching rules
            DoubleNegationUndesignated,
            ConjunctionNegatedDesignated,
            FDE.Rules.DisjunctionDesignated,
            FDE.Rules.DisjunctionNegatedUndesignated),
        group(
            # three-branching rules
            ConjunctionNegatedUndesignated),
        group(
            # four-branching rules
            ConjunctionUndesignated),
        # group(
        #     FDE.Rules.ExistentialDesignated,
        #     UniversalUndesignated,
        #     UniversalNegatedDesignated,
        #     UniversalNegatedUndesignated),
        # group(
        #     UniversalDesignated,
        #     FDE.Rules.ExistentialUndesignated,
        #     ExistentialNegatedDesignated,
        #     ExistentialNegatedUndesignated)
            )
    @classmethod
    def _check_groups(cls):
        for branching, group in enumerate(cls.groups):
            for rulecls in group:
                assert rulecls.branching == branching, f'{rulecls}'