
###################################################################
###################################################################
InstallGlobalFunction(HAP_GenericSL2OSubgroup,
function()
local type, G;

    type := NewType( FamilyObj([[[1,0],[0,1]]]),
                     IsGroup and
                     IsAttributeStoringRep and
                     IsFinitelyGeneratedGroup and
                     IsMatrixGroup and
                     IsHapSL2OSubgroup);

G:=rec(
    membership:= fail,
    membershipLight:=fail,
    tree:= fail,
    generators:= fail,
    level:= fail,
    cosetRep:= fail,
    cosetPos:= fail,
    ugrp:= fail,
    name:="Congruence subgroup");

ObjectifyWithAttributes(G, type,
DimensionOfMatrixGroup, 2,
IsIntegerMatrixGroup, true,
IsFinite, false,
IsHapSL2OSubgroup, true,
IsHapSL2Subgroup, true);

return G;
end);
###################################################################
###################################################################

###################################################################
###################################################################
InstallGlobalFunction(HAP_CongruenceSubgroupGamma0Ideal,
function(I)
local G, membership, membershipLight, CosetRep, CosetPos,CosetReps,R,g,x,y,a, one, zero;

G:=HAP_GenericSL2OSubgroup();
one:=One(I!.GeneratorsOfTwoSidedIdeal[1]);
zero:=Zero(one);

###################################################
membership:=function(g)
if not Determinant(g)=1 then return false; fi;
if not g[2][1] in I   then return false; fi;
return true; 
end;
###################################################
###################################################
membershipLight:=function(g)
if not g[2][1] in I   then return false; fi;
return true;
end;
###################################################

G!.membership:=membership;
G!.membershipLight:=membershipLight;
G!.level:=I;
if Norm(I)=1 then
  G!.GeneratorsOfMagmaWithInverses:=one*GeneratorsOfGroup(SL2QuadraticIntegers(AssociatedRing(I)!.bianchiInteger,true));
fi;
G!.name:="CongruenceSubgroupGamma0";
G!.ugrp:=Group(IdentityMat(2));

#if false then 
if IsPrime(I) then   #I need to extend this to no primes
R:=RightTransversal(I);
###########################################
CosetPos:=function(g)
if g[1][1] mod I =zero then return 1+Norm(I); fi;  
return Position(R, ((g[2][1]*InverseOp(I,g[1][1])) mod I));
end;
###########################################

###########################################
CosetRep:=function(g)
if g[1][1] mod I=zero then return one*[[0,-1],[1,0]]; fi;
return [one*[1,0],[(g[2][1]*InverseOp(g[1][1])) mod I,one]];
end;
###########################################

CosetReps:=List([1..Norm(I)],i->[one*[1,0],[R[i],one]]);
Add(CosetReps,one*[[0,-1],[1,0]]);
G!.cosetRep:=CosetRep;
G!.cosetReps:=CosetReps;
G!.cosetPos:=CosetPos;
G!.transversal:=List(CosetReps,x->x^-1);
fi;

return G;

end);
###################################################################
###################################################################

###################################################################
###################################################################
InstallGlobalFunction(HAP_PrincipalCongruenceSubgroupIdeal,
function(I)
local G, membership, membershipLight, g,x,y,a,one;

G:=HAP_GenericSL2OSubgroup();
one:=One(I!.AssociatedRing);

###################################################
membership:=function(g)
if not Determinant(g)=1 then return false; fi;
if not g[2][1] in I   then return false; fi;
if not g[1][2] in I   then return false; fi;
if not (g[1][1] -one) in I   then return false; fi;
if not (g[2][2] -one) in I   then return false; fi;
return true;
end;
###################################################
###################################################
membershipLight:=function(g)
if not g[2][1] in I   then return false; fi;
if not g[1][2] in I   then return false; fi;
if not (g[1][1] -one) in I   then return false; fi;
if not (g[2][2] -one) in I   then return false; fi;
return true;
end;
###################################################

G!.membership:=membership;
G!.membershipLight:=membershipLight;
G!.level:=I;
G!.name:="PrincipalCongruenceSubgroup";
G!.ugrp:=Group(one*IdentityMat(2));
return G;

end);
###################################################################
###################################################################


