import sympy
from sympy.combinatorics import Permutation, PermutationGroup
sympy.init_printing(perm_cyclic = True)
import cmath
from cmath import exp, pi
from math import sqrt
import numpy as np
np.set_printoptions(linewidth=200)
import numbers
import re
ň = None
from .tmatrices import zflip_tyty, xflip_tyty, yflip_tyty, zrotN_tyty, WignerD_yy_fromvector, identity_tyty, apply_ndmatrix_left
from .cyquaternions import IRot3
from .cycommon import get_mn_y

s3long = np.sqrt(np.longdouble(3.))

def grouprep_try(tdict, src, im, srcgens, imgens, immultop = None, imcmp = None):
    tdict[src] = im
    for i in range(len(srcgens)):
        new_src = src * srcgens[i]
        new_im = (im * imgens[i]) if (immultop is None) else immultop(im, imgens[i])
        if new_src not in tdict.keys():
            grouprep_try(tdict, new_src, new_im, srcgens, imgens, immultop, imcmp)
        elif ((new_im != tdict[new_src]) if (imcmp is None) else (not imcmp(new_im, tdict[new_src]))): # check consistency
            print(src, ' * ', srcgens[i], ' --> ', new_src)
            print(im)
            print(' * ')
            print(imgens[i])
            print(' --> ')
            print(new_im)
            print(' != ')
            print(tdict[new_src])
            raise ValueError("Homomorphism inconsistency detected")                
    return

def group_dps_try(elemlist, elem, gens):
    '''Deterministic group depth-first search'''
    elemlist.append(elem)
    for i in range(len(gens)):
        newelem = elem * gens[i]
        if newelem not in elemlist:
            group_dps_try(elemlist, newelem, gens)
    return

