#############################################################################
##
##  attributes/attr.gi
##  Copyright (C) 2013-2022                              James D. Mitchell
##
##  Licensing information can be found in the README file of this package.
##
#############################################################################
##

# This file contains methods for finding various attributes of finite
# semigroups, which satisfy CanUseFroidurePin or where no better method is
# known.

# Note about the difference between One and MultiplicativeNeutralElement
# (the same goes for Zero and MultplicativeZero):
#
# One(s) returns One(Representative(s)) if it belongs to s, so that
# One(s) = Transformation([1 .. DegreeOfTransformationSemigroup(s)]) if s is a
# transformation semigroup and it returns fail otherwise, or it returns
# PartialPerm([1 .. DegreeOfPartialPermSemigroup]) if this belongs to s.
#
# MultiplicativeNeutralElement on the other hand returns the element of s that
# acts as the identity, note that this can be equal to One(s) but it can also
# not be equal to One(s).
#
# A semigroup satisfies IsMonoidAsSemigroup(s) if
# MultiplicativeNeutralElement(x) <> fail, so it could be that One(s) returns
# fail but IsMonoidAsSemigroup is still true.

SEMIGROUPS.InjectionPrincipalFactor := function(D, constructor)
  local map, inv, G, mat, rep, R, L, x, RR, LL, rms, iso, hom, i, j;

  map := IsomorphismPermGroup(GroupHClass(D));
  inv := InverseGeneralMapping(map);

  G := Range(map);
  mat := [];
  rep := MultiplicativeNeutralElement(GroupHClass(D));
  R := HClassReps(LClass(D, rep));
  L := HClassReps(RClass(D, rep));

  for i in [1 .. Length(L)] do
    mat[i] := [];
    for j in [1 .. Length(R)] do
      x := L[i] * R[j];
      if x in D then
        mat[i][j] := x ^ map;
      else
        mat[i][j] := 0;
      fi;
    od;
  od;

  RR := EmptyPlist(Length(R));
  LL := EmptyPlist(Length(L));

  for j in [1 .. Length(R)] do
    for i in [1 .. Length(L)] do
      if mat[i][j] <> 0 then
        RR[j] := ((mat[i][j] ^ -1) ^ inv) * L[i];
        break;
      fi;
    od;
  od;

  for i in [1 .. Length(L)] do
    for j in [1 .. Length(R)] do
      if mat[i][j] <> 0 then
        LL[i] := R[j] * (mat[i][j] ^ -1) ^ inv;
        break;
      fi;
    od;
  od;

  rms := constructor(G, mat);

  iso := function(x)
    local i, j;

    if not x in D then
      return fail;
    fi;

    i := PositionProperty(R, y -> y in RClass(D, x));
    j := PositionProperty(L, y -> y in LClass(D, x));

    return Objectify(TypeReesMatrixSemigroupElements(rms),
                     [i, (rep * RR[i] * x * LL[j]) ^ map, j, mat]);
  end;

  inv := function(x)
    if x![1] = 0 then
      return fail;
    fi;
    return R[x![1]] * (x![2] ^ InverseGeneralMapping(map)) * L[x![3]];
  end;
  hom := MappingByFunction(D, rms, iso, inv);
  SetIsInjective(hom, true);
  SetIsTotal(hom, true);
  return hom;
end;

#############################################################################
## 1. Default methods, for which there are currently no better methods.
#############################################################################

# Don't use ClosureSemigroup here since the order of the generators matters
# and ClosureSemigroup shuffles the generators.

InstallMethod(GeneratorsSmallest,
"for a semigroup with CanUseFroidurePin",
[CanUseFroidurePin],
function(S)
  local iter, gens, T, closure, x;

  iter := IteratorSorted(S);
  gens := [NextIterator(iter)];
  T    := Semigroup(gens);

  if CanUseLibsemigroupsFroidurePin(S) then
    closure := {S, coll, opts} ->
               ClosureSemigroupOrMonoidNC(Semigroup, S, coll, opts);
  else
    closure := ClosureSemigroup;
  fi;

  for x in iter do
    if not x in T then
      T := closure(T, [x], SEMIGROUPS.OptionsRec(T));
      Add(gens, x);
      if T = S then
        break;
      fi;
    fi;
  od;
  return gens;
end);

InstallMethod(SmallestElementSemigroup, "for a semigroup",
[IsSemigroup],
function(S)
  if not IsFinite(S) then
    TryNextMethod();
  fi;
  return NextIterator(IteratorSorted(S));
end);

InstallMethod(LargestElementSemigroup, "for a semigroup",
[IsSemigroup],
function(S)
  if not IsFinite(S) then
    TryNextMethod();
  fi;
  return EnumeratorSorted(S)[Size(S)];
end);

InstallMethod(NrIdempotents, "for a semigroup", [IsSemigroup],
S -> Length(Idempotents(S)));