###################################################################
###################################################################
InstallGlobalFunction(HAP_SL2OSubgroupTree_slow,
function(G)
local d, gens, one, tree,InGmodU,Ugrp,v,p,g,s,n,q,vv,P,R, sublst,
      leaves,nodes,generators,Perturb, InNodesModG,  elements,
      generators2,pos,i,j,PermRep,elementsNumbers;;
if G!.tree=fail then

if G!.name="CongruenceSubgroupGamma0" then
  if IsPrime(G!.level) then
     HAP_SL2OSubgroupTree_fast(G); return true;      #This is SLOWER!
  fi;
fi;

d:=AssociatedRing(G!.level)!.bianchiInteger;


R:=ResolutionSL2QuadraticIntegers(d,2,true);;
#R:=BianchiGcomplex(d);
#R:=FreeGResolution(R,2);
P:=PresentationOfResolution(R);
gens:= R!.elts{P.gens};

#one:=IdentityMat(2);
one:=One(R!.elts[2]);

Ugrp:=G!.ugrp;
Ugrp:=Elements(Ugrp);

tree:=[];
leaves:=NewDictionary(one,true,SL2QuadraticIntegers(d));
elementsNumbers:=NewDictionary(one,true,SL2QuadraticIntegers(d));

if Size(Ugrp)>1 then
###########################################
InGmodU:=function(g)
local x;
for x in Ugrp do
if G!.membershipLight(x*g)  
then return true; fi;;
od;
return false;
end;
###########################################
else
     InGmodU:=G!.membershipLight;
fi;

###########################################
InNodesModG:=function(g)
local x,y,gg,B1,B2;

gg:=g^-1;

for x in nodes do
y:=x*gg;
if InGmodU(y) 
then

 return LookupDictionary(elementsNumbers,x); fi;
od;

return false;
end;
###########################################


if Size(Ugrp)>1 then
###########################################
Perturb:=function(g)
local u;
for u in Ugrp do
if G!.membershipLight(u*g) then return u*g; fi;
od;
return fail;
end;
###########################################
else
    Perturb:=function(g) return g; end;
fi;

nodes:=[one]; 
elements:=[one];
AddDictionary(elementsNumbers,one,1);
PermRep:=List(gens,i->[]);

for s in Reversed([1..Length(gens)]) do
 if not gens[s] in G then
  AddDictionary(leaves,gens[s],2);
  AddDictionary(elementsNumbers,gens[s],2);
 tree[2]:=[1,s];
 AddSet(nodes,gens[s]);
 Add(elements,gens[s]);
 PermRep[s][1]:=2;
 break;
 fi;
od;

generators:=[];


############Tree Construction########################
while Size(leaves)>0 do
vv:=leaves!.entries[1];
v:=vv[1];
    for s in [1..Length(gens)] do
        g:=v*gens[s]; 
        q:=InNodesModG(g);
        p:=LookupDictionary(leaves,v);
        if q=false then 
            AddDictionary(leaves,g,1+Size(tree));
            AddDictionary(elementsNumbers,g,1+Size(tree));
            AddSet(nodes,g);
            Add(tree,[p, s]);
            PermRep[s][p]:=Size(tree);
            Add(elements,g);
        else 
            Add(generators,[elements[q],g,v,s]);
            PermRep[s][p]:=q;
        fi;
    od;
RemoveDictionary(leaves,v);
od;
#####################################################

G!.tree:=tree;
G!.generators:=generators;
generators:=List(generators,x->Perturb(x[2]*x[1]^-1));
generators:=List(generators,x->Minimum(x,x^-1));
generators2:=SSortedList(generators);
generators2:=Filtered(generators2,x->not x=one);

G!.GeneratorsOfMagmaWithInverses:=generators2;
sublst:=Filtered([1..Length(generators)],i->generators[i] in generators2);
G!.generators:=G!.generators{sublst};
G!.elements:=elements;
G!.IndexInSL2O:=Length(tree);
G!.gens:=gens;
G!.presentation:=P;

if Length(tree)>1 then
for s in [1..Length(gens)] do
if not IsBound(PermRep[s][1]) then
PermRep[s][1]:=Filtered([1..Length(PermRep[s])],i->not i in PermRep[s])[1];
fi;
od;
G!.PermRep:=PermRep;
else
G!.IndexInSL2O:=1;
fi;
fi;
end);
###################################################################
###################################################################