class SVWFPointGroupInfo: # only for point groups, coz in svwf_rep() I use I_tyty, not I_ptypty or something alike
    def __init__(self,
                 name,
                 permgroupgens, # permutation group generators
                 irrepgens_dict, # dictionary with irrep generators,
                 svwf_rep_gen_func, # function that generates a tuple with svwf representation generators
                 rep3d_gens = None, # 3d (quaternion) representation generators of a point group: sequence of qpms.irep3 instances
                ):
        self.name = name
        self.permgroupgens = permgroupgens
        self.permgroup = PermutationGroup(*permgroupgens)
        self.irrepgens_dict = irrepgens_dict
        self.svwf_rep_gen_func = svwf_rep_gen_func
        self.irreps = dict()
        for irrepname, irrepgens in irrepgens_dict.items():
            is1d = isinstance(irrepgens[0], (int,float,complex))
            irrepdim = 1 if is1d else irrepgens[0].shape[0]
            self.irreps[irrepname] = generate_grouprep(self.permgroup, 
                                                       1 if is1d else np.eye(irrepdim),
                                                       permgroupgens, irrepgens,
                                                       immultop = None if is1d else np.dot,
                                                       imcmp = None if is1d else np.allclose
                                                      )
        self.rep3d_gens = rep3d_gens
        self.rep3d = None if rep3d_gens is None else generate_grouprep(
                self.permgroup,
                IRot3(),
                permgroupgens, rep3d_gens,
                immultop = None, imcmp = (lambda x, y: x.isclose(y))
                )

    def deterministic_elemlist(self):
        thelist = list()
        group_dps_try(thelist, self.permgroup.identity, self.permgroupgens)
        return thelist
    
    def svwf_rep(self, lMax, *rep_gen_func_args, **rep_gen_func_kwargs):
        '''
        This method generates full SVWF (reducible) representation of the group.
        '''
        svwfgens = self.svwf_rep_gen_func(lMax, *rep_gen_func_args, **rep_gen_func_kwargs)
        my, ny = get_mn_y(lMax)
        nelem = len(my)
        I_tyty = np.moveaxis(np.eye(2)[:,:,ň,ň] * np.eye(nelem), 2,1)
        return generate_grouprep(self.permgroup, I_tyty, self.permgroupgens, svwfgens, immultop = mmult_tyty, imcmp = np.allclose)
    
    def svwf_irrep_projectors(self, lMax, *rep_gen_func_args, **rep_gen_func_kwargs):
        return gen_point_group_svwfrep_projectors(self.permgroup, self.irreps, self.svwf_rep(lMax, *rep_gen_func_args, **rep_gen_func_kwargs))
    
    # alternative, for comparison and testing; should give the same results
    def svwf_irrep_projectors2(self, lMax, *rep_gen_func_args, **rep_gen_func_kwargs):
        return gen_point_group_svwfrep_projectors2(self.permgroup, self.irreps, self.svwf_rep(lMax, *rep_gen_func_args, **rep_gen_func_kwargs))

    def svwf_irrep_projectors2_w_bases(self, lMax, *rep_gen_func_args, **rep_gen_func_kwargs):
        return gen_point_group_svwfrep_projectors2_w_bases(self.permgroup, self.irreps, self.svwf_rep(lMax, *rep_gen_func_args, **rep_gen_func_kwargs))

    def generate_c_source(self):
        '''
        Generates a string with a chunk of C code with a definition of a qpms_finite_group_t instance.
        See also groups.h.
        '''
        permlist = self.deterministic_elemlist()
        order = len(permlist)
        permindices = {perm: i for i, perm in enumerate(permlist)} # 'invert' permlist
        identity = self.permgroup.identity
        s = "{\n"
        # char *name
        s += '  "%s", // name\n' % self.name
        # size_t order;
        s += '  %d, // order\n' % order
        # qpms_gmi_t idi
        s += '  %d, // idi\n' % permindices[identity]
        # qpms_gmi_t *mt
        s += '  (qpms_gmi_t[]) { // mt\n'
        for i in range(order):
            ss = ', '.join([str(permindices[permlist[i]*permlist[j]]) for j in range(order)])
            s += '    ' + ss + ',\n'
        s += '  },\n'
        # qpms_gmi_t *invi
        s += '  (qpms_gmi_t[]) { // invi\n'
        s += '    ' + ', '.join([str(permindices[permlist[j]**-1]) for j in range(order)])
        s += '\n  },\n'
        # qpms_gmi_t *gens
        s += '  (qpms_gmi_t[]) {' + ', '.join([str(permindices[g]) for g in self.permgroupgens]) + '}, // gens\n'
        # int ngens
        s += '  %d, // ngens\n' % len(self.permgroupgens)
        # qpms_permutation_t permrep[]
        s += '  (qpms_permutation_t[]){ // permrep\n'
        for i in range(order):
            s += '    "%s",\n' % str(permlist[i])
        s += '  },\n'
        # char **elemlabels
        s += '  NULL, // elemlabels\n'
        # int permrep_nelem
        s += '  %d, // permrep_nelem\n' % self.permgroup.degree
        # qpms_irot3_t rep3d[]
        if self.rep3d is None:
            s += '  NULL, // rep3d TODO!!!\n'
        else:
            s += '  (qpms_irot3_t[]) { // rep3d\n'
            for i in range(order):
                s += '   ' + self.rep3d[permlist[i]].crepr() + ',\n'
            s += '  },\n'
        # int nirreps
        s += '  %d, // nirreps\n' % len(self.irreps)
        # struct qpms_finite_grep_irrep_t irreps[]
        s += '  (struct qpms_finite_group_irrep_t[]) { // irreps\n'
        for irname  in sorted(self.irreps.keys()):
            irrep = self.irreps[irname]
            s += '    {\n' 
            is1d = isinstance(irrep[identity], (int, float, complex))
            dim = 1 if is1d else irrep[identity].shape[0]
            # int dim
            s += '      %d, // dim\n' % dim
            # char name[]
            s += '      "%s", //name\n' % re.escape(irname)

            # complex double *m
            if (is1d):
                s += '      (complex double []) {' + ', '.join([str(irrep[permlist[i]]) for i in range(order)]) + '} // m\n'
            else:
                s += '      (complex double []) {\n'
                for i in range(order):
                    s += '        // %s\n' % str(permlist[i])
                    for row in range(dim):
                        s += '        '
                        for col in range(dim):
                            s += '%s, ' % re.sub('j', '*I', str(irrep[permlist[i]][row,col]))
                        s += '\n'
                    mat = irrep[permlist[i]]
                s += '      }\n'

            #s += '       %d, // dim\n' %
            s += '    },\n'
        s += '  } // end of irreps\n'
        s += '}'
        return s