InstallMethod(GroupOfUnits, "for a semigroup", [IsSemigroup],
function(S)
  local H, map, U, iso;

  if not IsFinite(S) then
    TryNextMethod();
  elif MultiplicativeNeutralElement(S) = fail then
    return fail;
  fi;

  H := GreensHClassOfElementNC(S, MultiplicativeNeutralElement(S));
  map := IsomorphismPermGroup(H);

  U := Semigroup(List(GeneratorsOfGroup(Range(map)),
                      x -> x ^ InverseGeneralMapping(map)));

  iso := SemigroupIsomorphismByFunctionNC(U,
                                          Range(map),
                                          x -> x ^ map,
                                          x -> x ^ InverseGeneralMapping(map));
  SetIsomorphismPermGroup(U, iso);
  SetIsGroupAsSemigroup(U, true);
  UseIsomorphismRelation(U, Range(iso));
  return U;
end);

# same method for ideals

InstallMethod(IsomorphismReesMatrixSemigroup, "for a D-class",
[IsGreensDClass],
function(D)
  if NrIdempotents(D) <> NrHClasses(D) then
    ErrorNoReturn("the argument (a Green's D-class) is not a semigroup");
  fi;

  return InjectionPrincipalFactor(D);
end);

# same method for ideal

InstallMethod(IrredundantGeneratingSubset,
"for a multiplicative element collection",
[IsMultiplicativeElementCollection],
function(coll)
  local gens, nrgens, deg, out, redund, i, x;

  if (IsSemigroup(coll) and HasGeneratorsOfSemigroup(coll))
      or (HasIsSemigroupIdeal(coll) and IsSemigroupIdeal(coll)) then
    coll := ShallowCopy(GeneratorsOfSemigroup(coll));
  elif not IsMutable(coll) then
    coll := ShallowCopy(coll);
  fi;

  if Size(coll) = 1 then
    return coll;
  fi;

  gens := Set(coll);
  nrgens := Length(gens);

  if nrgens = 1 then
    return gens;
  elif IsGeneratorsOfActingSemigroup(coll) then
    deg := ActionDegree(coll);
    Shuffle(coll);
    Sort(coll, {x, y} -> ActionRank(x, deg) > ActionRank(y, deg));
  fi;

  out := EmptyPlist(Length(coll));
  redund := EmptyPlist(Length(coll));
  i := 0;

  repeat
    i := i + 1;
    x := coll[i];
    if InfoLevel(InfoSemigroups) >= 3 then
      PrintFormatted(
        "at \t{} of \t{} with \t{} redundant, \t{} non-redundant\n",
        i,
        Length(coll),
        Length(redund),
        Length(out));
    fi;

    if not x in redund and not x in out then
      if Length(gens) > 1 and x in Semigroup(Difference(gens, [x])) then
        AddSet(redund, x);
        gens := Difference(gens, [x]);
      else
        AddSet(out, x);
      fi;
    fi;
  until Length(redund) + Length(out) = nrgens;

  if InfoLevel(InfoSemigroups) >= 3 then
    Print("\n");
  fi;

  return out;
end);

# same method for ideals

InstallMethod(MinimalIdeal, "for a semigroup",
[IsSemigroup],
function(S)
  local I;
  if not IsFinite(S) then
    TryNextMethod();
  fi;
  I := SemigroupIdealByGeneratorsNC(S,
                                    [RepresentativeOfMinimalIdeal(S)],
                                    SEMIGROUPS.OptionsRec(S));
  SetIsSimpleSemigroup(I, true);
  return I;
end);

InstallMethod(PrincipalFactor, "for a Green's D-class",
[IsGreensDClass], D -> Range(InjectionPrincipalFactor(D)));

InstallMethod(NormalizedPrincipalFactor, "for a Green's D-class",
[IsGreensDClass], D -> Range(InjectionNormalizedPrincipalFactor(D)));

# different method for ideals, not yet implemented

InstallMethod(SmallSemigroupGeneratingSet, "for a list or collection",
[IsListOrCollection],
function(coll)
  if Length(coll) < 2 then
    return coll;
  fi;
  return GeneratorsOfSemigroup(Semigroup(coll, rec(small := true)));
end);

# different method for ideals, not yet implemented

InstallMethod(SmallSemigroupGeneratingSet,
"for a finite semigroup", [IsSemigroup and IsFinite],
S -> SmallSemigroupGeneratingSet(GeneratorsOfSemigroup(S)));

InstallMethod(SmallMonoidGeneratingSet,
"for a multiplicative element with one collection",
[IsMultiplicativeElementWithOneCollection],
function(coll)
  if Length(coll) < 2 then
    return coll;
  fi;
  return GeneratorsOfMonoid(Monoid(coll, rec(small := true)));
end);

# same method for ideals

InstallMethod(SmallMonoidGeneratingSet, "for a finite monoid",
[IsFinite and IsMonoid],
function(S)
  if Length(GeneratorsOfMonoid(S)) < 2 then
    return GeneratorsOfMonoid(S);
  fi;
  return SmallMonoidGeneratingSet(GeneratorsOfMonoid(S));
end);