###################################################################
###################################################################
InstallGlobalFunction(HAP_SL2OSubgroupTree_fast,
function(G)
local d, gens, one, tree,v,p,g,s,n,q,P,R, sublst,
      leaves,nodes,generators, InNodesModG,  
      generators2,pos,i,j,PermRep,elementsNumbers,Bnodes;;
if G!.tree=fail then

d:=AssociatedRing(G!.level)!.bianchiInteger;
if IsCyclotomic((G!.level)!.GeneratorsOfTwoSidedIdeal[1]) then
R:=ResolutionSL2QuadraticIntegers(d,2,true);;
else
R:=BianchiGcomplex(d);;
R:=FreeGResolution(R,2);
fi;
P:=PresentationOfResolution(R);
gens:= R!.elts{P.gens};
one:=One(R!.elts[2]);

tree:=[];
leaves:=[];

###########################################
InNodesModG:=function(g)
local pos,x,y,gg,B1,B2;

pos:=G!.cosetPos(g);
if Bnodes[pos]=false then return false;
else
return pos;
fi;

end;
###########################################

Bnodes:=List([1..Norm(G!.level)+1],i->false);
Bnodes[G!.cosetPos(one)]:=true;
PermRep:=List(gens,i->[]);

for s in Reversed([1..Length(gens)]) do
 if not gens[s] in G then
  Add(leaves,gens[s]);

 tree[G!.cosetPos(gens[s])]:=[G!.cosetPos(one),s];
Bnodes[G!.cosetPos(gens[s])]:=true;
PermRep[s][G!.cosetPos(one)]:=G!.cosetPos(gens[s]);
 break;
 fi;
od;

generators:=[];


############Tree Construction########################
while Size(leaves)>0 do
v:=leaves[1];
    for s in [1..Length(gens)] do
        g:=gens[s]*v;
        q:=InNodesModG(g);
        p:=G!.cosetPos(v);
        if q=false then
Add(leaves,g);
            Bnodes[G!.cosetPos(g)]:=true;
            tree[G!.cosetPos(g)]:=[p, s];
PermRep[s][p]:=G!.cosetPos(g);
        else
Add(generators,[G!.cosetReps[q],g,v,s]);
            PermRep[s][p]:=q;
        fi;
    od;
Remove(leaves,1);
od;
#####################################################

G!.tree:=tree;
G!.generators:=generators;
generators:=List(generators,x->x[1]^-1*x[2]);
generators:=List(generators,x->Minimum(x,x^-1));
generators2:=SSortedList(generators);
generators2:=Filtered(generators2,x->not x=one);

G!.GeneratorsOfMagmaWithInverses:=generators2;
sublst:=Filtered([1..Length(generators)],i->generators[i] in generators2);
G!.generators:=G!.generators{sublst};
G!.elements:=G!.cosetReps;
G!.IndexInSL2O:=Length(tree);
G!.gens:=gens;
G!.presentation:=P;

for s in [1..Length(gens)] do
if not IsBound(PermRep[s][1]) then 
PermRep[s][1]:=Filtered([1..Length(PermRep[s])],i->not i in PermRep[s])[1];
fi;
od;
n:=Length(PermRep[1]);   #Dec 2025
G!.PermRep:=List(PermRep, x->ListPerm(PermList(x)^-1,n)   );
fi;
end);
###################################################################
###################################################################


###################################################################
###################################################################
InstallGlobalFunction(HAP_TransversalCongruenceSubgroupsIdeal,
function(G,H)
local R, RR, lenR, x, poscan, gensH, I, t, one, P,Psorted,zero;

I:=H!.level;
one:=One(I!.AssociatedRing mod I);

if not (H!.name="CongruenceSubgroupGamma0" and IsPrime(I)) then
   HAP_SL2SubgroupTree(H);
   R:=H!.elements;
   lenR:=Length(R);
   ##########################################
   poscan:=function(x)
   local i, xinv;
   xinv:=x^-1;
   for i in [1..lenR] do
   #if R[i]*xinv in H then return i; fi;
   if H!.membershipLight(R[i]*xinv) then return i; fi;
   od;
   end;
   ##########################################
else
return HAP_TransversalCongruenceSubgroupInAmbientGroup(G,H ); 

   ######Redundant code#####################
   RR:=[]; P:=[];
   for t in RightTransversal(I) do
      Add(RR,[[1,0],[t,1]]);
   od;
   Add(RR,[[0,1],[-1,0]]);
   lenR:=Length(RR);
   P:=List(RR, x -> x[2]*one);
   P:=List(P, x->[x[1]![1],x[2]![1]]);
   Psorted:=SSortedList(P);
   R:=List(Psorted, p->RR[Position(P,p)]);
   zero:=Position(R,RR[lenR]); 
   ##########################################
   poscan:=function(x)
   local p;
   p:=x[2]*one; 
   if IsZero(p[2]) then return zero; fi;
   p:=p*(p[2]^-1); 
   return PositionSorted(Psorted,[p[1]![1],p[2]![1]]);
   end;
   ############end of redundant code#########
fi;

return Objectify( NewType( FamilyObj( G ),
                    IsHapRightTransversalSL2ZSubgroup and IsList and
                    IsDuplicateFreeList and IsAttributeStoringRep ),
          rec( group := G,
               subgroup := H,
               cosets:=R,
               poscan:=poscan ));

end);
###################################################################
###################################################################



