"""Wrapper for module mm_group_n.c for the subgroup N_0 of the monster

 TODO: adjust documentation to new version

 That group N_0 is described in [Conw85] and [Seys19]. More precisely, 
 we actually calculate in the fourfold cover N of N_0 of structure
  
      2**2 . 2**2 . 2**11 . 2**(11+11) . (Sym3 \times Mat24),

 using the notation in [Seys19]. Here Sym3 is the symmetric permutation
 group of 3 elements and Mat24 is the Mathieu group acting on 24 elements. 
 An element of N is represented as a 5-tuple (exp, f, e, delta, pi) of 
 integers of type uint32_t. Here


 exp    represents the element t**exp for the triality element t
        (t is called 'tau' in [Seys19], section 5);
 f      represents the element y_f, 0 <= f < 0x2000 an element of Pl;   
 e      represents the element x_e, 0 <= e < 0x2000 an element of Pl;   
 delta  represents the element x_delta, where 0 <= delta < 0x1000 is
        an element of the Golay cocode C*;
 pi     represents the element (pi) of Mat24 with number pi.  
        More precisely, pi represents the preimage of (pi) in the 
        automorphism group AutPl of the Parker loop Pl that maps all 
        basis vectors of Pl to positive elements of Pl.
        
 Elements tau, x_f, x_e, x_delta are defined as in [Seys19], setion 5. 
 For the numbering of the elements of the Parker loop Pl, the Golay
 cocode C*, and the Mathieu group Mat24, see module mat24_functions.c.
 The basis of the Parker loop Pl is also given in that module.
 For the notion of a positive element of the Parker loop (with 
 respect to a certain cocycle) of the Parker loop see [Seys19]. The 
 cocycle selected for Pl is also given in mat24_functions.c.
 The elements of N are multiplied in the order given by the 5-tuple.

 An element of the group N is represented as an instance of class 
 FastGroupN_Word.

 For interfacing with class GroupN_Word, generating elements
 of the group N may be given as tuples as follows:

 ('t', exp)     means  t**exp for an integer exp.
 ('y', f)       means  y_f for an integer f representing an
                element of the Parker loop.
 ('x', e)       means  x_e for an integer e representing an
                element of the Parker loop.
 ('d', delta)   means x_delta for an integer delta representing
                an element of the Golay cocode 
 ('p', pi)      means the element (pi) defined as follows:

The element (pi) may be given as a list of 24 integers representing
an element of Mat24 as described in module mat24_functions.c. It
may also be given as an integer representing the element of Mat24 
with that number, as described in module mat24_functions.c.

An instance of class FastGroupN_Word representing a element g of N
may be right multiplied with another element g1 of N using the '*'
operator. Then g1 may be:

   - a tuple representing an element of N as described above,
   - a list of such tuples (not yet implemented),
   - another instance of class FastGroupN_Word,
   - an element of group N, where N is an instance of class 
     mgroup_n.MGroupN representing a group similar to N. 
   
Note that class mgroup_n.MGroupN has methods for generating 
elements of the free product <N, T> of N and a group L of order 3 
generated by an element l. There is a homomorphsim from <N, L>
to the monster MM which maps N to the subgroup N_0 of MM 
discussed in [Conw85] and [Seys19] and l to the element xi
of MM defined in [Seys19], section 9.4.

For the sake of precision we mention that class mgroup_n.MGroupN 
may also represent some factor groups N' of N such that N_0 is a
factor group of N'. 

The tuples given above are also supported by the methods of class
mgroup_n.MGroupN. In addition, the tuple ('l', exp) represents the
element l**exp of MM. So we can describe elements of the monster MM 
by sequences of such tuples.

Class FastGroupN_Word executes the group operations in the group N
by using the fast routines in module mm_group_n.c.

Expnonentiation is also supported in the usual way, with g**2 = g*g  
etc., g**(-1) the inverse of g, and g1**g2 = g2**(-1) * g1 * g2.
""" 
from __future__ import absolute_import, division, print_function
from __future__ import  unicode_literals


import sys
import os
import re
import numpy as np
from numbers import Integral
from random import randint
import warnings
import time



from mmgroup.structures.abstract_mm_group import AbstractMMGroupWord
from mmgroup.structures.abstract_mm_group import AbstractMMGroup
from mmgroup.structures.parse_atoms import  AtomDict      
from mmgroup.structures.parse_atoms import ihex       
from mmgroup.structures.construct_mm import iter_mm       
from mmgroup.structures.construct_mm import load_group_name     


from mmgroup.generators import mm_group_n_mul_atom
from mmgroup.generators import mm_group_n_mul_element
from mmgroup.generators import mm_group_n_inv_element
from mmgroup.generators import mm_group_n_mul_inv_element
from mmgroup.generators import mm_group_n_mul_delta_pi, mm_group_n_mul_x
from mmgroup.generators import mm_group_n_mul_inv_delta_pi
from mmgroup.generators import mm_group_n_mul_y, mm_group_n_mul_t
from mmgroup.generators import mm_group_n_reduce_element
from mmgroup.generators import mm_group_n_copy_element
from mmgroup.generators import mm_group_n_right_coset_N_x0
from mmgroup.generators import mm_group_n_to_word_std

from mmgroup.mat24 import pow_ploop



######################################################################
# Modelling an element of the group
######################################################################