InstallMethod(SmallInverseSemigroupGeneratingSet,
"for a multiplicative element coll",
[IsMultiplicativeElementCollection],
function(coll)
  if not IsGeneratorsOfInverseSemigroup(coll) then
    ErrorNoReturn("the argument (a mult. elt. coll.) does not ",
                  "satisfy IsGeneratorsOfInverseSemigroup");
  fi;
  if Length(coll) < 2 then
    return coll;
  fi;
  return GeneratorsOfInverseSemigroup(InverseSemigroup(coll,
                                                       rec(small := true)));
end);

InstallMethod(SmallInverseSemigroupGeneratingSet,
"for an inverse semigroup with inverse op",
[IsInverseSemigroup and IsGeneratorsOfInverseSemigroup],
S -> SmallSemigroupGeneratingSet(GeneratorsOfInverseSemigroup(S)));

InstallMethod(SmallInverseMonoidGeneratingSet,
"for generators of an inverse monoid",
[IsMultiplicativeElementWithOneCollection],
function(coll)
  if not IsGeneratorsOfInverseSemigroup(coll) then
    ErrorNoReturn("the argument (a mult. elt. coll.) do not satisfy ",
                  "IsGeneratorsOfInverseSemigroup");
  fi;
  # The empty list does not satisfy IsGeneratorsOfInverseSemigroup
  Assert(1, not IsEmpty(coll));
  if Length(coll) = 1 then
    if coll[1] = One(coll) then
      return [];
    fi;
    return coll;
  fi;
  return GeneratorsOfInverseMonoid(InverseMonoid(coll, rec(small := true)));
end);

InstallMethod(SmallInverseMonoidGeneratingSet,
"for an inverse monoid with inverse op",
[IsInverseMonoid and IsGeneratorsOfInverseSemigroup],
function(S)
  if IsEmpty(GeneratorsOfInverseMonoid(S)) then
    return GeneratorsOfInverseMonoid(S);
  fi;
  return SmallMonoidGeneratingSet(GeneratorsOfInverseMonoid(S));
end);

InstallMethod(SmallGeneratingSet, "for a semigroup",
[IsSemigroup],
function(S)

  if HasGeneratorsOfSemigroupIdeal(S) then
    return MinimalIdealGeneratingSet(S);
  elif HasGeneratorsOfInverseMonoid(S) then
    return SmallInverseMonoidGeneratingSet(S);
  elif HasGeneratorsOfInverseSemigroup(S) then
    return SmallInverseSemigroupGeneratingSet(S);
  elif HasGeneratorsOfMonoid(S) then
    return SmallMonoidGeneratingSet(S);
  fi;

  return SmallSemigroupGeneratingSet(S);
end);

InstallMethod(StructureDescription, "for a Brandt semigroup",
[IsBrandtSemigroup],
function(S)
  local D;
  D := MaximalDClasses(S)[1];
  return Concatenation("B(", StructureDescription(GroupHClass(D)), ", ",
                       String(NrRClasses(D)), ")");
end);

# same method for ideals

InstallMethod(StructureDescription, "for a group as semigroup",
[IsGroupAsSemigroup],
function(S)
  if IsGroup(S) then
    TryNextMethod();
  fi;
  return StructureDescription(Range(IsomorphismPermGroup(S)));
end);

# same method for ideals

InstallMethod(MultiplicativeZero, "for a semigroup",
[IsSemigroup],
function(S)
  local gens, D, rep;

  if IsSemigroupIdeal(S)
      and HasMultiplicativeZero(SupersemigroupOfIdeal(S)) then
    return MultiplicativeZero(SupersemigroupOfIdeal(S));
  elif HasMinimalDClass(S) then
    D := MinimalDClass(S);
    if HasSize(D) then
      if IsTrivial(D) then
        return Representative(D);
      fi;
      return fail;
    fi;
  elif IsSemigroupIdeal(S) then
    return MultiplicativeZero(SupersemigroupOfIdeal(S));
  elif not IsFinite(S) then
    Info(InfoWarning,
         1,
         "may not be able to find the multiplicative zero, ",
         "the semigroup is infinite");
    # Cannot currently test this line, because the next method runs forever
    TryNextMethod();
  fi;

  rep := RepresentativeOfMinimalIdeal(S);
  gens := GeneratorsOfSemigroup(S);

  if ForAll(gens, x -> x * rep = rep and rep * x = rep) then
    return rep;
  fi;

  return fail;
end);

InstallMethod(MultiplicativeZero, "for a free semigroup",
[IsFreeSemigroup], ReturnFail);

InstallMethod(MultiplicativeZero, "for a free inverse semigroup",
[IsFreeInverseSemigroup], ReturnFail);

InstallMethod(LengthOfLongestDClassChain, "for a semigroup",
[IsSemigroup],
function(S)
  local D, min;

  if not IsFinite(S) then
    TryNextMethod();
  fi;

  D := DigraphReverse(PartialOrderOfDClasses(S));
  Assert(1, Length(DigraphSources(D)) = 1);
  min := DigraphSources(D)[1];  # minimal D-class
  SetMinimalDClass(S, GreensDClasses(S)[min]);
  SetRepresentativeOfMinimalIdeal(S, Representative(
                                     GreensDClasses(S)[min]));

  return DigraphLongestDistanceFromVertex(D, min);
end);