# srcgroup is expected to be PermutationGroup and srcgens of the TODO
# imcmp returns True if two elements of the image group are 'equal', otherwise False
def generate_grouprep(srcgroup, im_identity, srcgens, imgens, immultop = None, imcmp = None):
    sz = srcgens[0].size
    for g in srcgens:
        if g.size != sz:
            raise ValueError('All the generators must have the same "size"')
    tdict = dict()
    grouprep_try(tdict, Permutation(sz-1), im_identity, srcgens, imgens, immultop = immultop, imcmp = imcmp)
    if(srcgroup.order() != len(tdict.keys())): # basic check
        raise ValueError('The supplied "generators" failed to generate the preimage group: ', 
                         srcgroup.order(), " != ", len(tdict.keys()))
    return tdict
    
# matrices appearing in 2d representations of common groups as used in Bradley, Cracknell p. 61 (with arabic names instead of greek, because lambda is a keyword)
epsilon = np.eye(2)
alif = np.array(((-1/2,-s3long/2),(s3long/2,-1/2)))
bih = np.array(((-1/2,s3long/2),(-s3long/2,-1/2)))
kaf = np.array(((0,1),(1,0)))
lam = np.array(((1,0),(0,-1)))
ra = np.array(((0,-1),(1,0)))
mim =  np.array(((-1/2,-s3long/2),(-s3long/2,1/2)))
nun =  np.array(((-1/2,s3long/2),(s3long/2,1/2)))




def mmult_tyty(a, b):
        return(apply_ndmatrix_left(a, b, (-4,-3)))
def mmult_ptypty(a, b):
    return(apply_ndmatrix_left(a, b, (-6,-5,-4)))

def gen_point_group_svwfrep_irreps(permgroup, matrix_irreps_dict, sphrep_full):
    '''
    Gives the projection operators $P_kl('\Gamma')$ from Dresselhaus (4.28)
    for all irreps $\Gamma$ of D3h.;
    as an array with indices [k,l,t,y,t,y]
    
    Example of creating last argument:
    sphrep_full = generate_grouprep(D3h_permgroup, I_tyty, D3h_srcgens, [C3_tyty, vfl_tyty, zfl_tyty], 
                           immultop = mmult_tyty, imcmp = np.allclose)
    '''
    order = permgroup.order()
    sphreps = dict()
    nelem = sphrep_full[permgroup[0]].shape[-1] # quite ugly hack
    for repkey, matrixrep in matrix_irreps_dict.items():
        arepmatrix = matrixrep[permgroup[0]] # just one of the matrices to get the shape etc
        if isinstance(arepmatrix, numbers.Number):
            dim = 1 # repre dimension
            preprocess = lambda x: np.array([[x]])
        elif isinstance(arepmatrix, np.ndarray):
            if(len(arepmatrix.shape)) != 2 or arepmatrix.shape[0] != arepmatrix.shape[1]:
                raise ValueError("Arrays representing irrep matrices must be of square shape")
            dim = arepmatrix.shape[0]
            preprocess = lambda x: x
        else: 
            raise ValueError("Irrep is not a square array or number")        
        sphrep = np.zeros((dim,dim,2,nelem,2,nelem), dtype=complex)
        for i in permgroup.elements:
            sphrep += preprocess(matrixrep[i]).conj().transpose()[:,:,ň,ň,ň,ň] * sphrep_full[i]
        sphrep *= dim / order
        # clean the nonexact values here 
        for x in [0, 0.5, -0.5, 0.5j, -0.5j]:
            sphrep[np.isclose(sphrep,x)]=x
        sphreps[repkey] = sphrep
    return sphreps


def gen_point_group_svwfrep_projectors(permgroup, matrix_irreps_dict, sphrep_full):
    '''
    The same as gen_point_group_svwfrep_irreps, but summed over the kl diagonal, so
    one gets single projector onto each irrep space and the arrays have indices
    [t, y, t, y]
    '''
    summedprojs = dict()
    for repi, W in gen_point_group_svwfrep_irreps(permgroup, matrix_irreps_dict, sphrep_full).items():
        irrepd = W.shape[0]
        if irrepd == 1:
            mat = np.reshape(W, W.shape[-4:])
        else:
            mat = np.zeros(W.shape[-4:], dtype=complex) # TODO the result should be real — check!
            for d in range(irrepd):
                mat += W[d,d]
        if not np.allclose(mat.imag, 0):
            raise ValueError("The imaginary part of the resulting projector should be zero, damn!")
        else:
            summedprojs[repi] = mat.real
    return summedprojs


