r"""We deal with an extraspecial group that maps to the Leech lattice modulo 2

Let :math:`Q_{x0}` be the group generated by elements :math:`x_d, x_\delta`
with :math:`d \in \mathcal{P}, \delta \in \mathcal{C}^*`. Here 
:math:`\mathcal{P}` is the Parker loop and :math:`\mathcal{C}^*` is the
Golay cocode as in :cite:`Con85`. We take the following relations in
:math:`Q_x` from :cite:`Seysen20`:


.. math::

    x_d x_e = x_{d \cdot e} x_{A(d,e)}, \,
    x_\delta x_\epsilon = x_{\delta \epsilon}, \,
    [x_d x_\delta] = x_{-1}^{\langle d, \delta \rangle}; \;
    d, e \in \mathcal{P}; \;
    \delta , \epsilon \in \mathcal{C}^* \, .

Here :math:`A(d,e)` is the associator between :math:`d` and :math:`e`,
i.e. the element of :math:`\mathcal{C}^*` corresponding to the vector 
:math:`d \cap e`; and :math:`[.,.]` is the commutator in :math:`Q_{x0}`.
Then :math:`Q_{x0}` is an extraspecal group of structure 
:math:`2_+^{1+24}`.

An element of the group :math:`Q_x` is modelled as an instance of
class |XLeech2|.

In that class we encode the element 
:math:`x_d \cdot x_\delta` of :math:`Q_{x0}` as an integer 
:math:`x`  as follows:

.. math::

       x = 2^{12} \cdot d \oplus (\delta \oplus \theta(d)) \, .

Here elements of the Parker loop and elements of the cocode 
are encoded as integers as in section :ref:`parker-loop-label`
and :ref:`golay-label`.
:math:`\theta` is the cocycle given in section 
:ref:`basis-golay-label`, and ':math:`\oplus`' means bitwise
addition modulo 2. Note that a  Parker loop element is 13 bits 
long (with the most significant bit denoting the sign) and that 
a cocode element is 12 bits long.

There is a natural homomorphism from :math:`Q_{x0}` to the 
Leech lattice :math:`\Lambda` modulo 2 with kernel 
:math:`\{x_1, x_{-1}\}`, as described in  :cite:`Con85`. This 
homomorphism maps the group operation in  :math:`Q_{x0}`
to vector addition in :math:`\Lambda / 2\Lambda`. With our
numbering in :math:`Q_{x0}` that homomorphism can be realized
by simply dropping the sign bit 25; then vector addition in
:math:`\Lambda / 2\Lambda` corresponds to the :math:`\oplus`
operation on such numbers. The natural quadratic form on
the vector  in :math:`\Lambda / 2\Lambda` with the number
:math:`2^{12} \cdot d + \delta`, :math:`0 \leq d, \delta < 2^{12}`
is the parity of the bit vector :math:`d \oplus \delta`.



"""




from functools import reduce
from operator import __xor__
from numbers import Integral, Number
from random import randint

import numpy as np



from mmgroup.mm import mm_aux_index_sparse_to_leech2
from mmgroup.mm import mm_aux_index_extern_to_sparse
from mmgroup.mm import mm_aux_index_leech2_to_sparse

from mmgroup.structures.abstract_group import AbstractGroupWord
from mmgroup.structures.abstract_group import AbstractGroup
from mmgroup.structures.abstract_mm_group import AbstractMMGroupWord
from mmgroup.structures.parity import Parity
from mmgroup.structures.parse_atoms import ihex
from mmgroup.structures.mm_space_indices import tuple_to_sparse


from mmgroup.structures.gcode import GCode, GcVector
from mmgroup.structures.ploop import PLoop
from mmgroup.structures.cocode import Cocode
from mmgroup.structures.autpl import AutPL, AutPlGroup
from mmgroup.structures.construct_mm import add_to_embedded_classes

from mmgroup.generators import gen_leech2_mul
from mmgroup.generators import gen_leech2_pow
from mmgroup.generators import gen_leech2_scalprod
from mmgroup.generators import rand_get_seed, gen_leech2_type
from mmgroup.generators import gen_leech2_subtype
from mmgroup.generators import gen_leech2_op_word