InstallMethod(NilpotencyDegree, "for a finite semigroup",
[IsSemigroup and IsFinite],
function(S)
  if not IsNilpotentSemigroup(S) then
    return fail;
  fi;
  return LengthOfLongestDClassChain(S) + 1;
end);

InstallMethod(MinimalDClass, "for a semigroup", [IsSemigroup],
S -> GreensDClassOfElementNC(S, RepresentativeOfMinimalIdeal(S)));

#############################################################################
## 2. Methods for attributes where there are known better methods for acting
##    semigroups.
#############################################################################

InstallMethod(IsGreensDGreaterThanFunc,
"for a semigroup with CanUseFroidurePin",
[IsSemigroup and CanUseFroidurePin],
function(S)
  local D, id;

  if not IsFinite(S) then
    TryNextMethod();
  fi;

  D := PartialOrderOfDClasses(S);
  id := GreensDRelation(S)!.data.id;

  return function(x, y)
    local u, v;
    if x = y then
      return false;
    fi;
    u := id[PositionCanonical(S, x)];
    v := id[PositionCanonical(S, y)];
    return u <> v and IsReachable(D, u, v);
  end;
end);

InstallMethod(MaximalDClasses,
"for a semigroup with CanUseFroidurePin",
[IsSemigroup and CanUseFroidurePin],
function(S)
  local D;
  if NrDClasses(S) = 1 then
    return DClasses(S);
  fi;
  D := PartialOrderOfDClasses(S);
  return DClasses(S){DigraphSources(D)};
end);

InstallMethod(MaximalDClasses,
"for a finite monoid as semigroup with mult. neutral elt",
[IsFinite and IsMonoidAsSemigroup and HasMultiplicativeNeutralElement],
S -> [DClass(S, MultiplicativeNeutralElement(S))]);

InstallMethod(MaximalLClasses,
"for a semigroup that CanUseFroidurePin",
[IsSemigroup and CanUseFroidurePin],
function(S)

  if NrLClasses(S) = 1 then
    return LClasses(S);
  fi;

  return LClasses(S){DigraphSources(PartialOrderOfLClasses(S))};
end);

InstallMethod(MaximalRClasses,
"for a semigroup that CanUseFroidurePin",
[IsSemigroup and CanUseFroidurePin],
function(S)

  if NrRClasses(S) = 1 then
    return RClasses(S);
  fi;

  return RClasses(S){DigraphSources(PartialOrderOfRClasses(S))};
end);

# same method for ideals

InstallMethod(StructureDescriptionMaximalSubgroups,
"for a semigroup", [IsSemigroup],
function(S)
  local out, D;

  if not IsFinite(S) then
    TryNextMethod();
  fi;

  out := [];
  for D in RegularDClasses(S) do
    AddSet(out, StructureDescription(GroupHClass(D)));
  od;

  return out;
end);

InstallMethod(IdempotentGeneratedSubsemigroup, "for a semigroup",
[IsSemigroup],
function(S)
  local out;
  if not IsFinite(S) then
    TryNextMethod();
  fi;
  out := Semigroup(Idempotents(S), rec(small := true));
  SetIsIdempotentGenerated(out, true);
  if HasIsSemigroupWithCommutingIdempotents(S)
      and IsSemigroupWithCommutingIdempotents(S) then
    SetIsSemigroupWithCommutingIdempotents(out, true);
  fi;
  return out;
end);

InstallMethod(IdempotentGeneratedSubsemigroup,
"for a Rees matrix subsemigroup",
[IsReesMatrixSubsemigroup],
function(R)
  local mat, I, J, nrrows, nrcols, min, max, gens, out, i;

  if not IsFinite(R) or not IsReesMatrixSemigroup(R)
      or not IsGroup(UnderlyingSemigroup(R)) then
    TryNextMethod();
  fi;

  mat := Matrix(R);
  I := Rows(R);
  J := Columns(R);
  nrrows := Length(I);
  nrcols := Length(J);

  min := Minimum(nrrows, nrcols);
  max := Maximum(nrrows, nrcols);
  gens := EmptyPlist(max);
  for i in [1 .. min] do
    Add(gens, RMSElement(R, I[i], mat[J[i]][I[i]] ^ -1, J[i]));
  od;
  for i in [min + 1 .. nrrows] do
    Add(gens, RMSElement(R, I[i], mat[J[1]][I[i]] ^ -1, J[1]));
  od;
  for i in [min + 1 .. nrcols] do
    Add(gens, RMSElement(R, I[1], mat[J[i]][I[1]] ^ -1, J[i]));
  od;
  out := Semigroup(gens);
  SetIsRegularSemigroup(out, true);
  SetIsIdempotentGenerated(out, true);
  SetIsSimpleSemigroup(out, true);
  return out;
end);