def gen_point_group_svwfrep_projectors2_w_bases(permgroup, matrix_irreps_dict, sphrep_full):
    return gen_point_group_svwfrep_projectors2(permgroup, matrix_irreps_dict, sphrep_full, do_bases = True)

def gen_point_group_svwfrep_projectors2(permgroup, matrix_irreps_dict, sphrep_full, do_bases = False):
    '''
    an approach as in gen_hexlattice_Kpoint_svwf_rep_projectors; for comparison and testing
    '''
    if (do_bases):
        bases = dict()
    projectors = dict()
    for repi, W in gen_point_group_svwfrep_irreps(permgroup, matrix_irreps_dict, sphrep_full).items():
        nelem = W.shape[-1] # however, this should change between iterations
        totalvecs = 0
        tmplist = list()
        for t in (0,1):
         for y in range(nelem):
           for ai in range(W.shape[0]):
            for bi in range(W.shape[1]):
                v = np.zeros((2, nelem))
                v[t,y] = 1
                v1 = np.tensordot(W[ai,bi], v, axes = ([-2,-1],[0,1]))

                if not np.allclose(v1,0):
                    v1 = normalize(v1)
                    for v2 in tmplist:
                        dot = np.tensordot(v1.conjugate(),v2, axes=([-2,-1],[0,1]))
                        if not (np.allclose(dot,0)):
                            if not np.allclose(np.abs(dot),1):
                                raise ValueError('You have to fix this piece of code.')
                            break
                    else:
                        totalvecs += 1
                        tmplist.append(v1)
        theprojector = np.zeros((2,nelem, 2, nelem), dtype = float)
        if do_bases:
            thebasis = np.zeros((len(tmplist), 2, nelem), dtype=complex)
            for i, v in enumerate(tmplist):
                thebasis[i] = v
            bases[repi] = thebasis
        for v in tmplist:
            theprojector += (v[:,:,ň,ň] * v.conjugate()[ň,ň,:,:]).real 
        for x in [0, 1, -1, sqrt(.5), -sqrt(.5), .5, -.5]:
            theprojector[np.isclose(theprojector,x)] = x
        projectors[repi] = theprojector
    if do_bases:
        return projectors, bases
    else:
        return projectors


# Group D3h; mostly legacy code (kept because of the the honeycomb lattice K-point code, whose generalised version not yet implemented)
# Note that the size argument of permutations is necessary, otherwise e.g. c*c and  b*b would not be evaluated equal
# N.B. the weird elements as Permutation(N) – it means identity permutation of size N+1.
rot3_perm = Permutation(0,1,2, size=5) # C3 rotation
xflip_perm = Permutation(0,2, size=5) # vertical mirror
zflip_perm = Permutation(3,4, size=5) # horizontal mirror
D3h_srcgens = [rot3_perm,xflip_perm,zflip_perm]
D3h_permgroup = PermutationGroup(*D3h_srcgens) # D3h

D3h_irreps = {
    # Bradley, Cracknell p. 61
    "E'" : generate_grouprep(D3h_permgroup, epsilon, D3h_srcgens, [alif, lam, epsilon], immultop = np.dot, imcmp = np.allclose),
    "E''" : generate_grouprep(D3h_permgroup, epsilon, D3h_srcgens, [alif, lam, -epsilon], immultop = np.dot, imcmp = np.allclose),
    # Bradley, Cracknell p. 59, or Dresselhaus, Table A.14 (p. 482)
    "A1'" : generate_grouprep(D3h_permgroup, 1, D3h_srcgens, [1,1,1]),
    "A2'" : generate_grouprep(D3h_permgroup, 1, D3h_srcgens, [1,-1,1]),
    "A1''" : generate_grouprep(D3h_permgroup, 1, D3h_srcgens, [1,-1,-1]),
    "A2''" : generate_grouprep(D3h_permgroup, 1, D3h_srcgens, [1,1,-1]),
}