###################################################################
###################################################################
InstallGlobalFunction(HAP_TransversalCongruenceSubgroupsIdeal_alt,
function(G,H)
local gensG, gensH, N, L, GN, HN, R, R2, x, S,  one, iso, epi,epi2, poscan;

gensH:=GeneratorsOfGroup(H);
for x in gensH do
if not x in G then
Print("The second argument is not a subgroup of the first.\n");
return fail;fi;
od;
gensG:=GeneratorsOfGroup(G);
N:=H!.level;
S:= AssociatedRing(N) mod N;

one:=One(S);
GN:=Group(gensG*one);
iso:=IsomorphismPermGroup(GN);
epi:=GroupHomomorphismByImagesNC(G,Image(iso),gensG,List(gensG*one,y->Image(iso,y)));
epi2:=GroupHomomorphismByFunction(G,Image(iso),y->Image(iso,y*one)  );

HN:=Group(List(gensH*one,y->Image(iso,y)));
R:=RightTransversal(Image(iso,GN),HN);
R2:=List(R,x->PreImagesRepresentative(epi,x));
L:=Length(R2);

##########################################
poscan:=function(x)
return PositionCanonical(R,ImagesRepresentative(epi2,x));
end;
##########################################

return Objectify( NewType( FamilyObj( G ),
                    IsHapRightTransversalSL2ZSubgroup and IsList and
                    IsDuplicateFreeList and IsAttributeStoringRep ),
          rec( group := G,
               subgroup := H,
               cosets:=R2,
               poscan:=poscan ));

end);
###################################################################
###################################################################


#####################################################
#####################################################
InstallOtherMethod(AbelianInvariants,
"for HAPSL2OSubgroups",
[IsHapSL2OSubgroup],
1000000, #Hmm!
function(H)
local P,G, CosetTable,r,n;

HAP_SL2OSubgroupTree_slow(H);
P:=H!.presentation;
G:=P.freeGroup/P.relators;

#return AbelianInvariants(G);

#Something crashes sometimes in the following code.

n:=Length(H!.PermRep[1]);
CosetTable:=[];
for r in H!.PermRep do
Add(CosetTable,r);
Add(CosetTable, ListPerm(PermList(r)^-1,n)  );
od;

return AbelianInvariantsSubgroupFpGroupRrs(G,CosetTable);

end);
#####################################################
#####################################################

#####################################################
#####################################################
InstallMethod(AsFpGroup,
"for HAPSL2OSubgroups",
[IsHapSL2OSubgroup],
1000000, #Hmm!
function(H)
local I,d,R,P,G,gensG, gensH, HG, tree, loops,
vertex2word,x,iso,iso1,iso2, CosetTable,r;

HAP_SL2OSubgroupTree_slow(H);
P:=H!.presentation;
G:=P.freeGroup/P.relators;

gensG:=GeneratorsOfGroup(G);
HAP_SL2OSubgroupTree_slow(H);
tree:=H!.tree;
loops:=List(H!.generators,x->[Position(H!.elements,x[2]),Position(H!.elements,x[3]),x[4]]);

###################################
vertex2word:=function(v)
local word, x;
word:=One(G);
while IsBound(tree[v]) do
word:=gensG[tree[v][2]]*word;
v:=tree[v][1];
od;
return word;
end;
####################################

gensH:=[];
for x in loops do
Add(gensH, vertex2word(x[2])*gensG[x[3]]*(vertex2word(x[1]))^-1);
od;

#HG:=PresentationSubgroupRrs(G,CosetTable);
HG:=PresentationSubgroup(G,Group(gensH));
#HG!.TzOptions.printLevel:=0;
#TzEliminate(HG,Length(HG!.generators)-500);
HG:= FpGroupPresentation(HG);
return HG;
end);
#####################################################
#####################################################

############################################################
############################################################
InstallOtherMethod(RightTransversal,
"right transversal for finite index subgroups of SL2QuadraticIntegers(d))",
[IsHapSL2OSubgroup,IsHapSL2OSubgroup],
1000000, #Must be a better way than this to ensure this method
function(H,HH)
local N;

if H!.tree=true then    #This means that H=SL(2,O)
return HAP_TransversalCongruenceSubgroupsIdeal(H,HH);
else
return HAP_TransversalCongruenceSubgroupsIdeal_alt(H,HH);
fi;

end);
############################################################
############################################################

############################################################
############################################################
InstallMethod(IndexInSL2O,
"Index of HAP_congruence subgroup in SL(2,Integers)",
[IsHapSL2OSubgroup],
function(H)

HAP_SL2OSubgroupTree_slow(H);
H!.index:=Length(H!.tree);

return H!.index;

end);
############################################################
############################################################