InstallMethod(IdempotentGeneratedSubsemigroup,
"for a Rees 0-matrix subsemigroup",
[IsReesZeroMatrixSubsemigroup],
function(R)
  local mat, gr, gens, I, J, nrrows, k, out, i, j;

  if not IsFinite(R) or not IsReesZeroMatrixSemigroup(R)
      or not IsGroup(UnderlyingSemigroup(R)) then
    TryNextMethod();
  fi;

  mat := Matrix(R);
  gr := RZMSDigraph(R);
  gens := [];

  if IsCompleteBipartiteDigraph(ReducedDigraph(gr)) or IsEmptyDigraph(gr) then
    Add(gens, MultiplicativeZero(R));
  fi;

  gr := OutNeighbours(UndirectedSpanningForest(gr));
  I := Rows(R);
  J := Columns(R);
  nrrows := Length(I);

  for i in [1 .. nrrows] do
    for j in gr[i] do
      k := J[j - nrrows];
      Add(gens, RMSElement(R, I[i], mat[k][I[i]] ^ -1, k));
    od;
  od;

  out := Semigroup(gens);
  SetIsRegularSemigroup(out, true);
  SetIsIdempotentGenerated(out, true);
  return out;
end);

InstallMethod(InjectionPrincipalFactor, "for a Green's D-class (Semigroups)",
[IsGreensDClass],
function(D)
  if not IsRegularDClass(D) then
    ErrorNoReturn("the argument (a Green's D-class) is not regular");
  elif NrHClasses(D) = NrIdempotents(D) then
    return SEMIGROUPS.InjectionPrincipalFactor(D, ReesMatrixSemigroup);
  fi;
  return SEMIGROUPS.InjectionPrincipalFactor(D, ReesZeroMatrixSemigroup);
end);

InstallMethod(InjectionNormalizedPrincipalFactor,
"for a Green's D-class (Semigroups)",
[IsGreensDClass],
function(D)
  local iso1, iso2, rms, inv1, inv2, iso, inv, hom;

  if not IsRegularDClass(D) then
    ErrorNoReturn("the argument (a Green's D-class) is not regular");
  elif NrHClasses(D) = NrIdempotents(D) then
    iso1 := SEMIGROUPS.InjectionPrincipalFactor(D, ReesMatrixSemigroup);
    iso2 := RMSNormalization(Range(iso1));
  else
    iso1 := SEMIGROUPS.InjectionPrincipalFactor(D, ReesZeroMatrixSemigroup);
    iso2 := RZMSNormalization(Range(iso1));
  fi;

  rms := Range(iso2);
  inv1 := InverseGeneralMapping(iso1);
  inv2 := InverseGeneralMapping(iso2);
  iso := x -> (x ^ iso1) ^ iso2;
  inv := x -> (x ^ inv2) ^ inv1;
  hom := MappingByFunction(D, rms, iso, inv);
  SetIsInjective(hom, true);
  SetIsTotal(hom, true);
  return hom;
end);

InstallMethod(MultiplicativeNeutralElement,
"for a semigroup with CanUseFroidurePin + generators",
[IsSemigroup and CanUseFroidurePin and HasGeneratorsOfSemigroup],
function(S)
  local D, e;

  if not IsFinite(S) then
    TryNextMethod();
  elif IsMultiplicativeElementWithOneCollection(S) and One(S) in S then
    return One(S);
  elif Length(MaximalDClasses(S)) > 1 then
    return fail;
  fi;

  D := MaximalDClasses(S)[1];

  if NrHClasses(D) <> 1 or not IsRegularDClass(D) then
    return fail;
  fi;

  e := Idempotents(D)[1];

  if ForAll(GeneratorsOfSemigroup(S), x -> e * x = x and x * e = x) then
    return e;
  fi;
  return fail;
end);

# same method for inverse/ideals

InstallMethod(RepresentativeOfMinimalIdeal, "for a semigroup",
[IsSemigroup],
function(S)

  if HasMultiplicativeZero(S) and MultiplicativeZero(S) <> fail then
    return MultiplicativeZero(S);
  elif HasIsSimpleSemigroup(S) and IsSimpleSemigroup(S) then
    # This catches known trivial semigroups
    return Representative(S);
  elif IsSemigroupIdeal(S) and
      (HasRepresentativeOfMinimalIdeal(SupersemigroupOfIdeal(S))
       or not HasGeneratorsOfSemigroup(S)) then
    return RepresentativeOfMinimalIdeal(SupersemigroupOfIdeal(S));
  elif not IsFinite(S) then
    TryNextMethod();
  fi;

  return RepresentativeOfMinimalIdealNC(S);
end);

InstallMethod(RepresentativeOfMinimalIdealNC, "for a finite semigroup",
[IsSemigroup and IsFinite],
function(S)
  local D, pos;
  D   := PartialOrderOfDClasses(S);
  pos := DigraphSinks(D)[1];
  Assert(1, Length(DigraphSinks(D)) = 1);
  return Representative(DClasses(S)[pos]);
end);

InstallMethod(InversesOfSemigroupElementNC,
"for a group as semigroup and a multiplicative element",
[IsGroupAsSemigroup and CanUseFroidurePin, IsMultiplicativeElement],
function(G, x)
  local i, iso, inv;
  if IsMultiplicativeElementWithInverse(x) then
    i := InverseOp(x);
    if i <> fail then
      return [i];
    fi;
  fi;
  iso := IsomorphismPermGroup(G);
  inv := InverseGeneralMapping(iso);
  return [((x ^ iso) ^ -1) ^ inv];
end);