#TODO lepší název fce; legacy, use group_info['D3h'].generate_grouprep() instead
def gen_point_D3h_svwf_rep(lMax, vflip = 'x'):
    '''
    Gives the projection operators $P_kl('\Gamma')$ from Dresselhaus (4.28)
    for all irreps $\Gamma$ of D3h.;
    as an array with indices [k,l,t,y,t,y]
    '''

    my, ny = get_mn_y(lMax)
    nelem = len(my)
    C3_yy = WignerD_yy_fromvector(lMax, np.array([0,0,2*pi/3]))
    C3_tyty = np.moveaxis(np.eye(2)[:,:,ň,ň] * C3_yy, 2,1)
    zfl_tyty = zflip_tyty(lMax)
    #yfl_tyty = yflip_tyty(lMax)
    #xfl_tyty = xflip_tyty(lMax)
    vfl_tyty = yflip_tyty(lMax) if vflip == 'y' else xflip_tyty(lMax)
    I_tyty = np.moveaxis(np.eye(2)[:,:,ň,ň] * np.eye(nelem), 2,1)
    order = D3h_permgroup.order()
    sphrep_full = generate_grouprep(D3h_permgroup, I_tyty, D3h_srcgens, [C3_tyty, vfl_tyty, zfl_tyty], 
                           immultop = mmult_tyty, imcmp = np.allclose)
    sphreps = dict()
    for repkey, matrixrep in D3h_irreps.items():
        arepmatrix = matrixrep[rot3_perm] # just one of the matrices to get the shape etc
        if isinstance(arepmatrix, numbers.Number):
            dim = 1 # repre dimension
            preprocess = lambda x: np.array([[x]])
        elif isinstance(arepmatrix, np.ndarray):
            if(len(arepmatrix.shape)) != 2 or arepmatrix.shape[0] != arepmatrix.shape[1]:
                raise ValueError("Arrays representing irrep matrices must be of square shape")
            dim = arepmatrix.shape[0]
            preprocess = lambda x: x
        else: 
            raise ValueError("Irrep is not a square array or number")
        sphrep = np.zeros((dim,dim,2,nelem,2,nelem), dtype=complex)
        for i in D3h_permgroup.elements:
            sphrep += preprocess(matrixrep[i]).conj().transpose()[:,:,ň,ň,ň,ň] * sphrep_full[i]
        sphrep *= dim / order
        # clean the nonexact values here 
        for x in [0, 0.5, -0.5, 0.5j, -0.5j]:
            sphrep[np.isclose(sphrep,x)]=x
        sphreps[repkey] = sphrep
    return sphreps
        
def gen_hexlattice_Kpoint_svwf_rep(lMax, psi, vflip = 'x'):
    my, ny = get_mn_y(lMax)
    nelem = len(my)
    C3_yy = WignerD_yy_fromvector(lMax, np.array([0,0,2*pi/3]))
    C3_tyty = np.moveaxis(np.eye(2)[:,:,ň,ň] * C3_yy, 2,1)
    zfl_tyty = zflip_tyty(lMax)
    #yfl_tyty = yflip_tyty(lMax)
    #xfl_tyty = xflip_tyty(lMax)
    vfl_tyty = yflip_tyty(lMax) if vflip == 'y' else xflip_tyty(lMax)
    I_tyty = np.moveaxis(np.eye(2)[:,:,ň,ň] * np.eye(nelem), 2,1)
    hex_C3_K_ptypty = np.diag([exp(-psi*1j*2*pi/3),exp(+psi*1j*2*pi/3)])[:,ň,ň,:,ň,ň] * C3_tyty[ň,:,:,ň,:,:]
    hex_zfl_ptypty = np.eye(2)[:,ň,ň,:,ň,ň] * zfl_tyty[ň,:,:,ň,:,:]
    #hex_xfl_ptypty = np.array([[0,1],[1,0]])[:,ň,ň,:,ň,ň] * xfl_tyty[ň,:,:,ň,:,:]
    hex_vfl_ptypty = np.array([[0,1],[1,0]])[:,ň,ň,:,ň,ň] * vfl_tyty[ň,:,:,ň,:,:]
    hex_I_ptypty = np.eye((2*2*nelem)).reshape((2,2,nelem,2,2,nelem))
    order = D3h_permgroup.order()
    hex_K_sphrep_full = generate_grouprep(D3h_permgroup, hex_I_ptypty, D3h_srcgens, [hex_C3_K_ptypty, hex_vfl_ptypty, hex_zfl_ptypty], 
                           immultop = mmult_ptypty, imcmp = np.allclose)
    hex_K_sphreps = dict()
    for repkey, matrixrep in D3h_irreps.items():
        arepmatrix = matrixrep[rot3_perm] # just one of the matrices to get the shape etc
        if isinstance(arepmatrix, numbers.Number):
            dim = 1 # repre dimension
            preprocess = lambda x: np.array([[x]])
        elif isinstance(arepmatrix, np.ndarray):
            if(len(arepmatrix.shape)) != 2 or arepmatrix.shape[0] != arepmatrix.shape[1]:
                raise ValueError("Arrays representing irrep matrices must be of square shape")
            dim = arepmatrix.shape[0]
            preprocess = lambda x: x
        else: 
            raise ValueError("Irrep is not a square array or number")
        sphrep = np.zeros((dim,dim,2,2,nelem,2,2,nelem), dtype=complex)
        for i in D3h_permgroup.elements:
            sphrep += preprocess(matrixrep[i]).conj().transpose()[:,:,ň,ň,ň,ň,ň,ň] * hex_K_sphrep_full[i]
        sphrep *= dim / order
        # clean the nonexact values here 
        for x in [0, 0.5, -0.5, 0.5j, -0.5j]:
            sphrep[np.isclose(sphrep,x)]=x
        hex_K_sphreps[repkey] = sphrep
    return hex_K_sphreps        