ERR_RAND = "Illegal string for constricting type %s element" 

ERR_DIV4 = "%s object may be divided by 2 or 4 only"
ERR_DIV2 = "%s object may be divided by 2 only"


#######################################################################
# Import derived classed
#######################################################################

import_pending = True

def complete_import():
    """Internal function of this module

    If you need any of the objects declared above, do the following:

    if import_pending:
        complete_import()
    """
    global mat24
    global import_pending, MM0, std_q_element
    from mmgroup import mat24 
    from mmgroup.structures.mm0_group import MM0
    from mmgroup.structures.construct_mm import std_q_element
    import_pending = False



###########################################################################
# Import functions from module ``mmgroup.mm_order`` on demand
###########################################################################


# Functions to be imported from module mmgroup.mm_order
check_mm_in_g_x0 = None

def import_mm_order_functions():
    """Import functions from module ``mmgroup.mm_order``.

    We import these functions from module ``mmgroup.mm_order``
    on demand. This avoids an infinite recursion of imports.
    """
    global check_mm_in_g_x0
    from mmgroup.structures.mm_order import check_mm_in_g_x0 



#######################################################################
# Auxiliary functions
#######################################################################


ERR_XL_TUPLE = "Cannot convert tuple to XLeech2 object"

ERR_XL_IN_Q = "Monster group element is not in subgroup Q_x0"

ERR_XL_TYPE = "Cannot convert '%s' object to XLeech2 object"

ERR_XL_RAND = "No random object in class XLeech2 found"

ERR_XL_SHORT = "XLeech2 object does not map to short Leech lattice vector"


def MM_to_Q_x0(g):
    if check_mm_in_g_x0 is None:
        import_mm_order_functions()
    g = MM0('a', g.mmdata)
    if check_mm_in_g_x0(g) is None:
        raise ValueError(ERR_XL_IN_Q)
    g.reduce()
    res = 0;
    for atom in g.mmdata:
        tag = (atom >> 28) & 0x0f
        if res == 0 and tag == 3:
            res = ((atom & 0x1fff) << 12) ^ mat24.ploop_theta(atom)
        elif tag == 1:
            res ^= atom & 0xfff
        elif tag:
            raise ValueError(ERR_XL_IN_Q)
    return res


def rand_xleech2_type(vtype):
    if vtype in [3,4]:
        for i in range(1000):
            v = randint(0, 0x1ffffff)
            if gen_leech2_type(v) == vtype:
                 return v
        raise ValueError(ERR_XL_RAND)
    if vtype == 0:
        return 0
    if vtype == 2:
        ve = randint(300, 98579)
        vs = mm_aux_index_extern_to_sparse(ve)
        sign = randint(0,1)
        return mm_aux_index_sparse_to_leech2(vs) ^ (sign << 24) 
    raise ValueError(ERR_XL_RAND)


def value_from_ploop(ploop=0, cocode = None, *args):
    c = Cocode(cocode).ord if cocode else 0
    if isinstance(ploop, Integral):
        d = ploop & 0x1ffffff
    elif isinstance(ploop, PLoop):
        d = ploop.value & 0x1fff
        d = (d << 12) ^ mat24.ploop_theta(d) 
    elif isinstance(ploop, XLeech2):
        d = ploop.value
    elif isinstance(ploop, Cocode):
        d = ploop.value & 0xfff
    elif isinstance(ploop, AbstractMMGroupWord):
        d = MM_to_Q_x0(ploop)
    elif isinstance(ploop, str):
        if len(ploop) == 1 and ploop in "BCTXES":
            d = 0
            a = tuple_to_sparse(0xff,  ploop, cocode, *args)
            if len(a) == 1:
                a0 = a[0]
                d = mm_aux_index_sparse_to_leech2(a0)
                a0 &= 0xff
                if a0 == 0xfe:
                    d ^= 0x1000000
                elif a0 != 1:
                    d = 0
            if d:
                return d
        if ploop == "r":
             if cocode is None:
                 return randint(0, 0x1ffffff)
             elif cocode in [0,2,3,4]:
                 return  rand_xleech2_type(cocode)
        try:
            return std_q_element("q", ploop) 
        except:
            raise ValueError(ERR_XL_TUPLE)            
    else:
        return TypeError(ERR_XL_TYPE % type(ploop))
    return d ^ c

        