InstallMethod(InversesOfSemigroupElementNC,
"for a semigroup that can use froidure-pin and a multiplicative element",
[CanUseFroidurePin, IsMultiplicativeElement],
function(S, a)
  local R, L, inverses, e, f, s;
  R := RClass(S, a);
  L := LClass(S, a);
  inverses := EmptyPlist(NrIdempotents(R) * NrIdempotents(L));

  for e in Idempotents(R) do
    s := RightGreensMultiplierNC(S, a, e) * e;
    for f in Idempotents(L) do
      Add(inverses, f * s);
    od;
  od;
  return inverses;
end);

InstallMethod(InversesOfSemigroupElement,
"for a semigroup that can use froidure-pin and a multiplicative element",
[CanUseFroidurePin, IsMultiplicativeElement],  # to beat the library method
function(S, x)
  if not IsFinite(S) then
    TryNextMethod();
  elif not x in S then
    ErrorNoReturn("the 2nd argument (a mult. element) must belong to the 1st ",
                  "argument (a semigroup)");
  fi;
  return InversesOfSemigroupElementNC(S, x);
end);

InstallMethod(OneInverseOfSemigroupElementNC,
"for a semigroup and a multiplicative element",
[IsSemigroup, IsMultiplicativeElement],
function(S, x)
    if not IsFinite(S) then
        TryNextMethod();
    fi;
    return First(EnumeratorSorted(S),
    y -> x * y * x = x and y * x * y = y);
end);

InstallMethod(OneInverseOfSemigroupElementNC,
"for CanUseFroidurePin and a multiplicative element",
[CanUseFroidurePin, IsMultiplicativeElement],
function(S, a)
  local R, L, e, f, s;
  R := RClass(S, a);
  L := LClass(S, a);
  e := Idempotents(R);
  if IsEmpty(e) then
    return fail;
  fi;
  e := e[1];
  f := Idempotents(L);
  if IsEmpty(f) then
    return fail;
  fi;
  f := f[1];
  s := RightGreensMultiplierNC(S, a, e);
  return  f * s * e;
end);

InstallMethod(OneInverseOfSemigroupElement,
"for a semigroup and a multiplicative element",
[IsSemigroup, IsMultiplicativeElement],
function(S, x)
  if not IsFinite(S) then
    ErrorNoReturn("the semigroup is not finite");
  elif not x in S then
    ErrorNoReturn("the 2nd argument (a mult. element) must belong to the 1st ",
                  "argument (a semigroup)");
  fi;
  return OneInverseOfSemigroupElementNC(S, x);
end);

InstallMethod(UnderlyingSemigroupOfSemigroupWithAdjoinedZero,
"for a semigroup",
[IsSemigroup],
function(S)
  local zero, gens, T;

  if HasIsSemigroupWithAdjoinedZero(S)
      and not IsSemigroupWithAdjoinedZero(S) then
    return fail;
  fi;

  zero := MultiplicativeZero(S);
  if zero = fail then
    return fail;
  fi;

  gens := GeneratorsOfSemigroup(S);
  if Length(gens) = 1 then
    return fail;
  elif not zero in gens then
    return fail;
  fi;

  T := Semigroup(Difference(gens, [zero]));

  if zero in T then
    return fail;
  fi;
  return T;
end);

BindGlobal("_SemigroupSizeByIndexPeriod",
function(S)
  local gen, ind;
  gen := MinimalSemigroupGeneratingSet(S)[1];
  ind := IndexPeriodOfSemigroupElement(gen);
  if ind[1] = 1 then
    SetIsGroupAsSemigroup(S, true);
  fi;
  return Sum(ind) - 1;
end);

InstallMethod(Size,
"for a monogenic transformation semigroup with minimal generating set",
[IsMonogenicSemigroup and HasMinimalSemigroupGeneratingSet and
 IsTransformationSemigroup],
10,  # to beat IsActingSemigroup
_SemigroupSizeByIndexPeriod);

InstallMethod(Size,
"for a monogenic partial perm semigroup with minimal generating set",
[IsMonogenicSemigroup and HasMinimalSemigroupGeneratingSet and
 IsPartialPermSemigroup],
10,  # to beat IsActingSemigroup
_SemigroupSizeByIndexPeriod);

InstallMethod(Size,
"for a monogenic bipartition semigroup with minimal generating set",
[IsMonogenicSemigroup and HasMinimalSemigroupGeneratingSet and
 IsBipartitionSemigroup],
10,  # to beat IsActingSemigroup
_SemigroupSizeByIndexPeriod);

InstallMethod(Size,
"for a monogenic semigroup of matrices over finite field with minimal gen set",
[IsMonogenicSemigroup and HasMinimalSemigroupGeneratingSet and
 IsMatrixOverFiniteFieldSemigroup],
_SemigroupSizeByIndexPeriod);

MakeReadWriteGlobal("_SemigroupSizeByIndexPeriod");
Unbind(_SemigroupSizeByIndexPeriod);

