"""
/*********************************************************************
* Copyright (c) 2023 the Qrisp Authors
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
"""


from qrisp.core.quantum_variable import QuantumVariable
from qrisp.environments import control
import sys
class QuantumBool(QuantumVariable):
    """
    QuantumBools are the quantum type, which represents boolean truth values.
    They are the return type of comparison operators like the equality ``==``.
    
    Apart from their behavior as a QuantumVariable, they can also be treated like :ref:`ControlEnvironments <ControlEnvironment>`.
    
    .. note:
        QuantumBools that are evaluated directly after a ``with`` statement are uncomputed automatically upon leaving the ControlEnvironment.
        
    Examples
    --------
    
    We create a QuantumBool and set it to uniform superposition
    
    >>> from qrisp import QuantumBool, h
    >>> q_bool_0 = QuantumBool()
    >>> h(q_bool_0)
    >>> print(q_bool_0)
    {False: 0.5, True: 0.5}
    
    We create a second QuantumBool and evaluate some logical functions
    
    >>> q_bool_1 = QuantumBool()
    >>> print(q_bool_1 | q_bool_0)
    {False: 0.5, True: 0.5}
    >>> print(q_bool_1 & q_bool_0)
    {False: 1.0}
    
    QuantumBools are the results of comparisons:
    
    >>> from qrisp import QuantumFloat, QuantumChar
    >>> q_ch = QuantumChar()
    >>> q_ch[:] = {"g" : 1, "l" : -1}
    >>> q_bool_2 = (q_ch == "g")
    >>> q_bool_2.qs.statevector()
    sqrt(2)*(|g>*|True> - |l>*|False>)/2    
    
    For :ref:`QuantumFloats <QuantumFloat>`, numeric comparison is also possible:
    
    
    >>> qf = QuantumFloat(4)
    >>> h(qf[3])
    >>> print(qf)
    {0: 0.5, 8: 0.5}
    >>> q_bool_3 = (qf >=  4)
    >>> print(q_bool_3)
    {False: 0.5, True: 0.5}
    
    To use a QuantumBool as a :ref:`ControlEnvironment`, we simply put it in a ``with`` statement:
        
    ::
        
        with q_bool_3:
            qf += 2
    
    >>> print(qf)
    {0: 0.5, 10: 0.5}
    
    QuantumBools that are created directly after a ``with`` statement are uncomputed automatically:
    
    ::
        
        with qf == 10:
            q_bool_3.flip()
            
    >>> print(qf.qs)
    QuantumCircuit:
    ---------------
                       ┌────────────┐     ┌───────────┐               
            qf.0: ─────┤0           ├─────┤0          ├──o─────────o──
                       │            │     │           │  │         │  
            qf.1: ─────┤1           ├─────┤1          ├──■─────────■──
                       │            │     │  __iadd__ │  │         │  
            qf.2: ─────┤2           ├─────┤2          ├──o─────────o──
                  ┌───┐│  less_than │     │           │  │         │  
            qf.3: ┤ H ├┤3           ├─────┤3          ├──■─────────■──
                  └───┘│            │┌───┐└─────┬─────┘  │  ┌───┐  │  
        lt_qbl.0: ─────┤4           ├┤ X ├──────■────────┼──┤ X ├──┼──
                       │            │└───┘               │  └─┬─┘  │  
    lt_ancilla.0: ─────┤5           ├────────────────────┼────┼────┼──
                       └────────────┘                  ┌─┴─┐  │  ┌─┴─┐
      cond_env.0: ─────────────────────────────────────┤ X ├──■──┤ X ├
                                                       └───┘     └───┘
    Live QuantumVariables:
    ----------------------
    QuantumFloat qf
    QuantumBool lt_qbl
        
    Note that there is only a single QuantumBool listed in the "Live QuantumVariables" section, 
    because the QuantumBool of the comparison ``qf == 10`` (called ``cond_env``) has been uncomputed.

    """
    

    def __init__(self, qs = None, name = None):

        QuantumVariable.__init__(self, 1, qs = qs, name = name)
        
        self.qfloat_comparison = False
        
    def decoder(self, integer):
        return bool(integer)
    
    def __and__(self, other):
        from qrisp import mcx
        
        and_qbl = QuantumBool()
        
        mcx(self.reg + other.reg, and_qbl.reg)
        
        return and_qbl

    def __or__(self, other):
        from qrisp import mcx, x
        
        or_qbl = QuantumBool()

        x(self)
        x(other)
        
        mcx(self.reg + other.reg, or_qbl.reg)
        
        x(self)
        x(other)
        
        x(or_qbl)
        
        return or_qbl
    
    def __xor__(self, other):
        
        from qrisp import cx
        
        xor_qbl = QuantumBool()
        
        cx(self, xor_qbl)
        cx(other, xor_qbl)
        
        return xor_qbl
    
    def flip(self):
        """
        Flips the QuantumBool's value.

        """
        
        from qrisp import x
        
        x(self)
        return self
    
    def __enter__(self):
        
        self.env = control(self)
        self.env.__enter__()

    def __exit__(self, a, b, c):
        
        ref_count = sys.getrefcount(self)
        
        #If the refcount is 5, this means that the QuantumBool has been
        #created in a "with" statement and can therefore no longer be reached
        #after exiting. We therefore uncompute
        
        self.env.__exit__(a, b, c)
        if ref_count == 5:
            self.uncompute()