class GroupN_Word(AbstractMMGroupWord):
    def __init__(self,  tag = None, atom = None, *args, **kwds):
        self.data = np.zeros(5, dtype = np.uint32)
        atoms = iter_mm(self.group, tag, atom)
        for a in atoms:
            mm_group_n_mul_atom(self.data, a)
        mm_group_n_reduce_element(self.data)

    ####################################################################
    # Alternative multipication and division methods for tests
    ####################################################################

    def mul(self, g):
        return self.group.mul(self, g)
    def mul_atoms(self, g):
        return self.group.mul_atoms(self, g)
    def div(self, g):
        return self.group.div(self, g)
    def div_atoms(self, g):
        return self.group.div_atoms(self, g)

    @property
    def mmdata(self):
        element = self.data
        mm_group_n_reduce_element(element)
        a = []
        if element[0]: a.append(0x50000000 + element[0])
        if element[1]: a.append(0x40000000 + element[1])
        if element[2]: a.append(0x30000000 + element[2])
        if element[3]: a.append(0x10000000 + element[3])
        if element[4]: a.append(0x20000000 + element[4])
        return np.array(a, dtype = np.uint32)

    def right_coset_N_x0(self):
        """Decmpose element as right coset of ``N_x0``

        The function returns two group elements ``g_Nx0``, ``g_t``
        such that the product of these two elements is equal to
        this element, ``g_Nx0`` is in the subgroup ``N_x0`` of
        ``N_0``, and  ``g_t`` is a power of the triality element.
        """
        g_Nx0 = GroupN_Word()
        g_t = GroupN_Word()
        mm_group_n_copy_element(self.data, g_Nx0.data)
        g_t.data[0] = mm_group_n_right_coset_N_x0(g_Nx0.data)
        return g_Nx0, g_t

    def std_word(self):
        """Return element as word of generators in standard order

        The function returns the element as a word of generators
        of the Monster in standard order in a ``numpy`` array of
        length at most 5.
        """
        a = np.zeros(5, dtype = np.uint32)
        length = mm_group_n_to_word_std(self.data, a)
        assert 0 <= length <= 5
        return a[:length]


######################################################################
# Modelling the group
######################################################################

class GroupN(AbstractMMGroup):
    __instance = None
    group_name = "GroupN"
    word_type = GroupN_Word  # type of an element (=word) in the group 
    __instance = None

    def __new__(cls):
        if GroupN.__instance is None:
             GroupN.__instance = AbstractMMGroup.__new__(cls)
        return GroupN.__instance

    def __init__(self):
        super(GroupN, self).__init__()
        

    def copy_word(self, g1):
        """Return deep copy of group element g1"""
        g_copy = self.word_type(group=self)
        g_copy.data[:] = g1.data
        return g_copy

    def _equal_words(self, g1, g2):
        """Return True iff elements g1 and g2 are equal 

        This method is called for elements g1 and g2 of the group
        'self' only.
		
        In concrete group this method should be overwritten with
        a comparison of the relevant attributes of g1 and g2.
        """
        mm_group_n_reduce_element(g1.data)
        mm_group_n_reduce_element(g2.data)        
        return (g1.data == g2.data).all()


   
    def __call__(self, tag = None, atom = None):
       return self.word_type(tag, atom) 

    ####################################################################
    # group operation
    ####################################################################

        
    def _imul(self, g1, g2):
        mm_group_n_mul_element(g1.data, g2.data, g1.data)
        return g1

    def _invert(self, g1):
        g2 = GroupN_Word(group=self)
        mm_group_n_inv_element(g1.data, g2.data)
        return g2

    ####################################################################
    # alternative multiplication and division methods for tests
    ####################################################################

    def mul(self, g1, g2):
        assert g2.group == self
        mm_group_n_mul_element(g1.data, g2.data, g1.data)
        return g1

    def mul_atoms(self, g1, g2):
        assert g2.group == self
        for tag, data in g2.as_tuples():
           self.mul_atom(g1, tag, data)
        return g1

    def div(self, g1, g2):
        assert g2.group == self
        mm_group_n_mul_inv_element(g1.data, g2.data, g1.data)
        return g1

    def div_atoms(self, g1, g2):
        assert g2.group == self
        for tag, data in reversed(g2.as_tuples()):
           self.divide_atom(g1, tag, data)
        return g1

    ####################################################################
    # Low-level group operation
    ####################################################################


    def mul_atom(self, g, tag, data):
        """Auxiliary method for method mul()        """
        v = g.data
        if tag == "d":
            mm_group_n_mul_delta_pi(v, data, 0)
        elif tag == "p":
            mm_group_n_mul_delta_pi(v, 0, data)
        elif tag == 'x':
            mm_group_n_mul_x(v, data)
        elif tag == 'y':
            mm_group_n_mul_y(v, data)
        elif tag == 't':
            mm_group_n_mul_t(v, data)
        else:
            raise TypeError("Illegal tag %s in group word" % t)

    def divide_atom(self, g, tag, data):
        """Auxiliary method for method mul()        """
        v = g.data
        if tag == "d":
            mm_group_n_mul_inv_delta_pi(v, data, 0)
        elif tag == "p":
            mm_group_n_mul_inv_delta_pi(v, 0, data)
        elif tag == 'x':
            mm_group_n_mul_x(v, pow_ploop(data, 3))
        elif tag == 'y':
            mm_group_n_mul_y(v, pow_ploop(data, 3))
        elif tag == 't':
            mm_group_n_mul_t(v, -int(data) % 3)
        else:
            raise TypeError("Illegal tag %s in group word" % t)


StdGroupN = GroupN()
GroupN_Word.group = StdGroupN
load_group_name(StdGroupN)