def normalize(v):
    norm = np.linalg.norm(v.reshape((np.prod(v.shape),)), ord=2)
    if norm == 0: 
       return v*np.nan
    return v / norm

def gen_hexlattice_Kpoint_svwf_rep_projectors(lMax, psi, vflip='x', do_bases=False):
    nelem = lMax * (lMax+2)
    projectors = dict()
    if do_bases:
        bases = dict()
    for repi, W in gen_hexlattice_Kpoint_svwf_rep(lMax,psi,vflip=vflip).items():
        totalvecs = 0
        tmplist = list()
        for p in (0,1):
         for t in (0,1):
          for y in range(nelem):
           for ai in range(W.shape[0]):
            for bi in range(W.shape[1]):
                v = np.zeros((2,2,nelem))
                v[p,t,y] = 1
                #v = np.ones((2,2,nelem))
                v1 = np.tensordot(W[ai,bi],v, axes = ([-3,-2,-1],[0,1,2])) 


                if not np.allclose(v1,0):
                    v1 = normalize(v1)
                    for v2 in tmplist:
                        dot = np.tensordot(v1.conjugate(),v2,axes = ([-3,-2,-1],[0,1,2]))
                        if not np.allclose(dot,0):
                            if not np.allclose(np.abs(dot),1):
                                raise ValueError('You have to fix this piece of code.')# TODO maybe I should make sure that the absolute value is around 1
                            break
                    else:
                        totalvecs += 1
                        tmplist.append(v1)
                        #for index, x in np.ndenumerate(v1):
                        #    if x!=0:
                        #        print(index, x)
                        #print('----------')
        theprojector = np.zeros((2,2,nelem,2,2,nelem), dtype = float) 
        if do_bases:
            thebasis = np.zeros((len(tmplist), 2,2,nelem), dtype=complex)
            for i, v in enumerate(tmplist):
                thebasis[i] = v
            bases[repi] = thebasis
        for v in tmplist:
            theprojector += (v[:,:,:,ň,ň,ň] * v.conjugate()[ň,ň,ň,:,:,:]).real # TODO check is it possible to have imaginary elements?
        for x in [0, 1, -1,sqrt(0.5),-sqrt(0.5),0.5,-0.5]:
            theprojector[np.isclose(theprojector,x)]=x
        projectors[repi] = theprojector
    if do_bases:
        return projectors, bases
    else:
        return projectors