#######################################################################
# Class XLeech2
#######################################################################





class XLeech2(AbstractGroupWord):
    r"""This class models an element of the group :math:`Q_{x0}`.

    The group :math:`Q_{x0}` is an extraspecial 2 group of structure 
    :math:`2^{1+24}`. 

    The group operation is written multiplicatively. 
        
    The :math:`2^{1+24}` group elements are numbered from
    ``0`` to ``0x1ffffff``. Elements ``0`` to ``0xffffff`` are
    considered positive. The element with number  ``0x1000000 ^ i`` 
    is the negative of the element with number  ``i``.

    An element is constructed as a product :math:`x_d x_\delta`,
    where :math:`d` is in the Parker loop and :math:`\delta` is in
    the Golay cocode.

    :param value:

      This parameter describes the value of an element of 
      the group :math:`Q_{x0}` as indicated in the table below. 

    :param cocode:

      This parameter describes an element  :math:`x_\delta` (with
      :math:`\delta` in the Golay cocode) 
      to be right multiplied to the element of :math:`Q_{x0}` obtained
      from the first parameter. 

    :return: An element of the group  :math:`Q_{x0}`.
    :rtype:  an instance of class |XLeech2|

    :raise:
        * ValueError if the input cannot converted to an element of
          the group :math:`Q_{x0}`.
        * TypeError the ype of an input is illegal.

    .. Caution::
       Although the group  :math:`Q_{x0}` has a natural embedding
       into the subgroup :math:`G_{x0}` of the monster group, we do 
       not consider an instance :math:`q` of class |XLeech2| as an 
       element of :math:`G_{x0}`. We consider :math:`q` as a (possibly
       negated) basis vector of a space on which the subgroup
       :math:`G_{x0}` of the monster acts monomially by right
       multiplication.



    Depending on its type parameter **value** is  interpreted as follows:

    .. table:: Legal types for constructor of class ``XLeech2``
      :widths: 25 75

      ====================== ================================================
      type of ``value``      Evaluates to
      ====================== ================================================
      ``int``                Here the code word with number ``value`` is
                             returned.  ``0 <= value < 0x2000000`` must hold.
                              
      class |XLeech2|        A deep copy of the object ``value``
                             is created. 

      class |GCode|          The corresponding Golay code element is
                             converted to a (positive) element of
                             :math:`Q_{x0}`. 

      class |PLoop|          The corresponding Parker loop element is
                             converted to an element of :math:`Q_{x0}`. 

      class |MM|             Then ``value`` in an element of the monster
                             group. If that element is in the subgroup
                             :math:`Q_{x0}` of the monster, then it is
                             converted to the coresponding instance of 
                             class |XLeech2|.

      ``'r'``                Create random element depending on the string,
                             see explanation below.

      A letter ``BCTXE``     If ``value`` is a single capital letter in
                             ``BCTXE`` then we create an element
                             corresponding to a unit vector 
                             in :math:`\rho_p` as described below.

      Any other string ``s`` This is intepreted as the element ``MM('q', s)``
                             of the Monster group.
      ====================== ================================================

    If value is of one of the type listed above then the following
    parameter is converted to an element of the Golay cocode (as in
    the constructor of class |Cocode| multiplied to the converted
    parameter ``value`` from the right.

    Alternatively, parameter ``value`` may be a single letter. If
    ``value`` is one of the capital letters ``BCTXE`` then that letter
    and the following arguments are passed to the constructor of
    class |MMVector| in order to generate a (possible negated) basis
    vector of the representation :math:`\rho_p` of the monster.
    (Here the characteristic :math:`p` is ignored.) If that basis
    vector corresponds to an element of :math:`Q_{x0}` then we
    construct that element of :math:`Q_{x0}`; otherwise an error
    occurs.

    If parameter ``value`` is the letter ``'r'`` then we generate a
    random element of :math:`Q_{x0}`. If an integer ``i`` is given as a 
    second parameter ``'r'`` then we generate a random  element of 
    :math:`Q_{x0}` of type ``i``. Here the type of a vector 
    :math:`v \in \Lambda / 2 \Lambda` is the halved norm of the shortest
    vector in the set :math:`v + 2 \Lambda`. If ``i`` is specified
    then it must be equal to 0, 2, 3 or 4.
 
    **Standard operations**

    Let ``q`` be an instance of this class. The multiplication operator 
    ``*`` implements the group operation. Division by an element means
    multiplication by its inverse, and exponentiation means repeated 
    multiplication,  with ``q**(-1)`` the inverse of  ``q``,  as usual. 

    Multiplication with the integer ``1`` or ``-1`` means the 
    multiplication with the neutral element or with the central 
    involution :math:`x_{-1}`.
     
    The opration  ``&`` denotes the scalar product of the vectors
    in the Leech lattice modulo 2 obtained from an instance of this
    class, ignoring the sign.

    **Standard functions**
  
    ``abs(a)`` returns the element in the set ``{a, -a}`` which is
    positive.

    If an instance :math:`q` of class |XLeech2| maps to a vector of 
    type 2 in the Leech lattice modulo 2 then :math:`q` is also a unit 
    vector in the 196884-dimensional representation :math:`\rho` of 
    the monster group.

    One may use :math:`q` in the constructor of class |MM|
    (representing the monster group) for creating the corresponding
    element of the subgroup :math:`Q_{x0}` of the monster group.


    """
    __slots__ = "value",
 
    def __init__(self, ploop = 0, cocode = 0, *args):
        if import_pending:
            complete_import()
        self.value = value_from_ploop(ploop, cocode, *args)


    def __mul__(self, other):
        if isinstance(other, XLeech2):
            return XLeech2(gen_leech2_mul(self.value, other.value))
        elif isinstance(other, AbstractMMGroupWord):
            data = other.mmdata
            v = gen_leech2_op_word(self.value, data, len(data))
            return XLeech2(v)
        elif isinstance(other, Integral):
            if abs(other) == 1:
                return XLeech2(self.value ^ ((other & 2) << 23))
            elif other == 0:
                 return XLeech2(0)
            return NotImplemented
        else:           
            return NotImplemented

    def __imul__(self, other):
        self.value = self.__mul__(other).value
        return self

    def __rmul__(self, other):
        if isinstance(other, XLeech2):
            return XLeech2(gen_leech2_mul(other.value, self.value))
        elif isinstance(other, Integral):
            if abs(other) == 1:
                return XLeech2(self.value ^ ((other & 2) << 23))
            elif other == 0:
                 return XLeech2(0)
            return NotImplemented
        else:           
            return NotImplemented

    def __abs__(self):
        return  XLeech2(self.value & 0xffffff)

    def __pos__(self):
        return  self

    def __neg__(self):
        return  XLeech2(self.value ^ 0x1000000)

    def __pow__(self, other):
        if isinstance(other, Integral):            
            return XLeech2(gen_leech2_pow(self.value, other & 3))
        elif isinstance(other, XLeech2):
            ov = other.value
            v = self.value
            w = gen_leech2_mul(gen_leech2_pow(ov, 3), v)
            w = gen_leech2_mul(w, ov)
            return XLeech2(w)
        else:
            return NotImplemented
        
    def __truediv__(self, other):
        if isinstance(other, XLeech2):
            v = gen_leech2_pow(other.value, 3)
            return XLeech2(gen_leech2_mul(self.value, other.value))
        elif isinstance(other, Integral):
            if abs(other) == 1:
                v = (other & 2) << 23
            else:
                return NotImplemented  
        else:           
            return NotImplemented
        return XLeech2(gen_leech2_mul(self.value, v))

    def __itruediv__(self, other):
        self.value = self.__itruediv__(other).value
        return self


                             
    def __rtruediv__(self, other):
        v = gen_leech2_pow(other.value, 3)
        if isinstance(other, XLeech2):
            return XLeech2(gen_leech2_mul(other.value, v))
        elif isinstance(other, Integral):
            if abs(other) == 1:
                return XLeech2(v ^ ((other & 2) << 23))
            elif other == 0:
                 return XLeech2(0)
            return NotImplemented
        else:           
            return NotImplemented
 
       
    def __and__(self, other):
        if isinstance(other, XLeech2):
            ov = other
        elif isinstance(other, (GCode, PLoop)):
            d = other.value & 0xfff
            ov = (d << 12) ^ mat24.ploop_theta(d) 
        elif isinstance(ploop, Cocode):
            ov = ploop.value & 0xfff
        else:
            return NotImplemented
        return Parity(gen_leech2_scalprod(self.value, ov))


    __rand__ = __and__

    def __eq__(self, other):
        return (isinstance(other, XLeech2)  
            and (self.value ^ other.value) & 0x1ffffff == 0)

    def __ne__(self, other):
        return not self.__eq__(other)

    def __pos__(self):
        return self
        
    def __neg__(self):
        return  XLeech2(self.value ^ 0x1000000)


    @property
    def ord(self):
        r"""Return the number of the element of :math:`Q_{x0}` .

        We have ``0 <= i < 0x2000000`` for the returned number ``i``.
        """
        return self.value & 0x1ffffff


    @property
    def sign(self):
        r"""Return the sign of the element :math:`Q_{x0}`.

        This is ``1`` for a positive and ``-1`` for a negative element.
        """
        return 1 - ((self.value >> 23) & 2)

    def isplit(self):
        r"""Split element into a product :math:`x_d \cdot x_\delta`

        The method returns a pair  :math:`(d,  \delta)` such that
        :math:`x_d \cdot x_\delta` is equal to the given element
        of  :math:`Q_{x0}`.

        Here :math:`(d,  \delta)` is returned as a pair of integers.
        """
        v = self.value
        x = (v >> 12) & 0x1fff
        d = (mat24.ploop_theta(v >> 12) ^ v) & 0xfff
        return x, d


    def split(self):
        r"""Split element into a product :math:`x_d \cdot x_\delta`

        The method returns a pair  :math:`(d,  \delta)` such that
        :math:`x_d \cdot x_\delta` is equal to the given element
        of  :math:`Q_{x0}`.

        The element :math:`d` of the Parker loop is an instance of
        class |PLoop|. The element  :math:`\delta` of the Golay cocode
        is an instance of class |Cocode|.
        """
        x, d = self.isplit()
        return PLoop(x), Cocode(d)
 
    @property
    def mmdata(self):
        """Return internal representation of corresponding monster element.

        That internal representation is returned as a numpy array 
        of 32-bit integers.
        """
        v = self.value
        x = (v >> 12) & 0x1fff
        d = (mat24.ploop_theta(v >> 12) ^ v) & 0xfff
        return np.array([0x30000000 + x, 0x10000000 + d], 
            dtype = np.uint32)
     
    def str(self):
        v = self.value
        x = (v >> 12) & 0x1fff
        d = (mat24.ploop_theta(v >> 12) ^ v) & 0xfff
        return "XL2<x_%s*d_%s>" % (ihex(x, 3), ihex(d, 3))
    __repr__  = str


    
    @property
    def type(self):
        r"""Return the type of the element of :math:`Q_{x0}`.

        This is equal to the type of the corresponding vector
        :math:`v` in the Leech lattice :math:`\Lambda` modulo 2. 
        The type of a vector :math:`v \in \Lambda / 2 \Lambda` 
        is the halved norm of the shortest  vector in the set 
        :math:`v + 2 \Lambda`. That type is equal to 0, 2, 3 or 4.
        """
        return gen_leech2_type(self.value)

    @property
    def xsubtype(self):
        r"""Return subtype of the element of :math:`Q_{x0}`

        The function returns the integer ``16 * type + subtype``,
        where the pair ``(type, subtype)`` is as in property
        ``subtype``. See section :ref:`computation-leech2` in
        the **guide** for background.
        """
        return gen_leech2_subtype(self.value)

    @property
    def subtype(self):
        r"""Return pair ``(type, subtype)`` of the element of :math:`Q_{x0}`

        Here ``type`` is as in property ``type``, and subtype is
        a one-digit decimal integer describing the subtype of
        a vector in the Leech lattice modulo 2, as explained in
        the **guide** in section :ref:`computation-leech2`.
        """
        t =  gen_leech2_subtype(self.value)
        return t >> 4, t & 15


    def as_Q_x0_atom(self):
        return self.value & 0x1ffffff

    def vector_tuple(self):
        r"""Return unit vector in representation of the monster as tuple

        If the element of :math:`Q_{x0}` corresponds to a vector of
        type 2 in the Leech lattice then it also corresponds to a
        unit vector ``v`` in the representation  :math:`\rho` 
        of :math:`\mathbb{M}`; otherwise the function raises
        ValueError. 
        
        The function returns a  tuple ``(sign, tag, i0, i1)``, with  
        ``sign = +1 or -1``. Then the triple ``(tag, i0, i1)`` 
        corresponds to a basis vector ``u`` in  :math:`\rho` as 
        described in class |MMVector|, and we have ``v = sign * u``.         
        """
        sign = 1 - ((self.value >> 23) & 2)
        sp = mm_aux_index_leech2_to_sparse(self.value & 0xffffff)
        if sp == 0:
            raise ValueError(ERR_XL_SHORT)
        tag = "?ABCTXZY"[(sp >> 25) & 7]
        i0, i1 = (sp >> 14) & 0x7ff, (sp >> 8) & 0x3f
        return sign, tag, i0, i1

    def octad_number(self):
        r"""Return number of octad.

        Let :math:`x_d \cdot x_\delta` be equal to the given element
        of  :math:`Q_{x0}`. If :math:`d` corresponds to a (possibly
        complemented) octad in the Golay code then the function 
        returns the number of that octad as a nonnegative integer 
        less than 759. Otherwise the function raises ``ValueError``.

        Here the octads are numbered as in class |GCode|.
        """
        return mat24.gcode_to_octad(self.value >> 12, 0)


    @classmethod
    def gen_type(cls, vtype = 2, positive = True):
        r"""Return generator that yields the vectors of a given type 

        The function returns a generator that yields all elements of
        :math:`Q_{x0}` that map to a vector of the type ``vtype`` in 
        the Leech lattice (mod 2) as instances of class |XLeech2|.

        Parameter ``vtype`` must be 0, 2, 3 or 4, where ``vtype = 2``
        (default) means the short Leech lattice vectors.
        
        If parameter ``positive``is ``True`` (default) then the 
        elements with positive sign are generated only.
        """
        if vtype == 2:
            for ve in range(300, 98560):
                vs = mm_aux_index_extern_to_sparse(ve)
                yield XLeech2(vs)
                if not positive:
                    yield XLeech2(vs ^ 0x1000000)
        elif vtype == 0: 
            yield XLeech2(0)
            if not positive:
                yield XLeech2(vs ^ 0x1000000)
        elif vtype in [3,4]:
            for vs in range(0x1000000):
                if gen_leech2_type(vs) == vtype:
                    yield XLeech2(vs)
                    if not positive:
                        yield XLeech2(vs ^ 0x1000000)
        else:
            err = "Bad parameter 'vtype' in class XLeech2, method gen_type"
            raise ValueError(err)


    def as_Leech2_bitvector(self):
        """Convert element to a bit vector in a numpy array

        The element is returned as a numpy array ``v`` of length 24
        of 8-bit unsigned integers, with each entry equal to
        0 or 1. This array represents a vector in the Leech
        lattice mod 2 in the standard encoding.

        For an instance ``x`` of this class we have
        ``v[i] =  (x.ord >> i) & 1``, for 0 <= ``i`` < 24.
        """
        v = self.value & 0xffffff
        return np.array([(v >> i) & 1 for i in range(24)],
             dtype = np.uint8)


add_to_embedded_classes(XLeech2)