InstallMethod(IndecomposableElements, "for a semigroup", [IsSemigroup],
function(S)
  local out, D;

  if HasIsSurjectiveSemigroup(S) and IsSurjectiveSemigroup(S) then
    return [];
  fi;
  out := [];
  for D in MaximalDClasses(S) do
    if not IsRegularDClass(D) then
      AddSet(out, Representative(D));
    fi;
  od;
  return out;
end);

InstallMethod(MinimalSemigroupGeneratingSet, "for a free semigroup",
[IsFreeSemigroup], GeneratorsOfSemigroup);

InstallMethod(MinimalSemigroupGeneratingSet, "for a semigroup",
[IsSemigroup],
function(S)
  local gens, indecomp, id, T, zero, iso, inv, G, x, D, non_unit_gens, classes,
  po, nbs;

  if IsTrivial(S) then
    return [Representative(S)];
  elif IsGroupAsSemigroup(S) then
    iso := IsomorphismPermGroup(S);
    inv := InverseGeneralMapping(iso);
    G := Range(iso);
    return Images(inv, MinimalGeneratingSet(G));
  elif IsMonogenicSemigroup(S) then
    return [Representative(MaximalDClasses(S)[1])];
  fi;

  gens := IrredundantGeneratingSubset(S);

  # A semigroup that has a 2-generating set but is not monogenic has rank 2.
  if Length(gens) = 2 then
    return gens;
  fi;

  # A generating set for a semigroup that has either a one/zero adjoined is
  # minimal if and only if it contains that one/zero, and is minimal for the
  # semigroup without the one/zero.
  if IsMonoidAsSemigroup(S) and IsTrivial(GroupOfUnits(S)) then
    id := MultiplicativeNeutralElement(S);
    T := Semigroup(Filtered(gens, x -> x <> id));
    return Concatenation([id], MinimalSemigroupGeneratingSet(T));
  elif IsSemigroupWithAdjoinedZero(S) then
    zero := MultiplicativeZero(S);
    T := Semigroup(Filtered(gens, x -> x <> zero));
    return Concatenation([zero], MinimalSemigroupGeneratingSet(T));
  fi;

  # The indecomposable elements are contains in any generating set. If the
  # indecomposable elements (or if not, the indecomposable elements plus a
  # single element) generate the semigroup, then you have a minimal generating
  # set.
  indecomp := IndecomposableElements(S);
  if Length(gens) = Length(indecomp) then
    return indecomp;
  elif not IsEmpty(indecomp) then
    x := Difference(gens, Semigroup(indecomp));
    if Length(x) = 1 then
      return Concatenation(x, indecomp);
    fi;
  fi;

  # A generating set for a monoid that consists of a minimal generating set for
  # the group of units and exactly one generator in each D-class immediately
  # below the group of units is minimal.
  if IsMonoidAsSemigroup(S) then
    D := DClass(S, MultiplicativeNeutralElement(S));
    non_unit_gens := Filtered(gens, x -> not x in D);
    classes := List(non_unit_gens, x -> Position(DClasses(S), DClass(S, x)));
    if IsDuplicateFreeList(classes) then
      po := PartialOrderOfDClasses(S);
      po := DigraphReflexiveTransitiveReduction(po);
      nbs := OutNeighboursOfVertex(po, Position(DClasses(S), D));
      if ForAll(classes, x -> x in nbs) then
        iso := IsomorphismPermGroup(GroupOfUnits(S));
        inv := InverseGeneralMapping(iso);
        G := Range(iso);
        return Concatenation(Images(inv, MinimalGeneratingSet(G)),
                             non_unit_gens);
      fi;
    fi;
  fi;

  # An irredundant generating set of a finite semigroup that contains at most
  # one generator per D-class is minimal.
  if IsFinite(S) and (IsDTrivial(S) or
      Length(Set(gens, x -> DClass(S, x))) = Length(gens)) then
    return gens;
  fi;

  ErrorNoReturn("no further methods for computing minimal generating sets ",
                "are implemented");
end);

InstallMethod(MinimalMonoidGeneratingSet, "for a free monoid",
[IsFreeMonoid], GeneratorsOfMonoid);

InstallMethod(MinimalMonoidGeneratingSet, "for a monoid",
[IsMonoid],
function(S)
  local one, gens, new;

  one := One(S);
  if IsTrivial(S) then
    return [one];
  fi;

  gens := MinimalSemigroupGeneratingSet(S);
  if not IsTrivial(GroupOfUnits(S)) then
    return gens;
  fi;
  new := Difference(gens, [one]);
  if One(new) = one then
    # The One of the non-identity generators is the One, so One is not needed.
    return new;
  fi;

  # This currently applies to only partial perm monoids: sometimes, the One
  # of the non-One generators is not the One of the monoid. Therefore the One
  # has to be included in the GeneratorsOfMonoid. WARNING: therefore
  # MinimalMonoidGeneratingSet may include the One even though, mathematically,
  # it shouldn't be needed. For example, see symmetric inverse monoid on 1 pt.
  return gens;
end);