point_group_info = { # representation info of some useful point groups
    # TODO real trivial without generators
    'trivial_g' : SVWFPointGroupInfo('trivial_g',
                 # permutation group generators
                               (   # I put here the at least the identity for now (it is reduntant, but some functions are not robust enough to have an empty set of generators
                                   Permutation(),
                               ), 
                 # dictionary with irrep generators
                               {    
                                    "A" : (1,),
                               },
                 # function that generates a tuple with svwf representation generators
                               lambda lMax : (identity_tyty(lMax),),
                 # quaternion rep generators
                               rep3d_gens = (
                                   IRot3.identity(),
                                )
    ),
    'C2' : SVWFPointGroupInfo('C2',
                 # permutation group generators
                               (Permutation(0,1), # 180 deg rotation around z axis
                               ), 
                 # dictionary with irrep generators
                               {
                                    # Bradley, Cracknell p. 57;
                                    'A': (1,),
                                    'B': (-1,),    
                               },
                 # function that generates a tuple with svwf representation generators
                               lambda lMax : (zrotN_tyty(2, lMax),),
                 # quaternion rep generators
                                rep3d_gens = (
                                    IRot3.zrotN(2),
                                )

    ),
    'C2v' : SVWFPointGroupInfo('C2v',
                 # permutation group generators
                               (Permutation(0,1, size=4)(2,3), # x -> - x mirror operation (i.e. yz mirror plane)
                                Permutation(0,3, size=4)(1,2), # y -> - y mirror operation (i.e. xz mirror plane)
                               ), 
                 # dictionary with irrep generators
                               {
                                    # Bradley, Cracknell p. 58; not sure about the labels / axes here
                                    'A1': (1,1),
                                    'B2': (-1,1),
                                    'A2': (-1,-1),
                                    'B1': (1,-1),    
                               },
                 # function that generates a tuple with svwf representation generators
                               lambda lMax : (xflip_tyty(lMax), yflip_tyty(lMax)),
                 # quaternion rep generators
                                rep3d_gens = (
                                    IRot3.xflip(),
                                    IRot3.yflip(),
                                )

    ),
    'D2h' : SVWFPointGroupInfo('D2h',
                 # permutation group generators
                               (Permutation(0,1, size=6)(2,3), # x -> - x mirror operation (i.e. yz mirror plane)
                                Permutation(0,3, size=6)(1,2), # y -> - y mirror operation (i.e. xz mirror plane)
                                # ^^^ btw, I guess that Permutation(0,1, size=6) and Permutation(2,3, size=6) would
                                # do exactly the same job (they should; CHECK)
                                Permutation(4,5, size=6)       # z -> - z mirror operation (i.e. xy mirror plane)
                               ), 
                 # dictionary with irrep generators
                               {
                                    # Product of C2v and zflip; not sure about the labels / axes here
                                    "A1'": (1,1,1),
                                    "B2'": (-1,1,1),
                                    "A2'": (-1,-1,1),
                                    "B1'": (1,-1,1),
                                    "A1''": (-1,-1,-1),
                                    "B2''": (1,-1,-1),
                                    "A2''": (1,1,-1),
                                    "B1''": (-1,1,-1),
                               },
                 # function that generates a tuple with svwf representation generators
                               lambda lMax : (xflip_tyty(lMax), yflip_tyty(lMax), zflip_tyty(lMax)),
                 # quaternion rep generators
                               rep3d_gens = (
                                   IRot3.xflip(),
                                   IRot3.yflip(),
                                   IRot3.zflip(),
                                )
    ),
    'C4' : SVWFPointGroupInfo('C4',
                 # permutation group generators
                               (Permutation(0,1,2,3, size=4),), #C4 rotation
                 # dictionary with irrep generators
                               {
                                    # Bradley, Cracknell p. 58
                                    'A': (1,),
                                    'B': (-1,),
                                    '1E': (-1j,),
                                    '2E': (1j,),
                               },
                 # function that generates a tuple with svwf representation generators
                               lambda lMax : (zrotN_tyty(4, lMax), ),
                 # quaternion rep generators
                               rep3d_gens = (
                                   IRot3.zrotN(4),
                                )
    ),
    'C4v' : SVWFPointGroupInfo('C4v',
                 # permutation group generators
                               (Permutation(0,1,2,3, size=4), #C4 rotation
                                Permutation(0,1, size=4)(2,3)), # x -> - x mirror operation (i.e. yz mirror plane)
                 # dictionary with irrep generators
                               {
                                    # Bradley, Cracknell p. 62
                                    'E': (ra, -lam),
                                    # Bradley, Cracknell p. 59, or Dresselhaus, Table A.18
                                    'A1': (1,1),
                                    'A2': (1,-1),
                                    'B1': (-1,1),
                                    'B2': (-1,-1),    
                               },
                 # function that generates a tuple with svwf representation generators
                               lambda lMax : (zrotN_tyty(4, lMax), xflip_tyty(lMax)),
                 # quaternion rep generators
                               rep3d_gens = (
                                   IRot3.zrotN(4),
                                   IRot3.xflip(),
                                )
    ),
    'D4h' : SVWFPointGroupInfo('D4h',
                 # permutation group generators
                               (Permutation(0,1,2,3, size=6),  # C4 rotation
                                Permutation(0,1, size=6)(2,3), # x -> - x mirror operation (i.e. yz mirror plane)
                                Permutation(4,5, size=6), # horizontal mirror operation z -> -z (i.e. xy mirror plane)
                               ), 
                 # dictionary with irrep generators
                               {    # product of C4v and zflip
                                    "E'": (ra, -lam, epsilon),
                                    "E''":(ra, -lam, -epsilon),
                                    "A1'": (1,1,1),
                                    "A2'": (1,-1,1),
                                    "A1''": (1,-1,-1),
                                    "A2''": (1,1,-1),
                                    "B1'": (-1,1,1),
                                    "B2'": (-1,-1,1),
                                    "B1''": (-1,-1,-1),
                                    "B2''": (-1,1,-1), 
                               },
                 # function that generates a tuple with svwf representation generators
                               lambda lMax : (zrotN_tyty(4, lMax), xflip_tyty(lMax), zflip_tyty(lMax)),
                 # quaternion rep generators
                               rep3d_gens = (
                                   IRot3.zrotN(4),
                                   IRot3.xflip(),
                                   IRot3.zflip(),
                                )
    ),
    'D3h' : SVWFPointGroupInfo('D3h',
                 # permutation group generators
                               ( Permutation(0,1,2, size=5),  # C3 rotation
                                 Permutation(0,2, size=5), # vertical mirror
                                 Permutation(3,4, size=5), # horizontal mirror z -> -z (i.e. xy mirror plane)
                               ), 
                 # dictionary with irrep generators
                               {    # Bradley, Cracknell p. 61
                                    "E'" : (alif, lam, epsilon),
                                    "E''" : (alif, lam, -epsilon),
                                    # Bradley, Cracknell p. 59, or Dresselhaus, Table A.14 (p. 482)
                                    "A1'" : (1,1,1),
                                    "A2'" : (1,-1,1),
                                    "A1''" : (1,-1,-1),
                                    "A2''" : (1,1,-1),
                               },
                 # function that generates a tuple with svwf representation generators
                               lambda lMax, vflip: (zrotN_tyty(3, lMax), yflip_tyty(lMax) if vflip == 'y' else xflip_tyty(lMax), zflip_tyty(lMax)),
                 # quaternion rep generators
                               rep3d_gens = (
                                   IRot3.zrotN(3),
                                   IRot3.xflip(), # if vflip == 'y' else IRot3.xflip(), # FIXME enable to choose
                                   IRot3.zflip(),
                                )
    ),
    'x_and_z_flip': SVWFPointGroupInfo(
	'x_and_z_flip',
	(
	    Permutation(0,1, size=4), # x -> -x mirror op
	    Permutation(2,3, size=4), # z -> -z mirror op
	),
	{
	    "P'": (1, 1),
	    "R'": (-1, 1),
	    "P''": (-1,-1),
	    "R''": (1, -1),
	},
	lambda lMax : (xflip_tyty(lMax), zflip_tyty(lMax)),
        rep3d_gens = (
            IRot3.xflip(),
            IRot3.zflip(),
        )

    ),
    'y_and_z_flip': SVWFPointGroupInfo(
	'y_and_z_flip',
	(
	    Permutation(0,1, size=4), # y -> -y mirror op
	    Permutation(2,3, size=4), # z -> -z mirror op
	),
	{
	    "P'": (1, 1),
	    "R'": (-1, 1),
	    "P''": (-1,-1),
	    "R''": (1, -1),
	},
	lambda lMax : (yflip_tyty(lMax), zflip_tyty(lMax)),
        rep3d_gens = (
            IRot3.yflip(),
            IRot3.zflip(),
        )
    ),
}