InstallMethod(NambooripadPartialOrder, "for a semigroup",
[IsSemigroup],
function(S)
  local elts, p, func, out, i, j;

  if not IsFinite(S) then
    ErrorNoReturn("the argument (a semigroup) is not finite");
  elif not IsRegularSemigroup(S) then
    ErrorNoReturn("the argument (a semigroup) is not regular");
  elif IsInverseSemigroup(S) then
    return NaturalPartialOrder(S);
  fi;

  Info(InfoWarning, 2, "NambooripadPartialOrder: this method ",
                       "fully enumerates its argument!");

  elts := ShallowCopy(Elements(S));
  p    := Sortex(elts, {x, y} -> IsGreensDGreaterThanFunc(S)(y, x)) ^ -1;
  func := NambooripadLeqRegularSemigroup(S);
  out  := List([1 .. Size(S)], x -> []);

  for i in [1 .. Size(S)] do
    for j in [i + 1 .. Size(S)] do
      if func(elts[i], elts[j]) then
        AddSet(out[j ^ p], i ^ p);
      fi;
    od;
  od;
  return out;
end);

InstallMethod(NambooripadLeqRegularSemigroup, "for a semigroup",
[IsSemigroup],
function(S)
  if not IsFinite(S) then
    ErrorNoReturn("the argument (a semigroup) is not finite");
  elif not IsRegularSemigroup(S) then
    ErrorNoReturn("the argument (a semigroup) is not regular");
  elif IsInverseSemigroup(S) then
    return NaturalLeqInverseSemigroup(S);
  fi;

  return
    function(x, y)
      local E;
      E := Idempotents(S);
      return ForAny(E, e -> e * y = x)
        and ForAny(E, f -> y * f = x);
    end;
end);

InstallMethod(LeftIdentity,
"for semigroup with CanUseFroidurePin and mult. elt.",
[IsSemigroup and CanUseFroidurePin, IsMultiplicativeElement],
function(S, x)
  local i, p, result;
  if not x in S then
    Error("the 2nd argument (a mult. elt.) does not belong to the 1st ",
          "argument (a semigroup)");
  elif IsMonoid(S) then
    return One(S);
  elif IsMonoidAsSemigroup(S) then
    return MultiplicativeNeutralElement(S);
  elif IsIdempotent(x) then
    return x;
  fi;

  i := PositionCanonical(S, x);
  p := DigraphPath(LeftCayleyDigraph(S), i, i);
  if p = fail then
    return fail;
  fi;
  result := EvaluateWord(GeneratorsOfSemigroup(S), Reversed(p[2]));
  return result ^ SmallestIdempotentPower(result);
end);

InstallMethod(RightIdentity,
"for semgroup with CanUseFroidurePin and mult. elt.",
[IsSemigroup and CanUseFroidurePin, IsMultiplicativeElement],
function(S, x)
  local i, p, result;
  if not x in S then
    Error("the 2nd argument (a mult. elt.) does not belong to the 1st ",
          "argument (a semigroup)");
  elif IsMonoid(S) then
    return One(S);
  elif IsMonoidAsSemigroup(S) then
    return MultiplicativeNeutralElement(S);
  elif IsIdempotent(x) then
    return x;
  fi;

  i := PositionCanonical(S, x);
  p := DigraphPath(RightCayleyDigraph(S), i, i);
  if p = fail then
    return fail;
  fi;
  result := EvaluateWord(GeneratorsOfSemigroup(S), p[2]);
  return result ^ SmallestIdempotentPower(result);
end);

InstallMethod(MultiplicationTableWithCanonicalPositions,
"for a semigroup with CanUseFroidurePin",
[IsSemigroup and CanUseFroidurePin],
function(S)
  local n, sortedlist, t, tinv, M;
  n          := Size(S);
  sortedlist := AsSortedList(S);

  t    := PermList(List([1 .. n], i -> PositionCanonical(S, sortedlist[i])));
  tinv := t ^ -1;
  M    := MultiplicationTable(S);

  return List([1 .. n], i -> List([1 .. n], j -> M[i ^ tinv][j ^ tinv] ^ t));
end);

InstallMethod(TransposedMultiplicationTableWithCanonicalPositions,
"for a semigroup with CanUseFroidurePin",
[IsSemigroup and CanUseFroidurePin],
S -> TransposedMat(MultiplicationTableWithCanonicalPositions(S)));

InstallMethod(MinimalFaithfulTransformationDegree, "for a right zero semigroup",
[IsRightZeroSemigroup],
function(S)
  local max, deg;

  max := 0;
  deg := 0;
  while max < Size(S) do
    deg := deg + 1;
    if (deg mod 3) = 0 then
      max := 3 ^ (deg / 3);
    elif (deg mod 3) = 1 then
      max := 4 * 3 ^ ((deg - 4) / 3);
    else
      max := 2 * 3 ^ ((deg - 2) / 3);
    fi;
  od;
  return deg;
end);

InstallMethod(MinimalFaithfulTransformationDegree, "for a left zero semigroup",
[IsLeftZeroSemigroup],
function(S)
  local max, deg, N, r;
  max := 0;
  deg := 0;
  while max < Size(S) do
    deg := deg + 1;
    for r in [1 .. deg - 1] do
      N := r ^ (deg - r);
      if N > max then
        max := N;
      fi;
    od;
  od;
  return deg;
end);
